};
// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
- var global$F =
+ var global$1m =
// eslint-disable-next-line es/no-global-this -- safe
check(typeof globalThis == 'object' && globalThis) ||
check(typeof window == 'object' && window) ||
var objectGetOwnPropertyDescriptor = {};
- var fails$N = function (exec) {
+ var fails$S = function (exec) {
try {
return !!exec();
} catch (error) {
}
};
- var fails$M = fails$N;
+ var fails$R = fails$S;
// Detect IE8's incomplete defineProperty implementation
- var descriptors = !fails$M(function () {
+ var descriptors = !fails$R(function () {
// eslint-disable-next-line es/no-object-defineproperty -- required for testing
return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7;
});
+ var call$q = Function.prototype.call;
+
+ var functionCall = call$q.bind ? call$q.bind(call$q) : function () {
+ return call$q.apply(call$q, arguments);
+ };
+
var objectPropertyIsEnumerable = {};
- var $propertyIsEnumerable$1 = {}.propertyIsEnumerable;
+ var $propertyIsEnumerable$2 = {}.propertyIsEnumerable;
// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
var getOwnPropertyDescriptor$5 = Object.getOwnPropertyDescriptor;
// Nashorn ~ JDK8 bug
- var NASHORN_BUG = getOwnPropertyDescriptor$5 && !$propertyIsEnumerable$1.call({ 1: 2 }, 1);
+ var NASHORN_BUG = getOwnPropertyDescriptor$5 && !$propertyIsEnumerable$2.call({ 1: 2 }, 1);
// `Object.prototype.propertyIsEnumerable` method implementation
// https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
objectPropertyIsEnumerable.f = NASHORN_BUG ? function propertyIsEnumerable(V) {
var descriptor = getOwnPropertyDescriptor$5(this, V);
return !!descriptor && descriptor.enumerable;
- } : $propertyIsEnumerable$1;
+ } : $propertyIsEnumerable$2;
var createPropertyDescriptor$7 = function (bitmap, value) {
return {
};
};
- var toString$2 = {}.toString;
+ var FunctionPrototype$3 = Function.prototype;
+ var bind$h = FunctionPrototype$3.bind;
+ var call$p = FunctionPrototype$3.call;
+ var callBind = bind$h && bind$h.bind(call$p);
+
+ var functionUncurryThis = bind$h ? function (fn) {
+ return fn && callBind(call$p, fn);
+ } : function (fn) {
+ return fn && function () {
+ return call$p.apply(fn, arguments);
+ };
+ };
+
+ var uncurryThis$X = functionUncurryThis;
+
+ var toString$n = uncurryThis$X({}.toString);
+ var stringSlice$c = uncurryThis$X(''.slice);
var classofRaw$1 = function (it) {
- return toString$2.call(it).slice(8, -1);
+ return stringSlice$c(toString$n(it), 8, -1);
};
- var fails$L = fails$N;
- var classof$c = classofRaw$1;
+ var global$1l = global$1m;
+ var uncurryThis$W = functionUncurryThis;
+ var fails$Q = fails$S;
+ var classof$e = classofRaw$1;
- var split$1 = ''.split;
+ var Object$5 = global$1l.Object;
+ var split$4 = uncurryThis$W(''.split);
// fallback for non-array-like ES3 and non-enumerable old V8 strings
- var indexedObject = fails$L(function () {
+ var indexedObject = fails$Q(function () {
// throws an error in rhino, see https://github.com/mozilla/rhino/issues/346
// eslint-disable-next-line no-prototype-builtins -- safe
- return !Object('z').propertyIsEnumerable(0);
+ return !Object$5('z').propertyIsEnumerable(0);
}) ? function (it) {
- return classof$c(it) == 'String' ? split$1.call(it, '') : Object(it);
- } : Object;
+ return classof$e(it) == 'String' ? split$4(it, '') : Object$5(it);
+ } : Object$5;
+
+ var global$1k = global$1m;
+
+ var TypeError$o = global$1k.TypeError;
// `RequireObjectCoercible` abstract operation
// https://tc39.es/ecma262/#sec-requireobjectcoercible
var requireObjectCoercible$e = function (it) {
- if (it == undefined) throw TypeError("Can't call method on " + it);
+ if (it == undefined) throw TypeError$o("Can't call method on " + it);
return it;
};
var IndexedObject$4 = indexedObject;
var requireObjectCoercible$d = requireObjectCoercible$e;
- var toIndexedObject$b = function (it) {
+ var toIndexedObject$c = function (it) {
return IndexedObject$4(requireObjectCoercible$d(it));
};
- var isObject$r = function (it) {
- return typeof it === 'object' ? it !== null : typeof it === 'function';
+ // `IsCallable` abstract operation
+ // https://tc39.es/ecma262/#sec-iscallable
+ var isCallable$r = function (argument) {
+ return typeof argument == 'function';
};
- var isObject$q = isObject$r;
+ var isCallable$q = isCallable$r;
- // `ToPrimitive` abstract operation
- // https://tc39.es/ecma262/#sec-toprimitive
- // instead of the ES6 spec version, we didn't implement @@toPrimitive case
- // and the second argument - flag - preferred type is a string
- var toPrimitive$7 = function (input, PREFERRED_STRING) {
- if (!isObject$q(input)) return input;
+ var isObject$s = function (it) {
+ return typeof it == 'object' ? it !== null : isCallable$q(it);
+ };
+
+ var global$1j = global$1m;
+ var isCallable$p = isCallable$r;
+
+ var aFunction = function (argument) {
+ return isCallable$p(argument) ? argument : undefined;
+ };
+
+ var getBuiltIn$b = function (namespace, method) {
+ return arguments.length < 2 ? aFunction(global$1j[namespace]) : global$1j[namespace] && global$1j[namespace][method];
+ };
+
+ var uncurryThis$V = functionUncurryThis;
+
+ var objectIsPrototypeOf = uncurryThis$V({}.isPrototypeOf);
+
+ var getBuiltIn$a = getBuiltIn$b;
+
+ var engineUserAgent = getBuiltIn$a('navigator', 'userAgent') || '';
+
+ var global$1i = global$1m;
+ var userAgent$7 = engineUserAgent;
+
+ var process$4 = global$1i.process;
+ var Deno = global$1i.Deno;
+ var versions = process$4 && process$4.versions || Deno && Deno.version;
+ var v8 = versions && versions.v8;
+ var match, version$1;
+
+ if (v8) {
+ match = v8.split('.');
+ // in old Chrome, versions of V8 isn't V8 = Chrome / 10
+ // but their correct versions are not interesting for us
+ version$1 = match[0] > 0 && match[0] < 4 ? 1 : +(match[0] + match[1]);
+ }
+
+ // BrowserFS NodeJS `process` polyfill incorrectly set `.v8` to `0.0`
+ // so check `userAgent` even if `.v8` exists, but 0
+ if (!version$1 && userAgent$7) {
+ match = userAgent$7.match(/Edge\/(\d+)/);
+ if (!match || match[1] >= 74) {
+ match = userAgent$7.match(/Chrome\/(\d+)/);
+ if (match) version$1 = +match[1];
+ }
+ }
+
+ var engineV8Version = version$1;
+
+ /* eslint-disable es/no-symbol -- required for testing */
+
+ var V8_VERSION$3 = engineV8Version;
+ var fails$P = fails$S;
+
+ // eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing
+ var nativeSymbol = !!Object.getOwnPropertySymbols && !fails$P(function () {
+ var symbol = Symbol();
+ // Chrome 38 Symbol has incorrect toString conversion
+ // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances
+ return !String(symbol) || !(Object(symbol) instanceof Symbol) ||
+ // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances
+ !Symbol.sham && V8_VERSION$3 && V8_VERSION$3 < 41;
+ });
+
+ /* eslint-disable es/no-symbol -- required for testing */
+
+ var NATIVE_SYMBOL$3 = nativeSymbol;
+
+ var useSymbolAsUid = NATIVE_SYMBOL$3
+ && !Symbol.sham
+ && typeof Symbol.iterator == 'symbol';
+
+ var global$1h = global$1m;
+ var getBuiltIn$9 = getBuiltIn$b;
+ var isCallable$o = isCallable$r;
+ var isPrototypeOf$9 = objectIsPrototypeOf;
+ var USE_SYMBOL_AS_UID$1 = useSymbolAsUid;
+
+ var Object$4 = global$1h.Object;
+
+ var isSymbol$6 = USE_SYMBOL_AS_UID$1 ? function (it) {
+ return typeof it == 'symbol';
+ } : function (it) {
+ var $Symbol = getBuiltIn$9('Symbol');
+ return isCallable$o($Symbol) && isPrototypeOf$9($Symbol.prototype, Object$4(it));
+ };
+
+ var global$1g = global$1m;
+
+ var String$6 = global$1g.String;
+
+ var tryToString$5 = function (argument) {
+ try {
+ return String$6(argument);
+ } catch (error) {
+ return 'Object';
+ }
+ };
+
+ var global$1f = global$1m;
+ var isCallable$n = isCallable$r;
+ var tryToString$4 = tryToString$5;
+
+ var TypeError$n = global$1f.TypeError;
+
+ // `Assert: IsCallable(argument) is true`
+ var aCallable$a = function (argument) {
+ if (isCallable$n(argument)) return argument;
+ throw TypeError$n(tryToString$4(argument) + ' is not a function');
+ };
+
+ var aCallable$9 = aCallable$a;
+
+ // `GetMethod` abstract operation
+ // https://tc39.es/ecma262/#sec-getmethod
+ var getMethod$7 = function (V, P) {
+ var func = V[P];
+ return func == null ? undefined : aCallable$9(func);
+ };
+
+ var global$1e = global$1m;
+ var call$o = functionCall;
+ var isCallable$m = isCallable$r;
+ var isObject$r = isObject$s;
+
+ var TypeError$m = global$1e.TypeError;
+
+ // `OrdinaryToPrimitive` abstract operation
+ // https://tc39.es/ecma262/#sec-ordinarytoprimitive
+ var ordinaryToPrimitive$1 = function (input, pref) {
var fn, val;
- if (PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject$q(val = fn.call(input))) return val;
- if (typeof (fn = input.valueOf) == 'function' && !isObject$q(val = fn.call(input))) return val;
- if (!PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject$q(val = fn.call(input))) return val;
- throw TypeError("Can't convert object to primitive value");
+ if (pref === 'string' && isCallable$m(fn = input.toString) && !isObject$r(val = call$o(fn, input))) return val;
+ if (isCallable$m(fn = input.valueOf) && !isObject$r(val = call$o(fn, input))) return val;
+ if (pref !== 'string' && isCallable$m(fn = input.toString) && !isObject$r(val = call$o(fn, input))) return val;
+ throw TypeError$m("Can't convert object to primitive value");
+ };
+
+ var shared$5 = {exports: {}};
+
+ var isPure = false;
+
+ var global$1d = global$1m;
+
+ // eslint-disable-next-line es/no-object-defineproperty -- safe
+ var defineProperty$b = Object.defineProperty;
+
+ var setGlobal$3 = function (key, value) {
+ try {
+ defineProperty$b(global$1d, key, { value: value, configurable: true, writable: true });
+ } catch (error) {
+ global$1d[key] = value;
+ } return value;
};
+ var global$1c = global$1m;
+ var setGlobal$2 = setGlobal$3;
+
+ var SHARED = '__core-js_shared__';
+ var store$4 = global$1c[SHARED] || setGlobal$2(SHARED, {});
+
+ var sharedStore = store$4;
+
+ var store$3 = sharedStore;
+
+ (shared$5.exports = function (key, value) {
+ return store$3[key] || (store$3[key] = value !== undefined ? value : {});
+ })('versions', []).push({
+ version: '3.19.1',
+ mode: 'global',
+ copyright: '© 2021 Denis Pushkarev (zloirock.ru)'
+ });
+
+ var global$1b = global$1m;
var requireObjectCoercible$c = requireObjectCoercible$e;
+ var Object$3 = global$1b.Object;
+
// `ToObject` abstract operation
// https://tc39.es/ecma262/#sec-toobject
- var toObject$i = function (argument) {
- return Object(requireObjectCoercible$c(argument));
+ var toObject$j = function (argument) {
+ return Object$3(requireObjectCoercible$c(argument));
+ };
+
+ var uncurryThis$U = functionUncurryThis;
+ var toObject$i = toObject$j;
+
+ var hasOwnProperty$3 = uncurryThis$U({}.hasOwnProperty);
+
+ // `HasOwnProperty` abstract operation
+ // https://tc39.es/ecma262/#sec-hasownproperty
+ var hasOwnProperty_1 = Object.hasOwn || function hasOwn(it, key) {
+ return hasOwnProperty$3(toObject$i(it), key);
};
- var toObject$h = toObject$i;
+ var uncurryThis$T = functionUncurryThis;
+
+ var id$2 = 0;
+ var postfix = Math.random();
+ var toString$m = uncurryThis$T(1.0.toString);
+
+ var uid$5 = function (key) {
+ return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString$m(++id$2 + postfix, 36);
+ };
- var hasOwnProperty$3 = {}.hasOwnProperty;
+ var global$1a = global$1m;
+ var shared$4 = shared$5.exports;
+ var hasOwn$l = hasOwnProperty_1;
+ var uid$4 = uid$5;
+ var NATIVE_SYMBOL$2 = nativeSymbol;
+ var USE_SYMBOL_AS_UID = useSymbolAsUid;
- var has$j = Object.hasOwn || function hasOwn(it, key) {
- return hasOwnProperty$3.call(toObject$h(it), key);
+ var WellKnownSymbolsStore$1 = shared$4('wks');
+ var Symbol$3 = global$1a.Symbol;
+ var symbolFor = Symbol$3 && Symbol$3['for'];
+ var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol$3 : Symbol$3 && Symbol$3.withoutSetter || uid$4;
+
+ var wellKnownSymbol$t = function (name) {
+ if (!hasOwn$l(WellKnownSymbolsStore$1, name) || !(NATIVE_SYMBOL$2 || typeof WellKnownSymbolsStore$1[name] == 'string')) {
+ var description = 'Symbol.' + name;
+ if (NATIVE_SYMBOL$2 && hasOwn$l(Symbol$3, name)) {
+ WellKnownSymbolsStore$1[name] = Symbol$3[name];
+ } else if (USE_SYMBOL_AS_UID && symbolFor) {
+ WellKnownSymbolsStore$1[name] = symbolFor(description);
+ } else {
+ WellKnownSymbolsStore$1[name] = createWellKnownSymbol(description);
+ }
+ } return WellKnownSymbolsStore$1[name];
};
- var global$E = global$F;
- var isObject$p = isObject$r;
+ var global$19 = global$1m;
+ var call$n = functionCall;
+ var isObject$q = isObject$s;
+ var isSymbol$5 = isSymbol$6;
+ var getMethod$6 = getMethod$7;
+ var ordinaryToPrimitive = ordinaryToPrimitive$1;
+ var wellKnownSymbol$s = wellKnownSymbol$t;
+
+ var TypeError$l = global$19.TypeError;
+ var TO_PRIMITIVE$1 = wellKnownSymbol$s('toPrimitive');
+
+ // `ToPrimitive` abstract operation
+ // https://tc39.es/ecma262/#sec-toprimitive
+ var toPrimitive$3 = function (input, pref) {
+ if (!isObject$q(input) || isSymbol$5(input)) return input;
+ var exoticToPrim = getMethod$6(input, TO_PRIMITIVE$1);
+ var result;
+ if (exoticToPrim) {
+ if (pref === undefined) pref = 'default';
+ result = call$n(exoticToPrim, input, pref);
+ if (!isObject$q(result) || isSymbol$5(result)) return result;
+ throw TypeError$l("Can't convert object to primitive value");
+ }
+ if (pref === undefined) pref = 'number';
+ return ordinaryToPrimitive(input, pref);
+ };
- var document$3 = global$E.document;
+ var toPrimitive$2 = toPrimitive$3;
+ var isSymbol$4 = isSymbol$6;
+
+ // `ToPropertyKey` abstract operation
+ // https://tc39.es/ecma262/#sec-topropertykey
+ var toPropertyKey$5 = function (argument) {
+ var key = toPrimitive$2(argument, 'string');
+ return isSymbol$4(key) ? key : key + '';
+ };
+
+ var global$18 = global$1m;
+ var isObject$p = isObject$s;
+
+ var document$3 = global$18.document;
// typeof document.createElement is 'object' in old IE
- var EXISTS = isObject$p(document$3) && isObject$p(document$3.createElement);
+ var EXISTS$1 = isObject$p(document$3) && isObject$p(document$3.createElement);
- var documentCreateElement$1 = function (it) {
- return EXISTS ? document$3.createElement(it) : {};
+ var documentCreateElement$2 = function (it) {
+ return EXISTS$1 ? document$3.createElement(it) : {};
};
- var DESCRIPTORS$m = descriptors;
- var fails$K = fails$N;
- var createElement$1 = documentCreateElement$1;
+ var DESCRIPTORS$n = descriptors;
+ var fails$O = fails$S;
+ var createElement$1 = documentCreateElement$2;
// Thank's IE8 for his funny defineProperty
- var ie8DomDefine = !DESCRIPTORS$m && !fails$K(function () {
+ var ie8DomDefine = !DESCRIPTORS$n && !fails$O(function () {
// eslint-disable-next-line es/no-object-defineproperty -- requied for testing
return Object.defineProperty(createElement$1('div'), 'a', {
get: function () { return 7; }
}).a != 7;
});
- var DESCRIPTORS$l = descriptors;
+ var DESCRIPTORS$m = descriptors;
+ var call$m = functionCall;
var propertyIsEnumerableModule$2 = objectPropertyIsEnumerable;
var createPropertyDescriptor$6 = createPropertyDescriptor$7;
- var toIndexedObject$a = toIndexedObject$b;
- var toPrimitive$6 = toPrimitive$7;
- var has$i = has$j;
+ var toIndexedObject$b = toIndexedObject$c;
+ var toPropertyKey$4 = toPropertyKey$5;
+ var hasOwn$k = hasOwnProperty_1;
var IE8_DOM_DEFINE$1 = ie8DomDefine;
// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
// `Object.getOwnPropertyDescriptor` method
// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
- objectGetOwnPropertyDescriptor.f = DESCRIPTORS$l ? $getOwnPropertyDescriptor$1 : function getOwnPropertyDescriptor(O, P) {
- O = toIndexedObject$a(O);
- P = toPrimitive$6(P, true);
+ objectGetOwnPropertyDescriptor.f = DESCRIPTORS$m ? $getOwnPropertyDescriptor$1 : function getOwnPropertyDescriptor(O, P) {
+ O = toIndexedObject$b(O);
+ P = toPropertyKey$4(P);
if (IE8_DOM_DEFINE$1) try {
return $getOwnPropertyDescriptor$1(O, P);
} catch (error) { /* empty */ }
- if (has$i(O, P)) return createPropertyDescriptor$6(!propertyIsEnumerableModule$2.f.call(O, P), O[P]);
+ if (hasOwn$k(O, P)) return createPropertyDescriptor$6(!call$m(propertyIsEnumerableModule$2.f, O, P), O[P]);
};
var objectDefineProperty = {};
- var isObject$o = isObject$r;
+ var global$17 = global$1m;
+ var isObject$o = isObject$s;
- var anObject$m = function (it) {
- if (!isObject$o(it)) {
- throw TypeError(String(it) + ' is not an object');
- } return it;
+ var String$5 = global$17.String;
+ var TypeError$k = global$17.TypeError;
+
+ // `Assert: Type(argument) is Object`
+ var anObject$n = function (argument) {
+ if (isObject$o(argument)) return argument;
+ throw TypeError$k(String$5(argument) + ' is not an object');
};
- var DESCRIPTORS$k = descriptors;
+ var global$16 = global$1m;
+ var DESCRIPTORS$l = descriptors;
var IE8_DOM_DEFINE = ie8DomDefine;
- var anObject$l = anObject$m;
- var toPrimitive$5 = toPrimitive$7;
+ var anObject$m = anObject$n;
+ var toPropertyKey$3 = toPropertyKey$5;
+ var TypeError$j = global$16.TypeError;
// eslint-disable-next-line es/no-object-defineproperty -- safe
var $defineProperty$1 = Object.defineProperty;
// `Object.defineProperty` method
// https://tc39.es/ecma262/#sec-object.defineproperty
- objectDefineProperty.f = DESCRIPTORS$k ? $defineProperty$1 : function defineProperty(O, P, Attributes) {
- anObject$l(O);
- P = toPrimitive$5(P, true);
- anObject$l(Attributes);
+ objectDefineProperty.f = DESCRIPTORS$l ? $defineProperty$1 : function defineProperty(O, P, Attributes) {
+ anObject$m(O);
+ P = toPropertyKey$3(P);
+ anObject$m(Attributes);
if (IE8_DOM_DEFINE) try {
return $defineProperty$1(O, P, Attributes);
} catch (error) { /* empty */ }
- if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported');
+ if ('get' in Attributes || 'set' in Attributes) throw TypeError$j('Accessors not supported');
if ('value' in Attributes) O[P] = Attributes.value;
return O;
};
- var DESCRIPTORS$j = descriptors;
+ var DESCRIPTORS$k = descriptors;
var definePropertyModule$7 = objectDefineProperty;
var createPropertyDescriptor$5 = createPropertyDescriptor$7;
- var createNonEnumerableProperty$e = DESCRIPTORS$j ? function (object, key, value) {
+ var createNonEnumerableProperty$b = DESCRIPTORS$k ? function (object, key, value) {
return definePropertyModule$7.f(object, key, createPropertyDescriptor$5(1, value));
} : function (object, key, value) {
object[key] = value;
return object;
};
- var redefine$g = {exports: {}};
-
- var global$D = global$F;
- var createNonEnumerableProperty$d = createNonEnumerableProperty$e;
-
- var setGlobal$3 = function (key, value) {
- try {
- createNonEnumerableProperty$d(global$D, key, value);
- } catch (error) {
- global$D[key] = value;
- } return value;
- };
-
- var global$C = global$F;
- var setGlobal$2 = setGlobal$3;
-
- var SHARED = '__core-js_shared__';
- var store$4 = global$C[SHARED] || setGlobal$2(SHARED, {});
-
- var sharedStore = store$4;
+ var redefine$h = {exports: {}};
- var store$3 = sharedStore;
+ var uncurryThis$S = functionUncurryThis;
+ var isCallable$l = isCallable$r;
+ var store$2 = sharedStore;
- var functionToString = Function.toString;
+ var functionToString$1 = uncurryThis$S(Function.toString);
// this helper broken in `core-js@3.4.1-3.4.4`, so we can't use `shared` helper
- if (typeof store$3.inspectSource != 'function') {
- store$3.inspectSource = function (it) {
- return functionToString.call(it);
+ if (!isCallable$l(store$2.inspectSource)) {
+ store$2.inspectSource = function (it) {
+ return functionToString$1(it);
};
}
- var inspectSource$3 = store$3.inspectSource;
-
- var global$B = global$F;
- var inspectSource$2 = inspectSource$3;
-
- var WeakMap$1 = global$B.WeakMap;
-
- var nativeWeakMap = typeof WeakMap$1 === 'function' && /native code/.test(inspectSource$2(WeakMap$1));
-
- var shared$5 = {exports: {}};
-
- var isPure = false;
-
- var store$2 = sharedStore;
+ var inspectSource$4 = store$2.inspectSource;
- (shared$5.exports = function (key, value) {
- return store$2[key] || (store$2[key] = value !== undefined ? value : {});
- })('versions', []).push({
- version: '3.15.0',
- mode: 'global',
- copyright: '© 2021 Denis Pushkarev (zloirock.ru)'
- });
+ var global$15 = global$1m;
+ var isCallable$k = isCallable$r;
+ var inspectSource$3 = inspectSource$4;
- var id$2 = 0;
- var postfix = Math.random();
+ var WeakMap$1 = global$15.WeakMap;
- var uid$5 = function (key) {
- return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id$2 + postfix).toString(36);
- };
+ var nativeWeakMap = isCallable$k(WeakMap$1) && /native code/.test(inspectSource$3(WeakMap$1));
- var shared$4 = shared$5.exports;
- var uid$4 = uid$5;
+ var shared$3 = shared$5.exports;
+ var uid$3 = uid$5;
- var keys$3 = shared$4('keys');
+ var keys$3 = shared$3('keys');
var sharedKey$4 = function (key) {
- return keys$3[key] || (keys$3[key] = uid$4(key));
+ return keys$3[key] || (keys$3[key] = uid$3(key));
};
var hiddenKeys$6 = {};
var NATIVE_WEAK_MAP = nativeWeakMap;
- var global$A = global$F;
- var isObject$n = isObject$r;
- var createNonEnumerableProperty$c = createNonEnumerableProperty$e;
- var objectHas = has$j;
- var shared$3 = sharedStore;
+ var global$14 = global$1m;
+ var uncurryThis$R = functionUncurryThis;
+ var isObject$n = isObject$s;
+ var createNonEnumerableProperty$a = createNonEnumerableProperty$b;
+ var hasOwn$j = hasOwnProperty_1;
+ var shared$2 = sharedStore;
var sharedKey$3 = sharedKey$4;
var hiddenKeys$5 = hiddenKeys$6;
var OBJECT_ALREADY_INITIALIZED = 'Object already initialized';
- var WeakMap = global$A.WeakMap;
- var set$4, get$5, has$h;
+ var TypeError$i = global$14.TypeError;
+ var WeakMap = global$14.WeakMap;
+ var set$4, get$5, has;
var enforce = function (it) {
- return has$h(it) ? get$5(it) : set$4(it, {});
+ return has(it) ? get$5(it) : set$4(it, {});
};
var getterFor = function (TYPE) {
return function (it) {
var state;
if (!isObject$n(it) || (state = get$5(it)).type !== TYPE) {
- throw TypeError('Incompatible receiver, ' + TYPE + ' required');
+ throw TypeError$i('Incompatible receiver, ' + TYPE + ' required');
} return state;
};
};
- if (NATIVE_WEAK_MAP || shared$3.state) {
- var store$1 = shared$3.state || (shared$3.state = new WeakMap());
- var wmget = store$1.get;
- var wmhas = store$1.has;
- var wmset = store$1.set;
+ if (NATIVE_WEAK_MAP || shared$2.state) {
+ var store$1 = shared$2.state || (shared$2.state = new WeakMap());
+ var wmget = uncurryThis$R(store$1.get);
+ var wmhas = uncurryThis$R(store$1.has);
+ var wmset = uncurryThis$R(store$1.set);
set$4 = function (it, metadata) {
- if (wmhas.call(store$1, it)) throw new TypeError(OBJECT_ALREADY_INITIALIZED);
+ if (wmhas(store$1, it)) throw new TypeError$i(OBJECT_ALREADY_INITIALIZED);
metadata.facade = it;
- wmset.call(store$1, it, metadata);
+ wmset(store$1, it, metadata);
return metadata;
};
get$5 = function (it) {
- return wmget.call(store$1, it) || {};
+ return wmget(store$1, it) || {};
};
- has$h = function (it) {
- return wmhas.call(store$1, it);
+ has = function (it) {
+ return wmhas(store$1, it);
};
} else {
var STATE = sharedKey$3('state');
hiddenKeys$5[STATE] = true;
set$4 = function (it, metadata) {
- if (objectHas(it, STATE)) throw new TypeError(OBJECT_ALREADY_INITIALIZED);
+ if (hasOwn$j(it, STATE)) throw new TypeError$i(OBJECT_ALREADY_INITIALIZED);
metadata.facade = it;
- createNonEnumerableProperty$c(it, STATE, metadata);
+ createNonEnumerableProperty$a(it, STATE, metadata);
return metadata;
};
get$5 = function (it) {
- return objectHas(it, STATE) ? it[STATE] : {};
+ return hasOwn$j(it, STATE) ? it[STATE] : {};
};
- has$h = function (it) {
- return objectHas(it, STATE);
+ has = function (it) {
+ return hasOwn$j(it, STATE);
};
}
var internalState = {
set: set$4,
get: get$5,
- has: has$h,
+ has: has,
enforce: enforce,
getterFor: getterFor
};
- var global$z = global$F;
- var createNonEnumerableProperty$b = createNonEnumerableProperty$e;
- var has$g = has$j;
+ var DESCRIPTORS$j = descriptors;
+ var hasOwn$i = hasOwnProperty_1;
+
+ var FunctionPrototype$2 = Function.prototype;
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ var getDescriptor = DESCRIPTORS$j && Object.getOwnPropertyDescriptor;
+
+ var EXISTS = hasOwn$i(FunctionPrototype$2, 'name');
+ // additional protection from minified / mangled / dropped function names
+ var PROPER = EXISTS && (function something() { /* empty */ }).name === 'something';
+ var CONFIGURABLE = EXISTS && (!DESCRIPTORS$j || (DESCRIPTORS$j && getDescriptor(FunctionPrototype$2, 'name').configurable));
+
+ var functionName = {
+ EXISTS: EXISTS,
+ PROPER: PROPER,
+ CONFIGURABLE: CONFIGURABLE
+ };
+
+ var global$13 = global$1m;
+ var isCallable$j = isCallable$r;
+ var hasOwn$h = hasOwnProperty_1;
+ var createNonEnumerableProperty$9 = createNonEnumerableProperty$b;
var setGlobal$1 = setGlobal$3;
- var inspectSource$1 = inspectSource$3;
+ var inspectSource$2 = inspectSource$4;
var InternalStateModule$9 = internalState;
+ var CONFIGURABLE_FUNCTION_NAME$2 = functionName.CONFIGURABLE;
var getInternalState$7 = InternalStateModule$9.get;
var enforceInternalState$1 = InternalStateModule$9.enforce;
var TEMPLATE = String(String).split('String');
- (redefine$g.exports = function (O, key, value, options) {
+ (redefine$h.exports = function (O, key, value, options) {
var unsafe = options ? !!options.unsafe : false;
var simple = options ? !!options.enumerable : false;
var noTargetGet = options ? !!options.noTargetGet : false;
+ var name = options && options.name !== undefined ? options.name : key;
var state;
- if (typeof value == 'function') {
- if (typeof key == 'string' && !has$g(value, 'name')) {
- createNonEnumerableProperty$b(value, 'name', key);
+ if (isCallable$j(value)) {
+ if (String(name).slice(0, 7) === 'Symbol(') {
+ name = '[' + String(name).replace(/^Symbol\(([^)]*)\)/, '$1') + ']';
+ }
+ if (!hasOwn$h(value, 'name') || (CONFIGURABLE_FUNCTION_NAME$2 && value.name !== name)) {
+ createNonEnumerableProperty$9(value, 'name', name);
}
state = enforceInternalState$1(value);
if (!state.source) {
- state.source = TEMPLATE.join(typeof key == 'string' ? key : '');
+ state.source = TEMPLATE.join(typeof name == 'string' ? name : '');
}
}
- if (O === global$z) {
+ if (O === global$13) {
if (simple) O[key] = value;
else setGlobal$1(key, value);
return;
simple = true;
}
if (simple) O[key] = value;
- else createNonEnumerableProperty$b(O, key, value);
+ else createNonEnumerableProperty$9(O, key, value);
// add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
})(Function.prototype, 'toString', function toString() {
- return typeof this == 'function' && getInternalState$7(this).source || inspectSource$1(this);
+ return isCallable$j(this) && getInternalState$7(this).source || inspectSource$2(this);
});
- var global$y = global$F;
-
- var path$2 = global$y;
-
- var path$1 = path$2;
- var global$x = global$F;
-
- var aFunction$a = function (variable) {
- return typeof variable == 'function' ? variable : undefined;
- };
+ var objectGetOwnPropertyNames = {};
- var getBuiltIn$9 = function (namespace, method) {
- return arguments.length < 2 ? aFunction$a(path$1[namespace]) || aFunction$a(global$x[namespace])
- : path$1[namespace] && path$1[namespace][method] || global$x[namespace] && global$x[namespace][method];
+ var ceil$1 = Math.ceil;
+ var floor$8 = Math.floor;
+
+ // `ToIntegerOrInfinity` abstract operation
+ // https://tc39.es/ecma262/#sec-tointegerorinfinity
+ var toIntegerOrInfinity$b = function (argument) {
+ var number = +argument;
+ // eslint-disable-next-line no-self-compare -- safe
+ return number !== number || number === 0 ? 0 : (number > 0 ? floor$8 : ceil$1)(number);
};
- var objectGetOwnPropertyNames = {};
+ var toIntegerOrInfinity$a = toIntegerOrInfinity$b;
- var ceil$1 = Math.ceil;
- var floor$7 = Math.floor;
+ var max$4 = Math.max;
+ var min$9 = Math.min;
- // `ToInteger` abstract operation
- // https://tc39.es/ecma262/#sec-tointeger
- var toInteger$b = function (argument) {
- return isNaN(argument = +argument) ? 0 : (argument > 0 ? floor$7 : ceil$1)(argument);
+ // Helper for a popular repeating case of the spec:
+ // Let integer be ? ToInteger(index).
+ // If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length).
+ var toAbsoluteIndex$8 = function (index, length) {
+ var integer = toIntegerOrInfinity$a(index);
+ return integer < 0 ? max$4(integer + length, 0) : min$9(integer, length);
};
- var toInteger$a = toInteger$b;
+ var toIntegerOrInfinity$9 = toIntegerOrInfinity$b;
- var min$9 = Math.min;
+ var min$8 = Math.min;
// `ToLength` abstract operation
// https://tc39.es/ecma262/#sec-tolength
- var toLength$q = function (argument) {
- return argument > 0 ? min$9(toInteger$a(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991
+ var toLength$c = function (argument) {
+ return argument > 0 ? min$8(toIntegerOrInfinity$9(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991
};
- var toInteger$9 = toInteger$b;
-
- var max$4 = Math.max;
- var min$8 = Math.min;
+ var toLength$b = toLength$c;
- // Helper for a popular repeating case of the spec:
- // Let integer be ? ToInteger(index).
- // If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length).
- var toAbsoluteIndex$8 = function (index, length) {
- var integer = toInteger$9(index);
- return integer < 0 ? max$4(integer + length, 0) : min$8(integer, length);
+ // `LengthOfArrayLike` abstract operation
+ // https://tc39.es/ecma262/#sec-lengthofarraylike
+ var lengthOfArrayLike$g = function (obj) {
+ return toLength$b(obj.length);
};
- var toIndexedObject$9 = toIndexedObject$b;
- var toLength$p = toLength$q;
+ var toIndexedObject$a = toIndexedObject$c;
var toAbsoluteIndex$7 = toAbsoluteIndex$8;
+ var lengthOfArrayLike$f = lengthOfArrayLike$g;
// `Array.prototype.{ indexOf, includes }` methods implementation
var createMethod$6 = function (IS_INCLUDES) {
return function ($this, el, fromIndex) {
- var O = toIndexedObject$9($this);
- var length = toLength$p(O.length);
+ var O = toIndexedObject$a($this);
+ var length = lengthOfArrayLike$f(O);
var index = toAbsoluteIndex$7(fromIndex, length);
var value;
// Array#includes uses SameValueZero equality algorithm
indexOf: createMethod$6(false)
};
- var has$f = has$j;
- var toIndexedObject$8 = toIndexedObject$b;
- var indexOf = arrayIncludes.indexOf;
+ var uncurryThis$Q = functionUncurryThis;
+ var hasOwn$g = hasOwnProperty_1;
+ var toIndexedObject$9 = toIndexedObject$c;
+ var indexOf$1 = arrayIncludes.indexOf;
var hiddenKeys$4 = hiddenKeys$6;
+ var push$a = uncurryThis$Q([].push);
+
var objectKeysInternal = function (object, names) {
- var O = toIndexedObject$8(object);
+ var O = toIndexedObject$9(object);
var i = 0;
var result = [];
var key;
- for (key in O) !has$f(hiddenKeys$4, key) && has$f(O, key) && result.push(key);
+ for (key in O) !hasOwn$g(hiddenKeys$4, key) && hasOwn$g(O, key) && push$a(result, key);
// Don't enum bug & hidden keys
- while (names.length > i) if (has$f(O, key = names[i++])) {
- ~indexOf(result, key) || result.push(key);
+ while (names.length > i) if (hasOwn$g(O, key = names[i++])) {
+ ~indexOf$1(result, key) || push$a(result, key);
}
return result;
};
// eslint-disable-next-line es/no-object-getownpropertysymbols -- safe
objectGetOwnPropertySymbols.f = Object.getOwnPropertySymbols;
- var getBuiltIn$8 = getBuiltIn$9;
- var getOwnPropertyNamesModule$1 = objectGetOwnPropertyNames;
+ var getBuiltIn$8 = getBuiltIn$b;
+ var uncurryThis$P = functionUncurryThis;
+ var getOwnPropertyNamesModule$2 = objectGetOwnPropertyNames;
var getOwnPropertySymbolsModule$2 = objectGetOwnPropertySymbols;
- var anObject$k = anObject$m;
+ var anObject$l = anObject$n;
+
+ var concat$3 = uncurryThis$P([].concat);
// all object keys, includes non-enumerable and symbols
var ownKeys$1 = getBuiltIn$8('Reflect', 'ownKeys') || function ownKeys(it) {
- var keys = getOwnPropertyNamesModule$1.f(anObject$k(it));
+ var keys = getOwnPropertyNamesModule$2.f(anObject$l(it));
var getOwnPropertySymbols = getOwnPropertySymbolsModule$2.f;
- return getOwnPropertySymbols ? keys.concat(getOwnPropertySymbols(it)) : keys;
+ return getOwnPropertySymbols ? concat$3(keys, getOwnPropertySymbols(it)) : keys;
};
- var has$e = has$j;
+ var hasOwn$f = hasOwnProperty_1;
var ownKeys = ownKeys$1;
var getOwnPropertyDescriptorModule$3 = objectGetOwnPropertyDescriptor;
var definePropertyModule$6 = objectDefineProperty;
var getOwnPropertyDescriptor = getOwnPropertyDescriptorModule$3.f;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
- if (!has$e(target, key)) defineProperty(target, key, getOwnPropertyDescriptor(source, key));
+ if (!hasOwn$f(target, key)) defineProperty(target, key, getOwnPropertyDescriptor(source, key));
}
};
- var fails$J = fails$N;
+ var fails$N = fails$S;
+ var isCallable$i = isCallable$r;
var replacement = /#|\.prototype\./;
var value = data[normalize$1(feature)];
return value == POLYFILL ? true
: value == NATIVE ? false
- : typeof detection == 'function' ? fails$J(detection)
+ : isCallable$i(detection) ? fails$N(detection)
: !!detection;
};
var isForced_1 = isForced$5;
- var global$w = global$F;
+ var global$12 = global$1m;
var getOwnPropertyDescriptor$4 = objectGetOwnPropertyDescriptor.f;
- var createNonEnumerableProperty$a = createNonEnumerableProperty$e;
- var redefine$f = redefine$g.exports;
+ var createNonEnumerableProperty$8 = createNonEnumerableProperty$b;
+ var redefine$g = redefine$h.exports;
var setGlobal = setGlobal$3;
var copyConstructorProperties$1 = copyConstructorProperties$2;
var isForced$4 = isForced_1;
options.sham - add a flag to not completely full polyfills
options.enumerable - export as enumerable property
options.noTargetGet - prevent calling a getter on target
+ options.name - the .name of the function if it does not match the key
*/
var _export = function (options, source) {
var TARGET = options.target;
var STATIC = options.stat;
var FORCED, target, key, targetProperty, sourceProperty, descriptor;
if (GLOBAL) {
- target = global$w;
+ target = global$12;
} else if (STATIC) {
- target = global$w[TARGET] || setGlobal(TARGET, {});
+ target = global$12[TARGET] || setGlobal(TARGET, {});
} else {
- target = (global$w[TARGET] || {}).prototype;
+ target = (global$12[TARGET] || {}).prototype;
}
if (target) for (key in source) {
sourceProperty = source[key];
FORCED = isForced$4(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);
// contained in target
if (!FORCED && targetProperty !== undefined) {
- if (typeof sourceProperty === typeof targetProperty) continue;
+ if (typeof sourceProperty == typeof targetProperty) continue;
copyConstructorProperties$1(sourceProperty, targetProperty);
}
// add a flag to not completely full polyfills
if (options.sham || (targetProperty && targetProperty.sham)) {
- createNonEnumerableProperty$a(sourceProperty, 'sham', true);
+ createNonEnumerableProperty$8(sourceProperty, 'sham', true);
}
// extend global
- redefine$f(target, key, sourceProperty, options);
+ redefine$g(target, key, sourceProperty, options);
}
};
- var $$16 = _export;
+ var $$1e = _export;
+ var global$11 = global$1m;
+ var uncurryThis$O = functionUncurryThis;
+
+ var Date$1 = global$11.Date;
+ var getTime$2 = uncurryThis$O(Date$1.prototype.getTime);
// `Date.now` method
// https://tc39.es/ecma262/#sec-date.now
- $$16({ target: 'Date', stat: true }, {
+ $$1e({ target: 'Date', stat: true }, {
now: function now() {
- return new Date().getTime();
+ return getTime$2(new Date$1());
}
});
- var redefine$e = redefine$g.exports;
+ var uncurryThis$N = functionUncurryThis;
+ var redefine$f = redefine$h.exports;
var DatePrototype$1 = Date.prototype;
var INVALID_DATE = 'Invalid Date';
var TO_STRING$1 = 'toString';
- var nativeDateToString = DatePrototype$1[TO_STRING$1];
- var getTime$1 = DatePrototype$1.getTime;
+ var un$DateToString = uncurryThis$N(DatePrototype$1[TO_STRING$1]);
+ var getTime$1 = uncurryThis$N(DatePrototype$1.getTime);
// `Date.prototype.toString` method
// https://tc39.es/ecma262/#sec-date.prototype.tostring
- if (new Date(NaN) + '' != INVALID_DATE) {
- redefine$e(DatePrototype$1, TO_STRING$1, function toString() {
- var value = getTime$1.call(this);
+ if (String(new Date(NaN)) != INVALID_DATE) {
+ redefine$f(DatePrototype$1, TO_STRING$1, function toString() {
+ var value = getTime$1(this);
// eslint-disable-next-line no-self-compare -- NaN check
- return value === value ? nativeDateToString.call(this) : INVALID_DATE;
+ return value === value ? un$DateToString(this) : INVALID_DATE;
});
}
};
}
- var wellKnownSymbolWrapped = {};
-
- var getBuiltIn$7 = getBuiltIn$9;
-
- var engineUserAgent = getBuiltIn$7('navigator', 'userAgent') || '';
-
- var global$v = global$F;
- var userAgent$5 = engineUserAgent;
-
- var process$4 = global$v.process;
- var versions = process$4 && process$4.versions;
- var v8 = versions && versions.v8;
- var match, version$1;
-
- if (v8) {
- match = v8.split('.');
- version$1 = match[0] < 4 ? 1 : match[0] + match[1];
- } else if (userAgent$5) {
- match = userAgent$5.match(/Edge\/(\d+)/);
- if (!match || match[1] >= 74) {
- match = userAgent$5.match(/Chrome\/(\d+)/);
- if (match) version$1 = match[1];
- }
- }
-
- var engineV8Version = version$1 && +version$1;
-
- /* eslint-disable es/no-symbol -- required for testing */
-
- var V8_VERSION$3 = engineV8Version;
- var fails$I = fails$N;
+ var $$1d = _export;
+ var global$10 = global$1m;
- // eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing
- var nativeSymbol = !!Object.getOwnPropertySymbols && !fails$I(function () {
- var symbol = Symbol();
- // Chrome 38 Symbol has incorrect toString conversion
- // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances
- return !String(symbol) || !(Object(symbol) instanceof Symbol) ||
- // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances
- !Symbol.sham && V8_VERSION$3 && V8_VERSION$3 < 41;
+ // `globalThis` object
+ // https://tc39.es/ecma262/#sec-globalthis
+ $$1d({ global: true }, {
+ globalThis: global$10
});
- /* eslint-disable es/no-symbol -- required for testing */
-
- var NATIVE_SYMBOL$2 = nativeSymbol;
-
- var useSymbolAsUid = NATIVE_SYMBOL$2
- && !Symbol.sham
- && typeof Symbol.iterator == 'symbol';
-
- var global$u = global$F;
- var shared$2 = shared$5.exports;
- var has$d = has$j;
- var uid$3 = uid$5;
- var NATIVE_SYMBOL$1 = nativeSymbol;
- var USE_SYMBOL_AS_UID$1 = useSymbolAsUid;
+ var global$$ = global$1m;
- var WellKnownSymbolsStore$1 = shared$2('wks');
- var Symbol$1 = global$u.Symbol;
- var createWellKnownSymbol = USE_SYMBOL_AS_UID$1 ? Symbol$1 : Symbol$1 && Symbol$1.withoutSetter || uid$3;
+ var path$1 = global$$;
- var wellKnownSymbol$s = function (name) {
- if (!has$d(WellKnownSymbolsStore$1, name) || !(NATIVE_SYMBOL$1 || typeof WellKnownSymbolsStore$1[name] == 'string')) {
- if (NATIVE_SYMBOL$1 && has$d(Symbol$1, name)) {
- WellKnownSymbolsStore$1[name] = Symbol$1[name];
- } else {
- WellKnownSymbolsStore$1[name] = createWellKnownSymbol('Symbol.' + name);
- }
- } return WellKnownSymbolsStore$1[name];
- };
+ var wellKnownSymbolWrapped = {};
- var wellKnownSymbol$r = wellKnownSymbol$s;
+ var wellKnownSymbol$r = wellKnownSymbol$t;
wellKnownSymbolWrapped.f = wellKnownSymbol$r;
- var path = path$2;
- var has$c = has$j;
+ var path = path$1;
+ var hasOwn$e = hasOwnProperty_1;
var wrappedWellKnownSymbolModule$1 = wellKnownSymbolWrapped;
var defineProperty$a = objectDefineProperty.f;
var defineWellKnownSymbol$4 = function (NAME) {
var Symbol = path.Symbol || (path.Symbol = {});
- if (!has$c(Symbol, NAME)) defineProperty$a(Symbol, NAME, {
+ if (!hasOwn$e(Symbol, NAME)) defineProperty$a(Symbol, NAME, {
value: wrappedWellKnownSymbolModule$1.f(NAME)
});
};
var DESCRIPTORS$i = descriptors;
var definePropertyModule$5 = objectDefineProperty;
- var anObject$j = anObject$m;
+ var anObject$k = anObject$n;
+ var toIndexedObject$8 = toIndexedObject$c;
var objectKeys$3 = objectKeys$4;
// `Object.defineProperties` method
// https://tc39.es/ecma262/#sec-object.defineproperties
// eslint-disable-next-line es/no-object-defineproperties -- safe
var objectDefineProperties = DESCRIPTORS$i ? Object.defineProperties : function defineProperties(O, Properties) {
- anObject$j(O);
+ anObject$k(O);
+ var props = toIndexedObject$8(Properties);
var keys = objectKeys$3(Properties);
var length = keys.length;
var index = 0;
var key;
- while (length > index) definePropertyModule$5.f(O, key = keys[index++], Properties[key]);
+ while (length > index) definePropertyModule$5.f(O, key = keys[index++], props[key]);
return O;
};
- var getBuiltIn$6 = getBuiltIn$9;
+ var getBuiltIn$7 = getBuiltIn$b;
+
+ var html$2 = getBuiltIn$7('document', 'documentElement');
- var html$2 = getBuiltIn$6('document', 'documentElement');
+ /* global ActiveXObject -- old IE, WSH */
- var anObject$i = anObject$m;
+ var anObject$j = anObject$n;
var defineProperties$2 = objectDefineProperties;
var enumBugKeys = enumBugKeys$3;
var hiddenKeys$2 = hiddenKeys$6;
var html$1 = html$2;
- var documentCreateElement = documentCreateElement$1;
+ var documentCreateElement$1 = documentCreateElement$2;
var sharedKey$2 = sharedKey$4;
var GT = '>';
// Create object with fake `null` prototype: use iframe Object with cleared prototype
var NullProtoObjectViaIFrame = function () {
// Thrash, waste and sodomy: IE GC bug
- var iframe = documentCreateElement('iframe');
+ var iframe = documentCreateElement$1('iframe');
var JS = 'java' + SCRIPT + ':';
var iframeDocument;
iframe.style.display = 'none';
var activeXDocument;
var NullProtoObject = function () {
try {
- /* global ActiveXObject -- old IE */
- activeXDocument = document.domain && new ActiveXObject('htmlfile');
+ activeXDocument = new ActiveXObject('htmlfile');
} catch (error) { /* ignore */ }
- NullProtoObject = activeXDocument ? NullProtoObjectViaActiveX(activeXDocument) : NullProtoObjectViaIFrame();
+ NullProtoObject = typeof document != 'undefined'
+ ? document.domain && activeXDocument
+ ? NullProtoObjectViaActiveX(activeXDocument) // old IE
+ : NullProtoObjectViaIFrame()
+ : NullProtoObjectViaActiveX(activeXDocument); // WSH
var length = enumBugKeys.length;
while (length--) delete NullProtoObject[PROTOTYPE$2][enumBugKeys[length]];
return NullProtoObject();
var objectCreate = Object.create || function create(O, Properties) {
var result;
if (O !== null) {
- EmptyConstructor[PROTOTYPE$2] = anObject$i(O);
+ EmptyConstructor[PROTOTYPE$2] = anObject$j(O);
result = new EmptyConstructor();
EmptyConstructor[PROTOTYPE$2] = null;
// add "__proto__" for Object.getPrototypeOf polyfill
return Properties === undefined ? result : defineProperties$2(result, Properties);
};
- var wellKnownSymbol$q = wellKnownSymbol$s;
- var create$b = objectCreate;
+ var wellKnownSymbol$q = wellKnownSymbol$t;
+ var create$a = objectCreate;
var definePropertyModule$4 = objectDefineProperty;
var UNSCOPABLES = wellKnownSymbol$q('unscopables');
if (ArrayPrototype$1[UNSCOPABLES] == undefined) {
definePropertyModule$4.f(ArrayPrototype$1, UNSCOPABLES, {
configurable: true,
- value: create$b(null)
+ value: create$a(null)
});
}
// add a key to Array.prototype[@@unscopables]
- var addToUnscopables$5 = function (key) {
+ var addToUnscopables$6 = function (key) {
ArrayPrototype$1[UNSCOPABLES][key] = true;
};
var iterators = {};
- var fails$H = fails$N;
+ var fails$M = fails$S;
- var correctPrototypeGetter = !fails$H(function () {
+ var correctPrototypeGetter = !fails$M(function () {
function F() { /* empty */ }
F.prototype.constructor = null;
// eslint-disable-next-line es/no-object-getprototypeof -- required for testing
return Object.getPrototypeOf(new F()) !== F.prototype;
});
- var has$b = has$j;
- var toObject$g = toObject$i;
+ var global$_ = global$1m;
+ var hasOwn$d = hasOwnProperty_1;
+ var isCallable$h = isCallable$r;
+ var toObject$h = toObject$j;
var sharedKey$1 = sharedKey$4;
var CORRECT_PROTOTYPE_GETTER$1 = correctPrototypeGetter;
var IE_PROTO = sharedKey$1('IE_PROTO');
- var ObjectPrototype$3 = Object.prototype;
+ var Object$2 = global$_.Object;
+ var ObjectPrototype$4 = Object$2.prototype;
// `Object.getPrototypeOf` method
// https://tc39.es/ecma262/#sec-object.getprototypeof
- // eslint-disable-next-line es/no-object-getprototypeof -- safe
- var objectGetPrototypeOf = CORRECT_PROTOTYPE_GETTER$1 ? Object.getPrototypeOf : function (O) {
- O = toObject$g(O);
- if (has$b(O, IE_PROTO)) return O[IE_PROTO];
- if (typeof O.constructor == 'function' && O instanceof O.constructor) {
- return O.constructor.prototype;
- } return O instanceof Object ? ObjectPrototype$3 : null;
+ var objectGetPrototypeOf = CORRECT_PROTOTYPE_GETTER$1 ? Object$2.getPrototypeOf : function (O) {
+ var object = toObject$h(O);
+ if (hasOwn$d(object, IE_PROTO)) return object[IE_PROTO];
+ var constructor = object.constructor;
+ if (isCallable$h(constructor) && object instanceof constructor) {
+ return constructor.prototype;
+ } return object instanceof Object$2 ? ObjectPrototype$4 : null;
};
- var fails$G = fails$N;
+ var fails$L = fails$S;
+ var isCallable$g = isCallable$r;
var getPrototypeOf$4 = objectGetPrototypeOf;
- var createNonEnumerableProperty$9 = createNonEnumerableProperty$e;
- var has$a = has$j;
- var wellKnownSymbol$p = wellKnownSymbol$s;
+ var redefine$e = redefine$h.exports;
+ var wellKnownSymbol$p = wellKnownSymbol$t;
- var ITERATOR$8 = wellKnownSymbol$p('iterator');
+ var ITERATOR$a = wellKnownSymbol$p('iterator');
var BUGGY_SAFARI_ITERATORS$1 = false;
- var returnThis$2 = function () { return this; };
-
// `%IteratorPrototype%` object
// https://tc39.es/ecma262/#sec-%iteratorprototype%-object
var IteratorPrototype$2, PrototypeOfArrayIteratorPrototype, arrayIterator;
}
}
- var NEW_ITERATOR_PROTOTYPE = IteratorPrototype$2 == undefined || fails$G(function () {
+ var NEW_ITERATOR_PROTOTYPE = IteratorPrototype$2 == undefined || fails$L(function () {
var test = {};
// FF44- legacy iterators case
- return IteratorPrototype$2[ITERATOR$8].call(test) !== test;
+ return IteratorPrototype$2[ITERATOR$a].call(test) !== test;
});
if (NEW_ITERATOR_PROTOTYPE) IteratorPrototype$2 = {};
// `%IteratorPrototype%[@@iterator]()` method
// https://tc39.es/ecma262/#sec-%iteratorprototype%-@@iterator
- if (!has$a(IteratorPrototype$2, ITERATOR$8)) {
- createNonEnumerableProperty$9(IteratorPrototype$2, ITERATOR$8, returnThis$2);
+ if (!isCallable$g(IteratorPrototype$2[ITERATOR$a])) {
+ redefine$e(IteratorPrototype$2, ITERATOR$a, function () {
+ return this;
+ });
}
var iteratorsCore = {
};
var defineProperty$9 = objectDefineProperty.f;
- var has$9 = has$j;
- var wellKnownSymbol$o = wellKnownSymbol$s;
+ var hasOwn$c = hasOwnProperty_1;
+ var wellKnownSymbol$o = wellKnownSymbol$t;
var TO_STRING_TAG$4 = wellKnownSymbol$o('toStringTag');
var setToStringTag$a = function (it, TAG, STATIC) {
- if (it && !has$9(it = STATIC ? it : it.prototype, TO_STRING_TAG$4)) {
+ if (it && !hasOwn$c(it = STATIC ? it : it.prototype, TO_STRING_TAG$4)) {
defineProperty$9(it, TO_STRING_TAG$4, { configurable: true, value: TAG });
}
};
var IteratorPrototype$1 = iteratorsCore.IteratorPrototype;
- var create$a = objectCreate;
+ var create$9 = objectCreate;
var createPropertyDescriptor$4 = createPropertyDescriptor$7;
var setToStringTag$9 = setToStringTag$a;
var Iterators$4 = iterators;
var createIteratorConstructor$2 = function (IteratorConstructor, NAME, next) {
var TO_STRING_TAG = NAME + ' Iterator';
- IteratorConstructor.prototype = create$a(IteratorPrototype$1, { next: createPropertyDescriptor$4(1, next) });
+ IteratorConstructor.prototype = create$9(IteratorPrototype$1, { next: createPropertyDescriptor$4(1, next) });
setToStringTag$9(IteratorConstructor, TO_STRING_TAG, false);
Iterators$4[TO_STRING_TAG] = returnThis$1;
return IteratorConstructor;
};
- var isObject$m = isObject$r;
+ var global$Z = global$1m;
+ var isCallable$f = isCallable$r;
- var aPossiblePrototype$1 = function (it) {
- if (!isObject$m(it) && it !== null) {
- throw TypeError("Can't set " + String(it) + ' as a prototype');
- } return it;
+ var String$4 = global$Z.String;
+ var TypeError$h = global$Z.TypeError;
+
+ var aPossiblePrototype$1 = function (argument) {
+ if (typeof argument == 'object' || isCallable$f(argument)) return argument;
+ throw TypeError$h("Can't set " + String$4(argument) + ' as a prototype');
};
/* eslint-disable no-proto -- safe */
- var anObject$h = anObject$m;
+ var uncurryThis$M = functionUncurryThis;
+ var anObject$i = anObject$n;
var aPossiblePrototype = aPossiblePrototype$1;
// `Object.setPrototypeOf` method
var setter;
try {
// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
- setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set;
- setter.call(test, []);
+ setter = uncurryThis$M(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set);
+ setter(test, []);
CORRECT_SETTER = test instanceof Array;
} catch (error) { /* empty */ }
return function setPrototypeOf(O, proto) {
- anObject$h(O);
+ anObject$i(O);
aPossiblePrototype(proto);
- if (CORRECT_SETTER) setter.call(O, proto);
+ if (CORRECT_SETTER) setter(O, proto);
else O.__proto__ = proto;
return O;
};
}() : undefined);
- var $$15 = _export;
+ var $$1c = _export;
+ var call$l = functionCall;
+ var FunctionName$1 = functionName;
+ var isCallable$e = isCallable$r;
var createIteratorConstructor$1 = createIteratorConstructor$2;
var getPrototypeOf$3 = objectGetPrototypeOf;
var setPrototypeOf$6 = objectSetPrototypeOf;
var setToStringTag$8 = setToStringTag$a;
- var createNonEnumerableProperty$8 = createNonEnumerableProperty$e;
- var redefine$d = redefine$g.exports;
- var wellKnownSymbol$n = wellKnownSymbol$s;
+ var createNonEnumerableProperty$7 = createNonEnumerableProperty$b;
+ var redefine$d = redefine$h.exports;
+ var wellKnownSymbol$n = wellKnownSymbol$t;
var Iterators$3 = iterators;
var IteratorsCore = iteratorsCore;
+ var PROPER_FUNCTION_NAME$4 = FunctionName$1.PROPER;
+ var CONFIGURABLE_FUNCTION_NAME$1 = FunctionName$1.CONFIGURABLE;
var IteratorPrototype = IteratorsCore.IteratorPrototype;
var BUGGY_SAFARI_ITERATORS = IteratorsCore.BUGGY_SAFARI_ITERATORS;
- var ITERATOR$7 = wellKnownSymbol$n('iterator');
+ var ITERATOR$9 = wellKnownSymbol$n('iterator');
var KEYS = 'keys';
var VALUES = 'values';
var ENTRIES = 'entries';
var TO_STRING_TAG = NAME + ' Iterator';
var INCORRECT_VALUES_NAME = false;
var IterablePrototype = Iterable.prototype;
- var nativeIterator = IterablePrototype[ITERATOR$7]
+ var nativeIterator = IterablePrototype[ITERATOR$9]
|| IterablePrototype['@@iterator']
|| DEFAULT && IterablePrototype[DEFAULT];
var defaultIterator = !BUGGY_SAFARI_ITERATORS && nativeIterator || getIterationMethod(DEFAULT);
// fix native
if (anyNativeIterator) {
CurrentIteratorPrototype = getPrototypeOf$3(anyNativeIterator.call(new Iterable()));
- if (IteratorPrototype !== Object.prototype && CurrentIteratorPrototype.next) {
+ if (CurrentIteratorPrototype !== Object.prototype && CurrentIteratorPrototype.next) {
if (getPrototypeOf$3(CurrentIteratorPrototype) !== IteratorPrototype) {
if (setPrototypeOf$6) {
setPrototypeOf$6(CurrentIteratorPrototype, IteratorPrototype);
- } else if (typeof CurrentIteratorPrototype[ITERATOR$7] != 'function') {
- createNonEnumerableProperty$8(CurrentIteratorPrototype, ITERATOR$7, returnThis);
+ } else if (!isCallable$e(CurrentIteratorPrototype[ITERATOR$9])) {
+ redefine$d(CurrentIteratorPrototype, ITERATOR$9, returnThis);
}
}
// Set @@toStringTag to native iterators
}
// fix Array.prototype.{ values, @@iterator }.name in V8 / FF
- if (DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) {
- INCORRECT_VALUES_NAME = true;
- defaultIterator = function values() { return nativeIterator.call(this); };
- }
-
- // define iterator
- if (IterablePrototype[ITERATOR$7] !== defaultIterator) {
- createNonEnumerableProperty$8(IterablePrototype, ITERATOR$7, defaultIterator);
+ if (PROPER_FUNCTION_NAME$4 && DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) {
+ if (CONFIGURABLE_FUNCTION_NAME$1) {
+ createNonEnumerableProperty$7(IterablePrototype, 'name', VALUES);
+ } else {
+ INCORRECT_VALUES_NAME = true;
+ defaultIterator = function values() { return call$l(nativeIterator, this); };
+ }
}
- Iterators$3[NAME] = defaultIterator;
// export additional methods
if (DEFAULT) {
if (BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) {
redefine$d(IterablePrototype, KEY, methods[KEY]);
}
- } else $$15({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME }, methods);
+ } else $$1c({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME }, methods);
}
+ // define iterator
+ if (IterablePrototype[ITERATOR$9] !== defaultIterator) {
+ redefine$d(IterablePrototype, ITERATOR$9, defaultIterator, { name: DEFAULT });
+ }
+ Iterators$3[NAME] = defaultIterator;
+
return methods;
};
- var toIndexedObject$7 = toIndexedObject$b;
- var addToUnscopables$4 = addToUnscopables$5;
+ var toIndexedObject$7 = toIndexedObject$c;
+ var addToUnscopables$5 = addToUnscopables$6;
var Iterators$2 = iterators;
var InternalStateModule$8 = internalState;
var defineIterator$2 = defineIterator$3;
Iterators$2.Arguments = Iterators$2.Array;
// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
- addToUnscopables$4('keys');
- addToUnscopables$4('values');
- addToUnscopables$4('entries');
+ addToUnscopables$5('keys');
+ addToUnscopables$5('values');
+ addToUnscopables$5('entries');
- var wellKnownSymbol$m = wellKnownSymbol$s;
+ var wellKnownSymbol$m = wellKnownSymbol$t;
var TO_STRING_TAG$3 = wellKnownSymbol$m('toStringTag');
var test$2 = {};
var toStringTagSupport = String(test$2) === '[object z]';
+ var global$Y = global$1m;
var TO_STRING_TAG_SUPPORT$2 = toStringTagSupport;
+ var isCallable$d = isCallable$r;
var classofRaw = classofRaw$1;
- var wellKnownSymbol$l = wellKnownSymbol$s;
+ var wellKnownSymbol$l = wellKnownSymbol$t;
var TO_STRING_TAG$2 = wellKnownSymbol$l('toStringTag');
+ var Object$1 = global$Y.Object;
+
// ES3 wrong here
var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments';
};
// getting tag from ES6+ `Object.prototype.toString`
- var classof$b = TO_STRING_TAG_SUPPORT$2 ? classofRaw : function (it) {
+ var classof$d = TO_STRING_TAG_SUPPORT$2 ? classofRaw : function (it) {
var O, tag, result;
return it === undefined ? 'Undefined' : it === null ? 'Null'
// @@toStringTag case
- : typeof (tag = tryGet(O = Object(it), TO_STRING_TAG$2)) == 'string' ? tag
+ : typeof (tag = tryGet(O = Object$1(it), TO_STRING_TAG$2)) == 'string' ? tag
// builtinTag case
: CORRECT_ARGUMENTS ? classofRaw(O)
// ES3 arguments fallback
- : (result = classofRaw(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : result;
+ : (result = classofRaw(O)) == 'Object' && isCallable$d(O.callee) ? 'Arguments' : result;
};
var TO_STRING_TAG_SUPPORT$1 = toStringTagSupport;
- var classof$a = classof$b;
+ var classof$c = classof$d;
// `Object.prototype.toString` method implementation
// https://tc39.es/ecma262/#sec-object.prototype.tostring
var objectToString$1 = TO_STRING_TAG_SUPPORT$1 ? {}.toString : function toString() {
- return '[object ' + classof$a(this) + ']';
+ return '[object ' + classof$c(this) + ']';
};
var TO_STRING_TAG_SUPPORT = toStringTagSupport;
- var redefine$c = redefine$g.exports;
- var toString$1 = objectToString$1;
+ var redefine$c = redefine$h.exports;
+ var toString$l = objectToString$1;
// `Object.prototype.toString` method
// https://tc39.es/ecma262/#sec-object.prototype.tostring
if (!TO_STRING_TAG_SUPPORT) {
- redefine$c(Object.prototype, 'toString', toString$1, { unsafe: true });
+ redefine$c(Object.prototype, 'toString', toString$l, { unsafe: true });
}
- var toInteger$8 = toInteger$b;
+ var global$X = global$1m;
+ var classof$b = classof$d;
+
+ var String$3 = global$X.String;
+
+ var toString$k = function (argument) {
+ if (classof$b(argument) === 'Symbol') throw TypeError('Cannot convert a Symbol value to a string');
+ return String$3(argument);
+ };
+
+ var uncurryThis$L = functionUncurryThis;
+ var toIntegerOrInfinity$8 = toIntegerOrInfinity$b;
+ var toString$j = toString$k;
var requireObjectCoercible$b = requireObjectCoercible$e;
- // `String.prototype.{ codePointAt, at }` methods implementation
+ var charAt$8 = uncurryThis$L(''.charAt);
+ var charCodeAt$2 = uncurryThis$L(''.charCodeAt);
+ var stringSlice$b = uncurryThis$L(''.slice);
+
var createMethod$5 = function (CONVERT_TO_STRING) {
return function ($this, pos) {
- var S = String(requireObjectCoercible$b($this));
- var position = toInteger$8(pos);
+ var S = toString$j(requireObjectCoercible$b($this));
+ var position = toIntegerOrInfinity$8(pos);
var size = S.length;
var first, second;
if (position < 0 || position >= size) return CONVERT_TO_STRING ? '' : undefined;
- first = S.charCodeAt(position);
+ first = charCodeAt$2(S, position);
return first < 0xD800 || first > 0xDBFF || position + 1 === size
- || (second = S.charCodeAt(position + 1)) < 0xDC00 || second > 0xDFFF
- ? CONVERT_TO_STRING ? S.charAt(position) : first
- : CONVERT_TO_STRING ? S.slice(position, position + 2) : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000;
+ || (second = charCodeAt$2(S, position + 1)) < 0xDC00 || second > 0xDFFF
+ ? CONVERT_TO_STRING
+ ? charAt$8(S, position)
+ : first
+ : CONVERT_TO_STRING
+ ? stringSlice$b(S, position, position + 2)
+ : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000;
};
};
charAt: createMethod$5(true)
};
- var charAt$1 = stringMultibyte.charAt;
+ var charAt$7 = stringMultibyte.charAt;
+ var toString$i = toString$k;
var InternalStateModule$7 = internalState;
var defineIterator$1 = defineIterator$3;
defineIterator$1(String, 'String', function (iterated) {
setInternalState$7(this, {
type: STRING_ITERATOR,
- string: String(iterated),
+ string: toString$i(iterated),
index: 0
});
// `%StringIteratorPrototype%.next` method
var index = state.index;
var point;
if (index >= string.length) return { value: undefined, done: true };
- point = charAt$1(string, index);
+ point = charAt$7(string, index);
state.index += point.length;
return { value: point, done: false };
});
TouchList: 0
};
- var global$t = global$F;
+ // in old WebKit versions, `element.classList` is not an instance of global `DOMTokenList`
+ var documentCreateElement = documentCreateElement$2;
+
+ var classList$1 = documentCreateElement('span').classList;
+ var DOMTokenListPrototype$2 = classList$1 && classList$1.constructor && classList$1.constructor.prototype;
+
+ var domTokenListPrototype = DOMTokenListPrototype$2 === Object.prototype ? undefined : DOMTokenListPrototype$2;
+
+ var global$W = global$1m;
var DOMIterables$1 = domIterables;
+ var DOMTokenListPrototype$1 = domTokenListPrototype;
var ArrayIteratorMethods = es_array_iterator;
- var createNonEnumerableProperty$7 = createNonEnumerableProperty$e;
- var wellKnownSymbol$k = wellKnownSymbol$s;
+ var createNonEnumerableProperty$6 = createNonEnumerableProperty$b;
+ var wellKnownSymbol$k = wellKnownSymbol$t;
- var ITERATOR$6 = wellKnownSymbol$k('iterator');
+ var ITERATOR$8 = wellKnownSymbol$k('iterator');
var TO_STRING_TAG$1 = wellKnownSymbol$k('toStringTag');
var ArrayValues = ArrayIteratorMethods.values;
- for (var COLLECTION_NAME$1 in DOMIterables$1) {
- var Collection$1 = global$t[COLLECTION_NAME$1];
- var CollectionPrototype$1 = Collection$1 && Collection$1.prototype;
- if (CollectionPrototype$1) {
+ var handlePrototype$1 = function (CollectionPrototype, COLLECTION_NAME) {
+ if (CollectionPrototype) {
// some Chrome versions have non-configurable methods on DOMTokenList
- if (CollectionPrototype$1[ITERATOR$6] !== ArrayValues) try {
- createNonEnumerableProperty$7(CollectionPrototype$1, ITERATOR$6, ArrayValues);
+ if (CollectionPrototype[ITERATOR$8] !== ArrayValues) try {
+ createNonEnumerableProperty$6(CollectionPrototype, ITERATOR$8, ArrayValues);
} catch (error) {
- CollectionPrototype$1[ITERATOR$6] = ArrayValues;
+ CollectionPrototype[ITERATOR$8] = ArrayValues;
}
- if (!CollectionPrototype$1[TO_STRING_TAG$1]) {
- createNonEnumerableProperty$7(CollectionPrototype$1, TO_STRING_TAG$1, COLLECTION_NAME$1);
+ if (!CollectionPrototype[TO_STRING_TAG$1]) {
+ createNonEnumerableProperty$6(CollectionPrototype, TO_STRING_TAG$1, COLLECTION_NAME);
}
- if (DOMIterables$1[COLLECTION_NAME$1]) for (var METHOD_NAME in ArrayIteratorMethods) {
+ if (DOMIterables$1[COLLECTION_NAME]) for (var METHOD_NAME in ArrayIteratorMethods) {
// some Chrome versions have non-configurable methods on DOMTokenList
- if (CollectionPrototype$1[METHOD_NAME] !== ArrayIteratorMethods[METHOD_NAME]) try {
- createNonEnumerableProperty$7(CollectionPrototype$1, METHOD_NAME, ArrayIteratorMethods[METHOD_NAME]);
+ if (CollectionPrototype[METHOD_NAME] !== ArrayIteratorMethods[METHOD_NAME]) try {
+ createNonEnumerableProperty$6(CollectionPrototype, METHOD_NAME, ArrayIteratorMethods[METHOD_NAME]);
} catch (error) {
- CollectionPrototype$1[METHOD_NAME] = ArrayIteratorMethods[METHOD_NAME];
+ CollectionPrototype[METHOD_NAME] = ArrayIteratorMethods[METHOD_NAME];
}
}
}
+ };
+
+ for (var COLLECTION_NAME$1 in DOMIterables$1) {
+ handlePrototype$1(global$W[COLLECTION_NAME$1] && global$W[COLLECTION_NAME$1].prototype, COLLECTION_NAME$1);
}
- var classof$9 = classofRaw$1;
+ handlePrototype$1(DOMTokenListPrototype$1, 'DOMTokenList');
+
+ var FunctionPrototype$1 = Function.prototype;
+ var apply$9 = FunctionPrototype$1.apply;
+ var bind$g = FunctionPrototype$1.bind;
+ var call$k = FunctionPrototype$1.call;
+
+ // eslint-disable-next-line es/no-reflect -- safe
+ var functionApply = typeof Reflect == 'object' && Reflect.apply || (bind$g ? call$k.bind(apply$9) : function () {
+ return call$k.apply(apply$9, arguments);
+ });
+
+ var classof$a = classofRaw$1;
// `IsArray` abstract operation
// https://tc39.es/ecma262/#sec-isarray
// eslint-disable-next-line es/no-array-isarray -- safe
- var isArray$6 = Array.isArray || function isArray(arg) {
- return classof$9(arg) == 'Array';
+ var isArray$8 = Array.isArray || function isArray(argument) {
+ return classof$a(argument) == 'Array';
};
var objectGetOwnPropertyNamesExternal = {};
+ var uncurryThis$K = functionUncurryThis;
+
+ var arraySlice$c = uncurryThis$K([].slice);
+
/* eslint-disable es/no-object-getownpropertynames -- safe */
- var toIndexedObject$6 = toIndexedObject$b;
+ var classof$9 = classofRaw$1;
+ var toIndexedObject$6 = toIndexedObject$c;
var $getOwnPropertyNames$1 = objectGetOwnPropertyNames.f;
-
- var toString = {}.toString;
+ var arraySlice$b = arraySlice$c;
var windowNames = typeof window == 'object' && window && Object.getOwnPropertyNames
? Object.getOwnPropertyNames(window) : [];
try {
return $getOwnPropertyNames$1(it);
} catch (error) {
- return windowNames.slice();
+ return arraySlice$b(windowNames);
}
};
// fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window
objectGetOwnPropertyNamesExternal.f = function getOwnPropertyNames(it) {
- return windowNames && toString.call(it) == '[object Window]'
+ return windowNames && classof$9(it) == 'Window'
? getWindowNames(it)
: $getOwnPropertyNames$1(toIndexedObject$6(it));
};
- var aFunction$9 = function (it) {
- if (typeof it != 'function') {
- throw TypeError(String(it) + ' is not a function');
- } return it;
- };
+ var uncurryThis$J = functionUncurryThis;
+ var aCallable$8 = aCallable$a;
- var aFunction$8 = aFunction$9;
+ var bind$f = uncurryThis$J(uncurryThis$J.bind);
// optional / simple context binding
- var functionBindContext = function (fn, that, length) {
- aFunction$8(fn);
- if (that === undefined) return fn;
- switch (length) {
- case 0: return function () {
- return fn.call(that);
- };
- case 1: return function (a) {
- return fn.call(that, a);
- };
- case 2: return function (a, b) {
- return fn.call(that, a, b);
- };
- case 3: return function (a, b, c) {
- return fn.call(that, a, b, c);
- };
- }
- return function (/* ...args */) {
+ var functionBindContext = function (fn, that) {
+ aCallable$8(fn);
+ return that === undefined ? fn : bind$f ? bind$f(fn, that) : function (/* ...args */) {
return fn.apply(that, arguments);
};
};
- var isObject$l = isObject$r;
- var isArray$5 = isArray$6;
- var wellKnownSymbol$j = wellKnownSymbol$s;
+ var uncurryThis$I = functionUncurryThis;
+ var fails$K = fails$S;
+ var isCallable$c = isCallable$r;
+ var classof$8 = classof$d;
+ var getBuiltIn$6 = getBuiltIn$b;
+ var inspectSource$1 = inspectSource$4;
+
+ var noop$2 = function () { /* empty */ };
+ var empty$1 = [];
+ var construct$1 = getBuiltIn$6('Reflect', 'construct');
+ var constructorRegExp = /^\s*(?:class|function)\b/;
+ var exec$6 = uncurryThis$I(constructorRegExp.exec);
+ var INCORRECT_TO_STRING = !constructorRegExp.exec(noop$2);
+
+ var isConstructorModern = function (argument) {
+ if (!isCallable$c(argument)) return false;
+ try {
+ construct$1(noop$2, empty$1, argument);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ };
+
+ var isConstructorLegacy = function (argument) {
+ if (!isCallable$c(argument)) return false;
+ switch (classof$8(argument)) {
+ case 'AsyncFunction':
+ case 'GeneratorFunction':
+ case 'AsyncGeneratorFunction': return false;
+ // we can't check .prototype since constructors produced by .bind haven't it
+ } return INCORRECT_TO_STRING || !!exec$6(constructorRegExp, inspectSource$1(argument));
+ };
+
+ // `IsConstructor` abstract operation
+ // https://tc39.es/ecma262/#sec-isconstructor
+ var isConstructor$4 = !construct$1 || fails$K(function () {
+ var called;
+ return isConstructorModern(isConstructorModern.call)
+ || !isConstructorModern(Object)
+ || !isConstructorModern(function () { called = true; })
+ || called;
+ }) ? isConstructorLegacy : isConstructorModern;
+
+ var global$V = global$1m;
+ var isArray$7 = isArray$8;
+ var isConstructor$3 = isConstructor$4;
+ var isObject$m = isObject$s;
+ var wellKnownSymbol$j = wellKnownSymbol$t;
var SPECIES$6 = wellKnownSymbol$j('species');
+ var Array$6 = global$V.Array;
- // `ArraySpeciesCreate` abstract operation
+ // a part of `ArraySpeciesCreate` abstract operation
// https://tc39.es/ecma262/#sec-arrayspeciescreate
- var arraySpeciesCreate$3 = function (originalArray, length) {
+ var arraySpeciesConstructor$1 = function (originalArray) {
var C;
- if (isArray$5(originalArray)) {
+ if (isArray$7(originalArray)) {
C = originalArray.constructor;
// cross-realm fallback
- if (typeof C == 'function' && (C === Array || isArray$5(C.prototype))) C = undefined;
- else if (isObject$l(C)) {
+ if (isConstructor$3(C) && (C === Array$6 || isArray$7(C.prototype))) C = undefined;
+ else if (isObject$m(C)) {
C = C[SPECIES$6];
if (C === null) C = undefined;
}
- } return new (C === undefined ? Array : C)(length === 0 ? 0 : length);
+ } return C === undefined ? Array$6 : C;
};
- var bind$b = functionBindContext;
+ var arraySpeciesConstructor = arraySpeciesConstructor$1;
+
+ // `ArraySpeciesCreate` abstract operation
+ // https://tc39.es/ecma262/#sec-arrayspeciescreate
+ var arraySpeciesCreate$4 = function (originalArray, length) {
+ return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length);
+ };
+
+ var bind$e = functionBindContext;
+ var uncurryThis$H = functionUncurryThis;
var IndexedObject$3 = indexedObject;
- var toObject$f = toObject$i;
- var toLength$o = toLength$q;
- var arraySpeciesCreate$2 = arraySpeciesCreate$3;
+ var toObject$g = toObject$j;
+ var lengthOfArrayLike$e = lengthOfArrayLike$g;
+ var arraySpeciesCreate$3 = arraySpeciesCreate$4;
- var push = [].push;
+ var push$9 = uncurryThis$H([].push);
- // `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterOut }` methods implementation
+ // `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation
var createMethod$4 = function (TYPE) {
var IS_MAP = TYPE == 1;
var IS_FILTER = TYPE == 2;
var IS_SOME = TYPE == 3;
var IS_EVERY = TYPE == 4;
var IS_FIND_INDEX = TYPE == 6;
- var IS_FILTER_OUT = TYPE == 7;
+ var IS_FILTER_REJECT = TYPE == 7;
var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;
return function ($this, callbackfn, that, specificCreate) {
- var O = toObject$f($this);
+ var O = toObject$g($this);
var self = IndexedObject$3(O);
- var boundFunction = bind$b(callbackfn, that, 3);
- var length = toLength$o(self.length);
+ var boundFunction = bind$e(callbackfn, that);
+ var length = lengthOfArrayLike$e(self);
var index = 0;
- var create = specificCreate || arraySpeciesCreate$2;
- var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_OUT ? create($this, 0) : undefined;
+ var create = specificCreate || arraySpeciesCreate$3;
+ var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined;
var value, result;
for (;length > index; index++) if (NO_HOLES || index in self) {
value = self[index];
case 3: return true; // some
case 5: return value; // find
case 6: return index; // findIndex
- case 2: push.call(target, value); // filter
+ case 2: push$9(target, value); // filter
} else switch (TYPE) {
case 4: return false; // every
- case 7: push.call(target, value); // filterOut
+ case 7: push$9(target, value); // filterReject
}
}
}
// `Array.prototype.findIndex` method
// https://tc39.es/ecma262/#sec-array.prototype.findIndex
findIndex: createMethod$4(6),
- // `Array.prototype.filterOut` method
+ // `Array.prototype.filterReject` method
// https://github.com/tc39/proposal-array-filtering
- filterOut: createMethod$4(7)
+ filterReject: createMethod$4(7)
};
- var $$14 = _export;
- var global$s = global$F;
- var getBuiltIn$5 = getBuiltIn$9;
+ var $$1b = _export;
+ var global$U = global$1m;
+ var getBuiltIn$5 = getBuiltIn$b;
+ var apply$8 = functionApply;
+ var call$j = functionCall;
+ var uncurryThis$G = functionUncurryThis;
var DESCRIPTORS$h = descriptors;
- var NATIVE_SYMBOL = nativeSymbol;
- var USE_SYMBOL_AS_UID = useSymbolAsUid;
- var fails$F = fails$N;
- var has$8 = has$j;
- var isArray$4 = isArray$6;
- var isObject$k = isObject$r;
- var anObject$g = anObject$m;
- var toObject$e = toObject$i;
- var toIndexedObject$5 = toIndexedObject$b;
- var toPrimitive$4 = toPrimitive$7;
+ var NATIVE_SYMBOL$1 = nativeSymbol;
+ var fails$J = fails$S;
+ var hasOwn$b = hasOwnProperty_1;
+ var isArray$6 = isArray$8;
+ var isCallable$b = isCallable$r;
+ var isObject$l = isObject$s;
+ var isPrototypeOf$8 = objectIsPrototypeOf;
+ var isSymbol$3 = isSymbol$6;
+ var anObject$h = anObject$n;
+ var toObject$f = toObject$j;
+ var toIndexedObject$5 = toIndexedObject$c;
+ var toPropertyKey$2 = toPropertyKey$5;
+ var $toString$3 = toString$k;
var createPropertyDescriptor$3 = createPropertyDescriptor$7;
var nativeObjectCreate = objectCreate;
var objectKeys$2 = objectKeys$4;
- var getOwnPropertyNamesModule = objectGetOwnPropertyNames;
+ var getOwnPropertyNamesModule$1 = objectGetOwnPropertyNames;
var getOwnPropertyNamesExternal = objectGetOwnPropertyNamesExternal;
var getOwnPropertySymbolsModule$1 = objectGetOwnPropertySymbols;
var getOwnPropertyDescriptorModule$2 = objectGetOwnPropertyDescriptor;
var definePropertyModule$3 = objectDefineProperty;
var propertyIsEnumerableModule$1 = objectPropertyIsEnumerable;
- var createNonEnumerableProperty$6 = createNonEnumerableProperty$e;
- var redefine$b = redefine$g.exports;
+ var arraySlice$a = arraySlice$c;
+ var redefine$b = redefine$h.exports;
var shared$1 = shared$5.exports;
var sharedKey = sharedKey$4;
var hiddenKeys$1 = hiddenKeys$6;
var uid$2 = uid$5;
- var wellKnownSymbol$i = wellKnownSymbol$s;
+ var wellKnownSymbol$i = wellKnownSymbol$t;
var wrappedWellKnownSymbolModule = wellKnownSymbolWrapped;
var defineWellKnownSymbol$2 = defineWellKnownSymbol$4;
var setToStringTag$7 = setToStringTag$a;
var SYMBOL = 'Symbol';
var PROTOTYPE$1 = 'prototype';
var TO_PRIMITIVE = wellKnownSymbol$i('toPrimitive');
+
var setInternalState$6 = InternalStateModule$6.set;
var getInternalState$4 = InternalStateModule$6.getterFor(SYMBOL);
- var ObjectPrototype$2 = Object[PROTOTYPE$1];
- var $Symbol = global$s.Symbol;
+
+ var ObjectPrototype$3 = Object[PROTOTYPE$1];
+ var $Symbol = global$U.Symbol;
+ var SymbolPrototype$1 = $Symbol && $Symbol[PROTOTYPE$1];
+ var TypeError$g = global$U.TypeError;
+ var QObject = global$U.QObject;
var $stringify = getBuiltIn$5('JSON', 'stringify');
var nativeGetOwnPropertyDescriptor$2 = getOwnPropertyDescriptorModule$2.f;
var nativeDefineProperty$1 = definePropertyModule$3.f;
var nativeGetOwnPropertyNames = getOwnPropertyNamesExternal.f;
var nativePropertyIsEnumerable = propertyIsEnumerableModule$1.f;
+ var push$8 = uncurryThis$G([].push);
+
var AllSymbols = shared$1('symbols');
var ObjectPrototypeSymbols = shared$1('op-symbols');
var StringToSymbolRegistry = shared$1('string-to-symbol-registry');
var SymbolToStringRegistry = shared$1('symbol-to-string-registry');
var WellKnownSymbolsStore = shared$1('wks');
- var QObject = global$s.QObject;
+
// Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173
var USE_SETTER = !QObject || !QObject[PROTOTYPE$1] || !QObject[PROTOTYPE$1].findChild;
// fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687
- var setSymbolDescriptor = DESCRIPTORS$h && fails$F(function () {
+ var setSymbolDescriptor = DESCRIPTORS$h && fails$J(function () {
return nativeObjectCreate(nativeDefineProperty$1({}, 'a', {
get: function () { return nativeDefineProperty$1(this, 'a', { value: 7 }).a; }
})).a != 7;
}) ? function (O, P, Attributes) {
- var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor$2(ObjectPrototype$2, P);
- if (ObjectPrototypeDescriptor) delete ObjectPrototype$2[P];
+ var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor$2(ObjectPrototype$3, P);
+ if (ObjectPrototypeDescriptor) delete ObjectPrototype$3[P];
nativeDefineProperty$1(O, P, Attributes);
- if (ObjectPrototypeDescriptor && O !== ObjectPrototype$2) {
- nativeDefineProperty$1(ObjectPrototype$2, P, ObjectPrototypeDescriptor);
+ if (ObjectPrototypeDescriptor && O !== ObjectPrototype$3) {
+ nativeDefineProperty$1(ObjectPrototype$3, P, ObjectPrototypeDescriptor);
}
} : nativeDefineProperty$1;
var wrap$2 = function (tag, description) {
- var symbol = AllSymbols[tag] = nativeObjectCreate($Symbol[PROTOTYPE$1]);
+ var symbol = AllSymbols[tag] = nativeObjectCreate(SymbolPrototype$1);
setInternalState$6(symbol, {
type: SYMBOL,
tag: tag,
return symbol;
};
- var isSymbol$1 = USE_SYMBOL_AS_UID ? function (it) {
- return typeof it == 'symbol';
- } : function (it) {
- return Object(it) instanceof $Symbol;
- };
-
var $defineProperty = function defineProperty(O, P, Attributes) {
- if (O === ObjectPrototype$2) $defineProperty(ObjectPrototypeSymbols, P, Attributes);
- anObject$g(O);
- var key = toPrimitive$4(P, true);
- anObject$g(Attributes);
- if (has$8(AllSymbols, key)) {
+ if (O === ObjectPrototype$3) $defineProperty(ObjectPrototypeSymbols, P, Attributes);
+ anObject$h(O);
+ var key = toPropertyKey$2(P);
+ anObject$h(Attributes);
+ if (hasOwn$b(AllSymbols, key)) {
if (!Attributes.enumerable) {
- if (!has$8(O, HIDDEN)) nativeDefineProperty$1(O, HIDDEN, createPropertyDescriptor$3(1, {}));
+ if (!hasOwn$b(O, HIDDEN)) nativeDefineProperty$1(O, HIDDEN, createPropertyDescriptor$3(1, {}));
O[HIDDEN][key] = true;
} else {
- if (has$8(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false;
+ if (hasOwn$b(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false;
Attributes = nativeObjectCreate(Attributes, { enumerable: createPropertyDescriptor$3(0, false) });
} return setSymbolDescriptor(O, key, Attributes);
} return nativeDefineProperty$1(O, key, Attributes);
};
var $defineProperties = function defineProperties(O, Properties) {
- anObject$g(O);
+ anObject$h(O);
var properties = toIndexedObject$5(Properties);
var keys = objectKeys$2(properties).concat($getOwnPropertySymbols(properties));
$forEach$2(keys, function (key) {
- if (!DESCRIPTORS$h || $propertyIsEnumerable.call(properties, key)) $defineProperty(O, key, properties[key]);
+ if (!DESCRIPTORS$h || call$j($propertyIsEnumerable$1, properties, key)) $defineProperty(O, key, properties[key]);
});
return O;
};
return Properties === undefined ? nativeObjectCreate(O) : $defineProperties(nativeObjectCreate(O), Properties);
};
- var $propertyIsEnumerable = function propertyIsEnumerable(V) {
- var P = toPrimitive$4(V, true);
- var enumerable = nativePropertyIsEnumerable.call(this, P);
- if (this === ObjectPrototype$2 && has$8(AllSymbols, P) && !has$8(ObjectPrototypeSymbols, P)) return false;
- return enumerable || !has$8(this, P) || !has$8(AllSymbols, P) || has$8(this, HIDDEN) && this[HIDDEN][P] ? enumerable : true;
+ var $propertyIsEnumerable$1 = function propertyIsEnumerable(V) {
+ var P = toPropertyKey$2(V);
+ var enumerable = call$j(nativePropertyIsEnumerable, this, P);
+ if (this === ObjectPrototype$3 && hasOwn$b(AllSymbols, P) && !hasOwn$b(ObjectPrototypeSymbols, P)) return false;
+ return enumerable || !hasOwn$b(this, P) || !hasOwn$b(AllSymbols, P) || hasOwn$b(this, HIDDEN) && this[HIDDEN][P]
+ ? enumerable : true;
};
var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) {
var it = toIndexedObject$5(O);
- var key = toPrimitive$4(P, true);
- if (it === ObjectPrototype$2 && has$8(AllSymbols, key) && !has$8(ObjectPrototypeSymbols, key)) return;
+ var key = toPropertyKey$2(P);
+ if (it === ObjectPrototype$3 && hasOwn$b(AllSymbols, key) && !hasOwn$b(ObjectPrototypeSymbols, key)) return;
var descriptor = nativeGetOwnPropertyDescriptor$2(it, key);
- if (descriptor && has$8(AllSymbols, key) && !(has$8(it, HIDDEN) && it[HIDDEN][key])) {
+ if (descriptor && hasOwn$b(AllSymbols, key) && !(hasOwn$b(it, HIDDEN) && it[HIDDEN][key])) {
descriptor.enumerable = true;
}
return descriptor;
var names = nativeGetOwnPropertyNames(toIndexedObject$5(O));
var result = [];
$forEach$2(names, function (key) {
- if (!has$8(AllSymbols, key) && !has$8(hiddenKeys$1, key)) result.push(key);
+ if (!hasOwn$b(AllSymbols, key) && !hasOwn$b(hiddenKeys$1, key)) push$8(result, key);
});
return result;
};
var $getOwnPropertySymbols = function getOwnPropertySymbols(O) {
- var IS_OBJECT_PROTOTYPE = O === ObjectPrototype$2;
+ var IS_OBJECT_PROTOTYPE = O === ObjectPrototype$3;
var names = nativeGetOwnPropertyNames(IS_OBJECT_PROTOTYPE ? ObjectPrototypeSymbols : toIndexedObject$5(O));
var result = [];
$forEach$2(names, function (key) {
- if (has$8(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || has$8(ObjectPrototype$2, key))) {
- result.push(AllSymbols[key]);
+ if (hasOwn$b(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || hasOwn$b(ObjectPrototype$3, key))) {
+ push$8(result, AllSymbols[key]);
}
});
return result;
// `Symbol` constructor
// https://tc39.es/ecma262/#sec-symbol-constructor
- if (!NATIVE_SYMBOL) {
+ if (!NATIVE_SYMBOL$1) {
$Symbol = function Symbol() {
- if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor');
- var description = !arguments.length || arguments[0] === undefined ? undefined : String(arguments[0]);
+ if (isPrototypeOf$8(SymbolPrototype$1, this)) throw TypeError$g('Symbol is not a constructor');
+ var description = !arguments.length || arguments[0] === undefined ? undefined : $toString$3(arguments[0]);
var tag = uid$2(description);
var setter = function (value) {
- if (this === ObjectPrototype$2) setter.call(ObjectPrototypeSymbols, value);
- if (has$8(this, HIDDEN) && has$8(this[HIDDEN], tag)) this[HIDDEN][tag] = false;
+ if (this === ObjectPrototype$3) call$j(setter, ObjectPrototypeSymbols, value);
+ if (hasOwn$b(this, HIDDEN) && hasOwn$b(this[HIDDEN], tag)) this[HIDDEN][tag] = false;
setSymbolDescriptor(this, tag, createPropertyDescriptor$3(1, value));
};
- if (DESCRIPTORS$h && USE_SETTER) setSymbolDescriptor(ObjectPrototype$2, tag, { configurable: true, set: setter });
+ if (DESCRIPTORS$h && USE_SETTER) setSymbolDescriptor(ObjectPrototype$3, tag, { configurable: true, set: setter });
return wrap$2(tag, description);
};
- redefine$b($Symbol[PROTOTYPE$1], 'toString', function toString() {
+ SymbolPrototype$1 = $Symbol[PROTOTYPE$1];
+
+ redefine$b(SymbolPrototype$1, 'toString', function toString() {
return getInternalState$4(this).tag;
});
return wrap$2(uid$2(description), description);
});
- propertyIsEnumerableModule$1.f = $propertyIsEnumerable;
+ propertyIsEnumerableModule$1.f = $propertyIsEnumerable$1;
definePropertyModule$3.f = $defineProperty;
getOwnPropertyDescriptorModule$2.f = $getOwnPropertyDescriptor;
- getOwnPropertyNamesModule.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames;
+ getOwnPropertyNamesModule$1.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames;
getOwnPropertySymbolsModule$1.f = $getOwnPropertySymbols;
wrappedWellKnownSymbolModule.f = function (name) {
if (DESCRIPTORS$h) {
// https://github.com/tc39/proposal-Symbol-description
- nativeDefineProperty$1($Symbol[PROTOTYPE$1], 'description', {
+ nativeDefineProperty$1(SymbolPrototype$1, 'description', {
configurable: true,
get: function description() {
return getInternalState$4(this).description;
}
});
{
- redefine$b(ObjectPrototype$2, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true });
+ redefine$b(ObjectPrototype$3, 'propertyIsEnumerable', $propertyIsEnumerable$1, { unsafe: true });
}
}
}
- $$14({ global: true, wrap: true, forced: !NATIVE_SYMBOL, sham: !NATIVE_SYMBOL }, {
+ $$1b({ global: true, wrap: true, forced: !NATIVE_SYMBOL$1, sham: !NATIVE_SYMBOL$1 }, {
Symbol: $Symbol
});
defineWellKnownSymbol$2(name);
});
- $$14({ target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL }, {
+ $$1b({ target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL$1 }, {
// `Symbol.for` method
// https://tc39.es/ecma262/#sec-symbol.for
'for': function (key) {
- var string = String(key);
- if (has$8(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string];
+ var string = $toString$3(key);
+ if (hasOwn$b(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string];
var symbol = $Symbol(string);
StringToSymbolRegistry[string] = symbol;
SymbolToStringRegistry[symbol] = string;
// `Symbol.keyFor` method
// https://tc39.es/ecma262/#sec-symbol.keyfor
keyFor: function keyFor(sym) {
- if (!isSymbol$1(sym)) throw TypeError(sym + ' is not a symbol');
- if (has$8(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym];
+ if (!isSymbol$3(sym)) throw TypeError$g(sym + ' is not a symbol');
+ if (hasOwn$b(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym];
},
useSetter: function () { USE_SETTER = true; },
useSimple: function () { USE_SETTER = false; }
});
- $$14({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL, sham: !DESCRIPTORS$h }, {
+ $$1b({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL$1, sham: !DESCRIPTORS$h }, {
// `Object.create` method
// https://tc39.es/ecma262/#sec-object.create
create: $create,
getOwnPropertyDescriptor: $getOwnPropertyDescriptor
});
- $$14({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL }, {
+ $$1b({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL$1 }, {
// `Object.getOwnPropertyNames` method
// https://tc39.es/ecma262/#sec-object.getownpropertynames
getOwnPropertyNames: $getOwnPropertyNames,
// Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives
// https://bugs.chromium.org/p/v8/issues/detail?id=3443
- $$14({ target: 'Object', stat: true, forced: fails$F(function () { getOwnPropertySymbolsModule$1.f(1); }) }, {
+ $$1b({ target: 'Object', stat: true, forced: fails$J(function () { getOwnPropertySymbolsModule$1.f(1); }) }, {
getOwnPropertySymbols: function getOwnPropertySymbols(it) {
- return getOwnPropertySymbolsModule$1.f(toObject$e(it));
+ return getOwnPropertySymbolsModule$1.f(toObject$f(it));
}
});
// `JSON.stringify` method behavior with symbols
// https://tc39.es/ecma262/#sec-json.stringify
if ($stringify) {
- var FORCED_JSON_STRINGIFY = !NATIVE_SYMBOL || fails$F(function () {
+ var FORCED_JSON_STRINGIFY = !NATIVE_SYMBOL$1 || fails$J(function () {
var symbol = $Symbol();
// MS Edge converts symbol values to JSON as {}
return $stringify([symbol]) != '[null]'
|| $stringify(Object(symbol)) != '{}';
});
- $$14({ target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, {
+ $$1b({ target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, {
// eslint-disable-next-line no-unused-vars -- required for `.length`
stringify: function stringify(it, replacer, space) {
- var args = [it];
- var index = 1;
- var $replacer;
- while (arguments.length > index) args.push(arguments[index++]);
- $replacer = replacer;
- if (!isObject$k(replacer) && it === undefined || isSymbol$1(it)) return; // IE8 returns string on undefined
- if (!isArray$4(replacer)) replacer = function (key, value) {
- if (typeof $replacer == 'function') value = $replacer.call(this, key, value);
- if (!isSymbol$1(value)) return value;
+ var args = arraySlice$a(arguments);
+ var $replacer = replacer;
+ if (!isObject$l(replacer) && it === undefined || isSymbol$3(it)) return; // IE8 returns string on undefined
+ if (!isArray$6(replacer)) replacer = function (key, value) {
+ if (isCallable$b($replacer)) value = call$j($replacer, this, key, value);
+ if (!isSymbol$3(value)) return value;
};
args[1] = replacer;
- return $stringify.apply(null, args);
+ return apply$8($stringify, null, args);
}
});
}
// `Symbol.prototype[@@toPrimitive]` method
// https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive
- if (!$Symbol[PROTOTYPE$1][TO_PRIMITIVE]) {
- createNonEnumerableProperty$6($Symbol[PROTOTYPE$1], TO_PRIMITIVE, $Symbol[PROTOTYPE$1].valueOf);
+ if (!SymbolPrototype$1[TO_PRIMITIVE]) {
+ var valueOf = SymbolPrototype$1.valueOf;
+ // eslint-disable-next-line no-unused-vars -- required for .length
+ redefine$b(SymbolPrototype$1, TO_PRIMITIVE, function (hint) {
+ // TODO: improve hint logic
+ return call$j(valueOf, this);
+ });
}
// `Symbol.prototype[@@toStringTag]` property
// https://tc39.es/ecma262/#sec-symbol.prototype-@@tostringtag
hiddenKeys$1[HIDDEN] = true;
- var $$13 = _export;
+ var $$1a = _export;
var DESCRIPTORS$g = descriptors;
- var global$r = global$F;
- var has$7 = has$j;
- var isObject$j = isObject$r;
+ var global$T = global$1m;
+ var uncurryThis$F = functionUncurryThis;
+ var hasOwn$a = hasOwnProperty_1;
+ var isCallable$a = isCallable$r;
+ var isPrototypeOf$7 = objectIsPrototypeOf;
+ var toString$h = toString$k;
var defineProperty$8 = objectDefineProperty.f;
var copyConstructorProperties = copyConstructorProperties$2;
- var NativeSymbol = global$r.Symbol;
+ var NativeSymbol = global$T.Symbol;
+ var SymbolPrototype = NativeSymbol && NativeSymbol.prototype;
- if (DESCRIPTORS$g && typeof NativeSymbol == 'function' && (!('description' in NativeSymbol.prototype) ||
+ if (DESCRIPTORS$g && isCallable$a(NativeSymbol) && (!('description' in SymbolPrototype) ||
// Safari 12 bug
NativeSymbol().description !== undefined
)) {
var EmptyStringDescriptionStore = {};
// wrap Symbol constructor for correct work with undefined description
var SymbolWrapper = function Symbol() {
- var description = arguments.length < 1 || arguments[0] === undefined ? undefined : String(arguments[0]);
- var result = this instanceof SymbolWrapper
+ var description = arguments.length < 1 || arguments[0] === undefined ? undefined : toString$h(arguments[0]);
+ var result = isPrototypeOf$7(SymbolPrototype, this)
? new NativeSymbol(description)
// in Edge 13, String(Symbol(undefined)) === 'Symbol(undefined)'
: description === undefined ? NativeSymbol() : NativeSymbol(description);
if (description === '') EmptyStringDescriptionStore[result] = true;
return result;
};
+
copyConstructorProperties(SymbolWrapper, NativeSymbol);
- var symbolPrototype = SymbolWrapper.prototype = NativeSymbol.prototype;
- symbolPrototype.constructor = SymbolWrapper;
+ SymbolWrapper.prototype = SymbolPrototype;
+ SymbolPrototype.constructor = SymbolWrapper;
- var symbolToString = symbolPrototype.toString;
- var native = String(NativeSymbol('test')) == 'Symbol(test)';
+ var NATIVE_SYMBOL = String(NativeSymbol('test')) == 'Symbol(test)';
+ var symbolToString$1 = uncurryThis$F(SymbolPrototype.toString);
+ var symbolValueOf = uncurryThis$F(SymbolPrototype.valueOf);
var regexp = /^Symbol\((.*)\)[^)]+$/;
- defineProperty$8(symbolPrototype, 'description', {
+ var replace$8 = uncurryThis$F(''.replace);
+ var stringSlice$a = uncurryThis$F(''.slice);
+
+ defineProperty$8(SymbolPrototype, 'description', {
configurable: true,
get: function description() {
- var symbol = isObject$j(this) ? this.valueOf() : this;
- var string = symbolToString.call(symbol);
- if (has$7(EmptyStringDescriptionStore, symbol)) return '';
- var desc = native ? string.slice(7, -1) : string.replace(regexp, '$1');
+ var symbol = symbolValueOf(this);
+ var string = symbolToString$1(symbol);
+ if (hasOwn$a(EmptyStringDescriptionStore, symbol)) return '';
+ var desc = NATIVE_SYMBOL ? stringSlice$a(string, 7, -1) : replace$8(string, regexp, '$1');
return desc === '' ? undefined : desc;
}
});
- $$13({ global: true, forced: true }, {
+ $$1a({ global: true, forced: true }, {
Symbol: SymbolWrapper
});
}
// eslint-disable-next-line es/no-typed-arrays -- safe
- var arrayBufferNative = typeof ArrayBuffer !== 'undefined' && typeof DataView !== 'undefined';
+ var arrayBufferNative = typeof ArrayBuffer != 'undefined' && typeof DataView != 'undefined';
- var redefine$a = redefine$g.exports;
+ var redefine$a = redefine$h.exports;
var redefineAll$4 = function (target, src, options) {
for (var key in src) redefine$a(target, key, src[key], options);
return target;
};
- var anInstance$7 = function (it, Constructor, name) {
- if (!(it instanceof Constructor)) {
- throw TypeError('Incorrect ' + (name ? name + ' ' : '') + 'invocation');
- } return it;
+ var global$S = global$1m;
+ var isPrototypeOf$6 = objectIsPrototypeOf;
+
+ var TypeError$f = global$S.TypeError;
+
+ var anInstance$7 = function (it, Prototype) {
+ if (isPrototypeOf$6(Prototype, it)) return it;
+ throw TypeError$f('Incorrect invocation');
};
- var toInteger$7 = toInteger$b;
- var toLength$n = toLength$q;
+ var global$R = global$1m;
+ var toIntegerOrInfinity$7 = toIntegerOrInfinity$b;
+ var toLength$a = toLength$c;
+
+ var RangeError$b = global$R.RangeError;
// `ToIndex` abstract operation
// https://tc39.es/ecma262/#sec-toindex
var toIndex$2 = function (it) {
if (it === undefined) return 0;
- var number = toInteger$7(it);
- var length = toLength$n(number);
- if (number !== length) throw RangeError('Wrong length or index');
+ var number = toIntegerOrInfinity$7(it);
+ var length = toLength$a(number);
+ if (number !== length) throw RangeError$b('Wrong length or index');
return length;
};
// IEEE754 conversions based on https://github.com/feross/ieee754
+ var global$Q = global$1m;
+
+ var Array$5 = global$Q.Array;
var abs$4 = Math.abs;
var pow$2 = Math.pow;
- var floor$6 = Math.floor;
+ var floor$7 = Math.floor;
var log$2 = Math.log;
var LN2 = Math.LN2;
var pack = function (number, mantissaLength, bytes) {
- var buffer = new Array(bytes);
+ var buffer = Array$5(bytes);
var exponentLength = bytes * 8 - mantissaLength - 1;
var eMax = (1 << exponentLength) - 1;
var eBias = eMax >> 1;
mantissa = number != number ? 1 : 0;
exponent = eMax;
} else {
- exponent = floor$6(log$2(number) / LN2);
+ exponent = floor$7(log$2(number) / LN2);
if (number * (c = pow$2(2, -exponent)) < 1) {
exponent--;
c *= 2;
unpack: unpack
};
- var toObject$d = toObject$i;
+ var toObject$e = toObject$j;
var toAbsoluteIndex$6 = toAbsoluteIndex$8;
- var toLength$m = toLength$q;
+ var lengthOfArrayLike$d = lengthOfArrayLike$g;
// `Array.prototype.fill` method implementation
// https://tc39.es/ecma262/#sec-array.prototype.fill
var arrayFill$1 = function fill(value /* , start = 0, end = @length */) {
- var O = toObject$d(this);
- var length = toLength$m(O.length);
+ var O = toObject$e(this);
+ var length = lengthOfArrayLike$d(O);
var argumentsLength = arguments.length;
var index = toAbsoluteIndex$6(argumentsLength > 1 ? arguments[1] : undefined, length);
var end = argumentsLength > 2 ? arguments[2] : undefined;
return O;
};
- var global$q = global$F;
+ var global$P = global$1m;
+ var uncurryThis$E = functionUncurryThis;
var DESCRIPTORS$f = descriptors;
var NATIVE_ARRAY_BUFFER$2 = arrayBufferNative;
- var createNonEnumerableProperty$5 = createNonEnumerableProperty$e;
+ var FunctionName = functionName;
+ var createNonEnumerableProperty$5 = createNonEnumerableProperty$b;
var redefineAll$3 = redefineAll$4;
- var fails$E = fails$N;
+ var fails$I = fails$S;
var anInstance$6 = anInstance$7;
- var toInteger$6 = toInteger$b;
- var toLength$l = toLength$q;
+ var toIntegerOrInfinity$6 = toIntegerOrInfinity$b;
+ var toLength$9 = toLength$c;
var toIndex$1 = toIndex$2;
var IEEE754 = ieee754$2;
var getPrototypeOf$2 = objectGetPrototypeOf;
var getOwnPropertyNames$4 = objectGetOwnPropertyNames.f;
var defineProperty$7 = objectDefineProperty.f;
var arrayFill = arrayFill$1;
+ var arraySlice$9 = arraySlice$c;
var setToStringTag$6 = setToStringTag$a;
var InternalStateModule$5 = internalState;
+ var PROPER_FUNCTION_NAME$3 = FunctionName.PROPER;
+ var CONFIGURABLE_FUNCTION_NAME = FunctionName.CONFIGURABLE;
var getInternalState$3 = InternalStateModule$5.get;
var setInternalState$5 = InternalStateModule$5.set;
var ARRAY_BUFFER$1 = 'ArrayBuffer';
var PROTOTYPE = 'prototype';
var WRONG_LENGTH$1 = 'Wrong length';
var WRONG_INDEX = 'Wrong index';
- var NativeArrayBuffer$1 = global$q[ARRAY_BUFFER$1];
+ var NativeArrayBuffer$1 = global$P[ARRAY_BUFFER$1];
var $ArrayBuffer = NativeArrayBuffer$1;
- var $DataView = global$q[DATA_VIEW];
- var $DataViewPrototype = $DataView && $DataView[PROTOTYPE];
- var ObjectPrototype$1 = Object.prototype;
- var RangeError$2 = global$q.RangeError;
+ var ArrayBufferPrototype$1 = $ArrayBuffer && $ArrayBuffer[PROTOTYPE];
+ var $DataView = global$P[DATA_VIEW];
+ var DataViewPrototype$1 = $DataView && $DataView[PROTOTYPE];
+ var ObjectPrototype$2 = Object.prototype;
+ var Array$4 = global$P.Array;
+ var RangeError$a = global$P.RangeError;
+ var fill$1 = uncurryThis$E(arrayFill);
+ var reverse = uncurryThis$E([].reverse);
var packIEEE754 = IEEE754.pack;
var unpackIEEE754 = IEEE754.unpack;
var get$4 = function (view, count, index, isLittleEndian) {
var intIndex = toIndex$1(index);
var store = getInternalState$3(view);
- if (intIndex + count > store.byteLength) throw RangeError$2(WRONG_INDEX);
+ if (intIndex + count > store.byteLength) throw RangeError$a(WRONG_INDEX);
var bytes = getInternalState$3(store.buffer).bytes;
var start = intIndex + store.byteOffset;
- var pack = bytes.slice(start, start + count);
- return isLittleEndian ? pack : pack.reverse();
+ var pack = arraySlice$9(bytes, start, start + count);
+ return isLittleEndian ? pack : reverse(pack);
};
var set$3 = function (view, count, index, conversion, value, isLittleEndian) {
var intIndex = toIndex$1(index);
var store = getInternalState$3(view);
- if (intIndex + count > store.byteLength) throw RangeError$2(WRONG_INDEX);
+ if (intIndex + count > store.byteLength) throw RangeError$a(WRONG_INDEX);
var bytes = getInternalState$3(store.buffer).bytes;
var start = intIndex + store.byteOffset;
var pack = conversion(+value);
if (!NATIVE_ARRAY_BUFFER$2) {
$ArrayBuffer = function ArrayBuffer(length) {
- anInstance$6(this, $ArrayBuffer, ARRAY_BUFFER$1);
+ anInstance$6(this, ArrayBufferPrototype$1);
var byteLength = toIndex$1(length);
setInternalState$5(this, {
- bytes: arrayFill.call(new Array(byteLength), 0),
+ bytes: fill$1(Array$4(byteLength), 0),
byteLength: byteLength
});
if (!DESCRIPTORS$f) this.byteLength = byteLength;
};
+ ArrayBufferPrototype$1 = $ArrayBuffer[PROTOTYPE];
+
$DataView = function DataView(buffer, byteOffset, byteLength) {
- anInstance$6(this, $DataView, DATA_VIEW);
- anInstance$6(buffer, $ArrayBuffer, DATA_VIEW);
+ anInstance$6(this, DataViewPrototype$1);
+ anInstance$6(buffer, ArrayBufferPrototype$1);
var bufferLength = getInternalState$3(buffer).byteLength;
- var offset = toInteger$6(byteOffset);
- if (offset < 0 || offset > bufferLength) throw RangeError$2('Wrong offset');
- byteLength = byteLength === undefined ? bufferLength - offset : toLength$l(byteLength);
- if (offset + byteLength > bufferLength) throw RangeError$2(WRONG_LENGTH$1);
+ var offset = toIntegerOrInfinity$6(byteOffset);
+ if (offset < 0 || offset > bufferLength) throw RangeError$a('Wrong offset');
+ byteLength = byteLength === undefined ? bufferLength - offset : toLength$9(byteLength);
+ if (offset + byteLength > bufferLength) throw RangeError$a(WRONG_LENGTH$1);
setInternalState$5(this, {
buffer: buffer,
byteLength: byteLength,
}
};
+ DataViewPrototype$1 = $DataView[PROTOTYPE];
+
if (DESCRIPTORS$f) {
addGetter$1($ArrayBuffer, 'byteLength');
addGetter$1($DataView, 'buffer');
addGetter$1($DataView, 'byteOffset');
}
- redefineAll$3($DataView[PROTOTYPE], {
+ redefineAll$3(DataViewPrototype$1, {
getInt8: function getInt8(byteOffset) {
return get$4(this, 1, byteOffset)[0] << 24 >> 24;
},
}
});
} else {
+ var INCORRECT_ARRAY_BUFFER_NAME = PROPER_FUNCTION_NAME$3 && NativeArrayBuffer$1.name !== ARRAY_BUFFER$1;
/* eslint-disable no-new -- required for testing */
- if (!fails$E(function () {
+ if (!fails$I(function () {
NativeArrayBuffer$1(1);
- }) || !fails$E(function () {
+ }) || !fails$I(function () {
new NativeArrayBuffer$1(-1);
- }) || fails$E(function () {
+ }) || fails$I(function () {
new NativeArrayBuffer$1();
new NativeArrayBuffer$1(1.5);
new NativeArrayBuffer$1(NaN);
- return NativeArrayBuffer$1.name != ARRAY_BUFFER$1;
+ return INCORRECT_ARRAY_BUFFER_NAME && !CONFIGURABLE_FUNCTION_NAME;
})) {
/* eslint-enable no-new -- required for testing */
$ArrayBuffer = function ArrayBuffer(length) {
- anInstance$6(this, $ArrayBuffer);
+ anInstance$6(this, ArrayBufferPrototype$1);
return new NativeArrayBuffer$1(toIndex$1(length));
};
- var ArrayBufferPrototype = $ArrayBuffer[PROTOTYPE] = NativeArrayBuffer$1[PROTOTYPE];
+
+ $ArrayBuffer[PROTOTYPE] = ArrayBufferPrototype$1;
+
for (var keys$2 = getOwnPropertyNames$4(NativeArrayBuffer$1), j$2 = 0, key$1; keys$2.length > j$2;) {
if (!((key$1 = keys$2[j$2++]) in $ArrayBuffer)) {
createNonEnumerableProperty$5($ArrayBuffer, key$1, NativeArrayBuffer$1[key$1]);
}
}
- ArrayBufferPrototype.constructor = $ArrayBuffer;
+
+ ArrayBufferPrototype$1.constructor = $ArrayBuffer;
+ } else if (INCORRECT_ARRAY_BUFFER_NAME && CONFIGURABLE_FUNCTION_NAME) {
+ createNonEnumerableProperty$5(NativeArrayBuffer$1, 'name', ARRAY_BUFFER$1);
}
// WebKit bug - the same parent prototype for typed arrays and data view
- if (setPrototypeOf$5 && getPrototypeOf$2($DataViewPrototype) !== ObjectPrototype$1) {
- setPrototypeOf$5($DataViewPrototype, ObjectPrototype$1);
+ if (setPrototypeOf$5 && getPrototypeOf$2(DataViewPrototype$1) !== ObjectPrototype$2) {
+ setPrototypeOf$5(DataViewPrototype$1, ObjectPrototype$2);
}
// iOS Safari 7.x bug
var testView = new $DataView(new $ArrayBuffer(2));
- var $setInt8 = $DataViewPrototype.setInt8;
+ var $setInt8 = uncurryThis$E(DataViewPrototype$1.setInt8);
testView.setInt8(0, 2147483648);
testView.setInt8(1, 2147483649);
- if (testView.getInt8(0) || !testView.getInt8(1)) redefineAll$3($DataViewPrototype, {
+ if (testView.getInt8(0) || !testView.getInt8(1)) redefineAll$3(DataViewPrototype$1, {
setInt8: function setInt8(byteOffset, value) {
- $setInt8.call(this, byteOffset, value << 24 >> 24);
+ $setInt8(this, byteOffset, value << 24 >> 24);
},
setUint8: function setUint8(byteOffset, value) {
- $setInt8.call(this, byteOffset, value << 24 >> 24);
+ $setInt8(this, byteOffset, value << 24 >> 24);
}
}, { unsafe: true });
}
DataView: $DataView
};
- var anObject$f = anObject$m;
- var aFunction$7 = aFunction$9;
- var wellKnownSymbol$h = wellKnownSymbol$s;
+ var global$O = global$1m;
+ var isConstructor$2 = isConstructor$4;
+ var tryToString$3 = tryToString$5;
+
+ var TypeError$e = global$O.TypeError;
+
+ // `Assert: IsConstructor(argument) is true`
+ var aConstructor$3 = function (argument) {
+ if (isConstructor$2(argument)) return argument;
+ throw TypeError$e(tryToString$3(argument) + ' is not a constructor');
+ };
+
+ var anObject$g = anObject$n;
+ var aConstructor$2 = aConstructor$3;
+ var wellKnownSymbol$h = wellKnownSymbol$t;
var SPECIES$5 = wellKnownSymbol$h('species');
// `SpeciesConstructor` abstract operation
// https://tc39.es/ecma262/#sec-speciesconstructor
- var speciesConstructor$8 = function (O, defaultConstructor) {
- var C = anObject$f(O).constructor;
+ var speciesConstructor$5 = function (O, defaultConstructor) {
+ var C = anObject$g(O).constructor;
var S;
- return C === undefined || (S = anObject$f(C)[SPECIES$5]) == undefined ? defaultConstructor : aFunction$7(S);
+ return C === undefined || (S = anObject$g(C)[SPECIES$5]) == undefined ? defaultConstructor : aConstructor$2(S);
};
- var $$12 = _export;
- var fails$D = fails$N;
+ var $$19 = _export;
+ var uncurryThis$D = functionUncurryThis;
+ var fails$H = fails$S;
var ArrayBufferModule$2 = arrayBuffer;
- var anObject$e = anObject$m;
+ var anObject$f = anObject$n;
var toAbsoluteIndex$5 = toAbsoluteIndex$8;
- var toLength$k = toLength$q;
- var speciesConstructor$7 = speciesConstructor$8;
+ var toLength$8 = toLength$c;
+ var speciesConstructor$4 = speciesConstructor$5;
var ArrayBuffer$4 = ArrayBufferModule$2.ArrayBuffer;
var DataView$2 = ArrayBufferModule$2.DataView;
- var nativeArrayBufferSlice = ArrayBuffer$4.prototype.slice;
+ var DataViewPrototype = DataView$2.prototype;
+ var un$ArrayBufferSlice = uncurryThis$D(ArrayBuffer$4.prototype.slice);
+ var getUint8 = uncurryThis$D(DataViewPrototype.getUint8);
+ var setUint8 = uncurryThis$D(DataViewPrototype.setUint8);
- var INCORRECT_SLICE = fails$D(function () {
+ var INCORRECT_SLICE = fails$H(function () {
return !new ArrayBuffer$4(2).slice(1, undefined).byteLength;
});
// `ArrayBuffer.prototype.slice` method
// https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice
- $$12({ target: 'ArrayBuffer', proto: true, unsafe: true, forced: INCORRECT_SLICE }, {
+ $$19({ target: 'ArrayBuffer', proto: true, unsafe: true, forced: INCORRECT_SLICE }, {
slice: function slice(start, end) {
- if (nativeArrayBufferSlice !== undefined && end === undefined) {
- return nativeArrayBufferSlice.call(anObject$e(this), start); // FF fix
+ if (un$ArrayBufferSlice && end === undefined) {
+ return un$ArrayBufferSlice(anObject$f(this), start); // FF fix
}
- var length = anObject$e(this).byteLength;
+ var length = anObject$f(this).byteLength;
var first = toAbsoluteIndex$5(start, length);
var fin = toAbsoluteIndex$5(end === undefined ? length : end, length);
- var result = new (speciesConstructor$7(this, ArrayBuffer$4))(toLength$k(fin - first));
+ var result = new (speciesConstructor$4(this, ArrayBuffer$4))(toLength$8(fin - first));
var viewSource = new DataView$2(this);
var viewTarget = new DataView$2(result);
var index = 0;
while (first < fin) {
- viewTarget.setUint8(index++, viewSource.getUint8(first++));
+ setUint8(viewTarget, index++, getUint8(viewSource, first++));
} return result;
}
});
- var $$11 = _export;
+ var $$18 = _export;
var ArrayBufferModule$1 = arrayBuffer;
var NATIVE_ARRAY_BUFFER$1 = arrayBufferNative;
// `DataView` constructor
// https://tc39.es/ecma262/#sec-dataview-constructor
- $$11({ global: true, forced: !NATIVE_ARRAY_BUFFER$1 }, {
+ $$18({ global: true, forced: !NATIVE_ARRAY_BUFFER$1 }, {
DataView: ArrayBufferModule$1.DataView
});
var NATIVE_ARRAY_BUFFER = arrayBufferNative;
var DESCRIPTORS$e = descriptors;
- var global$p = global$F;
- var isObject$i = isObject$r;
- var has$6 = has$j;
- var classof$8 = classof$b;
- var createNonEnumerableProperty$4 = createNonEnumerableProperty$e;
- var redefine$9 = redefine$g.exports;
+ var global$N = global$1m;
+ var isCallable$9 = isCallable$r;
+ var isObject$k = isObject$s;
+ var hasOwn$9 = hasOwnProperty_1;
+ var classof$7 = classof$d;
+ var tryToString$2 = tryToString$5;
+ var createNonEnumerableProperty$4 = createNonEnumerableProperty$b;
+ var redefine$9 = redefine$h.exports;
var defineProperty$6 = objectDefineProperty.f;
+ var isPrototypeOf$5 = objectIsPrototypeOf;
var getPrototypeOf$1 = objectGetPrototypeOf;
var setPrototypeOf$4 = objectSetPrototypeOf;
- var wellKnownSymbol$g = wellKnownSymbol$s;
+ var wellKnownSymbol$g = wellKnownSymbol$t;
var uid$1 = uid$5;
- var Int8Array$3 = global$p.Int8Array;
+ var Int8Array$3 = global$N.Int8Array;
var Int8ArrayPrototype = Int8Array$3 && Int8Array$3.prototype;
- var Uint8ClampedArray = global$p.Uint8ClampedArray;
+ var Uint8ClampedArray = global$N.Uint8ClampedArray;
var Uint8ClampedArrayPrototype = Uint8ClampedArray && Uint8ClampedArray.prototype;
var TypedArray$1 = Int8Array$3 && getPrototypeOf$1(Int8Array$3);
var TypedArrayPrototype$1 = Int8ArrayPrototype && getPrototypeOf$1(Int8ArrayPrototype);
- var ObjectPrototype = Object.prototype;
- var isPrototypeOf = ObjectPrototype.isPrototypeOf;
+ var ObjectPrototype$1 = Object.prototype;
+ var TypeError$d = global$N.TypeError;
var TO_STRING_TAG = wellKnownSymbol$g('toStringTag');
var TYPED_ARRAY_TAG$1 = uid$1('TYPED_ARRAY_TAG');
+ var TYPED_ARRAY_CONSTRUCTOR$2 = uid$1('TYPED_ARRAY_CONSTRUCTOR');
// Fixing native typed arrays in Opera Presto crashes the browser, see #595
- var NATIVE_ARRAY_BUFFER_VIEWS$3 = NATIVE_ARRAY_BUFFER && !!setPrototypeOf$4 && classof$8(global$p.opera) !== 'Opera';
+ var NATIVE_ARRAY_BUFFER_VIEWS$3 = NATIVE_ARRAY_BUFFER && !!setPrototypeOf$4 && classof$7(global$N.opera) !== 'Opera';
var TYPED_ARRAY_TAG_REQIRED = false;
- var NAME$1;
+ var NAME$1, Constructor, Prototype;
var TypedArrayConstructorsList = {
Int8Array: 1,
};
var isView = function isView(it) {
- if (!isObject$i(it)) return false;
- var klass = classof$8(it);
+ if (!isObject$k(it)) return false;
+ var klass = classof$7(it);
return klass === 'DataView'
- || has$6(TypedArrayConstructorsList, klass)
- || has$6(BigIntArrayConstructorsList, klass);
+ || hasOwn$9(TypedArrayConstructorsList, klass)
+ || hasOwn$9(BigIntArrayConstructorsList, klass);
};
var isTypedArray$1 = function (it) {
- if (!isObject$i(it)) return false;
- var klass = classof$8(it);
- return has$6(TypedArrayConstructorsList, klass)
- || has$6(BigIntArrayConstructorsList, klass);
+ if (!isObject$k(it)) return false;
+ var klass = classof$7(it);
+ return hasOwn$9(TypedArrayConstructorsList, klass)
+ || hasOwn$9(BigIntArrayConstructorsList, klass);
};
var aTypedArray$m = function (it) {
if (isTypedArray$1(it)) return it;
- throw TypeError('Target is not a typed array');
+ throw TypeError$d('Target is not a typed array');
};
- var aTypedArrayConstructor$5 = function (C) {
- if (setPrototypeOf$4) {
- if (isPrototypeOf.call(TypedArray$1, C)) return C;
- } else for (var ARRAY in TypedArrayConstructorsList) if (has$6(TypedArrayConstructorsList, NAME$1)) {
- var TypedArrayConstructor = global$p[ARRAY];
- if (TypedArrayConstructor && (C === TypedArrayConstructor || isPrototypeOf.call(TypedArrayConstructor, C))) {
- return C;
- }
- } throw TypeError('Target is not a typed array constructor');
+ var aTypedArrayConstructor$3 = function (C) {
+ if (isCallable$9(C) && (!setPrototypeOf$4 || isPrototypeOf$5(TypedArray$1, C))) return C;
+ throw TypeError$d(tryToString$2(C) + ' is not a typed array constructor');
};
var exportTypedArrayMethod$n = function (KEY, property, forced) {
if (!DESCRIPTORS$e) return;
if (forced) for (var ARRAY in TypedArrayConstructorsList) {
- var TypedArrayConstructor = global$p[ARRAY];
- if (TypedArrayConstructor && has$6(TypedArrayConstructor.prototype, KEY)) try {
+ var TypedArrayConstructor = global$N[ARRAY];
+ if (TypedArrayConstructor && hasOwn$9(TypedArrayConstructor.prototype, KEY)) try {
delete TypedArrayConstructor.prototype[KEY];
} catch (error) { /* empty */ }
}
if (!DESCRIPTORS$e) return;
if (setPrototypeOf$4) {
if (forced) for (ARRAY in TypedArrayConstructorsList) {
- TypedArrayConstructor = global$p[ARRAY];
- if (TypedArrayConstructor && has$6(TypedArrayConstructor, KEY)) try {
+ TypedArrayConstructor = global$N[ARRAY];
+ if (TypedArrayConstructor && hasOwn$9(TypedArrayConstructor, KEY)) try {
delete TypedArrayConstructor[KEY];
} catch (error) { /* empty */ }
}
} else return;
}
for (ARRAY in TypedArrayConstructorsList) {
- TypedArrayConstructor = global$p[ARRAY];
+ TypedArrayConstructor = global$N[ARRAY];
if (TypedArrayConstructor && (!TypedArrayConstructor[KEY] || forced)) {
redefine$9(TypedArrayConstructor, KEY, property);
}
};
for (NAME$1 in TypedArrayConstructorsList) {
- if (!global$p[NAME$1]) NATIVE_ARRAY_BUFFER_VIEWS$3 = false;
+ Constructor = global$N[NAME$1];
+ Prototype = Constructor && Constructor.prototype;
+ if (Prototype) createNonEnumerableProperty$4(Prototype, TYPED_ARRAY_CONSTRUCTOR$2, Constructor);
+ else NATIVE_ARRAY_BUFFER_VIEWS$3 = false;
+ }
+
+ for (NAME$1 in BigIntArrayConstructorsList) {
+ Constructor = global$N[NAME$1];
+ Prototype = Constructor && Constructor.prototype;
+ if (Prototype) createNonEnumerableProperty$4(Prototype, TYPED_ARRAY_CONSTRUCTOR$2, Constructor);
}
// WebKit bug - typed arrays constructors prototype is Object.prototype
- if (!NATIVE_ARRAY_BUFFER_VIEWS$3 || typeof TypedArray$1 != 'function' || TypedArray$1 === Function.prototype) {
+ if (!NATIVE_ARRAY_BUFFER_VIEWS$3 || !isCallable$9(TypedArray$1) || TypedArray$1 === Function.prototype) {
// eslint-disable-next-line no-shadow -- safe
TypedArray$1 = function TypedArray() {
- throw TypeError('Incorrect invocation');
+ throw TypeError$d('Incorrect invocation');
};
if (NATIVE_ARRAY_BUFFER_VIEWS$3) for (NAME$1 in TypedArrayConstructorsList) {
- if (global$p[NAME$1]) setPrototypeOf$4(global$p[NAME$1], TypedArray$1);
+ if (global$N[NAME$1]) setPrototypeOf$4(global$N[NAME$1], TypedArray$1);
}
}
- if (!NATIVE_ARRAY_BUFFER_VIEWS$3 || !TypedArrayPrototype$1 || TypedArrayPrototype$1 === ObjectPrototype) {
+ if (!NATIVE_ARRAY_BUFFER_VIEWS$3 || !TypedArrayPrototype$1 || TypedArrayPrototype$1 === ObjectPrototype$1) {
TypedArrayPrototype$1 = TypedArray$1.prototype;
if (NATIVE_ARRAY_BUFFER_VIEWS$3) for (NAME$1 in TypedArrayConstructorsList) {
- if (global$p[NAME$1]) setPrototypeOf$4(global$p[NAME$1].prototype, TypedArrayPrototype$1);
+ if (global$N[NAME$1]) setPrototypeOf$4(global$N[NAME$1].prototype, TypedArrayPrototype$1);
}
}
setPrototypeOf$4(Uint8ClampedArrayPrototype, TypedArrayPrototype$1);
}
- if (DESCRIPTORS$e && !has$6(TypedArrayPrototype$1, TO_STRING_TAG)) {
+ if (DESCRIPTORS$e && !hasOwn$9(TypedArrayPrototype$1, TO_STRING_TAG)) {
TYPED_ARRAY_TAG_REQIRED = true;
defineProperty$6(TypedArrayPrototype$1, TO_STRING_TAG, { get: function () {
- return isObject$i(this) ? this[TYPED_ARRAY_TAG$1] : undefined;
+ return isObject$k(this) ? this[TYPED_ARRAY_TAG$1] : undefined;
} });
- for (NAME$1 in TypedArrayConstructorsList) if (global$p[NAME$1]) {
- createNonEnumerableProperty$4(global$p[NAME$1], TYPED_ARRAY_TAG$1, NAME$1);
+ for (NAME$1 in TypedArrayConstructorsList) if (global$N[NAME$1]) {
+ createNonEnumerableProperty$4(global$N[NAME$1], TYPED_ARRAY_TAG$1, NAME$1);
}
}
var arrayBufferViewCore = {
NATIVE_ARRAY_BUFFER_VIEWS: NATIVE_ARRAY_BUFFER_VIEWS$3,
+ TYPED_ARRAY_CONSTRUCTOR: TYPED_ARRAY_CONSTRUCTOR$2,
TYPED_ARRAY_TAG: TYPED_ARRAY_TAG_REQIRED && TYPED_ARRAY_TAG$1,
aTypedArray: aTypedArray$m,
- aTypedArrayConstructor: aTypedArrayConstructor$5,
+ aTypedArrayConstructor: aTypedArrayConstructor$3,
exportTypedArrayMethod: exportTypedArrayMethod$n,
exportTypedArrayStaticMethod: exportTypedArrayStaticMethod$1,
isView: isView,
TypedArrayPrototype: TypedArrayPrototype$1
};
- var $$10 = _export;
- var ArrayBufferViewCore$n = arrayBufferViewCore;
+ var $$17 = _export;
+ var ArrayBufferViewCore$o = arrayBufferViewCore;
- var NATIVE_ARRAY_BUFFER_VIEWS$2 = ArrayBufferViewCore$n.NATIVE_ARRAY_BUFFER_VIEWS;
+ var NATIVE_ARRAY_BUFFER_VIEWS$2 = ArrayBufferViewCore$o.NATIVE_ARRAY_BUFFER_VIEWS;
// `ArrayBuffer.isView` method
// https://tc39.es/ecma262/#sec-arraybuffer.isview
- $$10({ target: 'ArrayBuffer', stat: true, forced: !NATIVE_ARRAY_BUFFER_VIEWS$2 }, {
- isView: ArrayBufferViewCore$n.isView
+ $$17({ target: 'ArrayBuffer', stat: true, forced: !NATIVE_ARRAY_BUFFER_VIEWS$2 }, {
+ isView: ArrayBufferViewCore$o.isView
});
- var getBuiltIn$4 = getBuiltIn$9;
+ var getBuiltIn$4 = getBuiltIn$b;
var definePropertyModule$2 = objectDefineProperty;
- var wellKnownSymbol$f = wellKnownSymbol$s;
+ var wellKnownSymbol$f = wellKnownSymbol$t;
var DESCRIPTORS$d = descriptors;
var SPECIES$4 = wellKnownSymbol$f('species');
}
};
- var $$$ = _export;
- var global$o = global$F;
+ var $$16 = _export;
+ var global$M = global$1m;
var arrayBufferModule = arrayBuffer;
var setSpecies$4 = setSpecies$5;
var ARRAY_BUFFER = 'ArrayBuffer';
var ArrayBuffer$3 = arrayBufferModule[ARRAY_BUFFER];
- var NativeArrayBuffer = global$o[ARRAY_BUFFER];
+ var NativeArrayBuffer = global$M[ARRAY_BUFFER];
// `ArrayBuffer` constructor
// https://tc39.es/ecma262/#sec-arraybuffer-constructor
- $$$({ global: true, forced: NativeArrayBuffer !== ArrayBuffer$3 }, {
+ $$16({ global: true, forced: NativeArrayBuffer !== ArrayBuffer$3 }, {
ArrayBuffer: ArrayBuffer$3
});
setSpecies$4(ARRAY_BUFFER);
- var fails$C = fails$N;
+ var fails$G = fails$S;
- var arrayMethodIsStrict$8 = function (METHOD_NAME, argument) {
+ var arrayMethodIsStrict$9 = function (METHOD_NAME, argument) {
var method = [][METHOD_NAME];
- return !!method && fails$C(function () {
+ return !!method && fails$G(function () {
// eslint-disable-next-line no-useless-call,no-throw-literal -- required for testing
method.call(null, argument || function () { throw 1; }, 1);
});
};
/* eslint-disable es/no-array-prototype-indexof -- required for testing */
- var $$_ = _export;
- var $indexOf$1 = arrayIncludes.indexOf;
- var arrayMethodIsStrict$7 = arrayMethodIsStrict$8;
+ var $$15 = _export;
+ var uncurryThis$C = functionUncurryThis;
+ var $IndexOf = arrayIncludes.indexOf;
+ var arrayMethodIsStrict$8 = arrayMethodIsStrict$9;
- var nativeIndexOf = [].indexOf;
+ var un$IndexOf = uncurryThis$C([].indexOf);
- var NEGATIVE_ZERO$1 = !!nativeIndexOf && 1 / [1].indexOf(1, -0) < 0;
- var STRICT_METHOD$7 = arrayMethodIsStrict$7('indexOf');
+ var NEGATIVE_ZERO$1 = !!un$IndexOf && 1 / un$IndexOf([1], 1, -0) < 0;
+ var STRICT_METHOD$8 = arrayMethodIsStrict$8('indexOf');
// `Array.prototype.indexOf` method
// https://tc39.es/ecma262/#sec-array.prototype.indexof
- $$_({ target: 'Array', proto: true, forced: NEGATIVE_ZERO$1 || !STRICT_METHOD$7 }, {
+ $$15({ target: 'Array', proto: true, forced: NEGATIVE_ZERO$1 || !STRICT_METHOD$8 }, {
indexOf: function indexOf(searchElement /* , fromIndex = 0 */) {
+ var fromIndex = arguments.length > 1 ? arguments[1] : undefined;
return NEGATIVE_ZERO$1
// convert -0 to +0
- ? nativeIndexOf.apply(this, arguments) || 0
- : $indexOf$1(this, searchElement, arguments.length > 1 ? arguments[1] : undefined);
+ ? un$IndexOf(this, searchElement, fromIndex) || 0
+ : $IndexOf(this, searchElement, fromIndex);
}
});
- var fails$B = fails$N;
- var wellKnownSymbol$e = wellKnownSymbol$s;
+ var anObject$e = anObject$n;
+
+ // `RegExp.prototype.flags` getter implementation
+ // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
+ var regexpFlags$1 = function () {
+ var that = anObject$e(this);
+ var result = '';
+ if (that.global) result += 'g';
+ if (that.ignoreCase) result += 'i';
+ if (that.multiline) result += 'm';
+ if (that.dotAll) result += 's';
+ if (that.unicode) result += 'u';
+ if (that.sticky) result += 'y';
+ return result;
+ };
+
+ var regexpStickyHelpers = {};
+
+ var fails$F = fails$S;
+ var global$L = global$1m;
+
+ // babel-minify and Closure Compiler transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError
+ var $RegExp$2 = global$L.RegExp;
+
+ regexpStickyHelpers.UNSUPPORTED_Y = fails$F(function () {
+ var re = $RegExp$2('a', 'y');
+ re.lastIndex = 2;
+ return re.exec('abcd') != null;
+ });
+
+ regexpStickyHelpers.BROKEN_CARET = fails$F(function () {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=773687
+ var re = $RegExp$2('^r', 'gy');
+ re.lastIndex = 2;
+ return re.exec('str') != null;
+ });
+
+ var fails$E = fails$S;
+ var global$K = global$1m;
+
+ // babel-minify and Closure Compiler transpiles RegExp('.', 's') -> /./s and it causes SyntaxError
+ var $RegExp$1 = global$K.RegExp;
+
+ var regexpUnsupportedDotAll = fails$E(function () {
+ var re = $RegExp$1('.', 's');
+ return !(re.dotAll && re.exec('\n') && re.flags === 's');
+ });
+
+ var fails$D = fails$S;
+ var global$J = global$1m;
+
+ // babel-minify and Closure Compiler transpiles RegExp('(?<a>b)', 'g') -> /(?<a>b)/g and it causes SyntaxError
+ var $RegExp = global$J.RegExp;
+
+ var regexpUnsupportedNcg = fails$D(function () {
+ var re = $RegExp('(?<a>b)', 'g');
+ return re.exec('b').groups.a !== 'b' ||
+ 'b'.replace(re, '$<a>c') !== 'bc';
+ });
+
+ /* eslint-disable regexp/no-empty-capturing-group, regexp/no-empty-group, regexp/no-lazy-ends -- testing */
+ /* eslint-disable regexp/no-useless-quantifier -- testing */
+ var call$i = functionCall;
+ var uncurryThis$B = functionUncurryThis;
+ var toString$g = toString$k;
+ var regexpFlags = regexpFlags$1;
+ var stickyHelpers$2 = regexpStickyHelpers;
+ var shared = shared$5.exports;
+ var create$8 = objectCreate;
+ var getInternalState$2 = internalState.get;
+ var UNSUPPORTED_DOT_ALL$1 = regexpUnsupportedDotAll;
+ var UNSUPPORTED_NCG$1 = regexpUnsupportedNcg;
+
+ var nativeReplace = shared('native-string-replace', String.prototype.replace);
+ var nativeExec = RegExp.prototype.exec;
+ var patchedExec = nativeExec;
+ var charAt$6 = uncurryThis$B(''.charAt);
+ var indexOf = uncurryThis$B(''.indexOf);
+ var replace$7 = uncurryThis$B(''.replace);
+ var stringSlice$9 = uncurryThis$B(''.slice);
+
+ var UPDATES_LAST_INDEX_WRONG = (function () {
+ var re1 = /a/;
+ var re2 = /b*/g;
+ call$i(nativeExec, re1, 'a');
+ call$i(nativeExec, re2, 'a');
+ return re1.lastIndex !== 0 || re2.lastIndex !== 0;
+ })();
+
+ var UNSUPPORTED_Y$2 = stickyHelpers$2.UNSUPPORTED_Y || stickyHelpers$2.BROKEN_CARET;
+
+ // nonparticipating capturing group, copied from es5-shim's String#split patch.
+ var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;
+
+ var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y$2 || UNSUPPORTED_DOT_ALL$1 || UNSUPPORTED_NCG$1;
+
+ if (PATCH) {
+ // eslint-disable-next-line max-statements -- TODO
+ patchedExec = function exec(string) {
+ var re = this;
+ var state = getInternalState$2(re);
+ var str = toString$g(string);
+ var raw = state.raw;
+ var result, reCopy, lastIndex, match, i, object, group;
+
+ if (raw) {
+ raw.lastIndex = re.lastIndex;
+ result = call$i(patchedExec, raw, str);
+ re.lastIndex = raw.lastIndex;
+ return result;
+ }
+
+ var groups = state.groups;
+ var sticky = UNSUPPORTED_Y$2 && re.sticky;
+ var flags = call$i(regexpFlags, re);
+ var source = re.source;
+ var charsAdded = 0;
+ var strCopy = str;
+
+ if (sticky) {
+ flags = replace$7(flags, 'y', '');
+ if (indexOf(flags, 'g') === -1) {
+ flags += 'g';
+ }
+
+ strCopy = stringSlice$9(str, re.lastIndex);
+ // Support anchored sticky behavior.
+ if (re.lastIndex > 0 && (!re.multiline || re.multiline && charAt$6(str, re.lastIndex - 1) !== '\n')) {
+ source = '(?: ' + source + ')';
+ strCopy = ' ' + strCopy;
+ charsAdded++;
+ }
+ // ^(? + rx + ) is needed, in combination with some str slicing, to
+ // simulate the 'y' flag.
+ reCopy = new RegExp('^(?:' + source + ')', flags);
+ }
+
+ if (NPCG_INCLUDED) {
+ reCopy = new RegExp('^' + source + '$(?!\\s)', flags);
+ }
+ if (UPDATES_LAST_INDEX_WRONG) lastIndex = re.lastIndex;
+
+ match = call$i(nativeExec, sticky ? reCopy : re, strCopy);
+
+ if (sticky) {
+ if (match) {
+ match.input = stringSlice$9(match.input, charsAdded);
+ match[0] = stringSlice$9(match[0], charsAdded);
+ match.index = re.lastIndex;
+ re.lastIndex += match[0].length;
+ } else re.lastIndex = 0;
+ } else if (UPDATES_LAST_INDEX_WRONG && match) {
+ re.lastIndex = re.global ? match.index + match[0].length : lastIndex;
+ }
+ if (NPCG_INCLUDED && match && match.length > 1) {
+ // Fix browsers whose `exec` methods don't consistently return `undefined`
+ // for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/
+ call$i(nativeReplace, match[0], reCopy, function () {
+ for (i = 1; i < arguments.length - 2; i++) {
+ if (arguments[i] === undefined) match[i] = undefined;
+ }
+ });
+ }
+
+ if (match && groups) {
+ match.groups = object = create$8(null);
+ for (i = 0; i < groups.length; i++) {
+ group = groups[i];
+ object[group[0]] = match[group[1]];
+ }
+ }
+
+ return match;
+ };
+ }
+
+ var regexpExec$3 = patchedExec;
+
+ var $$14 = _export;
+ var exec$5 = regexpExec$3;
+
+ // `RegExp.prototype.exec` method
+ // https://tc39.es/ecma262/#sec-regexp.prototype.exec
+ $$14({ target: 'RegExp', proto: true, forced: /./.exec !== exec$5 }, {
+ exec: exec$5
+ });
+
+ var fails$C = fails$S;
+ var wellKnownSymbol$e = wellKnownSymbol$t;
var V8_VERSION$2 = engineV8Version;
var SPECIES$3 = wellKnownSymbol$e('species');
// We can't use this feature detection in V8 since it causes
// deoptimization and serious performance degradation
// https://github.com/zloirock/core-js/issues/677
- return V8_VERSION$2 >= 51 || !fails$B(function () {
+ return V8_VERSION$2 >= 51 || !fails$C(function () {
var array = [];
var constructor = array.constructor = {};
constructor[SPECIES$3] = function () {
});
};
- var $$Z = _export;
+ var $$13 = _export;
var $map$1 = arrayIteration.map;
var arrayMethodHasSpeciesSupport$4 = arrayMethodHasSpeciesSupport$5;
// `Array.prototype.map` method
// https://tc39.es/ecma262/#sec-array.prototype.map
// with adding support of @@species
- $$Z({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$3 }, {
+ $$13({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$3 }, {
map: function map(callbackfn /* , thisArg */) {
return $map$1(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
var $forEach$1 = arrayIteration.forEach;
- var arrayMethodIsStrict$6 = arrayMethodIsStrict$8;
+ var arrayMethodIsStrict$7 = arrayMethodIsStrict$9;
- var STRICT_METHOD$6 = arrayMethodIsStrict$6('forEach');
+ var STRICT_METHOD$7 = arrayMethodIsStrict$7('forEach');
// `Array.prototype.forEach` method implementation
// https://tc39.es/ecma262/#sec-array.prototype.foreach
- var arrayForEach = !STRICT_METHOD$6 ? function forEach(callbackfn /* , thisArg */) {
+ var arrayForEach = !STRICT_METHOD$7 ? function forEach(callbackfn /* , thisArg */) {
return $forEach$1(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
// eslint-disable-next-line es/no-array-prototype-foreach -- safe
} : [].forEach;
- var $$Y = _export;
+ var $$12 = _export;
var forEach$3 = arrayForEach;
// `Array.prototype.forEach` method
// https://tc39.es/ecma262/#sec-array.prototype.foreach
// eslint-disable-next-line es/no-array-prototype-foreach -- safe
- $$Y({ target: 'Array', proto: true, forced: [].forEach != forEach$3 }, {
+ $$12({ target: 'Array', proto: true, forced: [].forEach != forEach$3 }, {
forEach: forEach$3
});
- var global$n = global$F;
+ var global$I = global$1m;
var DOMIterables = domIterables;
+ var DOMTokenListPrototype = domTokenListPrototype;
var forEach$2 = arrayForEach;
- var createNonEnumerableProperty$3 = createNonEnumerableProperty$e;
+ var createNonEnumerableProperty$3 = createNonEnumerableProperty$b;
- for (var COLLECTION_NAME in DOMIterables) {
- var Collection = global$n[COLLECTION_NAME];
- var CollectionPrototype = Collection && Collection.prototype;
+ var handlePrototype = function (CollectionPrototype) {
// some Chrome versions have non-configurable methods on DOMTokenList
if (CollectionPrototype && CollectionPrototype.forEach !== forEach$2) try {
createNonEnumerableProperty$3(CollectionPrototype, 'forEach', forEach$2);
} catch (error) {
CollectionPrototype.forEach = forEach$2;
}
+ };
+
+ for (var COLLECTION_NAME in DOMIterables) {
+ if (DOMIterables[COLLECTION_NAME]) {
+ handlePrototype(global$I[COLLECTION_NAME] && global$I[COLLECTION_NAME].prototype);
+ }
}
- var $$X = _export;
- var isArray$3 = isArray$6;
+ handlePrototype(DOMTokenListPrototype);
+
+ var $$11 = _export;
+ var isArray$5 = isArray$8;
// `Array.isArray` method
// https://tc39.es/ecma262/#sec-array.isarray
- $$X({ target: 'Array', stat: true }, {
- isArray: isArray$3
+ $$11({ target: 'Array', stat: true }, {
+ isArray: isArray$5
});
- var $$W = _export;
- var fails$A = fails$N;
+ var $$10 = _export;
+ var fails$B = fails$S;
var getOwnPropertyNames$3 = objectGetOwnPropertyNamesExternal.f;
// eslint-disable-next-line es/no-object-getownpropertynames -- required for testing
- var FAILS_ON_PRIMITIVES$4 = fails$A(function () { return !Object.getOwnPropertyNames(1); });
+ var FAILS_ON_PRIMITIVES$5 = fails$B(function () { return !Object.getOwnPropertyNames(1); });
// `Object.getOwnPropertyNames` method
// https://tc39.es/ecma262/#sec-object.getownpropertynames
- $$W({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$4 }, {
+ $$10({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$5 }, {
getOwnPropertyNames: getOwnPropertyNames$3
});
- var global$m = global$F;
+ var global$H = global$1m;
- var nativePromiseConstructor = global$m.Promise;
+ var nativePromiseConstructor = global$H.Promise;
- var wellKnownSymbol$d = wellKnownSymbol$s;
+ var wellKnownSymbol$d = wellKnownSymbol$t;
var Iterators$1 = iterators;
- var ITERATOR$5 = wellKnownSymbol$d('iterator');
+ var ITERATOR$7 = wellKnownSymbol$d('iterator');
var ArrayPrototype = Array.prototype;
// check on default Array iterator
var isArrayIteratorMethod$3 = function (it) {
- return it !== undefined && (Iterators$1.Array === it || ArrayPrototype[ITERATOR$5] === it);
+ return it !== undefined && (Iterators$1.Array === it || ArrayPrototype[ITERATOR$7] === it);
};
- var classof$7 = classof$b;
+ var classof$6 = classof$d;
+ var getMethod$5 = getMethod$7;
var Iterators = iterators;
- var wellKnownSymbol$c = wellKnownSymbol$s;
+ var wellKnownSymbol$c = wellKnownSymbol$t;
- var ITERATOR$4 = wellKnownSymbol$c('iterator');
+ var ITERATOR$6 = wellKnownSymbol$c('iterator');
var getIteratorMethod$5 = function (it) {
- if (it != undefined) return it[ITERATOR$4]
- || it['@@iterator']
- || Iterators[classof$7(it)];
+ if (it != undefined) return getMethod$5(it, ITERATOR$6)
+ || getMethod$5(it, '@@iterator')
+ || Iterators[classof$6(it)];
};
- var anObject$d = anObject$m;
+ var global$G = global$1m;
+ var call$h = functionCall;
+ var aCallable$7 = aCallable$a;
+ var anObject$d = anObject$n;
+ var tryToString$1 = tryToString$5;
+ var getIteratorMethod$4 = getIteratorMethod$5;
+
+ var TypeError$c = global$G.TypeError;
+
+ var getIterator$4 = function (argument, usingIterator) {
+ var iteratorMethod = arguments.length < 2 ? getIteratorMethod$4(argument) : usingIterator;
+ if (aCallable$7(iteratorMethod)) return anObject$d(call$h(iteratorMethod, argument));
+ throw TypeError$c(tryToString$1(argument) + ' is not iterable');
+ };
+
+ var call$g = functionCall;
+ var anObject$c = anObject$n;
+ var getMethod$4 = getMethod$7;
- var iteratorClose$2 = function (iterator) {
- var returnMethod = iterator['return'];
- if (returnMethod !== undefined) {
- return anObject$d(returnMethod.call(iterator)).value;
+ var iteratorClose$2 = function (iterator, kind, value) {
+ var innerResult, innerError;
+ anObject$c(iterator);
+ try {
+ innerResult = getMethod$4(iterator, 'return');
+ if (!innerResult) {
+ if (kind === 'throw') throw value;
+ return value;
+ }
+ innerResult = call$g(innerResult, iterator);
+ } catch (error) {
+ innerError = true;
+ innerResult = error;
}
+ if (kind === 'throw') throw value;
+ if (innerError) throw innerResult;
+ anObject$c(innerResult);
+ return value;
};
- var anObject$c = anObject$m;
+ var global$F = global$1m;
+ var bind$d = functionBindContext;
+ var call$f = functionCall;
+ var anObject$b = anObject$n;
+ var tryToString = tryToString$5;
var isArrayIteratorMethod$2 = isArrayIteratorMethod$3;
- var toLength$j = toLength$q;
- var bind$a = functionBindContext;
- var getIteratorMethod$4 = getIteratorMethod$5;
+ var lengthOfArrayLike$c = lengthOfArrayLike$g;
+ var isPrototypeOf$4 = objectIsPrototypeOf;
+ var getIterator$3 = getIterator$4;
+ var getIteratorMethod$3 = getIteratorMethod$5;
var iteratorClose$1 = iteratorClose$2;
+ var TypeError$b = global$F.TypeError;
+
var Result = function (stopped, result) {
this.stopped = stopped;
this.result = result;
};
+ var ResultPrototype = Result.prototype;
+
var iterate$3 = function (iterable, unboundFunction, options) {
var that = options && options.that;
var AS_ENTRIES = !!(options && options.AS_ENTRIES);
var IS_ITERATOR = !!(options && options.IS_ITERATOR);
var INTERRUPTED = !!(options && options.INTERRUPTED);
- var fn = bind$a(unboundFunction, that, 1 + AS_ENTRIES + INTERRUPTED);
+ var fn = bind$d(unboundFunction, that);
var iterator, iterFn, index, length, result, next, step;
var stop = function (condition) {
- if (iterator) iteratorClose$1(iterator);
+ if (iterator) iteratorClose$1(iterator, 'normal', condition);
return new Result(true, condition);
};
var callFn = function (value) {
if (AS_ENTRIES) {
- anObject$c(value);
+ anObject$b(value);
return INTERRUPTED ? fn(value[0], value[1], stop) : fn(value[0], value[1]);
} return INTERRUPTED ? fn(value, stop) : fn(value);
};
if (IS_ITERATOR) {
iterator = iterable;
} else {
- iterFn = getIteratorMethod$4(iterable);
- if (typeof iterFn != 'function') throw TypeError('Target is not iterable');
+ iterFn = getIteratorMethod$3(iterable);
+ if (!iterFn) throw TypeError$b(tryToString(iterable) + ' is not iterable');
// optimisation for array iterators
if (isArrayIteratorMethod$2(iterFn)) {
- for (index = 0, length = toLength$j(iterable.length); length > index; index++) {
+ for (index = 0, length = lengthOfArrayLike$c(iterable); length > index; index++) {
result = callFn(iterable[index]);
- if (result && result instanceof Result) return result;
+ if (result && isPrototypeOf$4(ResultPrototype, result)) return result;
} return new Result(false);
}
- iterator = iterFn.call(iterable);
+ iterator = getIterator$3(iterable, iterFn);
}
next = iterator.next;
- while (!(step = next.call(iterator)).done) {
+ while (!(step = call$f(next, iterator)).done) {
try {
result = callFn(step.value);
} catch (error) {
- iteratorClose$1(iterator);
- throw error;
+ iteratorClose$1(iterator, 'throw', error);
}
- if (typeof result == 'object' && result && result instanceof Result) return result;
+ if (typeof result == 'object' && result && isPrototypeOf$4(ResultPrototype, result)) return result;
} return new Result(false);
};
- var wellKnownSymbol$b = wellKnownSymbol$s;
+ var wellKnownSymbol$b = wellKnownSymbol$t;
- var ITERATOR$3 = wellKnownSymbol$b('iterator');
+ var ITERATOR$5 = wellKnownSymbol$b('iterator');
var SAFE_CLOSING = false;
try {
SAFE_CLOSING = true;
}
};
- iteratorWithReturn[ITERATOR$3] = function () {
+ iteratorWithReturn[ITERATOR$5] = function () {
return this;
};
// eslint-disable-next-line es/no-array-from, no-throw-literal -- required for testing
var ITERATION_SUPPORT = false;
try {
var object = {};
- object[ITERATOR$3] = function () {
+ object[ITERATOR$5] = function () {
return {
next: function () {
return { done: ITERATION_SUPPORT = true };
return ITERATION_SUPPORT;
};
- var userAgent$4 = engineUserAgent;
+ var userAgent$6 = engineUserAgent;
- var engineIsIos = /(?:iphone|ipod|ipad).*applewebkit/i.test(userAgent$4);
+ var engineIsIos = /(?:ipad|iphone|ipod).*applewebkit/i.test(userAgent$6);
- var classof$6 = classofRaw$1;
- var global$l = global$F;
+ var classof$5 = classofRaw$1;
+ var global$E = global$1m;
- var engineIsNode = classof$6(global$l.process) == 'process';
+ var engineIsNode = classof$5(global$E.process) == 'process';
- var global$k = global$F;
- var fails$z = fails$N;
- var bind$9 = functionBindContext;
+ var global$D = global$1m;
+ var apply$7 = functionApply;
+ var bind$c = functionBindContext;
+ var isCallable$8 = isCallable$r;
+ var hasOwn$8 = hasOwnProperty_1;
+ var fails$A = fails$S;
var html = html$2;
- var createElement = documentCreateElement$1;
+ var arraySlice$8 = arraySlice$c;
+ var createElement = documentCreateElement$2;
var IS_IOS$1 = engineIsIos;
- var IS_NODE$3 = engineIsNode;
-
- var location$1 = global$k.location;
- var set$2 = global$k.setImmediate;
- var clear = global$k.clearImmediate;
- var process$3 = global$k.process;
- var MessageChannel = global$k.MessageChannel;
- var Dispatch$1 = global$k.Dispatch;
+ var IS_NODE$4 = engineIsNode;
+
+ var set$2 = global$D.setImmediate;
+ var clear = global$D.clearImmediate;
+ var process$3 = global$D.process;
+ var Dispatch$1 = global$D.Dispatch;
+ var Function$3 = global$D.Function;
+ var MessageChannel = global$D.MessageChannel;
+ var String$2 = global$D.String;
var counter = 0;
var queue = {};
var ONREADYSTATECHANGE = 'onreadystatechange';
- var defer, channel, port;
+ var location$1, defer, channel, port;
+
+ try {
+ // Deno throws a ReferenceError on `location` access without `--location` flag
+ location$1 = global$D.location;
+ } catch (error) { /* empty */ }
var run = function (id) {
- // eslint-disable-next-line no-prototype-builtins -- safe
- if (queue.hasOwnProperty(id)) {
+ if (hasOwn$8(queue, id)) {
var fn = queue[id];
delete queue[id];
fn();
var post = function (id) {
// old engines have not location.origin
- global$k.postMessage(id + '', location$1.protocol + '//' + location$1.host);
+ global$D.postMessage(String$2(id), location$1.protocol + '//' + location$1.host);
};
// Node.js 0.9+ & IE10+ has setImmediate, otherwise:
if (!set$2 || !clear) {
set$2 = function setImmediate(fn) {
- var args = [];
- var i = 1;
- while (arguments.length > i) args.push(arguments[i++]);
+ var args = arraySlice$8(arguments, 1);
queue[++counter] = function () {
- // eslint-disable-next-line no-new-func -- spec requirement
- (typeof fn == 'function' ? fn : Function(fn)).apply(undefined, args);
+ apply$7(isCallable$8(fn) ? fn : Function$3(fn), undefined, args);
};
defer(counter);
return counter;
delete queue[id];
};
// Node.js 0.8-
- if (IS_NODE$3) {
+ if (IS_NODE$4) {
defer = function (id) {
process$3.nextTick(runner(id));
};
channel = new MessageChannel();
port = channel.port2;
channel.port1.onmessage = listener;
- defer = bind$9(port.postMessage, port, 1);
+ defer = bind$c(port.postMessage, port);
// Browsers with postMessage, skip WebWorkers
// IE8 has postMessage, but it's sync & typeof its postMessage is 'object'
} else if (
- global$k.addEventListener &&
- typeof postMessage == 'function' &&
- !global$k.importScripts &&
+ global$D.addEventListener &&
+ isCallable$8(global$D.postMessage) &&
+ !global$D.importScripts &&
location$1 && location$1.protocol !== 'file:' &&
- !fails$z(post)
+ !fails$A(post)
) {
defer = post;
- global$k.addEventListener('message', listener, false);
+ global$D.addEventListener('message', listener, false);
// IE8-
} else if (ONREADYSTATECHANGE in createElement('script')) {
defer = function (id) {
clear: clear
};
- var userAgent$3 = engineUserAgent;
+ var userAgent$5 = engineUserAgent;
+ var global$C = global$1m;
+
+ var engineIsIosPebble = /ipad|iphone|ipod/i.test(userAgent$5) && global$C.Pebble !== undefined;
- var engineIsWebosWebkit = /web0s(?!.*chrome)/i.test(userAgent$3);
+ var userAgent$4 = engineUserAgent;
+
+ var engineIsWebosWebkit = /web0s(?!.*chrome)/i.test(userAgent$4);
- var global$j = global$F;
+ var global$B = global$1m;
+ var bind$b = functionBindContext;
var getOwnPropertyDescriptor$3 = objectGetOwnPropertyDescriptor.f;
var macrotask = task$1.set;
var IS_IOS = engineIsIos;
+ var IS_IOS_PEBBLE = engineIsIosPebble;
var IS_WEBOS_WEBKIT = engineIsWebosWebkit;
- var IS_NODE$2 = engineIsNode;
+ var IS_NODE$3 = engineIsNode;
- var MutationObserver = global$j.MutationObserver || global$j.WebKitMutationObserver;
- var document$2 = global$j.document;
- var process$2 = global$j.process;
- var Promise$1 = global$j.Promise;
+ var MutationObserver = global$B.MutationObserver || global$B.WebKitMutationObserver;
+ var document$2 = global$B.document;
+ var process$2 = global$B.process;
+ var Promise$1 = global$B.Promise;
// Node.js 11 shows ExperimentalWarning on getting `queueMicrotask`
- var queueMicrotaskDescriptor = getOwnPropertyDescriptor$3(global$j, 'queueMicrotask');
+ var queueMicrotaskDescriptor = getOwnPropertyDescriptor$3(global$B, 'queueMicrotask');
var queueMicrotask = queueMicrotaskDescriptor && queueMicrotaskDescriptor.value;
var flush, head, last, notify$1, toggle, node, promise, then;
if (!queueMicrotask) {
flush = function () {
var parent, fn;
- if (IS_NODE$2 && (parent = process$2.domain)) parent.exit();
+ if (IS_NODE$3 && (parent = process$2.domain)) parent.exit();
while (head) {
fn = head.fn;
head = head.next;
// browsers with MutationObserver, except iOS - https://github.com/zloirock/core-js/issues/339
// also except WebOS Webkit https://github.com/zloirock/core-js/issues/898
- if (!IS_IOS && !IS_NODE$2 && !IS_WEBOS_WEBKIT && MutationObserver && document$2) {
+ if (!IS_IOS && !IS_NODE$3 && !IS_WEBOS_WEBKIT && MutationObserver && document$2) {
toggle = true;
node = document$2.createTextNode('');
new MutationObserver(flush).observe(node, { characterData: true });
node.data = toggle = !toggle;
};
// environments with maybe non-completely correct, but existent Promise
- } else if (Promise$1 && Promise$1.resolve) {
+ } else if (!IS_IOS_PEBBLE && Promise$1 && Promise$1.resolve) {
// Promise.resolve without an argument throws an error in LG WebOS 2
promise = Promise$1.resolve(undefined);
// workaround of WebKit ~ iOS Safari 10.1 bug
promise.constructor = Promise$1;
- then = promise.then;
+ then = bind$b(promise.then, promise);
notify$1 = function () {
- then.call(promise, flush);
+ then(flush);
};
// Node.js without promises
- } else if (IS_NODE$2) {
+ } else if (IS_NODE$3) {
notify$1 = function () {
process$2.nextTick(flush);
};
// - onreadystatechange
// - setTimeout
} else {
+ // strange IE + webpack dev server bug - use .bind(global)
+ macrotask = bind$b(macrotask, global$B);
notify$1 = function () {
- // strange IE + webpack dev server bug - use .call(global)
- macrotask.call(global$j, flush);
+ macrotask(flush);
};
}
}
var newPromiseCapability$2 = {};
- var aFunction$6 = aFunction$9;
+ var aCallable$6 = aCallable$a;
var PromiseCapability = function (C) {
var resolve, reject;
resolve = $$resolve;
reject = $$reject;
});
- this.resolve = aFunction$6(resolve);
- this.reject = aFunction$6(reject);
+ this.resolve = aCallable$6(resolve);
+ this.reject = aCallable$6(reject);
};
// `NewPromiseCapability` abstract operation
return new PromiseCapability(C);
};
- var anObject$b = anObject$m;
- var isObject$h = isObject$r;
+ var anObject$a = anObject$n;
+ var isObject$j = isObject$s;
var newPromiseCapability$1 = newPromiseCapability$2;
var promiseResolve$2 = function (C, x) {
- anObject$b(C);
- if (isObject$h(x) && x.constructor === C) return x;
+ anObject$a(C);
+ if (isObject$j(x) && x.constructor === C) return x;
var promiseCapability = newPromiseCapability$1.f(C);
var resolve = promiseCapability.resolve;
resolve(x);
return promiseCapability.promise;
};
- var global$i = global$F;
+ var global$A = global$1m;
var hostReportErrors$1 = function (a, b) {
- var console = global$i.console;
+ var console = global$A.console;
if (console && console.error) {
- arguments.length === 1 ? console.error(a) : console.error(a, b);
+ arguments.length == 1 ? console.error(a) : console.error(a, b);
}
};
var engineIsBrowser = typeof window == 'object';
- var $$V = _export;
- var global$h = global$F;
- var getBuiltIn$3 = getBuiltIn$9;
+ var $$$ = _export;
+ var global$z = global$1m;
+ var getBuiltIn$3 = getBuiltIn$b;
+ var call$e = functionCall;
var NativePromise$1 = nativePromiseConstructor;
- var redefine$8 = redefine$g.exports;
+ var redefine$8 = redefine$h.exports;
var redefineAll$2 = redefineAll$4;
var setPrototypeOf$3 = objectSetPrototypeOf;
var setToStringTag$5 = setToStringTag$a;
var setSpecies$3 = setSpecies$5;
- var isObject$g = isObject$r;
- var aFunction$5 = aFunction$9;
+ var aCallable$5 = aCallable$a;
+ var isCallable$7 = isCallable$r;
+ var isObject$i = isObject$s;
var anInstance$5 = anInstance$7;
- var inspectSource = inspectSource$3;
+ var inspectSource = inspectSource$4;
var iterate$2 = iterate$3;
var checkCorrectnessOfIteration$3 = checkCorrectnessOfIteration$4;
- var speciesConstructor$6 = speciesConstructor$8;
+ var speciesConstructor$3 = speciesConstructor$5;
var task = task$1.set;
var microtask = microtask$1;
var promiseResolve$1 = promiseResolve$2;
var perform = perform$1;
var InternalStateModule$4 = internalState;
var isForced$3 = isForced_1;
- var wellKnownSymbol$a = wellKnownSymbol$s;
+ var wellKnownSymbol$a = wellKnownSymbol$t;
var IS_BROWSER = engineIsBrowser;
- var IS_NODE$1 = engineIsNode;
+ var IS_NODE$2 = engineIsNode;
var V8_VERSION$1 = engineV8Version;
var SPECIES$2 = wellKnownSymbol$a('species');
var PROMISE = 'Promise';
- var getInternalState$2 = InternalStateModule$4.get;
+
+ var getInternalState$1 = InternalStateModule$4.get;
var setInternalState$4 = InternalStateModule$4.set;
var getInternalPromiseState = InternalStateModule$4.getterFor(PROMISE);
var NativePromisePrototype = NativePromise$1 && NativePromise$1.prototype;
var PromiseConstructor = NativePromise$1;
- var PromiseConstructorPrototype = NativePromisePrototype;
- var TypeError$1 = global$h.TypeError;
- var document$1 = global$h.document;
- var process$1 = global$h.process;
+ var PromisePrototype = NativePromisePrototype;
+ var TypeError$a = global$z.TypeError;
+ var document$1 = global$z.document;
+ var process$1 = global$z.process;
var newPromiseCapability = newPromiseCapabilityModule.f;
var newGenericPromiseCapability = newPromiseCapability;
- var DISPATCH_EVENT = !!(document$1 && document$1.createEvent && global$h.dispatchEvent);
- var NATIVE_REJECTION_EVENT = typeof PromiseRejectionEvent == 'function';
+
+ var DISPATCH_EVENT = !!(document$1 && document$1.createEvent && global$z.dispatchEvent);
+ var NATIVE_REJECTION_EVENT = isCallable$7(global$z.PromiseRejectionEvent);
var UNHANDLED_REJECTION = 'unhandledrejection';
var REJECTION_HANDLED = 'rejectionhandled';
var PENDING = 0;
var HANDLED = 1;
var UNHANDLED = 2;
var SUBCLASSING = false;
+
var Internal, OwnPromiseCapability, PromiseWrapper, nativeThen;
- var FORCED$f = isForced$3(PROMISE, function () {
- var GLOBAL_CORE_JS_PROMISE = inspectSource(PromiseConstructor) !== String(PromiseConstructor);
+ var FORCED$h = isForced$3(PROMISE, function () {
+ var PROMISE_CONSTRUCTOR_SOURCE = inspectSource(PromiseConstructor);
+ var GLOBAL_CORE_JS_PROMISE = PROMISE_CONSTRUCTOR_SOURCE !== String(PromiseConstructor);
// V8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables
// https://bugs.chromium.org/p/chromium/issues/detail?id=830565
// We can't detect it synchronously, so just check versions
// We can't use @@species feature detection in V8 since it causes
// deoptimization and performance degradation
// https://github.com/zloirock/core-js/issues/679
- if (V8_VERSION$1 >= 51 && /native code/.test(PromiseConstructor)) return false;
+ if (V8_VERSION$1 >= 51 && /native code/.test(PROMISE_CONSTRUCTOR_SOURCE)) return false;
// Detect correctness of subclassing with @@species support
var promise = new PromiseConstructor(function (resolve) { resolve(1); });
var FakePromise = function (exec) {
return !GLOBAL_CORE_JS_PROMISE && IS_BROWSER && !NATIVE_REJECTION_EVENT;
});
- var INCORRECT_ITERATION$1 = FORCED$f || !checkCorrectnessOfIteration$3(function (iterable) {
+ var INCORRECT_ITERATION$1 = FORCED$h || !checkCorrectnessOfIteration$3(function (iterable) {
PromiseConstructor.all(iterable)['catch'](function () { /* empty */ });
});
// helpers
var isThenable = function (it) {
var then;
- return isObject$g(it) && typeof (then = it.then) == 'function' ? then : false;
+ return isObject$i(it) && isCallable$7(then = it.then) ? then : false;
};
var notify = function (state, isReject) {
}
}
if (result === reaction.promise) {
- reject(TypeError$1('Promise-chain cycle'));
+ reject(TypeError$a('Promise-chain cycle'));
} else if (then = isThenable(result)) {
- then.call(result, resolve, reject);
+ call$e(then, result, resolve, reject);
} else resolve(result);
} else reject(value);
} catch (error) {
event.promise = promise;
event.reason = reason;
event.initEvent(name, false, true);
- global$h.dispatchEvent(event);
+ global$z.dispatchEvent(event);
} else event = { promise: promise, reason: reason };
- if (!NATIVE_REJECTION_EVENT && (handler = global$h['on' + name])) handler(event);
+ if (!NATIVE_REJECTION_EVENT && (handler = global$z['on' + name])) handler(event);
else if (name === UNHANDLED_REJECTION) hostReportErrors('Unhandled promise rejection', reason);
};
var onUnhandled = function (state) {
- task.call(global$h, function () {
+ call$e(task, global$z, function () {
var promise = state.facade;
var value = state.value;
var IS_UNHANDLED = isUnhandled(state);
var result;
if (IS_UNHANDLED) {
result = perform(function () {
- if (IS_NODE$1) {
+ if (IS_NODE$2) {
process$1.emit('unhandledRejection', value, promise);
} else dispatchEvent$1(UNHANDLED_REJECTION, promise, value);
});
// Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should
- state.rejection = IS_NODE$1 || isUnhandled(state) ? UNHANDLED : HANDLED;
+ state.rejection = IS_NODE$2 || isUnhandled(state) ? UNHANDLED : HANDLED;
if (result.error) throw result.value;
}
});
};
var onHandleUnhandled = function (state) {
- task.call(global$h, function () {
+ call$e(task, global$z, function () {
var promise = state.facade;
- if (IS_NODE$1) {
+ if (IS_NODE$2) {
process$1.emit('rejectionHandled', promise);
} else dispatchEvent$1(REJECTION_HANDLED, promise, state.value);
});
};
- var bind$8 = function (fn, state, unwrap) {
+ var bind$a = function (fn, state, unwrap) {
return function (value) {
fn(state, value, unwrap);
};
state.done = true;
if (unwrap) state = unwrap;
try {
- if (state.facade === value) throw TypeError$1("Promise can't be resolved itself");
+ if (state.facade === value) throw TypeError$a("Promise can't be resolved itself");
var then = isThenable(value);
if (then) {
microtask(function () {
var wrapper = { done: false };
try {
- then.call(value,
- bind$8(internalResolve, wrapper, state),
- bind$8(internalReject, wrapper, state)
+ call$e(then, value,
+ bind$a(internalResolve, wrapper, state),
+ bind$a(internalReject, wrapper, state)
);
} catch (error) {
internalReject(wrapper, error, state);
};
// constructor polyfill
- if (FORCED$f) {
+ if (FORCED$h) {
// 25.4.3.1 Promise(executor)
PromiseConstructor = function Promise(executor) {
- anInstance$5(this, PromiseConstructor, PROMISE);
- aFunction$5(executor);
- Internal.call(this);
- var state = getInternalState$2(this);
+ anInstance$5(this, PromisePrototype);
+ aCallable$5(executor);
+ call$e(Internal, this);
+ var state = getInternalState$1(this);
try {
- executor(bind$8(internalResolve, state), bind$8(internalReject, state));
+ executor(bind$a(internalResolve, state), bind$a(internalReject, state));
} catch (error) {
internalReject(state, error);
}
};
- PromiseConstructorPrototype = PromiseConstructor.prototype;
+ PromisePrototype = PromiseConstructor.prototype;
// eslint-disable-next-line no-unused-vars -- required for `.length`
Internal = function Promise(executor) {
setInternalState$4(this, {
value: undefined
});
};
- Internal.prototype = redefineAll$2(PromiseConstructorPrototype, {
+ Internal.prototype = redefineAll$2(PromisePrototype, {
// `Promise.prototype.then` method
// https://tc39.es/ecma262/#sec-promise.prototype.then
then: function then(onFulfilled, onRejected) {
var state = getInternalPromiseState(this);
- var reaction = newPromiseCapability(speciesConstructor$6(this, PromiseConstructor));
- reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true;
- reaction.fail = typeof onRejected == 'function' && onRejected;
- reaction.domain = IS_NODE$1 ? process$1.domain : undefined;
+ var reactions = state.reactions;
+ var reaction = newPromiseCapability(speciesConstructor$3(this, PromiseConstructor));
+ reaction.ok = isCallable$7(onFulfilled) ? onFulfilled : true;
+ reaction.fail = isCallable$7(onRejected) && onRejected;
+ reaction.domain = IS_NODE$2 ? process$1.domain : undefined;
state.parent = true;
- state.reactions.push(reaction);
+ reactions[reactions.length] = reaction;
if (state.state != PENDING) notify(state, false);
return reaction.promise;
},
});
OwnPromiseCapability = function () {
var promise = new Internal();
- var state = getInternalState$2(promise);
+ var state = getInternalState$1(promise);
this.promise = promise;
- this.resolve = bind$8(internalResolve, state);
- this.reject = bind$8(internalReject, state);
+ this.resolve = bind$a(internalResolve, state);
+ this.reject = bind$a(internalReject, state);
};
newPromiseCapabilityModule.f = newPromiseCapability = function (C) {
return C === PromiseConstructor || C === PromiseWrapper
: newGenericPromiseCapability(C);
};
- if (typeof NativePromise$1 == 'function' && NativePromisePrototype !== Object.prototype) {
+ if (isCallable$7(NativePromise$1) && NativePromisePrototype !== Object.prototype) {
nativeThen = NativePromisePrototype.then;
if (!SUBCLASSING) {
redefine$8(NativePromisePrototype, 'then', function then(onFulfilled, onRejected) {
var that = this;
return new PromiseConstructor(function (resolve, reject) {
- nativeThen.call(that, resolve, reject);
+ call$e(nativeThen, that, resolve, reject);
}).then(onFulfilled, onRejected);
// https://github.com/zloirock/core-js/issues/640
}, { unsafe: true });
// makes sure that native promise-based APIs `Promise#catch` properly works with patched `Promise#then`
- redefine$8(NativePromisePrototype, 'catch', PromiseConstructorPrototype['catch'], { unsafe: true });
+ redefine$8(NativePromisePrototype, 'catch', PromisePrototype['catch'], { unsafe: true });
}
// make `.constructor === Promise` work for native promise-based APIs
// make `instanceof Promise` work for native promise-based APIs
if (setPrototypeOf$3) {
- setPrototypeOf$3(NativePromisePrototype, PromiseConstructorPrototype);
+ setPrototypeOf$3(NativePromisePrototype, PromisePrototype);
}
}
}
- $$V({ global: true, wrap: true, forced: FORCED$f }, {
+ $$$({ global: true, wrap: true, forced: FORCED$h }, {
Promise: PromiseConstructor
});
PromiseWrapper = getBuiltIn$3(PROMISE);
// statics
- $$V({ target: PROMISE, stat: true, forced: FORCED$f }, {
+ $$$({ target: PROMISE, stat: true, forced: FORCED$h }, {
// `Promise.reject` method
// https://tc39.es/ecma262/#sec-promise.reject
reject: function reject(r) {
var capability = newPromiseCapability(this);
- capability.reject.call(undefined, r);
+ call$e(capability.reject, undefined, r);
return capability.promise;
}
});
- $$V({ target: PROMISE, stat: true, forced: FORCED$f }, {
+ $$$({ target: PROMISE, stat: true, forced: FORCED$h }, {
// `Promise.resolve` method
// https://tc39.es/ecma262/#sec-promise.resolve
resolve: function resolve(x) {
}
});
- $$V({ target: PROMISE, stat: true, forced: INCORRECT_ITERATION$1 }, {
+ $$$({ target: PROMISE, stat: true, forced: INCORRECT_ITERATION$1 }, {
// `Promise.all` method
// https://tc39.es/ecma262/#sec-promise.all
all: function all(iterable) {
var resolve = capability.resolve;
var reject = capability.reject;
var result = perform(function () {
- var $promiseResolve = aFunction$5(C.resolve);
+ var $promiseResolve = aCallable$5(C.resolve);
var values = [];
var counter = 0;
var remaining = 1;
iterate$2(iterable, function (promise) {
var index = counter++;
var alreadyCalled = false;
- values.push(undefined);
remaining++;
- $promiseResolve.call(C, promise).then(function (value) {
+ call$e($promiseResolve, C, promise).then(function (value) {
if (alreadyCalled) return;
alreadyCalled = true;
values[index] = value;
var capability = newPromiseCapability(C);
var reject = capability.reject;
var result = perform(function () {
- var $promiseResolve = aFunction$5(C.resolve);
+ var $promiseResolve = aCallable$5(C.resolve);
iterate$2(iterable, function (promise) {
- $promiseResolve.call(C, promise).then(capability.resolve, reject);
+ call$e($promiseResolve, C, promise).then(capability.resolve, reject);
});
});
if (result.error) reject(result.value);
/* eslint-disable no-new -- required for testing */
- var global$g = global$F;
- var fails$y = fails$N;
+ var global$y = global$1m;
+ var fails$z = fails$S;
var checkCorrectnessOfIteration$2 = checkCorrectnessOfIteration$4;
var NATIVE_ARRAY_BUFFER_VIEWS$1 = arrayBufferViewCore.NATIVE_ARRAY_BUFFER_VIEWS;
- var ArrayBuffer$2 = global$g.ArrayBuffer;
- var Int8Array$2 = global$g.Int8Array;
+ var ArrayBuffer$2 = global$y.ArrayBuffer;
+ var Int8Array$2 = global$y.Int8Array;
- var typedArrayConstructorsRequireWrappers = !NATIVE_ARRAY_BUFFER_VIEWS$1 || !fails$y(function () {
+ var typedArrayConstructorsRequireWrappers = !NATIVE_ARRAY_BUFFER_VIEWS$1 || !fails$z(function () {
Int8Array$2(1);
- }) || !fails$y(function () {
+ }) || !fails$z(function () {
new Int8Array$2(-1);
}) || !checkCorrectnessOfIteration$2(function (iterable) {
new Int8Array$2();
new Int8Array$2(null);
new Int8Array$2(1.5);
new Int8Array$2(iterable);
- }, true) || fails$y(function () {
+ }, true) || fails$z(function () {
// Safari (11+) bug - a reason why even Safari 13 should load a typed array polyfill
return new Int8Array$2(new ArrayBuffer$2(2), 1, undefined).length !== 1;
});
- var toInteger$5 = toInteger$b;
+ var isObject$h = isObject$s;
+
+ var floor$6 = Math.floor;
+
+ // `IsIntegralNumber` abstract operation
+ // https://tc39.es/ecma262/#sec-isintegralnumber
+ // eslint-disable-next-line es/no-number-isinteger -- safe
+ var isIntegralNumber$1 = Number.isInteger || function isInteger(it) {
+ return !isObject$h(it) && isFinite(it) && floor$6(it) === it;
+ };
+
+ var global$x = global$1m;
+ var toIntegerOrInfinity$5 = toIntegerOrInfinity$b;
+
+ var RangeError$9 = global$x.RangeError;
var toPositiveInteger$1 = function (it) {
- var result = toInteger$5(it);
- if (result < 0) throw RangeError("The argument can't be less than 0");
+ var result = toIntegerOrInfinity$5(it);
+ if (result < 0) throw RangeError$9("The argument can't be less than 0");
return result;
};
+ var global$w = global$1m;
var toPositiveInteger = toPositiveInteger$1;
+ var RangeError$8 = global$w.RangeError;
+
var toOffset$2 = function (it, BYTES) {
var offset = toPositiveInteger(it);
- if (offset % BYTES) throw RangeError('Wrong offset');
+ if (offset % BYTES) throw RangeError$8('Wrong offset');
return offset;
};
- var toObject$c = toObject$i;
- var toLength$i = toLength$q;
- var getIteratorMethod$3 = getIteratorMethod$5;
+ var bind$9 = functionBindContext;
+ var call$d = functionCall;
+ var aConstructor$1 = aConstructor$3;
+ var toObject$d = toObject$j;
+ var lengthOfArrayLike$b = lengthOfArrayLike$g;
+ var getIterator$2 = getIterator$4;
+ var getIteratorMethod$2 = getIteratorMethod$5;
var isArrayIteratorMethod$1 = isArrayIteratorMethod$3;
- var bind$7 = functionBindContext;
- var aTypedArrayConstructor$4 = arrayBufferViewCore.aTypedArrayConstructor;
+ var aTypedArrayConstructor$2 = arrayBufferViewCore.aTypedArrayConstructor;
var typedArrayFrom$2 = function from(source /* , mapfn, thisArg */) {
- var O = toObject$c(source);
+ var C = aConstructor$1(this);
+ var O = toObject$d(source);
var argumentsLength = arguments.length;
var mapfn = argumentsLength > 1 ? arguments[1] : undefined;
var mapping = mapfn !== undefined;
- var iteratorMethod = getIteratorMethod$3(O);
+ var iteratorMethod = getIteratorMethod$2(O);
var i, length, result, step, iterator, next;
- if (iteratorMethod != undefined && !isArrayIteratorMethod$1(iteratorMethod)) {
- iterator = iteratorMethod.call(O);
+ if (iteratorMethod && !isArrayIteratorMethod$1(iteratorMethod)) {
+ iterator = getIterator$2(O, iteratorMethod);
next = iterator.next;
O = [];
- while (!(step = next.call(iterator)).done) {
+ while (!(step = call$d(next, iterator)).done) {
O.push(step.value);
}
}
if (mapping && argumentsLength > 2) {
- mapfn = bind$7(mapfn, arguments[2], 2);
+ mapfn = bind$9(mapfn, arguments[2]);
}
- length = toLength$i(O.length);
- result = new (aTypedArrayConstructor$4(this))(length);
+ length = lengthOfArrayLike$b(O);
+ result = new (aTypedArrayConstructor$2(C))(length);
for (i = 0; length > i; i++) {
result[i] = mapping ? mapfn(O[i], i) : O[i];
}
return result;
};
- var isObject$f = isObject$r;
+ var isCallable$6 = isCallable$r;
+ var isObject$g = isObject$s;
var setPrototypeOf$2 = objectSetPrototypeOf;
// makes subclassing work correct for wrapped built-ins
// it can work only with native `setPrototypeOf`
setPrototypeOf$2 &&
// we haven't completely correct pre-ES6 way for getting `new.target`, so use this
- typeof (NewTarget = dummy.constructor) == 'function' &&
+ isCallable$6(NewTarget = dummy.constructor) &&
NewTarget !== Wrapper &&
- isObject$f(NewTargetPrototype = NewTarget.prototype) &&
+ isObject$g(NewTargetPrototype = NewTarget.prototype) &&
NewTargetPrototype !== Wrapper.prototype
) setPrototypeOf$2($this, NewTargetPrototype);
return $this;
};
- var $$U = _export;
- var global$f = global$F;
+ var $$_ = _export;
+ var global$v = global$1m;
+ var call$c = functionCall;
var DESCRIPTORS$c = descriptors;
var TYPED_ARRAYS_CONSTRUCTORS_REQUIRES_WRAPPERS$1 = typedArrayConstructorsRequireWrappers;
- var ArrayBufferViewCore$m = arrayBufferViewCore;
+ var ArrayBufferViewCore$n = arrayBufferViewCore;
var ArrayBufferModule = arrayBuffer;
var anInstance$4 = anInstance$7;
var createPropertyDescriptor$2 = createPropertyDescriptor$7;
- var createNonEnumerableProperty$2 = createNonEnumerableProperty$e;
- var toLength$h = toLength$q;
+ var createNonEnumerableProperty$2 = createNonEnumerableProperty$b;
+ var isIntegralNumber = isIntegralNumber$1;
+ var toLength$7 = toLength$c;
var toIndex = toIndex$2;
var toOffset$1 = toOffset$2;
- var toPrimitive$3 = toPrimitive$7;
- var has$5 = has$j;
- var classof$5 = classof$b;
- var isObject$e = isObject$r;
- var create$9 = objectCreate;
+ var toPropertyKey$1 = toPropertyKey$5;
+ var hasOwn$7 = hasOwnProperty_1;
+ var classof$4 = classof$d;
+ var isObject$f = isObject$s;
+ var isSymbol$2 = isSymbol$6;
+ var create$7 = objectCreate;
+ var isPrototypeOf$3 = objectIsPrototypeOf;
var setPrototypeOf$1 = objectSetPrototypeOf;
var getOwnPropertyNames$2 = objectGetOwnPropertyNames.f;
var typedArrayFrom$1 = typedArrayFrom$2;
var InternalStateModule$3 = internalState;
var inheritIfRequired$3 = inheritIfRequired$4;
- var getInternalState$1 = InternalStateModule$3.get;
+ var getInternalState = InternalStateModule$3.get;
var setInternalState$3 = InternalStateModule$3.set;
var nativeDefineProperty = definePropertyModule$1.f;
var nativeGetOwnPropertyDescriptor$1 = getOwnPropertyDescriptorModule$1.f;
var round = Math.round;
- var RangeError$1 = global$f.RangeError;
+ var RangeError$7 = global$v.RangeError;
var ArrayBuffer$1 = ArrayBufferModule.ArrayBuffer;
+ var ArrayBufferPrototype = ArrayBuffer$1.prototype;
var DataView$1 = ArrayBufferModule.DataView;
- var NATIVE_ARRAY_BUFFER_VIEWS = ArrayBufferViewCore$m.NATIVE_ARRAY_BUFFER_VIEWS;
- var TYPED_ARRAY_TAG = ArrayBufferViewCore$m.TYPED_ARRAY_TAG;
- var TypedArray = ArrayBufferViewCore$m.TypedArray;
- var TypedArrayPrototype = ArrayBufferViewCore$m.TypedArrayPrototype;
- var aTypedArrayConstructor$3 = ArrayBufferViewCore$m.aTypedArrayConstructor;
- var isTypedArray = ArrayBufferViewCore$m.isTypedArray;
+ var NATIVE_ARRAY_BUFFER_VIEWS = ArrayBufferViewCore$n.NATIVE_ARRAY_BUFFER_VIEWS;
+ var TYPED_ARRAY_CONSTRUCTOR$1 = ArrayBufferViewCore$n.TYPED_ARRAY_CONSTRUCTOR;
+ var TYPED_ARRAY_TAG = ArrayBufferViewCore$n.TYPED_ARRAY_TAG;
+ var TypedArray = ArrayBufferViewCore$n.TypedArray;
+ var TypedArrayPrototype = ArrayBufferViewCore$n.TypedArrayPrototype;
+ var aTypedArrayConstructor$1 = ArrayBufferViewCore$n.aTypedArrayConstructor;
+ var isTypedArray = ArrayBufferViewCore$n.isTypedArray;
var BYTES_PER_ELEMENT = 'BYTES_PER_ELEMENT';
var WRONG_LENGTH = 'Wrong length';
var fromList = function (C, list) {
+ aTypedArrayConstructor$1(C);
var index = 0;
var length = list.length;
- var result = new (aTypedArrayConstructor$3(C))(length);
+ var result = new C(length);
while (length > index) result[index] = list[index++];
return result;
};
var addGetter = function (it, key) {
nativeDefineProperty(it, key, { get: function () {
- return getInternalState$1(this)[key];
+ return getInternalState(this)[key];
} });
};
var isArrayBuffer = function (it) {
var klass;
- return it instanceof ArrayBuffer$1 || (klass = classof$5(it)) == 'ArrayBuffer' || klass == 'SharedArrayBuffer';
+ return isPrototypeOf$3(ArrayBufferPrototype, it) || (klass = classof$4(it)) == 'ArrayBuffer' || klass == 'SharedArrayBuffer';
};
var isTypedArrayIndex = function (target, key) {
return isTypedArray(target)
- && typeof key != 'symbol'
+ && !isSymbol$2(key)
&& key in target
- && String(+key) == String(key);
+ && isIntegralNumber(+key)
+ && key >= 0;
};
var wrappedGetOwnPropertyDescriptor = function getOwnPropertyDescriptor(target, key) {
- return isTypedArrayIndex(target, key = toPrimitive$3(key, true))
+ key = toPropertyKey$1(key);
+ return isTypedArrayIndex(target, key)
? createPropertyDescriptor$2(2, target[key])
: nativeGetOwnPropertyDescriptor$1(target, key);
};
var wrappedDefineProperty = function defineProperty(target, key, descriptor) {
- if (isTypedArrayIndex(target, key = toPrimitive$3(key, true))
- && isObject$e(descriptor)
- && has$5(descriptor, 'value')
- && !has$5(descriptor, 'get')
- && !has$5(descriptor, 'set')
+ key = toPropertyKey$1(key);
+ if (isTypedArrayIndex(target, key)
+ && isObject$f(descriptor)
+ && hasOwn$7(descriptor, 'value')
+ && !hasOwn$7(descriptor, 'get')
+ && !hasOwn$7(descriptor, 'set')
// TODO: add validation descriptor w/o calling accessors
&& !descriptor.configurable
- && (!has$5(descriptor, 'writable') || descriptor.writable)
- && (!has$5(descriptor, 'enumerable') || descriptor.enumerable)
+ && (!hasOwn$7(descriptor, 'writable') || descriptor.writable)
+ && (!hasOwn$7(descriptor, 'enumerable') || descriptor.enumerable)
) {
target[key] = descriptor.value;
return target;
addGetter(TypedArrayPrototype, 'length');
}
- $$U({ target: 'Object', stat: true, forced: !NATIVE_ARRAY_BUFFER_VIEWS }, {
+ $$_({ target: 'Object', stat: true, forced: !NATIVE_ARRAY_BUFFER_VIEWS }, {
getOwnPropertyDescriptor: wrappedGetOwnPropertyDescriptor,
defineProperty: wrappedDefineProperty
});
var CONSTRUCTOR_NAME = TYPE + (CLAMPED ? 'Clamped' : '') + 'Array';
var GETTER = 'get' + TYPE;
var SETTER = 'set' + TYPE;
- var NativeTypedArrayConstructor = global$f[CONSTRUCTOR_NAME];
+ var NativeTypedArrayConstructor = global$v[CONSTRUCTOR_NAME];
var TypedArrayConstructor = NativeTypedArrayConstructor;
var TypedArrayConstructorPrototype = TypedArrayConstructor && TypedArrayConstructor.prototype;
var exported = {};
var getter = function (that, index) {
- var data = getInternalState$1(that);
+ var data = getInternalState(that);
return data.view[GETTER](index * BYTES + data.byteOffset, true);
};
var setter = function (that, index, value) {
- var data = getInternalState$1(that);
+ var data = getInternalState(that);
if (CLAMPED) value = (value = round(value)) < 0 ? 0 : value > 0xFF ? 0xFF : value & 0xFF;
data.view[SETTER](index * BYTES + data.byteOffset, value, true);
};
if (!NATIVE_ARRAY_BUFFER_VIEWS) {
TypedArrayConstructor = wrapper(function (that, data, offset, $length) {
- anInstance$4(that, TypedArrayConstructor, CONSTRUCTOR_NAME);
+ anInstance$4(that, TypedArrayConstructorPrototype);
var index = 0;
var byteOffset = 0;
var buffer, byteLength, length;
- if (!isObject$e(data)) {
+ if (!isObject$f(data)) {
length = toIndex(data);
byteLength = length * BYTES;
buffer = new ArrayBuffer$1(byteLength);
byteOffset = toOffset$1(offset, BYTES);
var $len = data.byteLength;
if ($length === undefined) {
- if ($len % BYTES) throw RangeError$1(WRONG_LENGTH);
+ if ($len % BYTES) throw RangeError$7(WRONG_LENGTH);
byteLength = $len - byteOffset;
- if (byteLength < 0) throw RangeError$1(WRONG_LENGTH);
+ if (byteLength < 0) throw RangeError$7(WRONG_LENGTH);
} else {
- byteLength = toLength$h($length) * BYTES;
- if (byteLength + byteOffset > $len) throw RangeError$1(WRONG_LENGTH);
+ byteLength = toLength$7($length) * BYTES;
+ if (byteLength + byteOffset > $len) throw RangeError$7(WRONG_LENGTH);
}
length = byteLength / BYTES;
} else if (isTypedArray(data)) {
return fromList(TypedArrayConstructor, data);
} else {
- return typedArrayFrom$1.call(TypedArrayConstructor, data);
+ return call$c(typedArrayFrom$1, TypedArrayConstructor, data);
}
setInternalState$3(that, {
buffer: buffer,
});
if (setPrototypeOf$1) setPrototypeOf$1(TypedArrayConstructor, TypedArray);
- TypedArrayConstructorPrototype = TypedArrayConstructor.prototype = create$9(TypedArrayPrototype);
+ TypedArrayConstructorPrototype = TypedArrayConstructor.prototype = create$7(TypedArrayPrototype);
} else if (TYPED_ARRAYS_CONSTRUCTORS_REQUIRES_WRAPPERS$1) {
TypedArrayConstructor = wrapper(function (dummy, data, typedArrayOffset, $length) {
- anInstance$4(dummy, TypedArrayConstructor, CONSTRUCTOR_NAME);
+ anInstance$4(dummy, TypedArrayConstructorPrototype);
return inheritIfRequired$3(function () {
- if (!isObject$e(data)) return new NativeTypedArrayConstructor(toIndex(data));
+ if (!isObject$f(data)) return new NativeTypedArrayConstructor(toIndex(data));
if (isArrayBuffer(data)) return $length !== undefined
? new NativeTypedArrayConstructor(data, toOffset$1(typedArrayOffset, BYTES), $length)
: typedArrayOffset !== undefined
? new NativeTypedArrayConstructor(data, toOffset$1(typedArrayOffset, BYTES))
: new NativeTypedArrayConstructor(data);
if (isTypedArray(data)) return fromList(TypedArrayConstructor, data);
- return typedArrayFrom$1.call(TypedArrayConstructor, data);
+ return call$c(typedArrayFrom$1, TypedArrayConstructor, data);
}(), dummy, TypedArrayConstructor);
});
createNonEnumerableProperty$2(TypedArrayConstructorPrototype, 'constructor', TypedArrayConstructor);
}
+ createNonEnumerableProperty$2(TypedArrayConstructorPrototype, TYPED_ARRAY_CONSTRUCTOR$1, TypedArrayConstructor);
+
if (TYPED_ARRAY_TAG) {
createNonEnumerableProperty$2(TypedArrayConstructorPrototype, TYPED_ARRAY_TAG, CONSTRUCTOR_NAME);
}
exported[CONSTRUCTOR_NAME] = TypedArrayConstructor;
- $$U({
+ $$_({
global: true, forced: TypedArrayConstructor != NativeTypedArrayConstructor, sham: !NATIVE_ARRAY_BUFFER_VIEWS
}, exported);
};
});
- var toObject$b = toObject$i;
+ var toObject$c = toObject$j;
var toAbsoluteIndex$4 = toAbsoluteIndex$8;
- var toLength$g = toLength$q;
+ var lengthOfArrayLike$a = lengthOfArrayLike$g;
var min$7 = Math.min;
// https://tc39.es/ecma262/#sec-array.prototype.copywithin
// eslint-disable-next-line es/no-array-prototype-copywithin -- safe
var arrayCopyWithin = [].copyWithin || function copyWithin(target /* = 0 */, start /* = 0, end = @length */) {
- var O = toObject$b(this);
- var len = toLength$g(O.length);
+ var O = toObject$c(this);
+ var len = lengthOfArrayLike$a(O);
var to = toAbsoluteIndex$4(target, len);
var from = toAbsoluteIndex$4(start, len);
var end = arguments.length > 2 ? arguments[2] : undefined;
} return O;
};
- var ArrayBufferViewCore$l = arrayBufferViewCore;
- var $copyWithin = arrayCopyWithin;
+ var uncurryThis$A = functionUncurryThis;
+ var ArrayBufferViewCore$m = arrayBufferViewCore;
+ var $ArrayCopyWithin = arrayCopyWithin;
- var aTypedArray$l = ArrayBufferViewCore$l.aTypedArray;
- var exportTypedArrayMethod$m = ArrayBufferViewCore$l.exportTypedArrayMethod;
+ var u$ArrayCopyWithin = uncurryThis$A($ArrayCopyWithin);
+ var aTypedArray$l = ArrayBufferViewCore$m.aTypedArray;
+ var exportTypedArrayMethod$m = ArrayBufferViewCore$m.exportTypedArrayMethod;
// `%TypedArray%.prototype.copyWithin` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin
exportTypedArrayMethod$m('copyWithin', function copyWithin(target, start /* , end */) {
- return $copyWithin.call(aTypedArray$l(this), target, start, arguments.length > 2 ? arguments[2] : undefined);
+ return u$ArrayCopyWithin(aTypedArray$l(this), target, start, arguments.length > 2 ? arguments[2] : undefined);
});
- var ArrayBufferViewCore$k = arrayBufferViewCore;
+ var ArrayBufferViewCore$l = arrayBufferViewCore;
var $every$1 = arrayIteration.every;
- var aTypedArray$k = ArrayBufferViewCore$k.aTypedArray;
- var exportTypedArrayMethod$l = ArrayBufferViewCore$k.exportTypedArrayMethod;
+ var aTypedArray$k = ArrayBufferViewCore$l.aTypedArray;
+ var exportTypedArrayMethod$l = ArrayBufferViewCore$l.exportTypedArrayMethod;
// `%TypedArray%.prototype.every` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.every
return $every$1(aTypedArray$k(this), callbackfn, arguments.length > 1 ? arguments[1] : undefined);
});
- var ArrayBufferViewCore$j = arrayBufferViewCore;
+ var ArrayBufferViewCore$k = arrayBufferViewCore;
+ var call$b = functionCall;
var $fill = arrayFill$1;
- var aTypedArray$j = ArrayBufferViewCore$j.aTypedArray;
- var exportTypedArrayMethod$k = ArrayBufferViewCore$j.exportTypedArrayMethod;
+ var aTypedArray$j = ArrayBufferViewCore$k.aTypedArray;
+ var exportTypedArrayMethod$k = ArrayBufferViewCore$k.exportTypedArrayMethod;
// `%TypedArray%.prototype.fill` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill
- // eslint-disable-next-line no-unused-vars -- required for `.length`
exportTypedArrayMethod$k('fill', function fill(value /* , start, end */) {
- return $fill.apply(aTypedArray$j(this), arguments);
+ var length = arguments.length;
+ return call$b(
+ $fill,
+ aTypedArray$j(this),
+ value,
+ length > 1 ? arguments[1] : undefined,
+ length > 2 ? arguments[2] : undefined
+ );
});
- var aTypedArrayConstructor$2 = arrayBufferViewCore.aTypedArrayConstructor;
- var speciesConstructor$5 = speciesConstructor$8;
-
- var typedArrayFromSpeciesAndList = function (instance, list) {
- var C = speciesConstructor$5(instance, instance.constructor);
+ var arrayFromConstructorAndList$1 = function (Constructor, list) {
var index = 0;
var length = list.length;
- var result = new (aTypedArrayConstructor$2(C))(length);
+ var result = new Constructor(length);
while (length > index) result[index] = list[index++];
return result;
};
+ var ArrayBufferViewCore$j = arrayBufferViewCore;
+ var speciesConstructor$2 = speciesConstructor$5;
+
+ var TYPED_ARRAY_CONSTRUCTOR = ArrayBufferViewCore$j.TYPED_ARRAY_CONSTRUCTOR;
+ var aTypedArrayConstructor = ArrayBufferViewCore$j.aTypedArrayConstructor;
+
+ // a part of `TypedArraySpeciesCreate` abstract operation
+ // https://tc39.es/ecma262/#typedarray-species-create
+ var typedArraySpeciesConstructor$4 = function (originalArray) {
+ return aTypedArrayConstructor(speciesConstructor$2(originalArray, originalArray[TYPED_ARRAY_CONSTRUCTOR]));
+ };
+
+ var arrayFromConstructorAndList = arrayFromConstructorAndList$1;
+ var typedArraySpeciesConstructor$3 = typedArraySpeciesConstructor$4;
+
+ var typedArrayFromSpeciesAndList = function (instance, list) {
+ return arrayFromConstructorAndList(typedArraySpeciesConstructor$3(instance), list);
+ };
+
var ArrayBufferViewCore$i = arrayBufferViewCore;
var $filter$1 = arrayIteration.filter;
var fromSpeciesAndList = typedArrayFromSpeciesAndList;
return $indexOf(aTypedArray$d(this), searchElement, arguments.length > 1 ? arguments[1] : undefined);
});
- var global$e = global$F;
+ var global$u = global$1m;
+ var uncurryThis$z = functionUncurryThis;
+ var PROPER_FUNCTION_NAME$2 = functionName.PROPER;
var ArrayBufferViewCore$c = arrayBufferViewCore;
var ArrayIterators = es_array_iterator;
- var wellKnownSymbol$9 = wellKnownSymbol$s;
+ var wellKnownSymbol$9 = wellKnownSymbol$t;
- var ITERATOR$2 = wellKnownSymbol$9('iterator');
- var Uint8Array$2 = global$e.Uint8Array;
- var arrayValues = ArrayIterators.values;
- var arrayKeys = ArrayIterators.keys;
- var arrayEntries = ArrayIterators.entries;
+ var ITERATOR$4 = wellKnownSymbol$9('iterator');
+ var Uint8Array$2 = global$u.Uint8Array;
+ var arrayValues = uncurryThis$z(ArrayIterators.values);
+ var arrayKeys = uncurryThis$z(ArrayIterators.keys);
+ var arrayEntries = uncurryThis$z(ArrayIterators.entries);
var aTypedArray$c = ArrayBufferViewCore$c.aTypedArray;
var exportTypedArrayMethod$d = ArrayBufferViewCore$c.exportTypedArrayMethod;
- var nativeTypedArrayIterator = Uint8Array$2 && Uint8Array$2.prototype[ITERATOR$2];
+ var nativeTypedArrayIterator = Uint8Array$2 && Uint8Array$2.prototype[ITERATOR$4];
- var CORRECT_ITER_NAME = !!nativeTypedArrayIterator
- && (nativeTypedArrayIterator.name == 'values' || nativeTypedArrayIterator.name == undefined);
+ var PROPER_ARRAY_VALUES_NAME = !!nativeTypedArrayIterator && nativeTypedArrayIterator.name === 'values';
var typedArrayValues = function values() {
- return arrayValues.call(aTypedArray$c(this));
+ return arrayValues(aTypedArray$c(this));
};
// `%TypedArray%.prototype.entries` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries
exportTypedArrayMethod$d('entries', function entries() {
- return arrayEntries.call(aTypedArray$c(this));
+ return arrayEntries(aTypedArray$c(this));
});
// `%TypedArray%.prototype.keys` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys
exportTypedArrayMethod$d('keys', function keys() {
- return arrayKeys.call(aTypedArray$c(this));
+ return arrayKeys(aTypedArray$c(this));
});
// `%TypedArray%.prototype.values` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.values
- exportTypedArrayMethod$d('values', typedArrayValues, !CORRECT_ITER_NAME);
+ exportTypedArrayMethod$d('values', typedArrayValues, PROPER_FUNCTION_NAME$2 && !PROPER_ARRAY_VALUES_NAME);
// `%TypedArray%.prototype[@@iterator]` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype-@@iterator
- exportTypedArrayMethod$d(ITERATOR$2, typedArrayValues, !CORRECT_ITER_NAME);
+ exportTypedArrayMethod$d(ITERATOR$4, typedArrayValues, PROPER_FUNCTION_NAME$2 && !PROPER_ARRAY_VALUES_NAME);
var ArrayBufferViewCore$b = arrayBufferViewCore;
+ var uncurryThis$y = functionUncurryThis;
var aTypedArray$b = ArrayBufferViewCore$b.aTypedArray;
var exportTypedArrayMethod$c = ArrayBufferViewCore$b.exportTypedArrayMethod;
- var $join = [].join;
+ var $join = uncurryThis$y([].join);
// `%TypedArray%.prototype.join` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.join
- // eslint-disable-next-line no-unused-vars -- required for `.length`
exportTypedArrayMethod$c('join', function join(separator) {
- return $join.apply(aTypedArray$b(this), arguments);
+ return $join(aTypedArray$b(this), separator);
});
/* eslint-disable es/no-array-prototype-lastindexof -- safe */
- var toIndexedObject$4 = toIndexedObject$b;
- var toInteger$4 = toInteger$b;
- var toLength$f = toLength$q;
- var arrayMethodIsStrict$5 = arrayMethodIsStrict$8;
+ var apply$6 = functionApply;
+ var toIndexedObject$4 = toIndexedObject$c;
+ var toIntegerOrInfinity$4 = toIntegerOrInfinity$b;
+ var lengthOfArrayLike$9 = lengthOfArrayLike$g;
+ var arrayMethodIsStrict$6 = arrayMethodIsStrict$9;
var min$6 = Math.min;
var $lastIndexOf$1 = [].lastIndexOf;
var NEGATIVE_ZERO = !!$lastIndexOf$1 && 1 / [1].lastIndexOf(1, -0) < 0;
- var STRICT_METHOD$5 = arrayMethodIsStrict$5('lastIndexOf');
- var FORCED$e = NEGATIVE_ZERO || !STRICT_METHOD$5;
+ var STRICT_METHOD$6 = arrayMethodIsStrict$6('lastIndexOf');
+ var FORCED$g = NEGATIVE_ZERO || !STRICT_METHOD$6;
// `Array.prototype.lastIndexOf` method implementation
// https://tc39.es/ecma262/#sec-array.prototype.lastindexof
- var arrayLastIndexOf = FORCED$e ? function lastIndexOf(searchElement /* , fromIndex = @[*-1] */) {
+ var arrayLastIndexOf = FORCED$g ? function lastIndexOf(searchElement /* , fromIndex = @[*-1] */) {
// convert -0 to +0
- if (NEGATIVE_ZERO) return $lastIndexOf$1.apply(this, arguments) || 0;
+ if (NEGATIVE_ZERO) return apply$6($lastIndexOf$1, this, arguments) || 0;
var O = toIndexedObject$4(this);
- var length = toLength$f(O.length);
+ var length = lengthOfArrayLike$9(O);
var index = length - 1;
- if (arguments.length > 1) index = min$6(index, toInteger$4(arguments[1]));
+ if (arguments.length > 1) index = min$6(index, toIntegerOrInfinity$4(arguments[1]));
if (index < 0) index = length + index;
for (;index >= 0; index--) if (index in O && O[index] === searchElement) return index || 0;
return -1;
} : $lastIndexOf$1;
var ArrayBufferViewCore$a = arrayBufferViewCore;
+ var apply$5 = functionApply;
var $lastIndexOf = arrayLastIndexOf;
var aTypedArray$a = ArrayBufferViewCore$a.aTypedArray;
// `%TypedArray%.prototype.lastIndexOf` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof
- // eslint-disable-next-line no-unused-vars -- required for `.length`
exportTypedArrayMethod$b('lastIndexOf', function lastIndexOf(searchElement /* , fromIndex */) {
- return $lastIndexOf.apply(aTypedArray$a(this), arguments);
+ var length = arguments.length;
+ return apply$5($lastIndexOf, aTypedArray$a(this), length > 1 ? [searchElement, arguments[1]] : [searchElement]);
});
var ArrayBufferViewCore$9 = arrayBufferViewCore;
var $map = arrayIteration.map;
- var speciesConstructor$4 = speciesConstructor$8;
+ var typedArraySpeciesConstructor$2 = typedArraySpeciesConstructor$4;
var aTypedArray$9 = ArrayBufferViewCore$9.aTypedArray;
- var aTypedArrayConstructor$1 = ArrayBufferViewCore$9.aTypedArrayConstructor;
var exportTypedArrayMethod$a = ArrayBufferViewCore$9.exportTypedArrayMethod;
// `%TypedArray%.prototype.map` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.map
exportTypedArrayMethod$a('map', function map(mapfn /* , thisArg */) {
return $map(aTypedArray$9(this), mapfn, arguments.length > 1 ? arguments[1] : undefined, function (O, length) {
- return new (aTypedArrayConstructor$1(speciesConstructor$4(O, O.constructor)))(length);
+ return new (typedArraySpeciesConstructor$2(O))(length);
});
});
- var aFunction$4 = aFunction$9;
- var toObject$a = toObject$i;
+ var global$t = global$1m;
+ var aCallable$4 = aCallable$a;
+ var toObject$b = toObject$j;
var IndexedObject$2 = indexedObject;
- var toLength$e = toLength$q;
+ var lengthOfArrayLike$8 = lengthOfArrayLike$g;
+
+ var TypeError$9 = global$t.TypeError;
// `Array.prototype.{ reduce, reduceRight }` methods implementation
var createMethod$3 = function (IS_RIGHT) {
return function (that, callbackfn, argumentsLength, memo) {
- aFunction$4(callbackfn);
- var O = toObject$a(that);
+ aCallable$4(callbackfn);
+ var O = toObject$b(that);
var self = IndexedObject$2(O);
- var length = toLength$e(O.length);
+ var length = lengthOfArrayLike$8(O);
var index = IS_RIGHT ? length - 1 : 0;
var i = IS_RIGHT ? -1 : 1;
if (argumentsLength < 2) while (true) {
}
index += i;
if (IS_RIGHT ? index < 0 : length <= index) {
- throw TypeError('Reduce of empty array with no initial value');
+ throw TypeError$9('Reduce of empty array with no initial value');
}
}
for (;IS_RIGHT ? index >= 0 : length > index; index += i) if (index in self) {
// `%TypedArray%.prototype.reduce` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce
exportTypedArrayMethod$9('reduce', function reduce(callbackfn /* , initialValue */) {
- return $reduce$1(aTypedArray$8(this), callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined);
+ var length = arguments.length;
+ return $reduce$1(aTypedArray$8(this), callbackfn, length, length > 1 ? arguments[1] : undefined);
});
var ArrayBufferViewCore$7 = arrayBufferViewCore;
- var $reduceRight = arrayReduce.right;
+ var $reduceRight$1 = arrayReduce.right;
var aTypedArray$7 = ArrayBufferViewCore$7.aTypedArray;
var exportTypedArrayMethod$8 = ArrayBufferViewCore$7.exportTypedArrayMethod;
// `%TypedArray%.prototype.reduceRicht` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright
exportTypedArrayMethod$8('reduceRight', function reduceRight(callbackfn /* , initialValue */) {
- return $reduceRight(aTypedArray$7(this), callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined);
+ var length = arguments.length;
+ return $reduceRight$1(aTypedArray$7(this), callbackfn, length, length > 1 ? arguments[1] : undefined);
});
var ArrayBufferViewCore$6 = arrayBufferViewCore;
} return that;
});
+ var global$s = global$1m;
var ArrayBufferViewCore$5 = arrayBufferViewCore;
- var toLength$d = toLength$q;
+ var lengthOfArrayLike$7 = lengthOfArrayLike$g;
var toOffset = toOffset$2;
- var toObject$9 = toObject$i;
- var fails$x = fails$N;
+ var toObject$a = toObject$j;
+ var fails$y = fails$S;
+ var RangeError$6 = global$s.RangeError;
var aTypedArray$5 = ArrayBufferViewCore$5.aTypedArray;
var exportTypedArrayMethod$6 = ArrayBufferViewCore$5.exportTypedArrayMethod;
- var FORCED$d = fails$x(function () {
+ var FORCED$f = fails$y(function () {
// eslint-disable-next-line es/no-typed-arrays -- required for testing
new Int8Array(1).set({});
});
aTypedArray$5(this);
var offset = toOffset(arguments.length > 1 ? arguments[1] : undefined, 1);
var length = this.length;
- var src = toObject$9(arrayLike);
- var len = toLength$d(src.length);
+ var src = toObject$a(arrayLike);
+ var len = lengthOfArrayLike$7(src);
var index = 0;
- if (len + offset > length) throw RangeError('Wrong length');
+ if (len + offset > length) throw RangeError$6('Wrong length');
while (index < len) this[offset + index] = src[index++];
- }, FORCED$d);
+ }, FORCED$f);
var ArrayBufferViewCore$4 = arrayBufferViewCore;
- var speciesConstructor$3 = speciesConstructor$8;
- var fails$w = fails$N;
+ var typedArraySpeciesConstructor$1 = typedArraySpeciesConstructor$4;
+ var fails$x = fails$S;
+ var arraySlice$7 = arraySlice$c;
var aTypedArray$4 = ArrayBufferViewCore$4.aTypedArray;
- var aTypedArrayConstructor = ArrayBufferViewCore$4.aTypedArrayConstructor;
var exportTypedArrayMethod$5 = ArrayBufferViewCore$4.exportTypedArrayMethod;
- var $slice$1 = [].slice;
- var FORCED$c = fails$w(function () {
+ var FORCED$e = fails$x(function () {
// eslint-disable-next-line es/no-typed-arrays -- required for testing
new Int8Array(1).slice();
});
// `%TypedArray%.prototype.slice` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice
exportTypedArrayMethod$5('slice', function slice(start, end) {
- var list = $slice$1.call(aTypedArray$4(this), start, end);
- var C = speciesConstructor$3(this, this.constructor);
+ var list = arraySlice$7(aTypedArray$4(this), start, end);
+ var C = typedArraySpeciesConstructor$1(this);
var index = 0;
var length = list.length;
- var result = new (aTypedArrayConstructor(C))(length);
+ var result = new C(length);
while (length > index) result[index] = list[index++];
return result;
- }, FORCED$c);
+ }, FORCED$e);
var ArrayBufferViewCore$3 = arrayBufferViewCore;
var $some$1 = arrayIteration.some;
return $some$1(aTypedArray$3(this), callbackfn, arguments.length > 1 ? arguments[1] : undefined);
});
- // TODO: use something more complex like timsort?
+ var arraySlice$6 = arraySlice$c;
+
var floor$4 = Math.floor;
var mergeSort = function (array, comparefn) {
var length = array.length;
var middle = floor$4(length / 2);
return length < 8 ? insertionSort(array, comparefn) : merge$5(
- mergeSort(array.slice(0, middle), comparefn),
- mergeSort(array.slice(middle), comparefn),
+ array,
+ mergeSort(arraySlice$6(array, 0, middle), comparefn),
+ mergeSort(arraySlice$6(array, middle), comparefn),
comparefn
);
};
} return array;
};
- var merge$5 = function (left, right, comparefn) {
+ var merge$5 = function (array, left, right, comparefn) {
var llength = left.length;
var rlength = right.length;
var lindex = 0;
var rindex = 0;
- var result = [];
while (lindex < llength || rindex < rlength) {
- if (lindex < llength && rindex < rlength) {
- result.push(comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]);
- } else {
- result.push(lindex < llength ? left[lindex++] : right[rindex++]);
- }
- } return result;
+ array[lindex + rindex] = (lindex < llength && rindex < rlength)
+ ? comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++]
+ : lindex < llength ? left[lindex++] : right[rindex++];
+ } return array;
};
- var arraySort = mergeSort;
+ var arraySort$1 = mergeSort;
- var userAgent$2 = engineUserAgent;
+ var userAgent$3 = engineUserAgent;
- var firefox = userAgent$2.match(/firefox\/(\d+)/i);
+ var firefox = userAgent$3.match(/firefox\/(\d+)/i);
var engineFfVersion = !!firefox && +firefox[1];
var engineIsIeOrEdge = /MSIE|Trident/.test(UA);
- var userAgent$1 = engineUserAgent;
+ var userAgent$2 = engineUserAgent;
- var webkit = userAgent$1.match(/AppleWebKit\/(\d+)\./);
+ var webkit = userAgent$2.match(/AppleWebKit\/(\d+)\./);
var engineWebkitVersion = !!webkit && +webkit[1];
+ var global$r = global$1m;
+ var uncurryThis$x = functionUncurryThis;
+ var fails$w = fails$S;
+ var aCallable$3 = aCallable$a;
+ var internalSort$1 = arraySort$1;
var ArrayBufferViewCore$2 = arrayBufferViewCore;
- var global$d = global$F;
- var fails$v = fails$N;
- var aFunction$3 = aFunction$9;
- var toLength$c = toLength$q;
- var internalSort$1 = arraySort;
var FF$1 = engineFfVersion;
var IE_OR_EDGE$1 = engineIsIeOrEdge;
var V8$1 = engineV8Version;
var WEBKIT$1 = engineWebkitVersion;
+ var Array$3 = global$r.Array;
var aTypedArray$2 = ArrayBufferViewCore$2.aTypedArray;
var exportTypedArrayMethod$3 = ArrayBufferViewCore$2.exportTypedArrayMethod;
- var Uint16Array = global$d.Uint16Array;
- var nativeSort$1 = Uint16Array && Uint16Array.prototype.sort;
+ var Uint16Array = global$r.Uint16Array;
+ var un$Sort$1 = Uint16Array && uncurryThis$x(Uint16Array.prototype.sort);
// WebKit
- var ACCEPT_INCORRECT_ARGUMENTS = !!nativeSort$1 && !fails$v(function () {
- var array = new Uint16Array(2);
- array.sort(null);
- array.sort({});
- });
+ var ACCEPT_INCORRECT_ARGUMENTS = !!un$Sort$1 && !(fails$w(function () {
+ un$Sort$1(new Uint16Array(2), null);
+ }) && fails$w(function () {
+ un$Sort$1(new Uint16Array(2), {});
+ }));
- var STABLE_SORT$1 = !!nativeSort$1 && !fails$v(function () {
+ var STABLE_SORT$1 = !!un$Sort$1 && !fails$w(function () {
// feature detection can be too slow, so check engines versions
if (V8$1) return V8$1 < 74;
if (FF$1) return FF$1 < 67;
if (WEBKIT$1) return WEBKIT$1 < 602;
var array = new Uint16Array(516);
- var expected = Array(516);
+ var expected = Array$3(516);
var index, mod;
for (index = 0; index < 516; index++) {
expected[index] = index - 2 * mod + 3;
}
- array.sort(function (a, b) {
+ un$Sort$1(array, function (a, b) {
return (a / 4 | 0) - (b / 4 | 0);
});
// `%TypedArray%.prototype.sort` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort
exportTypedArrayMethod$3('sort', function sort(comparefn) {
- var array = this;
- if (comparefn !== undefined) aFunction$3(comparefn);
- if (STABLE_SORT$1) return nativeSort$1.call(array, comparefn);
-
- aTypedArray$2(array);
- var arrayLength = toLength$c(array.length);
- var items = Array(arrayLength);
- var index;
-
- for (index = 0; index < arrayLength; index++) {
- items[index] = array[index];
- }
+ if (comparefn !== undefined) aCallable$3(comparefn);
+ if (STABLE_SORT$1) return un$Sort$1(this, comparefn);
- items = internalSort$1(array, getSortCompare$1(comparefn));
-
- for (index = 0; index < arrayLength; index++) {
- array[index] = items[index];
- }
-
- return array;
+ return internalSort$1(aTypedArray$2(this), getSortCompare$1(comparefn));
}, !STABLE_SORT$1 || ACCEPT_INCORRECT_ARGUMENTS);
var ArrayBufferViewCore$1 = arrayBufferViewCore;
- var toLength$b = toLength$q;
+ var toLength$6 = toLength$c;
var toAbsoluteIndex$3 = toAbsoluteIndex$8;
- var speciesConstructor$2 = speciesConstructor$8;
+ var typedArraySpeciesConstructor = typedArraySpeciesConstructor$4;
var aTypedArray$1 = ArrayBufferViewCore$1.aTypedArray;
var exportTypedArrayMethod$2 = ArrayBufferViewCore$1.exportTypedArrayMethod;
var O = aTypedArray$1(this);
var length = O.length;
var beginIndex = toAbsoluteIndex$3(begin, length);
- return new (speciesConstructor$2(O, O.constructor))(
+ var C = typedArraySpeciesConstructor(O);
+ return new C(
O.buffer,
O.byteOffset + beginIndex * O.BYTES_PER_ELEMENT,
- toLength$b((end === undefined ? length : toAbsoluteIndex$3(end, length)) - beginIndex)
+ toLength$6((end === undefined ? length : toAbsoluteIndex$3(end, length)) - beginIndex)
);
});
- var global$c = global$F;
+ var global$q = global$1m;
+ var apply$4 = functionApply;
var ArrayBufferViewCore = arrayBufferViewCore;
- var fails$u = fails$N;
+ var fails$v = fails$S;
+ var arraySlice$5 = arraySlice$c;
- var Int8Array$1 = global$c.Int8Array;
+ var Int8Array$1 = global$q.Int8Array;
var aTypedArray = ArrayBufferViewCore.aTypedArray;
var exportTypedArrayMethod$1 = ArrayBufferViewCore.exportTypedArrayMethod;
var $toLocaleString = [].toLocaleString;
- var $slice = [].slice;
// iOS Safari 6.x fails here
- var TO_LOCALE_STRING_BUG = !!Int8Array$1 && fails$u(function () {
+ var TO_LOCALE_STRING_BUG = !!Int8Array$1 && fails$v(function () {
$toLocaleString.call(new Int8Array$1(1));
});
- var FORCED$b = fails$u(function () {
+ var FORCED$d = fails$v(function () {
return [1, 2].toLocaleString() != new Int8Array$1([1, 2]).toLocaleString();
- }) || !fails$u(function () {
+ }) || !fails$v(function () {
Int8Array$1.prototype.toLocaleString.call([1, 2]);
});
// `%TypedArray%.prototype.toLocaleString` method
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring
exportTypedArrayMethod$1('toLocaleString', function toLocaleString() {
- return $toLocaleString.apply(TO_LOCALE_STRING_BUG ? $slice.call(aTypedArray(this)) : aTypedArray(this), arguments);
- }, FORCED$b);
+ return apply$4(
+ $toLocaleString,
+ TO_LOCALE_STRING_BUG ? arraySlice$5(aTypedArray(this)) : aTypedArray(this),
+ arraySlice$5(arguments)
+ );
+ }, FORCED$d);
var exportTypedArrayMethod = arrayBufferViewCore.exportTypedArrayMethod;
- var fails$t = fails$N;
- var global$b = global$F;
+ var fails$u = fails$S;
+ var global$p = global$1m;
+ var uncurryThis$w = functionUncurryThis;
- var Uint8Array$1 = global$b.Uint8Array;
+ var Uint8Array$1 = global$p.Uint8Array;
var Uint8ArrayPrototype = Uint8Array$1 && Uint8Array$1.prototype || {};
var arrayToString = [].toString;
- var arrayJoin = [].join;
+ var join$5 = uncurryThis$w([].join);
- if (fails$t(function () { arrayToString.call({}); })) {
+ if (fails$u(function () { arrayToString.call({}); })) {
arrayToString = function toString() {
- return arrayJoin.call(this);
+ return join$5(this);
};
}
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.tostring
exportTypedArrayMethod('toString', arrayToString, IS_NOT_ARRAY_METHOD);
- var $$T = _export;
+ var $$Z = _export;
+ var uncurryThis$v = functionUncurryThis;
var IndexedObject$1 = indexedObject;
- var toIndexedObject$3 = toIndexedObject$b;
- var arrayMethodIsStrict$4 = arrayMethodIsStrict$8;
+ var toIndexedObject$3 = toIndexedObject$c;
+ var arrayMethodIsStrict$5 = arrayMethodIsStrict$9;
- var nativeJoin = [].join;
+ var un$Join = uncurryThis$v([].join);
var ES3_STRINGS = IndexedObject$1 != Object;
- var STRICT_METHOD$4 = arrayMethodIsStrict$4('join', ',');
+ var STRICT_METHOD$5 = arrayMethodIsStrict$5('join', ',');
// `Array.prototype.join` method
// https://tc39.es/ecma262/#sec-array.prototype.join
- $$T({ target: 'Array', proto: true, forced: ES3_STRINGS || !STRICT_METHOD$4 }, {
+ $$Z({ target: 'Array', proto: true, forced: ES3_STRINGS || !STRICT_METHOD$5 }, {
join: function join(separator) {
- return nativeJoin.call(toIndexedObject$3(this), separator === undefined ? ',' : separator);
+ return un$Join(toIndexedObject$3(this), separator === undefined ? ',' : separator);
}
});
- var toPrimitive$2 = toPrimitive$7;
+ var toPropertyKey = toPropertyKey$5;
var definePropertyModule = objectDefineProperty;
var createPropertyDescriptor$1 = createPropertyDescriptor$7;
var createProperty$4 = function (object, key, value) {
- var propertyKey = toPrimitive$2(key);
+ var propertyKey = toPropertyKey(key);
if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor$1(0, value));
else object[propertyKey] = value;
};
- var $$S = _export;
- var isObject$d = isObject$r;
- var isArray$2 = isArray$6;
+ var $$Y = _export;
+ var global$o = global$1m;
+ var isArray$4 = isArray$8;
+ var isConstructor$1 = isConstructor$4;
+ var isObject$e = isObject$s;
var toAbsoluteIndex$2 = toAbsoluteIndex$8;
- var toLength$a = toLength$q;
- var toIndexedObject$2 = toIndexedObject$b;
+ var lengthOfArrayLike$6 = lengthOfArrayLike$g;
+ var toIndexedObject$2 = toIndexedObject$c;
var createProperty$3 = createProperty$4;
- var wellKnownSymbol$8 = wellKnownSymbol$s;
+ var wellKnownSymbol$8 = wellKnownSymbol$t;
var arrayMethodHasSpeciesSupport$3 = arrayMethodHasSpeciesSupport$5;
+ var un$Slice = arraySlice$c;
var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport$3('slice');
var SPECIES$1 = wellKnownSymbol$8('species');
- var nativeSlice = [].slice;
+ var Array$2 = global$o.Array;
var max$3 = Math.max;
// `Array.prototype.slice` method
// https://tc39.es/ecma262/#sec-array.prototype.slice
// fallback for not array-like ES3 strings and DOM objects
- $$S({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$2 }, {
+ $$Y({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$2 }, {
slice: function slice(start, end) {
var O = toIndexedObject$2(this);
- var length = toLength$a(O.length);
+ var length = lengthOfArrayLike$6(O);
var k = toAbsoluteIndex$2(start, length);
var fin = toAbsoluteIndex$2(end === undefined ? length : end, length);
// inline `ArraySpeciesCreate` for usage native `Array#slice` where it's possible
var Constructor, result, n;
- if (isArray$2(O)) {
+ if (isArray$4(O)) {
Constructor = O.constructor;
// cross-realm fallback
- if (typeof Constructor == 'function' && (Constructor === Array || isArray$2(Constructor.prototype))) {
+ if (isConstructor$1(Constructor) && (Constructor === Array$2 || isArray$4(Constructor.prototype))) {
Constructor = undefined;
- } else if (isObject$d(Constructor)) {
+ } else if (isObject$e(Constructor)) {
Constructor = Constructor[SPECIES$1];
if (Constructor === null) Constructor = undefined;
}
- if (Constructor === Array || Constructor === undefined) {
- return nativeSlice.call(O, k, fin);
+ if (Constructor === Array$2 || Constructor === undefined) {
+ return un$Slice(O, k, fin);
}
}
- result = new (Constructor === undefined ? Array : Constructor)(max$3(fin - k, 0));
+ result = new (Constructor === undefined ? Array$2 : Constructor)(max$3(fin - k, 0));
for (n = 0; k < fin; k++, n++) if (k in O) createProperty$3(result, n, O[k]);
result.length = n;
return result;
}
});
- var fails$s = fails$N;
- var wellKnownSymbol$7 = wellKnownSymbol$s;
+ var fails$t = fails$S;
+ var wellKnownSymbol$7 = wellKnownSymbol$t;
var IS_PURE = isPure;
- var ITERATOR$1 = wellKnownSymbol$7('iterator');
+ var ITERATOR$3 = wellKnownSymbol$7('iterator');
- var nativeUrl = !fails$s(function () {
+ var nativeUrl = !fails$t(function () {
var url = new URL('b?a=1&b=2&c=3', 'http://a');
var searchParams = url.searchParams;
var result = '';
|| url.href !== 'http://a/c%20d?a=1&c=3'
|| searchParams.get('c') !== '3'
|| String(new URLSearchParams('?a=1')) !== 'a=1'
- || !searchParams[ITERATOR$1]
+ || !searchParams[ITERATOR$3]
// throws in Edge
|| new URL('https://a@b').username !== 'a'
|| new URLSearchParams(new URLSearchParams('a=b')).get('a') !== 'b'
|| new URL('http://x', undefined).host !== 'x';
});
- var DESCRIPTORS$b = descriptors;
- var fails$r = fails$N;
- var objectKeys$1 = objectKeys$4;
- var getOwnPropertySymbolsModule = objectGetOwnPropertySymbols;
- var propertyIsEnumerableModule = objectPropertyIsEnumerable;
- var toObject$8 = toObject$i;
- var IndexedObject = indexedObject;
-
- // eslint-disable-next-line es/no-object-assign -- safe
- var $assign = Object.assign;
- // eslint-disable-next-line es/no-object-defineproperty -- required for testing
- var defineProperty$5 = Object.defineProperty;
-
- // `Object.assign` method
- // https://tc39.es/ecma262/#sec-object.assign
- var objectAssign = !$assign || fails$r(function () {
- // should have correct order of operations (Edge bug)
- if (DESCRIPTORS$b && $assign({ b: 1 }, $assign(defineProperty$5({}, 'a', {
- enumerable: true,
- get: function () {
- defineProperty$5(this, 'b', {
- value: 3,
- enumerable: false
- });
- }
- }), { b: 2 })).b !== 1) return true;
- // should work with symbols and should have deterministic property order (V8 bug)
- var A = {};
- var B = {};
- // eslint-disable-next-line es/no-symbol -- safe
- var symbol = Symbol();
- var alphabet = 'abcdefghijklmnopqrst';
- A[symbol] = 7;
- alphabet.split('').forEach(function (chr) { B[chr] = chr; });
- return $assign({}, A)[symbol] != 7 || objectKeys$1($assign({}, B)).join('') != alphabet;
- }) ? function assign(target, source) { // eslint-disable-line no-unused-vars -- required for `.length`
- var T = toObject$8(target);
- var argumentsLength = arguments.length;
- var index = 1;
- var getOwnPropertySymbols = getOwnPropertySymbolsModule.f;
- var propertyIsEnumerable = propertyIsEnumerableModule.f;
- while (argumentsLength > index) {
- var S = IndexedObject(arguments[index++]);
- var keys = getOwnPropertySymbols ? objectKeys$1(S).concat(getOwnPropertySymbols(S)) : objectKeys$1(S);
- var length = keys.length;
- var j = 0;
- var key;
- while (length > j) {
- key = keys[j++];
- if (!DESCRIPTORS$b || propertyIsEnumerable.call(S, key)) T[key] = S[key];
- }
- } return T;
- } : $assign;
-
- var anObject$a = anObject$m;
- var iteratorClose = iteratorClose$2;
-
- // call something on iterator step with safe closing on error
- var callWithSafeIterationClosing$1 = function (iterator, fn, value, ENTRIES) {
- try {
- return ENTRIES ? fn(anObject$a(value)[0], value[1]) : fn(value);
- } catch (error) {
- iteratorClose(iterator);
- throw error;
- }
- };
-
- var bind$6 = functionBindContext;
- var toObject$7 = toObject$i;
- var callWithSafeIterationClosing = callWithSafeIterationClosing$1;
- var isArrayIteratorMethod = isArrayIteratorMethod$3;
- var toLength$9 = toLength$q;
- var createProperty$2 = createProperty$4;
- var getIteratorMethod$2 = getIteratorMethod$5;
-
- // `Array.from` method implementation
- // https://tc39.es/ecma262/#sec-array.from
- var arrayFrom$1 = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) {
- var O = toObject$7(arrayLike);
- var C = typeof this == 'function' ? this : Array;
- var argumentsLength = arguments.length;
- var mapfn = argumentsLength > 1 ? arguments[1] : undefined;
- var mapping = mapfn !== undefined;
- var iteratorMethod = getIteratorMethod$2(O);
- var index = 0;
- var length, result, step, iterator, next, value;
- if (mapping) mapfn = bind$6(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2);
- // if the target is not iterable or it's an array with the default iterator - use a simple case
- if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) {
- iterator = iteratorMethod.call(O);
- next = iterator.next;
- result = new C();
- for (;!(step = next.call(iterator)).done; index++) {
- value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value;
- createProperty$2(result, index, value);
- }
- } else {
- length = toLength$9(O.length);
- result = new C(length);
- for (;length > index; index++) {
- value = mapping ? mapfn(O[index], index) : O[index];
- createProperty$2(result, index, value);
- }
- }
- result.length = index;
- return result;
- };
-
- // based on https://github.com/bestiejs/punycode.js/blob/master/punycode.js
- var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
- var base = 36;
- var tMin = 1;
- var tMax = 26;
- var skew = 38;
- var damp = 700;
- var initialBias = 72;
- var initialN = 128; // 0x80
- var delimiter = '-'; // '\x2D'
- var regexNonASCII = /[^\0-\u007E]/; // non-ASCII chars
- var regexSeparators = /[.\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
- var OVERFLOW_ERROR = 'Overflow: input needs wider integers to process';
- var baseMinusTMin = base - tMin;
- var floor$3 = Math.floor;
- var stringFromCharCode = String.fromCharCode;
-
- /**
- * Creates an array containing the numeric code points of each Unicode
- * character in the string. While JavaScript uses UCS-2 internally,
- * this function will convert a pair of surrogate halves (each of which
- * UCS-2 exposes as separate characters) into a single code point,
- * matching UTF-16.
- */
- var ucs2decode = function (string) {
- var output = [];
- var counter = 0;
- var length = string.length;
- while (counter < length) {
- var value = string.charCodeAt(counter++);
- if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
- // It's a high surrogate, and there is a next character.
- var extra = string.charCodeAt(counter++);
- if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
- output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
- } else {
- // It's an unmatched surrogate; only append this code unit, in case the
- // next code unit is the high surrogate of a surrogate pair.
- output.push(value);
- counter--;
- }
- } else {
- output.push(value);
- }
- }
- return output;
- };
-
- /**
- * Converts a digit/integer into a basic code point.
- */
- var digitToBasic = function (digit) {
- // 0..25 map to ASCII a..z or A..Z
- // 26..35 map to ASCII 0..9
- return digit + 22 + 75 * (digit < 26);
- };
-
- /**
- * Bias adaptation function as per section 3.4 of RFC 3492.
- * https://tools.ietf.org/html/rfc3492#section-3.4
- */
- var adapt = function (delta, numPoints, firstTime) {
- var k = 0;
- delta = firstTime ? floor$3(delta / damp) : delta >> 1;
- delta += floor$3(delta / numPoints);
- for (; delta > baseMinusTMin * tMax >> 1; k += base) {
- delta = floor$3(delta / baseMinusTMin);
- }
- return floor$3(k + (baseMinusTMin + 1) * delta / (delta + skew));
- };
-
- /**
- * Converts a string of Unicode symbols (e.g. a domain name label) to a
- * Punycode string of ASCII-only symbols.
- */
- // eslint-disable-next-line max-statements -- TODO
- var encode = function (input) {
- var output = [];
-
- // Convert the input in UCS-2 to an array of Unicode code points.
- input = ucs2decode(input);
-
- // Cache the length.
- var inputLength = input.length;
-
- // Initialize the state.
- var n = initialN;
- var delta = 0;
- var bias = initialBias;
- var i, currentValue;
-
- // Handle the basic code points.
- for (i = 0; i < input.length; i++) {
- currentValue = input[i];
- if (currentValue < 0x80) {
- output.push(stringFromCharCode(currentValue));
- }
- }
-
- var basicLength = output.length; // number of basic code points.
- var handledCPCount = basicLength; // number of code points that have been handled;
-
- // Finish the basic string with a delimiter unless it's empty.
- if (basicLength) {
- output.push(delimiter);
- }
-
- // Main encoding loop:
- while (handledCPCount < inputLength) {
- // All non-basic code points < n have been handled already. Find the next larger one:
- var m = maxInt;
- for (i = 0; i < input.length; i++) {
- currentValue = input[i];
- if (currentValue >= n && currentValue < m) {
- m = currentValue;
- }
- }
-
- // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, but guard against overflow.
- var handledCPCountPlusOne = handledCPCount + 1;
- if (m - n > floor$3((maxInt - delta) / handledCPCountPlusOne)) {
- throw RangeError(OVERFLOW_ERROR);
- }
-
- delta += (m - n) * handledCPCountPlusOne;
- n = m;
-
- for (i = 0; i < input.length; i++) {
- currentValue = input[i];
- if (currentValue < n && ++delta > maxInt) {
- throw RangeError(OVERFLOW_ERROR);
- }
- if (currentValue == n) {
- // Represent delta as a generalized variable-length integer.
- var q = delta;
- for (var k = base; /* no condition */; k += base) {
- var t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
- if (q < t) break;
- var qMinusT = q - t;
- var baseMinusT = base - t;
- output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT)));
- q = floor$3(qMinusT / baseMinusT);
- }
-
- output.push(stringFromCharCode(digitToBasic(q)));
- bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
- delta = 0;
- ++handledCPCount;
- }
- }
-
- ++delta;
- ++n;
- }
- return output.join('');
- };
-
- var stringPunycodeToAscii = function (input) {
- var encoded = [];
- var labels = input.toLowerCase().replace(regexSeparators, '\u002E').split('.');
- var i, label;
- for (i = 0; i < labels.length; i++) {
- label = labels[i];
- encoded.push(regexNonASCII.test(label) ? 'xn--' + encode(label) : label);
- }
- return encoded.join('.');
- };
-
- var anObject$9 = anObject$m;
- var getIteratorMethod$1 = getIteratorMethod$5;
-
- var getIterator$1 = function (it) {
- var iteratorMethod = getIteratorMethod$1(it);
- if (typeof iteratorMethod != 'function') {
- throw TypeError(String(it) + ' is not iterable');
- } return anObject$9(iteratorMethod.call(it));
- };
-
// TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env`
- var $$R = _export;
- var getBuiltIn$2 = getBuiltIn$9;
+ var $$X = _export;
+ var global$n = global$1m;
+ var getBuiltIn$2 = getBuiltIn$b;
+ var call$a = functionCall;
+ var uncurryThis$u = functionUncurryThis;
var USE_NATIVE_URL$1 = nativeUrl;
- var redefine$7 = redefine$g.exports;
+ var redefine$7 = redefine$h.exports;
var redefineAll$1 = redefineAll$4;
var setToStringTag$4 = setToStringTag$a;
var createIteratorConstructor = createIteratorConstructor$2;
var InternalStateModule$2 = internalState;
var anInstance$3 = anInstance$7;
- var hasOwn = has$j;
- var bind$5 = functionBindContext;
- var classof$4 = classof$b;
- var anObject$8 = anObject$m;
- var isObject$c = isObject$r;
- var create$8 = objectCreate;
+ var isCallable$5 = isCallable$r;
+ var hasOwn$6 = hasOwnProperty_1;
+ var bind$8 = functionBindContext;
+ var classof$3 = classof$d;
+ var anObject$9 = anObject$n;
+ var isObject$d = isObject$s;
+ var $toString$2 = toString$k;
+ var create$6 = objectCreate;
var createPropertyDescriptor = createPropertyDescriptor$7;
- var getIterator = getIterator$1;
- var getIteratorMethod = getIteratorMethod$5;
- var wellKnownSymbol$6 = wellKnownSymbol$s;
+ var getIterator$1 = getIterator$4;
+ var getIteratorMethod$1 = getIteratorMethod$5;
+ var wellKnownSymbol$6 = wellKnownSymbol$t;
+ var arraySort = arraySort$1;
- var $fetch = getBuiltIn$2('fetch');
- var Headers$1 = getBuiltIn$2('Headers');
- var ITERATOR = wellKnownSymbol$6('iterator');
+ var ITERATOR$2 = wellKnownSymbol$6('iterator');
var URL_SEARCH_PARAMS = 'URLSearchParams';
var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator';
var setInternalState$2 = InternalStateModule$2.set;
var getInternalParamsState = InternalStateModule$2.getterFor(URL_SEARCH_PARAMS);
var getInternalIteratorState = InternalStateModule$2.getterFor(URL_SEARCH_PARAMS_ITERATOR);
+ var n$Fetch = getBuiltIn$2('fetch');
+ var N$Request = getBuiltIn$2('Request');
+ var Headers$1 = getBuiltIn$2('Headers');
+ var RequestPrototype = N$Request && N$Request.prototype;
+ var HeadersPrototype = Headers$1 && Headers$1.prototype;
+ var RegExp$1 = global$n.RegExp;
+ var TypeError$8 = global$n.TypeError;
+ var decodeURIComponent$1 = global$n.decodeURIComponent;
+ var encodeURIComponent$1 = global$n.encodeURIComponent;
+ var charAt$5 = uncurryThis$u(''.charAt);
+ var join$4 = uncurryThis$u([].join);
+ var push$7 = uncurryThis$u([].push);
+ var replace$6 = uncurryThis$u(''.replace);
+ var shift$1 = uncurryThis$u([].shift);
+ var splice = uncurryThis$u([].splice);
+ var split$3 = uncurryThis$u(''.split);
+ var stringSlice$8 = uncurryThis$u(''.slice);
+
var plus = /\+/g;
var sequences = Array(4);
var percentSequence = function (bytes) {
- return sequences[bytes - 1] || (sequences[bytes - 1] = RegExp('((?:%[\\da-f]{2}){' + bytes + '})', 'gi'));
+ return sequences[bytes - 1] || (sequences[bytes - 1] = RegExp$1('((?:%[\\da-f]{2}){' + bytes + '})', 'gi'));
};
var percentDecode = function (sequence) {
try {
- return decodeURIComponent(sequence);
+ return decodeURIComponent$1(sequence);
} catch (error) {
return sequence;
}
};
var deserialize = function (it) {
- var result = it.replace(plus, ' ');
+ var result = replace$6(it, plus, ' ');
var bytes = 4;
try {
- return decodeURIComponent(result);
+ return decodeURIComponent$1(result);
} catch (error) {
while (bytes) {
- result = result.replace(percentSequence(bytes--), percentDecode);
+ result = replace$6(result, percentSequence(bytes--), percentDecode);
}
return result;
}
var find$1 = /[!'()~]|%20/g;
- var replace$1 = {
+ var replacements = {
'!': '%21',
"'": '%27',
'(': '%28',
};
var replacer = function (match) {
- return replace$1[match];
+ return replacements[match];
};
var serialize = function (it) {
- return encodeURIComponent(it).replace(find$1, replacer);
+ return replace$6(encodeURIComponent$1(it), find$1, replacer);
};
var parseSearchParams = function (result, query) {
if (query) {
- var attributes = query.split('&');
+ var attributes = split$3(query, '&');
var index = 0;
var attribute, entry;
while (index < attributes.length) {
attribute = attributes[index++];
if (attribute.length) {
- entry = attribute.split('=');
- result.push({
- key: deserialize(entry.shift()),
- value: deserialize(entry.join('='))
+ entry = split$3(attribute, '=');
+ push$7(result, {
+ key: deserialize(shift$1(entry)),
+ value: deserialize(join$4(entry, '='))
});
}
}
};
var validateArgumentsLength = function (passed, required) {
- if (passed < required) throw TypeError('Not enough arguments');
+ if (passed < required) throw TypeError$8('Not enough arguments');
};
var URLSearchParamsIterator = createIteratorConstructor(function Iterator(params, kind) {
setInternalState$2(this, {
type: URL_SEARCH_PARAMS_ITERATOR,
- iterator: getIterator(getInternalParamsState(params).entries),
+ iterator: getIterator$1(getInternalParamsState(params).entries),
kind: kind
});
}, 'Iterator', function next() {
// `URLSearchParams` constructor
// https://url.spec.whatwg.org/#interface-urlsearchparams
var URLSearchParamsConstructor = function URLSearchParams(/* init */) {
- anInstance$3(this, URLSearchParamsConstructor, URL_SEARCH_PARAMS);
+ anInstance$3(this, URLSearchParamsPrototype);
var init = arguments.length > 0 ? arguments[0] : undefined;
var that = this;
var entries = [];
});
if (init !== undefined) {
- if (isObject$c(init)) {
- iteratorMethod = getIteratorMethod(init);
- if (typeof iteratorMethod === 'function') {
- iterator = iteratorMethod.call(init);
+ if (isObject$d(init)) {
+ iteratorMethod = getIteratorMethod$1(init);
+ if (iteratorMethod) {
+ iterator = getIterator$1(init, iteratorMethod);
next = iterator.next;
- while (!(step = next.call(iterator)).done) {
- entryIterator = getIterator(anObject$8(step.value));
+ while (!(step = call$a(next, iterator)).done) {
+ entryIterator = getIterator$1(anObject$9(step.value));
entryNext = entryIterator.next;
if (
- (first = entryNext.call(entryIterator)).done ||
- (second = entryNext.call(entryIterator)).done ||
- !entryNext.call(entryIterator).done
- ) throw TypeError('Expected sequence with length 2');
- entries.push({ key: first.value + '', value: second.value + '' });
+ (first = call$a(entryNext, entryIterator)).done ||
+ (second = call$a(entryNext, entryIterator)).done ||
+ !call$a(entryNext, entryIterator).done
+ ) throw TypeError$8('Expected sequence with length 2');
+ push$7(entries, { key: $toString$2(first.value), value: $toString$2(second.value) });
}
- } else for (key in init) if (hasOwn(init, key)) entries.push({ key: key, value: init[key] + '' });
+ } else for (key in init) if (hasOwn$6(init, key)) push$7(entries, { key: key, value: $toString$2(init[key]) });
} else {
- parseSearchParams(entries, typeof init === 'string' ? init.charAt(0) === '?' ? init.slice(1) : init : init + '');
+ parseSearchParams(
+ entries,
+ typeof init == 'string' ? charAt$5(init, 0) === '?' ? stringSlice$8(init, 1) : init : $toString$2(init)
+ );
}
}
};
append: function append(name, value) {
validateArgumentsLength(arguments.length, 2);
var state = getInternalParamsState(this);
- state.entries.push({ key: name + '', value: value + '' });
+ push$7(state.entries, { key: $toString$2(name), value: $toString$2(value) });
state.updateURL();
},
// `URLSearchParams.prototype.delete` method
validateArgumentsLength(arguments.length, 1);
var state = getInternalParamsState(this);
var entries = state.entries;
- var key = name + '';
+ var key = $toString$2(name);
var index = 0;
while (index < entries.length) {
- if (entries[index].key === key) entries.splice(index, 1);
+ if (entries[index].key === key) splice(entries, index, 1);
else index++;
}
state.updateURL();
get: function get(name) {
validateArgumentsLength(arguments.length, 1);
var entries = getInternalParamsState(this).entries;
- var key = name + '';
+ var key = $toString$2(name);
var index = 0;
for (; index < entries.length; index++) {
if (entries[index].key === key) return entries[index].value;
getAll: function getAll(name) {
validateArgumentsLength(arguments.length, 1);
var entries = getInternalParamsState(this).entries;
- var key = name + '';
+ var key = $toString$2(name);
var result = [];
var index = 0;
for (; index < entries.length; index++) {
- if (entries[index].key === key) result.push(entries[index].value);
+ if (entries[index].key === key) push$7(result, entries[index].value);
}
return result;
},
has: function has(name) {
validateArgumentsLength(arguments.length, 1);
var entries = getInternalParamsState(this).entries;
- var key = name + '';
+ var key = $toString$2(name);
var index = 0;
while (index < entries.length) {
if (entries[index++].key === key) return true;
var state = getInternalParamsState(this);
var entries = state.entries;
var found = false;
- var key = name + '';
- var val = value + '';
+ var key = $toString$2(name);
+ var val = $toString$2(value);
var index = 0;
var entry;
for (; index < entries.length; index++) {
entry = entries[index];
if (entry.key === key) {
- if (found) entries.splice(index--, 1);
+ if (found) splice(entries, index--, 1);
else {
found = true;
entry.value = val;
}
}
}
- if (!found) entries.push({ key: key, value: val });
+ if (!found) push$7(entries, { key: key, value: val });
state.updateURL();
},
// `URLSearchParams.prototype.sort` method
// https://url.spec.whatwg.org/#dom-urlsearchparams-sort
sort: function sort() {
var state = getInternalParamsState(this);
- var entries = state.entries;
- // Array#sort is not stable in some engines
- var slice = entries.slice();
- var entry, entriesIndex, sliceIndex;
- entries.length = 0;
- for (sliceIndex = 0; sliceIndex < slice.length; sliceIndex++) {
- entry = slice[sliceIndex];
- for (entriesIndex = 0; entriesIndex < sliceIndex; entriesIndex++) {
- if (entries[entriesIndex].key > entry.key) {
- entries.splice(entriesIndex, 0, entry);
- break;
- }
- }
- if (entriesIndex === sliceIndex) entries.push(entry);
- }
+ arraySort(state.entries, function (a, b) {
+ return a.key > b.key ? 1 : -1;
+ });
state.updateURL();
},
// `URLSearchParams.prototype.forEach` method
forEach: function forEach(callback /* , thisArg */) {
var entries = getInternalParamsState(this).entries;
- var boundFunction = bind$5(callback, arguments.length > 1 ? arguments[1] : undefined, 3);
+ var boundFunction = bind$8(callback, arguments.length > 1 ? arguments[1] : undefined);
var index = 0;
var entry;
while (index < entries.length) {
}, { enumerable: true });
// `URLSearchParams.prototype[@@iterator]` method
- redefine$7(URLSearchParamsPrototype, ITERATOR, URLSearchParamsPrototype.entries);
+ redefine$7(URLSearchParamsPrototype, ITERATOR$2, URLSearchParamsPrototype.entries, { name: 'entries' });
// `URLSearchParams.prototype.toString` method
// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
var entry;
while (index < entries.length) {
entry = entries[index++];
- result.push(serialize(entry.key) + '=' + serialize(entry.value));
- } return result.join('&');
+ push$7(result, serialize(entry.key) + '=' + serialize(entry.value));
+ } return join$4(result, '&');
}, { enumerable: true });
setToStringTag$4(URLSearchParamsConstructor, URL_SEARCH_PARAMS);
- $$R({ global: true, forced: !USE_NATIVE_URL$1 }, {
+ $$X({ global: true, forced: !USE_NATIVE_URL$1 }, {
URLSearchParams: URLSearchParamsConstructor
});
- // Wrap `fetch` for correct work with polyfilled `URLSearchParams`
- // https://github.com/zloirock/core-js/issues/674
- if (!USE_NATIVE_URL$1 && typeof $fetch == 'function' && typeof Headers$1 == 'function') {
- $$R({ global: true, enumerable: true, forced: true }, {
- fetch: function fetch(input /* , init */) {
- var args = [input];
- var init, body, headers;
- if (arguments.length > 1) {
- init = arguments[1];
- if (isObject$c(init)) {
- body = init.body;
- if (classof$4(body) === URL_SEARCH_PARAMS) {
- headers = init.headers ? new Headers$1(init.headers) : new Headers$1();
- if (!headers.has('content-type')) {
- headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
- }
- init = create$8(init, {
- body: createPropertyDescriptor(0, String(body)),
- headers: createPropertyDescriptor(0, headers)
- });
- }
- }
- args.push(init);
- } return $fetch.apply(this, args);
- }
- });
+ // Wrap `fetch` and `Request` for correct work with polyfilled `URLSearchParams`
+ if (!USE_NATIVE_URL$1 && isCallable$5(Headers$1)) {
+ var headersHas = uncurryThis$u(HeadersPrototype.has);
+ var headersSet = uncurryThis$u(HeadersPrototype.set);
+
+ var wrapRequestOptions = function (init) {
+ if (isObject$d(init)) {
+ var body = init.body;
+ var headers;
+ if (classof$3(body) === URL_SEARCH_PARAMS) {
+ headers = init.headers ? new Headers$1(init.headers) : new Headers$1();
+ if (!headersHas(headers, 'content-type')) {
+ headersSet(headers, 'content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
+ }
+ return create$6(init, {
+ body: createPropertyDescriptor(0, $toString$2(body)),
+ headers: createPropertyDescriptor(0, headers)
+ });
+ }
+ } return init;
+ };
+
+ if (isCallable$5(n$Fetch)) {
+ $$X({ global: true, enumerable: true, forced: true }, {
+ fetch: function fetch(input /* , init */) {
+ return n$Fetch(input, arguments.length > 1 ? wrapRequestOptions(arguments[1]) : {});
+ }
+ });
+ }
+
+ if (isCallable$5(N$Request)) {
+ var RequestConstructor = function Request(input /* , init */) {
+ anInstance$3(this, RequestPrototype);
+ return new N$Request(input, arguments.length > 1 ? wrapRequestOptions(arguments[1]) : {});
+ };
+
+ RequestPrototype.constructor = RequestConstructor;
+ RequestConstructor.prototype = RequestPrototype;
+
+ $$X({ global: true, forced: true }, {
+ Request: RequestConstructor
+ });
+ }
}
var web_urlSearchParams = {
getState: getInternalParamsState
};
- // TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env`
+ var uncurryThis$t = functionUncurryThis;
+ var PROPER_FUNCTION_NAME$1 = functionName.PROPER;
+ var redefine$6 = redefine$h.exports;
+ var anObject$8 = anObject$n;
+ var isPrototypeOf$2 = objectIsPrototypeOf;
+ var $toString$1 = toString$k;
+ var fails$s = fails$S;
+ var regExpFlags$2 = regexpFlags$1;
- var $$Q = _export;
- var DESCRIPTORS$a = descriptors;
- var USE_NATIVE_URL = nativeUrl;
- var global$a = global$F;
- var defineProperties$1 = objectDefineProperties;
- var redefine$6 = redefine$g.exports;
- var anInstance$2 = anInstance$7;
- var has$4 = has$j;
- var assign$2 = objectAssign;
- var arrayFrom = arrayFrom$1;
- var codeAt = stringMultibyte.codeAt;
- var toASCII = stringPunycodeToAscii;
- var setToStringTag$3 = setToStringTag$a;
- var URLSearchParamsModule = web_urlSearchParams;
- var InternalStateModule$1 = internalState;
+ var TO_STRING = 'toString';
+ var RegExpPrototype$3 = RegExp.prototype;
+ var n$ToString = RegExpPrototype$3[TO_STRING];
+ var getFlags$1 = uncurryThis$t(regExpFlags$2);
- var NativeURL = global$a.URL;
- var URLSearchParams$1 = URLSearchParamsModule.URLSearchParams;
- var getInternalSearchParamsState = URLSearchParamsModule.getState;
- var setInternalState$1 = InternalStateModule$1.set;
- var getInternalURLState = InternalStateModule$1.getterFor('URL');
- var floor$2 = Math.floor;
- var pow$1 = Math.pow;
+ var NOT_GENERIC = fails$s(function () { return n$ToString.call({ source: 'a', flags: 'b' }) != '/a/b'; });
+ // FF44- RegExp#toString has a wrong name
+ var INCORRECT_NAME = PROPER_FUNCTION_NAME$1 && n$ToString.name != TO_STRING;
- var INVALID_AUTHORITY = 'Invalid authority';
- var INVALID_SCHEME = 'Invalid scheme';
- var INVALID_HOST = 'Invalid host';
- var INVALID_PORT = 'Invalid port';
+ // `RegExp.prototype.toString` method
+ // https://tc39.es/ecma262/#sec-regexp.prototype.tostring
+ if (NOT_GENERIC || INCORRECT_NAME) {
+ redefine$6(RegExp.prototype, TO_STRING, function toString() {
+ var R = anObject$8(this);
+ var p = $toString$1(R.source);
+ var rf = R.flags;
+ var f = $toString$1(rf === undefined && isPrototypeOf$2(RegExpPrototype$3, R) && !('flags' in RegExpPrototype$3) ? getFlags$1(R) : rf);
+ return '/' + p + '/' + f;
+ }, { unsafe: true });
+ }
- var ALPHA = /[A-Za-z]/;
- // eslint-disable-next-line regexp/no-obscure-range -- safe
- var ALPHANUMERIC = /[\d+-.A-Za-z]/;
- var DIGIT = /\d/;
- var HEX_START = /^0x/i;
- var OCT = /^[0-7]+$/;
- var DEC = /^\d+$/;
- var HEX = /^[\dA-Fa-f]+$/;
- /* eslint-disable no-control-regex -- safe */
- var FORBIDDEN_HOST_CODE_POINT = /[\0\t\n\r #%/:<>?@[\\\]^|]/;
- var FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT = /[\0\t\n\r #/:<>?@[\\\]^|]/;
- var LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE = /^[\u0000-\u001F ]+|[\u0000-\u001F ]+$/g;
- var TAB_AND_NEW_LINE = /[\t\n\r]/g;
- /* eslint-enable no-control-regex -- safe */
- var EOF;
+ // TODO: Remove from `core-js@4` since it's moved to entry points
- var parseHost = function (url, input) {
- var result, codePoints, index;
- if (input.charAt(0) == '[') {
- if (input.charAt(input.length - 1) != ']') return INVALID_HOST;
- result = parseIPv6(input.slice(1, -1));
- if (!result) return INVALID_HOST;
- url.host = result;
- // opaque host
- } else if (!isSpecial(url)) {
- if (FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT.test(input)) return INVALID_HOST;
- result = '';
- codePoints = arrayFrom(input);
- for (index = 0; index < codePoints.length; index++) {
- result += percentEncode(codePoints[index], C0ControlPercentEncodeSet);
- }
- url.host = result;
- } else {
- input = toASCII(input);
- if (FORBIDDEN_HOST_CODE_POINT.test(input)) return INVALID_HOST;
- result = parseIPv4(input);
- if (result === null) return INVALID_HOST;
- url.host = result;
- }
- };
+ var uncurryThis$s = functionUncurryThis;
+ var redefine$5 = redefine$h.exports;
+ var regexpExec$2 = regexpExec$3;
+ var fails$r = fails$S;
+ var wellKnownSymbol$5 = wellKnownSymbol$t;
+ var createNonEnumerableProperty$1 = createNonEnumerableProperty$b;
- var parseIPv4 = function (input) {
- var parts = input.split('.');
- var partsLength, numbers, index, part, radix, number, ipv4;
- if (parts.length && parts[parts.length - 1] == '') {
- parts.pop();
- }
- partsLength = parts.length;
- if (partsLength > 4) return input;
- numbers = [];
- for (index = 0; index < partsLength; index++) {
- part = parts[index];
- if (part == '') return input;
- radix = 10;
- if (part.length > 1 && part.charAt(0) == '0') {
- radix = HEX_START.test(part) ? 16 : 8;
- part = part.slice(radix == 8 ? 1 : 2);
- }
- if (part === '') {
- number = 0;
- } else {
- if (!(radix == 10 ? DEC : radix == 8 ? OCT : HEX).test(part)) return input;
- number = parseInt(part, radix);
- }
- numbers.push(number);
- }
- for (index = 0; index < partsLength; index++) {
- number = numbers[index];
- if (index == partsLength - 1) {
- if (number >= pow$1(256, 5 - partsLength)) return null;
- } else if (number > 255) return null;
- }
- ipv4 = numbers.pop();
- for (index = 0; index < numbers.length; index++) {
- ipv4 += numbers[index] * pow$1(256, 3 - index);
- }
- return ipv4;
- };
+ var SPECIES = wellKnownSymbol$5('species');
+ var RegExpPrototype$2 = RegExp.prototype;
- // eslint-disable-next-line max-statements -- TODO
- var parseIPv6 = function (input) {
- var address = [0, 0, 0, 0, 0, 0, 0, 0];
- var pieceIndex = 0;
- var compress = null;
- var pointer = 0;
- var value, length, numbersSeen, ipv4Piece, number, swaps, swap;
+ var fixRegexpWellKnownSymbolLogic = function (KEY, exec, FORCED, SHAM) {
+ var SYMBOL = wellKnownSymbol$5(KEY);
- var char = function () {
- return input.charAt(pointer);
- };
+ var DELEGATES_TO_SYMBOL = !fails$r(function () {
+ // String methods call symbol-named RegEp methods
+ var O = {};
+ O[SYMBOL] = function () { return 7; };
+ return ''[KEY](O) != 7;
+ });
- if (char() == ':') {
- if (input.charAt(1) != ':') return;
- pointer += 2;
- pieceIndex++;
- compress = pieceIndex;
- }
- while (char()) {
- if (pieceIndex == 8) return;
- if (char() == ':') {
- if (compress !== null) return;
- pointer++;
- pieceIndex++;
- compress = pieceIndex;
- continue;
- }
- value = length = 0;
- while (length < 4 && HEX.test(char())) {
- value = value * 16 + parseInt(char(), 16);
- pointer++;
- length++;
+ var DELEGATES_TO_EXEC = DELEGATES_TO_SYMBOL && !fails$r(function () {
+ // Symbol-named RegExp methods call .exec
+ var execCalled = false;
+ var re = /a/;
+
+ if (KEY === 'split') {
+ // We can't use real regex here since it causes deoptimization
+ // and serious performance degradation in V8
+ // https://github.com/zloirock/core-js/issues/306
+ re = {};
+ // RegExp[@@split] doesn't call the regex's exec method, but first creates
+ // a new one. We need to return the patched regex when creating the new one.
+ re.constructor = {};
+ re.constructor[SPECIES] = function () { return re; };
+ re.flags = '';
+ re[SYMBOL] = /./[SYMBOL];
}
- if (char() == '.') {
- if (length == 0) return;
- pointer -= length;
- if (pieceIndex > 6) return;
- numbersSeen = 0;
- while (char()) {
- ipv4Piece = null;
- if (numbersSeen > 0) {
- if (char() == '.' && numbersSeen < 4) pointer++;
- else return;
- }
- if (!DIGIT.test(char())) return;
- while (DIGIT.test(char())) {
- number = parseInt(char(), 10);
- if (ipv4Piece === null) ipv4Piece = number;
- else if (ipv4Piece == 0) return;
- else ipv4Piece = ipv4Piece * 10 + number;
- if (ipv4Piece > 255) return;
- pointer++;
+
+ re.exec = function () { execCalled = true; return null; };
+
+ re[SYMBOL]('');
+ return !execCalled;
+ });
+
+ if (
+ !DELEGATES_TO_SYMBOL ||
+ !DELEGATES_TO_EXEC ||
+ FORCED
+ ) {
+ var uncurriedNativeRegExpMethod = uncurryThis$s(/./[SYMBOL]);
+ var methods = exec(SYMBOL, ''[KEY], function (nativeMethod, regexp, str, arg2, forceStringMethod) {
+ var uncurriedNativeMethod = uncurryThis$s(nativeMethod);
+ var $exec = regexp.exec;
+ if ($exec === regexpExec$2 || $exec === RegExpPrototype$2.exec) {
+ if (DELEGATES_TO_SYMBOL && !forceStringMethod) {
+ // The native String method already delegates to @@method (this
+ // polyfilled function), leasing to infinite recursion.
+ // We avoid it by directly calling the native @@method method.
+ return { done: true, value: uncurriedNativeRegExpMethod(regexp, str, arg2) };
}
- address[pieceIndex] = address[pieceIndex] * 256 + ipv4Piece;
- numbersSeen++;
- if (numbersSeen == 2 || numbersSeen == 4) pieceIndex++;
+ return { done: true, value: uncurriedNativeMethod(str, regexp, arg2) };
}
- if (numbersSeen != 4) return;
- break;
- } else if (char() == ':') {
- pointer++;
- if (!char()) return;
- } else if (char()) return;
- address[pieceIndex++] = value;
- }
- if (compress !== null) {
- swaps = pieceIndex - compress;
- pieceIndex = 7;
- while (pieceIndex != 0 && swaps > 0) {
- swap = address[pieceIndex];
- address[pieceIndex--] = address[compress + swaps - 1];
- address[compress + --swaps] = swap;
- }
- } else if (pieceIndex != 8) return;
- return address;
- };
+ return { done: false };
+ });
- var findLongestZeroSequence = function (ipv6) {
- var maxIndex = null;
- var maxLength = 1;
- var currStart = null;
- var currLength = 0;
- var index = 0;
- for (; index < 8; index++) {
- if (ipv6[index] !== 0) {
- if (currLength > maxLength) {
- maxIndex = currStart;
- maxLength = currLength;
- }
- currStart = null;
- currLength = 0;
- } else {
- if (currStart === null) currStart = index;
- ++currLength;
- }
- }
- if (currLength > maxLength) {
- maxIndex = currStart;
- maxLength = currLength;
+ redefine$5(String.prototype, KEY, methods[0]);
+ redefine$5(RegExpPrototype$2, SYMBOL, methods[1]);
}
- return maxIndex;
- };
- var serializeHost = function (host) {
- var result, index, compress, ignore0;
- // ipv4
- if (typeof host == 'number') {
- result = [];
- for (index = 0; index < 4; index++) {
- result.unshift(host % 256);
- host = floor$2(host / 256);
- } return result.join('.');
- // ipv6
- } else if (typeof host == 'object') {
- result = '';
- compress = findLongestZeroSequence(host);
- for (index = 0; index < 8; index++) {
- if (ignore0 && host[index] === 0) continue;
- if (ignore0) ignore0 = false;
- if (compress === index) {
- result += index ? ':' : '::';
- ignore0 = true;
- } else {
- result += host[index].toString(16);
- if (index < 7) result += ':';
- }
- }
- return '[' + result + ']';
- } return host;
+ if (SHAM) createNonEnumerableProperty$1(RegExpPrototype$2[SYMBOL], 'sham', true);
};
- var C0ControlPercentEncodeSet = {};
- var fragmentPercentEncodeSet = assign$2({}, C0ControlPercentEncodeSet, {
- ' ': 1, '"': 1, '<': 1, '>': 1, '`': 1
- });
- var pathPercentEncodeSet = assign$2({}, fragmentPercentEncodeSet, {
- '#': 1, '?': 1, '{': 1, '}': 1
- });
- var userinfoPercentEncodeSet = assign$2({}, pathPercentEncodeSet, {
- '/': 1, ':': 1, ';': 1, '=': 1, '@': 1, '[': 1, '\\': 1, ']': 1, '^': 1, '|': 1
- });
-
- var percentEncode = function (char, set) {
- var code = codeAt(char, 0);
- return code > 0x20 && code < 0x7F && !has$4(set, char) ? char : encodeURIComponent(char);
- };
+ var charAt$4 = stringMultibyte.charAt;
- var specialSchemes = {
- ftp: 21,
- file: null,
- http: 80,
- https: 443,
- ws: 80,
- wss: 443
+ // `AdvanceStringIndex` abstract operation
+ // https://tc39.es/ecma262/#sec-advancestringindex
+ var advanceStringIndex$3 = function (S, index, unicode) {
+ return index + (unicode ? charAt$4(S, index).length : 1);
};
- var isSpecial = function (url) {
- return has$4(specialSchemes, url.scheme);
- };
+ var uncurryThis$r = functionUncurryThis;
+ var toObject$9 = toObject$j;
- var includesCredentials = function (url) {
- return url.username != '' || url.password != '';
- };
+ var floor$3 = Math.floor;
+ var charAt$3 = uncurryThis$r(''.charAt);
+ var replace$5 = uncurryThis$r(''.replace);
+ var stringSlice$7 = uncurryThis$r(''.slice);
+ var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d{1,2}|<[^>]*>)/g;
+ var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&'`]|\d{1,2})/g;
- var cannotHaveUsernamePasswordPort = function (url) {
- return !url.host || url.cannotBeABaseURL || url.scheme == 'file';
+ // `GetSubstitution` abstract operation
+ // https://tc39.es/ecma262/#sec-getsubstitution
+ var getSubstitution$1 = function (matched, str, position, captures, namedCaptures, replacement) {
+ var tailPos = position + matched.length;
+ var m = captures.length;
+ var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;
+ if (namedCaptures !== undefined) {
+ namedCaptures = toObject$9(namedCaptures);
+ symbols = SUBSTITUTION_SYMBOLS;
+ }
+ return replace$5(replacement, symbols, function (match, ch) {
+ var capture;
+ switch (charAt$3(ch, 0)) {
+ case '$': return '$';
+ case '&': return matched;
+ case '`': return stringSlice$7(str, 0, position);
+ case "'": return stringSlice$7(str, tailPos);
+ case '<':
+ capture = namedCaptures[stringSlice$7(ch, 1, -1)];
+ break;
+ default: // \d\d?
+ var n = +ch;
+ if (n === 0) return match;
+ if (n > m) {
+ var f = floor$3(n / 10);
+ if (f === 0) return match;
+ if (f <= m) return captures[f - 1] === undefined ? charAt$3(ch, 1) : captures[f - 1] + charAt$3(ch, 1);
+ return match;
+ }
+ capture = captures[n - 1];
+ }
+ return capture === undefined ? '' : capture;
+ });
};
- var isWindowsDriveLetter = function (string, normalized) {
- var second;
- return string.length == 2 && ALPHA.test(string.charAt(0))
- && ((second = string.charAt(1)) == ':' || (!normalized && second == '|'));
- };
+ var global$m = global$1m;
+ var call$9 = functionCall;
+ var anObject$7 = anObject$n;
+ var isCallable$4 = isCallable$r;
+ var classof$2 = classofRaw$1;
+ var regexpExec$1 = regexpExec$3;
- var startsWithWindowsDriveLetter = function (string) {
- var third;
- return string.length > 1 && isWindowsDriveLetter(string.slice(0, 2)) && (
- string.length == 2 ||
- ((third = string.charAt(2)) === '/' || third === '\\' || third === '?' || third === '#')
- );
- };
+ var TypeError$7 = global$m.TypeError;
- var shortenURLsPath = function (url) {
- var path = url.path;
- var pathSize = path.length;
- if (pathSize && (url.scheme != 'file' || pathSize != 1 || !isWindowsDriveLetter(path[0], true))) {
- path.pop();
+ // `RegExpExec` abstract operation
+ // https://tc39.es/ecma262/#sec-regexpexec
+ var regexpExecAbstract = function (R, S) {
+ var exec = R.exec;
+ if (isCallable$4(exec)) {
+ var result = call$9(exec, R, S);
+ if (result !== null) anObject$7(result);
+ return result;
}
+ if (classof$2(R) === 'RegExp') return call$9(regexpExec$1, R, S);
+ throw TypeError$7('RegExp#exec called on incompatible receiver');
};
- var isSingleDot = function (segment) {
- return segment === '.' || segment.toLowerCase() === '%2e';
- };
+ var apply$3 = functionApply;
+ var call$8 = functionCall;
+ var uncurryThis$q = functionUncurryThis;
+ var fixRegExpWellKnownSymbolLogic$3 = fixRegexpWellKnownSymbolLogic;
+ var fails$q = fails$S;
+ var anObject$6 = anObject$n;
+ var isCallable$3 = isCallable$r;
+ var toIntegerOrInfinity$3 = toIntegerOrInfinity$b;
+ var toLength$5 = toLength$c;
+ var toString$f = toString$k;
+ var requireObjectCoercible$a = requireObjectCoercible$e;
+ var advanceStringIndex$2 = advanceStringIndex$3;
+ var getMethod$3 = getMethod$7;
+ var getSubstitution = getSubstitution$1;
+ var regExpExec$3 = regexpExecAbstract;
+ var wellKnownSymbol$4 = wellKnownSymbol$t;
- var isDoubleDot = function (segment) {
- segment = segment.toLowerCase();
- return segment === '..' || segment === '%2e.' || segment === '.%2e' || segment === '%2e%2e';
- };
+ var REPLACE = wellKnownSymbol$4('replace');
+ var max$2 = Math.max;
+ var min$5 = Math.min;
+ var concat$2 = uncurryThis$q([].concat);
+ var push$6 = uncurryThis$q([].push);
+ var stringIndexOf$2 = uncurryThis$q(''.indexOf);
+ var stringSlice$6 = uncurryThis$q(''.slice);
- // States:
- var SCHEME_START = {};
- var SCHEME = {};
- var NO_SCHEME = {};
- var SPECIAL_RELATIVE_OR_AUTHORITY = {};
- var PATH_OR_AUTHORITY = {};
- var RELATIVE = {};
- var RELATIVE_SLASH = {};
- var SPECIAL_AUTHORITY_SLASHES = {};
- var SPECIAL_AUTHORITY_IGNORE_SLASHES = {};
- var AUTHORITY = {};
- var HOST = {};
- var HOSTNAME = {};
- var PORT = {};
- var FILE = {};
- var FILE_SLASH = {};
- var FILE_HOST = {};
- var PATH_START = {};
- var PATH = {};
- var CANNOT_BE_A_BASE_URL_PATH = {};
- var QUERY = {};
- var FRAGMENT = {};
+ var maybeToString = function (it) {
+ return it === undefined ? it : String(it);
+ };
- // eslint-disable-next-line max-statements -- TODO
- var parseURL = function (url, input, stateOverride, base) {
- var state = stateOverride || SCHEME_START;
- var pointer = 0;
- var buffer = '';
- var seenAt = false;
- var seenBracket = false;
- var seenPasswordToken = false;
- var codePoints, char, bufferCodePoints, failure;
+ // IE <= 11 replaces $0 with the whole match, as if it was $&
+ // https://stackoverflow.com/questions/6024666/getting-ie-to-replace-a-regex-with-the-literal-string-0
+ var REPLACE_KEEPS_$0 = (function () {
+ // eslint-disable-next-line regexp/prefer-escape-replacement-dollar-char -- required for testing
+ return 'a'.replace(/./, '$0') === '$0';
+ })();
- if (!stateOverride) {
- url.scheme = '';
- url.username = '';
- url.password = '';
- url.host = null;
- url.port = null;
- url.path = [];
- url.query = null;
- url.fragment = null;
- url.cannotBeABaseURL = false;
- input = input.replace(LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE, '');
+ // Safari <= 13.0.3(?) substitutes nth capture where n>m with an empty string
+ var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = (function () {
+ if (/./[REPLACE]) {
+ return /./[REPLACE]('a', '$0') === '';
}
+ return false;
+ })();
- input = input.replace(TAB_AND_NEW_LINE, '');
+ var REPLACE_SUPPORTS_NAMED_GROUPS = !fails$q(function () {
+ var re = /./;
+ re.exec = function () {
+ var result = [];
+ result.groups = { a: '7' };
+ return result;
+ };
+ // eslint-disable-next-line regexp/no-useless-dollar-replacements -- false positive
+ return ''.replace(re, '$<a>') !== '7';
+ });
- codePoints = arrayFrom(input);
+ // @@replace logic
+ fixRegExpWellKnownSymbolLogic$3('replace', function (_, nativeReplace, maybeCallNative) {
+ var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? '$' : '$0';
- while (pointer <= codePoints.length) {
- char = codePoints[pointer];
- switch (state) {
- case SCHEME_START:
- if (char && ALPHA.test(char)) {
- buffer += char.toLowerCase();
- state = SCHEME;
- } else if (!stateOverride) {
- state = NO_SCHEME;
- continue;
- } else return INVALID_SCHEME;
- break;
+ return [
+ // `String.prototype.replace` method
+ // https://tc39.es/ecma262/#sec-string.prototype.replace
+ function replace(searchValue, replaceValue) {
+ var O = requireObjectCoercible$a(this);
+ var replacer = searchValue == undefined ? undefined : getMethod$3(searchValue, REPLACE);
+ return replacer
+ ? call$8(replacer, searchValue, O, replaceValue)
+ : call$8(nativeReplace, toString$f(O), searchValue, replaceValue);
+ },
+ // `RegExp.prototype[@@replace]` method
+ // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
+ function (string, replaceValue) {
+ var rx = anObject$6(this);
+ var S = toString$f(string);
- case SCHEME:
- if (char && (ALPHANUMERIC.test(char) || char == '+' || char == '-' || char == '.')) {
- buffer += char.toLowerCase();
- } else if (char == ':') {
- if (stateOverride && (
- (isSpecial(url) != has$4(specialSchemes, buffer)) ||
- (buffer == 'file' && (includesCredentials(url) || url.port !== null)) ||
- (url.scheme == 'file' && !url.host)
- )) return;
- url.scheme = buffer;
- if (stateOverride) {
- if (isSpecial(url) && specialSchemes[url.scheme] == url.port) url.port = null;
- return;
- }
- buffer = '';
- if (url.scheme == 'file') {
- state = FILE;
- } else if (isSpecial(url) && base && base.scheme == url.scheme) {
- state = SPECIAL_RELATIVE_OR_AUTHORITY;
- } else if (isSpecial(url)) {
- state = SPECIAL_AUTHORITY_SLASHES;
- } else if (codePoints[pointer + 1] == '/') {
- state = PATH_OR_AUTHORITY;
- pointer++;
- } else {
- url.cannotBeABaseURL = true;
- url.path.push('');
- state = CANNOT_BE_A_BASE_URL_PATH;
- }
- } else if (!stateOverride) {
- buffer = '';
- state = NO_SCHEME;
- pointer = 0;
- continue;
- } else return INVALID_SCHEME;
- break;
+ if (
+ typeof replaceValue == 'string' &&
+ stringIndexOf$2(replaceValue, UNSAFE_SUBSTITUTE) === -1 &&
+ stringIndexOf$2(replaceValue, '$<') === -1
+ ) {
+ var res = maybeCallNative(nativeReplace, rx, S, replaceValue);
+ if (res.done) return res.value;
+ }
- case NO_SCHEME:
- if (!base || (base.cannotBeABaseURL && char != '#')) return INVALID_SCHEME;
- if (base.cannotBeABaseURL && char == '#') {
- url.scheme = base.scheme;
- url.path = base.path.slice();
- url.query = base.query;
- url.fragment = '';
- url.cannotBeABaseURL = true;
- state = FRAGMENT;
- break;
- }
- state = base.scheme == 'file' ? FILE : RELATIVE;
- continue;
+ var functionalReplace = isCallable$3(replaceValue);
+ if (!functionalReplace) replaceValue = toString$f(replaceValue);
- case SPECIAL_RELATIVE_OR_AUTHORITY:
- if (char == '/' && codePoints[pointer + 1] == '/') {
- state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
- pointer++;
- } else {
- state = RELATIVE;
- continue;
- } break;
+ var global = rx.global;
+ if (global) {
+ var fullUnicode = rx.unicode;
+ rx.lastIndex = 0;
+ }
+ var results = [];
+ while (true) {
+ var result = regExpExec$3(rx, S);
+ if (result === null) break;
- case PATH_OR_AUTHORITY:
- if (char == '/') {
- state = AUTHORITY;
- break;
+ push$6(results, result);
+ if (!global) break;
+
+ var matchStr = toString$f(result[0]);
+ if (matchStr === '') rx.lastIndex = advanceStringIndex$2(S, toLength$5(rx.lastIndex), fullUnicode);
+ }
+
+ var accumulatedResult = '';
+ var nextSourcePosition = 0;
+ for (var i = 0; i < results.length; i++) {
+ result = results[i];
+
+ var matched = toString$f(result[0]);
+ var position = max$2(min$5(toIntegerOrInfinity$3(result.index), S.length), 0);
+ var captures = [];
+ // NOTE: This is equivalent to
+ // captures = result.slice(1).map(maybeToString)
+ // but for some reason `nativeSlice.call(result, 1, result.length)` (called in
+ // the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and
+ // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.
+ for (var j = 1; j < result.length; j++) push$6(captures, maybeToString(result[j]));
+ var namedCaptures = result.groups;
+ if (functionalReplace) {
+ var replacerArgs = concat$2([matched], captures, position, S);
+ if (namedCaptures !== undefined) push$6(replacerArgs, namedCaptures);
+ var replacement = toString$f(apply$3(replaceValue, undefined, replacerArgs));
} else {
- state = PATH;
- continue;
+ replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);
}
+ if (position >= nextSourcePosition) {
+ accumulatedResult += stringSlice$6(S, nextSourcePosition, position) + replacement;
+ nextSourcePosition = position + matched.length;
+ }
+ }
+ return accumulatedResult + stringSlice$6(S, nextSourcePosition);
+ }
+ ];
+ }, !REPLACE_SUPPORTS_NAMED_GROUPS || !REPLACE_KEEPS_$0 || REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE);
- case RELATIVE:
- url.scheme = base.scheme;
- if (char == EOF) {
- url.username = base.username;
- url.password = base.password;
- url.host = base.host;
- url.port = base.port;
- url.path = base.path.slice();
- url.query = base.query;
- } else if (char == '/' || (char == '\\' && isSpecial(url))) {
- state = RELATIVE_SLASH;
- } else if (char == '?') {
- url.username = base.username;
- url.password = base.password;
- url.host = base.host;
- url.port = base.port;
- url.path = base.path.slice();
- url.query = '';
- state = QUERY;
- } else if (char == '#') {
- url.username = base.username;
- url.password = base.password;
- url.host = base.host;
- url.port = base.port;
- url.path = base.path.slice();
- url.query = base.query;
- url.fragment = '';
- state = FRAGMENT;
- } else {
- url.username = base.username;
- url.password = base.password;
- url.host = base.host;
- url.port = base.port;
- url.path = base.path.slice();
- url.path.pop();
- state = PATH;
- continue;
- } break;
-
- case RELATIVE_SLASH:
- if (isSpecial(url) && (char == '/' || char == '\\')) {
- state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
- } else if (char == '/') {
- state = AUTHORITY;
- } else {
- url.username = base.username;
- url.password = base.password;
- url.host = base.host;
- url.port = base.port;
- state = PATH;
- continue;
- } break;
-
- case SPECIAL_AUTHORITY_SLASHES:
- state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
- if (char != '/' || buffer.charAt(pointer + 1) != '/') continue;
- pointer++;
- break;
+ var isObject$c = isObject$s;
+ var classof$1 = classofRaw$1;
+ var wellKnownSymbol$3 = wellKnownSymbol$t;
- case SPECIAL_AUTHORITY_IGNORE_SLASHES:
- if (char != '/' && char != '\\') {
- state = AUTHORITY;
- continue;
- } break;
+ var MATCH$2 = wellKnownSymbol$3('match');
- case AUTHORITY:
- if (char == '@') {
- if (seenAt) buffer = '%40' + buffer;
- seenAt = true;
- bufferCodePoints = arrayFrom(buffer);
- for (var i = 0; i < bufferCodePoints.length; i++) {
- var codePoint = bufferCodePoints[i];
- if (codePoint == ':' && !seenPasswordToken) {
- seenPasswordToken = true;
- continue;
- }
- var encodedCodePoints = percentEncode(codePoint, userinfoPercentEncodeSet);
- if (seenPasswordToken) url.password += encodedCodePoints;
- else url.username += encodedCodePoints;
- }
- buffer = '';
- } else if (
- char == EOF || char == '/' || char == '?' || char == '#' ||
- (char == '\\' && isSpecial(url))
- ) {
- if (seenAt && buffer == '') return INVALID_AUTHORITY;
- pointer -= arrayFrom(buffer).length + 1;
- buffer = '';
- state = HOST;
- } else buffer += char;
- break;
+ // `IsRegExp` abstract operation
+ // https://tc39.es/ecma262/#sec-isregexp
+ var isRegexp = function (it) {
+ var isRegExp;
+ return isObject$c(it) && ((isRegExp = it[MATCH$2]) !== undefined ? !!isRegExp : classof$1(it) == 'RegExp');
+ };
- case HOST:
- case HOSTNAME:
- if (stateOverride && url.scheme == 'file') {
- state = FILE_HOST;
- continue;
- } else if (char == ':' && !seenBracket) {
- if (buffer == '') return INVALID_HOST;
- failure = parseHost(url, buffer);
- if (failure) return failure;
- buffer = '';
- state = PORT;
- if (stateOverride == HOSTNAME) return;
- } else if (
- char == EOF || char == '/' || char == '?' || char == '#' ||
- (char == '\\' && isSpecial(url))
- ) {
- if (isSpecial(url) && buffer == '') return INVALID_HOST;
- if (stateOverride && buffer == '' && (includesCredentials(url) || url.port !== null)) return;
- failure = parseHost(url, buffer);
- if (failure) return failure;
- buffer = '';
- state = PATH_START;
- if (stateOverride) return;
- continue;
- } else {
- if (char == '[') seenBracket = true;
- else if (char == ']') seenBracket = false;
- buffer += char;
- } break;
+ var apply$2 = functionApply;
+ var call$7 = functionCall;
+ var uncurryThis$p = functionUncurryThis;
+ var fixRegExpWellKnownSymbolLogic$2 = fixRegexpWellKnownSymbolLogic;
+ var isRegExp$2 = isRegexp;
+ var anObject$5 = anObject$n;
+ var requireObjectCoercible$9 = requireObjectCoercible$e;
+ var speciesConstructor$1 = speciesConstructor$5;
+ var advanceStringIndex$1 = advanceStringIndex$3;
+ var toLength$4 = toLength$c;
+ var toString$e = toString$k;
+ var getMethod$2 = getMethod$7;
+ var arraySlice$4 = arraySlice$c;
+ var callRegExpExec = regexpExecAbstract;
+ var regexpExec = regexpExec$3;
+ var stickyHelpers$1 = regexpStickyHelpers;
+ var fails$p = fails$S;
- case PORT:
- if (DIGIT.test(char)) {
- buffer += char;
- } else if (
- char == EOF || char == '/' || char == '?' || char == '#' ||
- (char == '\\' && isSpecial(url)) ||
- stateOverride
- ) {
- if (buffer != '') {
- var port = parseInt(buffer, 10);
- if (port > 0xFFFF) return INVALID_PORT;
- url.port = (isSpecial(url) && port === specialSchemes[url.scheme]) ? null : port;
- buffer = '';
- }
- if (stateOverride) return;
- state = PATH_START;
- continue;
- } else return INVALID_PORT;
- break;
+ var UNSUPPORTED_Y$1 = stickyHelpers$1.UNSUPPORTED_Y;
+ var MAX_UINT32 = 0xFFFFFFFF;
+ var min$4 = Math.min;
+ var $push = [].push;
+ var exec$4 = uncurryThis$p(/./.exec);
+ var push$5 = uncurryThis$p($push);
+ var stringSlice$5 = uncurryThis$p(''.slice);
- case FILE:
- url.scheme = 'file';
- if (char == '/' || char == '\\') state = FILE_SLASH;
- else if (base && base.scheme == 'file') {
- if (char == EOF) {
- url.host = base.host;
- url.path = base.path.slice();
- url.query = base.query;
- } else if (char == '?') {
- url.host = base.host;
- url.path = base.path.slice();
- url.query = '';
- state = QUERY;
- } else if (char == '#') {
- url.host = base.host;
- url.path = base.path.slice();
- url.query = base.query;
- url.fragment = '';
- state = FRAGMENT;
- } else {
- if (!startsWithWindowsDriveLetter(codePoints.slice(pointer).join(''))) {
- url.host = base.host;
- url.path = base.path.slice();
- shortenURLsPath(url);
- }
- state = PATH;
- continue;
- }
- } else {
- state = PATH;
- continue;
- } break;
+ // Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
+ // Weex JS has frozen built-in prototypes, so use try / catch wrapper
+ var SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = !fails$p(function () {
+ // eslint-disable-next-line regexp/no-empty-group -- required for testing
+ var re = /(?:)/;
+ var originalExec = re.exec;
+ re.exec = function () { return originalExec.apply(this, arguments); };
+ var result = 'ab'.split(re);
+ return result.length !== 2 || result[0] !== 'a' || result[1] !== 'b';
+ });
- case FILE_SLASH:
- if (char == '/' || char == '\\') {
- state = FILE_HOST;
- break;
- }
- if (base && base.scheme == 'file' && !startsWithWindowsDriveLetter(codePoints.slice(pointer).join(''))) {
- if (isWindowsDriveLetter(base.path[0], true)) url.path.push(base.path[0]);
- else url.host = base.host;
+ // @@split logic
+ fixRegExpWellKnownSymbolLogic$2('split', function (SPLIT, nativeSplit, maybeCallNative) {
+ var internalSplit;
+ if (
+ 'abbc'.split(/(b)*/)[1] == 'c' ||
+ // eslint-disable-next-line regexp/no-empty-group -- required for testing
+ 'test'.split(/(?:)/, -1).length != 4 ||
+ 'ab'.split(/(?:ab)*/).length != 2 ||
+ '.'.split(/(.?)(.?)/).length != 4 ||
+ // eslint-disable-next-line regexp/no-empty-capturing-group, regexp/no-empty-group -- required for testing
+ '.'.split(/()()/).length > 1 ||
+ ''.split(/.?/).length
+ ) {
+ // based on es5-shim implementation, need to rework it
+ internalSplit = function (separator, limit) {
+ var string = toString$e(requireObjectCoercible$9(this));
+ var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;
+ if (lim === 0) return [];
+ if (separator === undefined) return [string];
+ // If `separator` is not a regex, use native split
+ if (!isRegExp$2(separator)) {
+ return call$7(nativeSplit, string, separator, lim);
+ }
+ var output = [];
+ var flags = (separator.ignoreCase ? 'i' : '') +
+ (separator.multiline ? 'm' : '') +
+ (separator.unicode ? 'u' : '') +
+ (separator.sticky ? 'y' : '');
+ var lastLastIndex = 0;
+ // Make `global` and avoid `lastIndex` issues by working with a copy
+ var separatorCopy = new RegExp(separator.source, flags + 'g');
+ var match, lastIndex, lastLength;
+ while (match = call$7(regexpExec, separatorCopy, string)) {
+ lastIndex = separatorCopy.lastIndex;
+ if (lastIndex > lastLastIndex) {
+ push$5(output, stringSlice$5(string, lastLastIndex, match.index));
+ if (match.length > 1 && match.index < string.length) apply$2($push, output, arraySlice$4(match, 1));
+ lastLength = match[0].length;
+ lastLastIndex = lastIndex;
+ if (output.length >= lim) break;
}
- state = PATH;
- continue;
+ if (separatorCopy.lastIndex === match.index) separatorCopy.lastIndex++; // Avoid an infinite loop
+ }
+ if (lastLastIndex === string.length) {
+ if (lastLength || !exec$4(separatorCopy, '')) push$5(output, '');
+ } else push$5(output, stringSlice$5(string, lastLastIndex));
+ return output.length > lim ? arraySlice$4(output, 0, lim) : output;
+ };
+ // Chakra, V8
+ } else if ('0'.split(undefined, 0).length) {
+ internalSplit = function (separator, limit) {
+ return separator === undefined && limit === 0 ? [] : call$7(nativeSplit, this, separator, limit);
+ };
+ } else internalSplit = nativeSplit;
- case FILE_HOST:
- if (char == EOF || char == '/' || char == '\\' || char == '?' || char == '#') {
- if (!stateOverride && isWindowsDriveLetter(buffer)) {
- state = PATH;
- } else if (buffer == '') {
- url.host = '';
- if (stateOverride) return;
- state = PATH_START;
- } else {
- failure = parseHost(url, buffer);
- if (failure) return failure;
- if (url.host == 'localhost') url.host = '';
- if (stateOverride) return;
- buffer = '';
- state = PATH_START;
- } continue;
- } else buffer += char;
- break;
+ return [
+ // `String.prototype.split` method
+ // https://tc39.es/ecma262/#sec-string.prototype.split
+ function split(separator, limit) {
+ var O = requireObjectCoercible$9(this);
+ var splitter = separator == undefined ? undefined : getMethod$2(separator, SPLIT);
+ return splitter
+ ? call$7(splitter, separator, O, limit)
+ : call$7(internalSplit, toString$e(O), separator, limit);
+ },
+ // `RegExp.prototype[@@split]` method
+ // https://tc39.es/ecma262/#sec-regexp.prototype-@@split
+ //
+ // NOTE: This cannot be properly polyfilled in engines that don't support
+ // the 'y' flag.
+ function (string, limit) {
+ var rx = anObject$5(this);
+ var S = toString$e(string);
+ var res = maybeCallNative(internalSplit, rx, S, limit, internalSplit !== nativeSplit);
- case PATH_START:
- if (isSpecial(url)) {
- state = PATH;
- if (char != '/' && char != '\\') continue;
- } else if (!stateOverride && char == '?') {
- url.query = '';
- state = QUERY;
- } else if (!stateOverride && char == '#') {
- url.fragment = '';
- state = FRAGMENT;
- } else if (char != EOF) {
- state = PATH;
- if (char != '/') continue;
- } break;
+ if (res.done) return res.value;
- case PATH:
+ var C = speciesConstructor$1(rx, RegExp);
+
+ var unicodeMatching = rx.unicode;
+ var flags = (rx.ignoreCase ? 'i' : '') +
+ (rx.multiline ? 'm' : '') +
+ (rx.unicode ? 'u' : '') +
+ (UNSUPPORTED_Y$1 ? 'g' : 'y');
+
+ // ^(? + rx + ) is needed, in combination with some S slicing, to
+ // simulate the 'y' flag.
+ var splitter = new C(UNSUPPORTED_Y$1 ? '^(?:' + rx.source + ')' : rx, flags);
+ var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;
+ if (lim === 0) return [];
+ if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : [];
+ var p = 0;
+ var q = 0;
+ var A = [];
+ while (q < S.length) {
+ splitter.lastIndex = UNSUPPORTED_Y$1 ? 0 : q;
+ var z = callRegExpExec(splitter, UNSUPPORTED_Y$1 ? stringSlice$5(S, q) : S);
+ var e;
if (
- char == EOF || char == '/' ||
- (char == '\\' && isSpecial(url)) ||
- (!stateOverride && (char == '?' || char == '#'))
+ z === null ||
+ (e = min$4(toLength$4(splitter.lastIndex + (UNSUPPORTED_Y$1 ? q : 0)), S.length)) === p
) {
- if (isDoubleDot(buffer)) {
- shortenURLsPath(url);
- if (char != '/' && !(char == '\\' && isSpecial(url))) {
- url.path.push('');
- }
- } else if (isSingleDot(buffer)) {
- if (char != '/' && !(char == '\\' && isSpecial(url))) {
- url.path.push('');
- }
- } else {
- if (url.scheme == 'file' && !url.path.length && isWindowsDriveLetter(buffer)) {
- if (url.host) url.host = '';
- buffer = buffer.charAt(0) + ':'; // normalize windows drive letter
- }
- url.path.push(buffer);
- }
- buffer = '';
- if (url.scheme == 'file' && (char == EOF || char == '?' || char == '#')) {
- while (url.path.length > 1 && url.path[0] === '') {
- url.path.shift();
- }
- }
- if (char == '?') {
- url.query = '';
- state = QUERY;
- } else if (char == '#') {
- url.fragment = '';
- state = FRAGMENT;
- }
+ q = advanceStringIndex$1(S, q, unicodeMatching);
} else {
- buffer += percentEncode(char, pathPercentEncodeSet);
- } break;
+ push$5(A, stringSlice$5(S, p, q));
+ if (A.length === lim) return A;
+ for (var i = 1; i <= z.length - 1; i++) {
+ push$5(A, z[i]);
+ if (A.length === lim) return A;
+ }
+ q = p = e;
+ }
+ }
+ push$5(A, stringSlice$5(S, p));
+ return A;
+ }
+ ];
+ }, !SPLIT_WORKS_WITH_OVERWRITTEN_EXEC, UNSUPPORTED_Y$1);
- case CANNOT_BE_A_BASE_URL_PATH:
- if (char == '?') {
- url.query = '';
- state = QUERY;
- } else if (char == '#') {
- url.fragment = '';
- state = FRAGMENT;
- } else if (char != EOF) {
- url.path[0] += percentEncode(char, C0ControlPercentEncodeSet);
- } break;
+ // a string of all valid unicode whitespaces
+ var whitespaces$4 = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' +
+ '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF';
- case QUERY:
- if (!stateOverride && char == '#') {
- url.fragment = '';
- state = FRAGMENT;
- } else if (char != EOF) {
- if (char == "'" && isSpecial(url)) url.query += '%27';
- else if (char == '#') url.query += '%23';
- else url.query += percentEncode(char, C0ControlPercentEncodeSet);
- } break;
+ var uncurryThis$o = functionUncurryThis;
+ var requireObjectCoercible$8 = requireObjectCoercible$e;
+ var toString$d = toString$k;
+ var whitespaces$3 = whitespaces$4;
- case FRAGMENT:
- if (char != EOF) url.fragment += percentEncode(char, fragmentPercentEncodeSet);
- break;
- }
+ var replace$4 = uncurryThis$o(''.replace);
+ var whitespace = '[' + whitespaces$3 + ']';
+ var ltrim = RegExp('^' + whitespace + whitespace + '*');
+ var rtrim$2 = RegExp(whitespace + whitespace + '*$');
- pointer++;
- }
+ // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation
+ var createMethod$2 = function (TYPE) {
+ return function ($this) {
+ var string = toString$d(requireObjectCoercible$8($this));
+ if (TYPE & 1) string = replace$4(string, ltrim, '');
+ if (TYPE & 2) string = replace$4(string, rtrim$2, '');
+ return string;
+ };
};
- // `URL` constructor
- // https://url.spec.whatwg.org/#url-class
- var URLConstructor = function URL(url /* , base */) {
- var that = anInstance$2(this, URLConstructor, 'URL');
- var base = arguments.length > 1 ? arguments[1] : undefined;
- var urlString = String(url);
- var state = setInternalState$1(that, { type: 'URL' });
- var baseState, failure;
- if (base !== undefined) {
- if (base instanceof URLConstructor) baseState = getInternalURLState(base);
- else {
- failure = parseURL(baseState = {}, String(base));
- if (failure) throw TypeError(failure);
- }
- }
- failure = parseURL(state, urlString, null, baseState);
- if (failure) throw TypeError(failure);
- var searchParams = state.searchParams = new URLSearchParams$1();
- var searchParamsState = getInternalSearchParamsState(searchParams);
- searchParamsState.updateSearchParams(state.query);
- searchParamsState.updateURL = function () {
- state.query = String(searchParams) || null;
- };
- if (!DESCRIPTORS$a) {
- that.href = serializeURL.call(that);
- that.origin = getOrigin.call(that);
- that.protocol = getProtocol.call(that);
- that.username = getUsername.call(that);
- that.password = getPassword.call(that);
- that.host = getHost.call(that);
- that.hostname = getHostname.call(that);
- that.port = getPort.call(that);
- that.pathname = getPathname.call(that);
- that.search = getSearch.call(that);
- that.searchParams = getSearchParams.call(that);
- that.hash = getHash.call(that);
- }
+ var stringTrim = {
+ // `String.prototype.{ trimLeft, trimStart }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimstart
+ start: createMethod$2(1),
+ // `String.prototype.{ trimRight, trimEnd }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimend
+ end: createMethod$2(2),
+ // `String.prototype.trim` method
+ // https://tc39.es/ecma262/#sec-string.prototype.trim
+ trim: createMethod$2(3)
};
- var URLPrototype = URLConstructor.prototype;
+ var PROPER_FUNCTION_NAME = functionName.PROPER;
+ var fails$o = fails$S;
+ var whitespaces$2 = whitespaces$4;
- var serializeURL = function () {
- var url = getInternalURLState(this);
- var scheme = url.scheme;
- var username = url.username;
- var password = url.password;
- var host = url.host;
- var port = url.port;
- var path = url.path;
- var query = url.query;
- var fragment = url.fragment;
- var output = scheme + ':';
- if (host !== null) {
- output += '//';
- if (includesCredentials(url)) {
- output += username + (password ? ':' + password : '') + '@';
- }
- output += serializeHost(host);
- if (port !== null) output += ':' + port;
- } else if (scheme == 'file') output += '//';
- output += url.cannotBeABaseURL ? path[0] : path.length ? '/' + path.join('/') : '';
- if (query !== null) output += '?' + query;
- if (fragment !== null) output += '#' + fragment;
- return output;
+ var non = '\u200B\u0085\u180E';
+
+ // check that a method works with the correct list
+ // of whitespaces and has a correct name
+ var stringTrimForced = function (METHOD_NAME) {
+ return fails$o(function () {
+ return !!whitespaces$2[METHOD_NAME]()
+ || non[METHOD_NAME]() !== non
+ || (PROPER_FUNCTION_NAME && whitespaces$2[METHOD_NAME].name !== METHOD_NAME);
+ });
};
- var getOrigin = function () {
- var url = getInternalURLState(this);
- var scheme = url.scheme;
- var port = url.port;
- if (scheme == 'blob') try {
- return new URLConstructor(scheme.path[0]).origin;
- } catch (error) {
- return 'null';
+ var $$W = _export;
+ var $trim = stringTrim.trim;
+ var forcedStringTrimMethod$2 = stringTrimForced;
+
+ // `String.prototype.trim` method
+ // https://tc39.es/ecma262/#sec-string.prototype.trim
+ $$W({ target: 'String', proto: true, forced: forcedStringTrimMethod$2('trim') }, {
+ trim: function trim() {
+ return $trim(this);
}
- if (scheme == 'file' || !isSpecial(url)) return 'null';
- return scheme + '://' + serializeHost(url.host) + (port !== null ? ':' + port : '');
- };
+ });
- var getProtocol = function () {
- return getInternalURLState(this).scheme + ':';
- };
+ var DESCRIPTORS$b = descriptors;
+ var FUNCTION_NAME_EXISTS = functionName.EXISTS;
+ var uncurryThis$n = functionUncurryThis;
+ var defineProperty$5 = objectDefineProperty.f;
- var getUsername = function () {
- return getInternalURLState(this).username;
- };
+ var FunctionPrototype = Function.prototype;
+ var functionToString = uncurryThis$n(FunctionPrototype.toString);
+ var nameRE = /^\s*function ([^ (]*)/;
+ var regExpExec$2 = uncurryThis$n(nameRE.exec);
+ var NAME = 'name';
- var getPassword = function () {
- return getInternalURLState(this).password;
- };
+ // Function instances `.name` property
+ // https://tc39.es/ecma262/#sec-function-instances-name
+ if (DESCRIPTORS$b && !FUNCTION_NAME_EXISTS) {
+ defineProperty$5(FunctionPrototype, NAME, {
+ configurable: true,
+ get: function () {
+ try {
+ return regExpExec$2(nameRE, functionToString(this))[1];
+ } catch (error) {
+ return '';
+ }
+ }
+ });
+ }
- var getHost = function () {
- var url = getInternalURLState(this);
- var host = url.host;
- var port = url.port;
- return host === null ? ''
- : port === null ? serializeHost(host)
- : serializeHost(host) + ':' + port;
- };
+ var $$V = _export;
+ var DESCRIPTORS$a = descriptors;
+ var create$5 = objectCreate;
- var getHostname = function () {
- var host = getInternalURLState(this).host;
- return host === null ? '' : serializeHost(host);
- };
+ // `Object.create` method
+ // https://tc39.es/ecma262/#sec-object.create
+ $$V({ target: 'Object', stat: true, sham: !DESCRIPTORS$a }, {
+ create: create$5
+ });
- var getPort = function () {
- var port = getInternalURLState(this).port;
- return port === null ? '' : String(port);
- };
+ var $$U = _export;
+ var global$l = global$1m;
+ var apply$1 = functionApply;
+ var isCallable$2 = isCallable$r;
+ var userAgent$1 = engineUserAgent;
+ var arraySlice$3 = arraySlice$c;
- var getPathname = function () {
- var url = getInternalURLState(this);
- var path = url.path;
- return url.cannotBeABaseURL ? path[0] : path.length ? '/' + path.join('/') : '';
- };
+ var MSIE = /MSIE .\./.test(userAgent$1); // <- dirty ie9- check
+ var Function$2 = global$l.Function;
- var getSearch = function () {
- var query = getInternalURLState(this).query;
- return query ? '?' + query : '';
+ var wrap$1 = function (scheduler) {
+ return function (handler, timeout /* , ...arguments */) {
+ var boundArgs = arguments.length > 2;
+ var args = boundArgs ? arraySlice$3(arguments, 2) : undefined;
+ return scheduler(boundArgs ? function () {
+ apply$1(isCallable$2(handler) ? handler : Function$2(handler), this, args);
+ } : handler, timeout);
+ };
};
- var getSearchParams = function () {
- return getInternalURLState(this).searchParams;
- };
+ // ie9- setTimeout & setInterval additional parameters fix
+ // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
+ $$U({ global: true, bind: true, forced: MSIE }, {
+ // `setTimeout` method
+ // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout
+ setTimeout: wrap$1(global$l.setTimeout),
+ // `setInterval` method
+ // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval
+ setInterval: wrap$1(global$l.setInterval)
+ });
- var getHash = function () {
- var fragment = getInternalURLState(this).fragment;
- return fragment ? '#' + fragment : '';
+ var global$k = typeof globalThis !== 'undefined' && globalThis || typeof self !== 'undefined' && self || typeof global$k !== 'undefined' && global$k;
+ var support = {
+ searchParams: 'URLSearchParams' in global$k,
+ iterable: 'Symbol' in global$k && 'iterator' in Symbol,
+ blob: 'FileReader' in global$k && 'Blob' in global$k && function () {
+ try {
+ new Blob();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }(),
+ formData: 'FormData' in global$k,
+ arrayBuffer: 'ArrayBuffer' in global$k
};
- var accessorDescriptor = function (getter, setter) {
- return { get: getter, set: setter, configurable: true, enumerable: true };
- };
+ function isDataView(obj) {
+ return obj && DataView.prototype.isPrototypeOf(obj);
+ }
- if (DESCRIPTORS$a) {
- defineProperties$1(URLPrototype, {
- // `URL.prototype.href` accessors pair
- // https://url.spec.whatwg.org/#dom-url-href
- href: accessorDescriptor(serializeURL, function (href) {
- var url = getInternalURLState(this);
- var urlString = String(href);
- var failure = parseURL(url, urlString);
- if (failure) throw TypeError(failure);
- getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query);
- }),
- // `URL.prototype.origin` getter
- // https://url.spec.whatwg.org/#dom-url-origin
- origin: accessorDescriptor(getOrigin),
- // `URL.prototype.protocol` accessors pair
- // https://url.spec.whatwg.org/#dom-url-protocol
- protocol: accessorDescriptor(getProtocol, function (protocol) {
- var url = getInternalURLState(this);
- parseURL(url, String(protocol) + ':', SCHEME_START);
- }),
- // `URL.prototype.username` accessors pair
- // https://url.spec.whatwg.org/#dom-url-username
- username: accessorDescriptor(getUsername, function (username) {
- var url = getInternalURLState(this);
- var codePoints = arrayFrom(String(username));
- if (cannotHaveUsernamePasswordPort(url)) return;
- url.username = '';
- for (var i = 0; i < codePoints.length; i++) {
- url.username += percentEncode(codePoints[i], userinfoPercentEncodeSet);
- }
- }),
- // `URL.prototype.password` accessors pair
- // https://url.spec.whatwg.org/#dom-url-password
- password: accessorDescriptor(getPassword, function (password) {
- var url = getInternalURLState(this);
- var codePoints = arrayFrom(String(password));
- if (cannotHaveUsernamePasswordPort(url)) return;
- url.password = '';
- for (var i = 0; i < codePoints.length; i++) {
- url.password += percentEncode(codePoints[i], userinfoPercentEncodeSet);
- }
- }),
- // `URL.prototype.host` accessors pair
- // https://url.spec.whatwg.org/#dom-url-host
- host: accessorDescriptor(getHost, function (host) {
- var url = getInternalURLState(this);
- if (url.cannotBeABaseURL) return;
- parseURL(url, String(host), HOST);
- }),
- // `URL.prototype.hostname` accessors pair
- // https://url.spec.whatwg.org/#dom-url-hostname
- hostname: accessorDescriptor(getHostname, function (hostname) {
- var url = getInternalURLState(this);
- if (url.cannotBeABaseURL) return;
- parseURL(url, String(hostname), HOSTNAME);
- }),
- // `URL.prototype.port` accessors pair
- // https://url.spec.whatwg.org/#dom-url-port
- port: accessorDescriptor(getPort, function (port) {
- var url = getInternalURLState(this);
- if (cannotHaveUsernamePasswordPort(url)) return;
- port = String(port);
- if (port == '') url.port = null;
- else parseURL(url, port, PORT);
- }),
- // `URL.prototype.pathname` accessors pair
- // https://url.spec.whatwg.org/#dom-url-pathname
- pathname: accessorDescriptor(getPathname, function (pathname) {
- var url = getInternalURLState(this);
- if (url.cannotBeABaseURL) return;
- url.path = [];
- parseURL(url, pathname + '', PATH_START);
- }),
- // `URL.prototype.search` accessors pair
- // https://url.spec.whatwg.org/#dom-url-search
- search: accessorDescriptor(getSearch, function (search) {
- var url = getInternalURLState(this);
- search = String(search);
- if (search == '') {
- url.query = null;
- } else {
- if ('?' == search.charAt(0)) search = search.slice(1);
- url.query = '';
- parseURL(url, search, QUERY);
- }
- getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query);
- }),
- // `URL.prototype.searchParams` getter
- // https://url.spec.whatwg.org/#dom-url-searchparams
- searchParams: accessorDescriptor(getSearchParams),
- // `URL.prototype.hash` accessors pair
- // https://url.spec.whatwg.org/#dom-url-hash
- hash: accessorDescriptor(getHash, function (hash) {
- var url = getInternalURLState(this);
- hash = String(hash);
- if (hash == '') {
- url.fragment = null;
- return;
- }
- if ('#' == hash.charAt(0)) hash = hash.slice(1);
- url.fragment = '';
- parseURL(url, hash, FRAGMENT);
- })
- });
+ if (support.arrayBuffer) {
+ var viewClasses = ['[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]'];
+
+ var isArrayBufferView = ArrayBuffer.isView || function (obj) {
+ return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
+ };
}
- // `URL.prototype.toJSON` method
- // https://url.spec.whatwg.org/#dom-url-tojson
- redefine$6(URLPrototype, 'toJSON', function toJSON() {
- return serializeURL.call(this);
- }, { enumerable: true });
+ function normalizeName(name) {
+ if (typeof name !== 'string') {
+ name = String(name);
+ }
- // `URL.prototype.toString` method
- // https://url.spec.whatwg.org/#URL-stringification-behavior
- redefine$6(URLPrototype, 'toString', function toString() {
- return serializeURL.call(this);
- }, { enumerable: true });
+ if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
+ throw new TypeError('Invalid character in header field name: "' + name + '"');
+ }
- if (NativeURL) {
- var nativeCreateObjectURL = NativeURL.createObjectURL;
- var nativeRevokeObjectURL = NativeURL.revokeObjectURL;
- // `URL.createObjectURL` method
- // https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
- // eslint-disable-next-line no-unused-vars -- required for `.length`
- if (nativeCreateObjectURL) redefine$6(URLConstructor, 'createObjectURL', function createObjectURL(blob) {
- return nativeCreateObjectURL.apply(NativeURL, arguments);
- });
- // `URL.revokeObjectURL` method
- // https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL
- // eslint-disable-next-line no-unused-vars -- required for `.length`
- if (nativeRevokeObjectURL) redefine$6(URLConstructor, 'revokeObjectURL', function revokeObjectURL(url) {
- return nativeRevokeObjectURL.apply(NativeURL, arguments);
- });
+ return name.toLowerCase();
}
- setToStringTag$3(URLConstructor, 'URL');
+ function normalizeValue(value) {
+ if (typeof value !== 'string') {
+ value = String(value);
+ }
- $$Q({ global: true, forced: !USE_NATIVE_URL, sham: !DESCRIPTORS$a }, {
- URL: URLConstructor
- });
+ return value;
+ } // Build a destructive iterator for the value list
- var anObject$7 = anObject$m;
- // `RegExp.prototype.flags` getter implementation
- // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
- var regexpFlags$1 = function () {
- var that = anObject$7(this);
- var result = '';
- if (that.global) result += 'g';
- if (that.ignoreCase) result += 'i';
- if (that.multiline) result += 'm';
- if (that.dotAll) result += 's';
- if (that.unicode) result += 'u';
- if (that.sticky) result += 'y';
- return result;
- };
+ function iteratorFor(items) {
+ var iterator = {
+ next: function next() {
+ var value = items.shift();
+ return {
+ done: value === undefined,
+ value: value
+ };
+ }
+ };
- var redefine$5 = redefine$g.exports;
- var anObject$6 = anObject$m;
- var fails$q = fails$N;
- var flags = regexpFlags$1;
+ if (support.iterable) {
+ iterator[Symbol.iterator] = function () {
+ return iterator;
+ };
+ }
- var TO_STRING = 'toString';
- var RegExpPrototype$2 = RegExp.prototype;
- var nativeToString = RegExpPrototype$2[TO_STRING];
+ return iterator;
+ }
- var NOT_GENERIC = fails$q(function () { return nativeToString.call({ source: 'a', flags: 'b' }) != '/a/b'; });
- // FF44- RegExp#toString has a wrong name
- var INCORRECT_NAME = nativeToString.name != TO_STRING;
+ function Headers(headers) {
+ this.map = {};
- // `RegExp.prototype.toString` method
- // https://tc39.es/ecma262/#sec-regexp.prototype.tostring
- if (NOT_GENERIC || INCORRECT_NAME) {
- redefine$5(RegExp.prototype, TO_STRING, function toString() {
- var R = anObject$6(this);
- var p = String(R.source);
- var rf = R.flags;
- var f = String(rf === undefined && R instanceof RegExp && !('flags' in RegExpPrototype$2) ? flags.call(R) : rf);
- return '/' + p + '/' + f;
- }, { unsafe: true });
+ if (headers instanceof Headers) {
+ headers.forEach(function (value, name) {
+ this.append(name, value);
+ }, this);
+ } else if (Array.isArray(headers)) {
+ headers.forEach(function (header) {
+ this.append(header[0], header[1]);
+ }, this);
+ } else if (headers) {
+ Object.getOwnPropertyNames(headers).forEach(function (name) {
+ this.append(name, headers[name]);
+ }, this);
+ }
}
- var regexpStickyHelpers = {};
+ Headers.prototype.append = function (name, value) {
+ name = normalizeName(name);
+ value = normalizeValue(value);
+ var oldValue = this.map[name];
+ this.map[name] = oldValue ? oldValue + ', ' + value : value;
+ };
- var fails$p = fails$N;
+ Headers.prototype['delete'] = function (name) {
+ delete this.map[normalizeName(name)];
+ };
- // babel-minify transpiles RegExp('a', 'y') -> /a/y and it causes SyntaxError,
- var RE = function (s, f) {
- return RegExp(s, f);
+ Headers.prototype.get = function (name) {
+ name = normalizeName(name);
+ return this.has(name) ? this.map[name] : null;
};
- regexpStickyHelpers.UNSUPPORTED_Y = fails$p(function () {
- var re = RE('a', 'y');
- re.lastIndex = 2;
- return re.exec('abcd') != null;
- });
+ Headers.prototype.has = function (name) {
+ return this.map.hasOwnProperty(normalizeName(name));
+ };
- regexpStickyHelpers.BROKEN_CARET = fails$p(function () {
- // https://bugzilla.mozilla.org/show_bug.cgi?id=773687
- var re = RE('^r', 'gy');
- re.lastIndex = 2;
- return re.exec('str') != null;
- });
+ Headers.prototype.set = function (name, value) {
+ this.map[normalizeName(name)] = normalizeValue(value);
+ };
- var fails$o = fails$N;
+ Headers.prototype.forEach = function (callback, thisArg) {
+ for (var name in this.map) {
+ if (this.map.hasOwnProperty(name)) {
+ callback.call(thisArg, this.map[name], name, this);
+ }
+ }
+ };
- var regexpUnsupportedDotAll = fails$o(function () {
- // babel-minify transpiles RegExp('.', 's') -> /./s and it causes SyntaxError
- var re = RegExp('.', (typeof '').charAt(0));
- return !(re.dotAll && re.exec('\n') && re.flags === 's');
- });
+ Headers.prototype.keys = function () {
+ var items = [];
+ this.forEach(function (value, name) {
+ items.push(name);
+ });
+ return iteratorFor(items);
+ };
- var fails$n = fails$N;
+ Headers.prototype.values = function () {
+ var items = [];
+ this.forEach(function (value) {
+ items.push(value);
+ });
+ return iteratorFor(items);
+ };
- var regexpUnsupportedNcg = fails$n(function () {
- // babel-minify transpiles RegExp('.', 'g') -> /./g and it causes SyntaxError
- var re = RegExp('(?<a>b)', (typeof '').charAt(5));
- return re.exec('b').groups.a !== 'b' ||
- 'b'.replace(re, '$<a>c') !== 'bc';
- });
+ Headers.prototype.entries = function () {
+ var items = [];
+ this.forEach(function (value, name) {
+ items.push([name, value]);
+ });
+ return iteratorFor(items);
+ };
- /* eslint-disable regexp/no-assertion-capturing-group, regexp/no-empty-group, regexp/no-lazy-ends -- testing */
- /* eslint-disable regexp/no-useless-quantifier -- testing */
- var regexpFlags = regexpFlags$1;
- var stickyHelpers$2 = regexpStickyHelpers;
- var shared = shared$5.exports;
- var create$7 = objectCreate;
- var getInternalState = internalState.get;
- var UNSUPPORTED_DOT_ALL$1 = regexpUnsupportedDotAll;
- var UNSUPPORTED_NCG$1 = regexpUnsupportedNcg;
+ if (support.iterable) {
+ Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
+ }
- var nativeExec = RegExp.prototype.exec;
- var nativeReplace = shared('native-string-replace', String.prototype.replace);
+ function consumed(body) {
+ if (body.bodyUsed) {
+ return Promise.reject(new TypeError('Already read'));
+ }
- var patchedExec = nativeExec;
+ body.bodyUsed = true;
+ }
- var UPDATES_LAST_INDEX_WRONG = (function () {
- var re1 = /a/;
- var re2 = /b*/g;
- nativeExec.call(re1, 'a');
- nativeExec.call(re2, 'a');
- return re1.lastIndex !== 0 || re2.lastIndex !== 0;
- })();
+ function fileReaderReady(reader) {
+ return new Promise(function (resolve, reject) {
+ reader.onload = function () {
+ resolve(reader.result);
+ };
- var UNSUPPORTED_Y$2 = stickyHelpers$2.UNSUPPORTED_Y || stickyHelpers$2.BROKEN_CARET;
+ reader.onerror = function () {
+ reject(reader.error);
+ };
+ });
+ }
- // nonparticipating capturing group, copied from es5-shim's String#split patch.
- var NPCG_INCLUDED = /()??/.exec('')[1] !== undefined;
+ function readBlobAsArrayBuffer(blob) {
+ var reader = new FileReader();
+ var promise = fileReaderReady(reader);
+ reader.readAsArrayBuffer(blob);
+ return promise;
+ }
- var PATCH = UPDATES_LAST_INDEX_WRONG || NPCG_INCLUDED || UNSUPPORTED_Y$2 || UNSUPPORTED_DOT_ALL$1 || UNSUPPORTED_NCG$1;
+ function readBlobAsText(blob) {
+ var reader = new FileReader();
+ var promise = fileReaderReady(reader);
+ reader.readAsText(blob);
+ return promise;
+ }
- if (PATCH) {
- // eslint-disable-next-line max-statements -- TODO
- patchedExec = function exec(str) {
- var re = this;
- var state = getInternalState(re);
- var raw = state.raw;
- var result, reCopy, lastIndex, match, i, object, group;
+ function readArrayBufferAsText(buf) {
+ var view = new Uint8Array(buf);
+ var chars = new Array(view.length);
- if (raw) {
- raw.lastIndex = re.lastIndex;
- result = patchedExec.call(raw, str);
- re.lastIndex = raw.lastIndex;
- return result;
- }
+ for (var i = 0; i < view.length; i++) {
+ chars[i] = String.fromCharCode(view[i]);
+ }
- var groups = state.groups;
- var sticky = UNSUPPORTED_Y$2 && re.sticky;
- var flags = regexpFlags.call(re);
- var source = re.source;
- var charsAdded = 0;
- var strCopy = str;
+ return chars.join('');
+ }
- if (sticky) {
- flags = flags.replace('y', '');
- if (flags.indexOf('g') === -1) {
- flags += 'g';
- }
+ function bufferClone(buf) {
+ if (buf.slice) {
+ return buf.slice(0);
+ } else {
+ var view = new Uint8Array(buf.byteLength);
+ view.set(new Uint8Array(buf));
+ return view.buffer;
+ }
+ }
- strCopy = String(str).slice(re.lastIndex);
- // Support anchored sticky behavior.
- if (re.lastIndex > 0 && (!re.multiline || re.multiline && str[re.lastIndex - 1] !== '\n')) {
- source = '(?: ' + source + ')';
- strCopy = ' ' + strCopy;
- charsAdded++;
- }
- // ^(? + rx + ) is needed, in combination with some str slicing, to
- // simulate the 'y' flag.
- reCopy = new RegExp('^(?:' + source + ')', flags);
- }
+ function Body() {
+ this.bodyUsed = false;
- if (NPCG_INCLUDED) {
- reCopy = new RegExp('^' + source + '$(?!\\s)', flags);
- }
- if (UPDATES_LAST_INDEX_WRONG) lastIndex = re.lastIndex;
+ this._initBody = function (body) {
+ /*
+ fetch-mock wraps the Response object in an ES6 Proxy to
+ provide useful test harness features such as flush. However, on
+ ES5 browsers without fetch or Proxy support pollyfills must be used;
+ the proxy-pollyfill is unable to proxy an attribute unless it exists
+ on the object before the Proxy is created. This change ensures
+ Response.bodyUsed exists on the instance, while maintaining the
+ semantic of setting Request.bodyUsed in the constructor before
+ _initBody is called.
+ */
+ this.bodyUsed = this.bodyUsed;
+ this._bodyInit = body;
- match = nativeExec.call(sticky ? reCopy : re, strCopy);
+ if (!body) {
+ this._bodyText = '';
+ } else if (typeof body === 'string') {
+ this._bodyText = body;
+ } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
+ this._bodyBlob = body;
+ } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
+ this._bodyFormData = body;
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+ this._bodyText = body.toString();
+ } else if (support.arrayBuffer && support.blob && isDataView(body)) {
+ this._bodyArrayBuffer = bufferClone(body.buffer); // IE 10-11 can't handle a DataView body.
- if (sticky) {
- if (match) {
- match.input = match.input.slice(charsAdded);
- match[0] = match[0].slice(charsAdded);
- match.index = re.lastIndex;
- re.lastIndex += match[0].length;
- } else re.lastIndex = 0;
- } else if (UPDATES_LAST_INDEX_WRONG && match) {
- re.lastIndex = re.global ? match.index + match[0].length : lastIndex;
- }
- if (NPCG_INCLUDED && match && match.length > 1) {
- // Fix browsers whose `exec` methods don't consistently return `undefined`
- // for NPCG, like IE8. NOTE: This doesn' work for /(.?)?/
- nativeReplace.call(match[0], reCopy, function () {
- for (i = 1; i < arguments.length - 2; i++) {
- if (arguments[i] === undefined) match[i] = undefined;
- }
- });
+ this._bodyInit = new Blob([this._bodyArrayBuffer]);
+ } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
+ this._bodyArrayBuffer = bufferClone(body);
+ } else {
+ this._bodyText = body = Object.prototype.toString.call(body);
}
- if (match && groups) {
- match.groups = object = create$7(null);
- for (i = 0; i < groups.length; i++) {
- group = groups[i];
- object[group[0]] = match[group[1]];
+ if (!this.headers.get('content-type')) {
+ if (typeof body === 'string') {
+ this.headers.set('content-type', 'text/plain;charset=UTF-8');
+ } else if (this._bodyBlob && this._bodyBlob.type) {
+ this.headers.set('content-type', this._bodyBlob.type);
+ } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
+ this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
}
}
-
- return match;
};
- }
-
- var regexpExec$3 = patchedExec;
- var $$P = _export;
- var exec = regexpExec$3;
+ if (support.blob) {
+ this.blob = function () {
+ var rejected = consumed(this);
- // `RegExp.prototype.exec` method
- // https://tc39.es/ecma262/#sec-regexp.prototype.exec
- $$P({ target: 'RegExp', proto: true, forced: /./.exec !== exec }, {
- exec: exec
- });
+ if (rejected) {
+ return rejected;
+ }
- // TODO: Remove from `core-js@4` since it's moved to entry points
+ if (this._bodyBlob) {
+ return Promise.resolve(this._bodyBlob);
+ } else if (this._bodyArrayBuffer) {
+ return Promise.resolve(new Blob([this._bodyArrayBuffer]));
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as blob');
+ } else {
+ return Promise.resolve(new Blob([this._bodyText]));
+ }
+ };
- var redefine$4 = redefine$g.exports;
- var regexpExec$2 = regexpExec$3;
- var fails$m = fails$N;
- var wellKnownSymbol$5 = wellKnownSymbol$s;
- var createNonEnumerableProperty$1 = createNonEnumerableProperty$e;
+ this.arrayBuffer = function () {
+ if (this._bodyArrayBuffer) {
+ var isConsumed = consumed(this);
- var SPECIES = wellKnownSymbol$5('species');
- var RegExpPrototype$1 = RegExp.prototype;
+ if (isConsumed) {
+ return isConsumed;
+ }
- var fixRegexpWellKnownSymbolLogic = function (KEY, exec, FORCED, SHAM) {
- var SYMBOL = wellKnownSymbol$5(KEY);
+ if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
+ return Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset, this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength));
+ } else {
+ return Promise.resolve(this._bodyArrayBuffer);
+ }
+ } else {
+ return this.blob().then(readBlobAsArrayBuffer);
+ }
+ };
+ }
- var DELEGATES_TO_SYMBOL = !fails$m(function () {
- // String methods call symbol-named RegEp methods
- var O = {};
- O[SYMBOL] = function () { return 7; };
- return ''[KEY](O) != 7;
- });
+ this.text = function () {
+ var rejected = consumed(this);
- var DELEGATES_TO_EXEC = DELEGATES_TO_SYMBOL && !fails$m(function () {
- // Symbol-named RegExp methods call .exec
- var execCalled = false;
- var re = /a/;
+ if (rejected) {
+ return rejected;
+ }
- if (KEY === 'split') {
- // We can't use real regex here since it causes deoptimization
- // and serious performance degradation in V8
- // https://github.com/zloirock/core-js/issues/306
- re = {};
- // RegExp[@@split] doesn't call the regex's exec method, but first creates
- // a new one. We need to return the patched regex when creating the new one.
- re.constructor = {};
- re.constructor[SPECIES] = function () { return re; };
- re.flags = '';
- re[SYMBOL] = /./[SYMBOL];
+ if (this._bodyBlob) {
+ return readBlobAsText(this._bodyBlob);
+ } else if (this._bodyArrayBuffer) {
+ return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as text');
+ } else {
+ return Promise.resolve(this._bodyText);
}
+ };
- re.exec = function () { execCalled = true; return null; };
+ if (support.formData) {
+ this.formData = function () {
+ return this.text().then(decode);
+ };
+ }
- re[SYMBOL]('');
- return !execCalled;
- });
+ this.json = function () {
+ return this.text().then(JSON.parse);
+ };
- if (
- !DELEGATES_TO_SYMBOL ||
- !DELEGATES_TO_EXEC ||
- FORCED
- ) {
- var nativeRegExpMethod = /./[SYMBOL];
- var methods = exec(SYMBOL, ''[KEY], function (nativeMethod, regexp, str, arg2, forceStringMethod) {
- var $exec = regexp.exec;
- if ($exec === regexpExec$2 || $exec === RegExpPrototype$1.exec) {
- if (DELEGATES_TO_SYMBOL && !forceStringMethod) {
- // The native String method already delegates to @@method (this
- // polyfilled function), leasing to infinite recursion.
- // We avoid it by directly calling the native @@method method.
- return { done: true, value: nativeRegExpMethod.call(regexp, str, arg2) };
- }
- return { done: true, value: nativeMethod.call(str, regexp, arg2) };
- }
- return { done: false };
- });
+ return this;
+ } // HTTP methods whose capitalization should be normalized
- redefine$4(String.prototype, KEY, methods[0]);
- redefine$4(RegExpPrototype$1, SYMBOL, methods[1]);
- }
- if (SHAM) createNonEnumerableProperty$1(RegExpPrototype$1[SYMBOL], 'sham', true);
- };
+ var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
- var charAt = stringMultibyte.charAt;
+ function normalizeMethod(method) {
+ var upcased = method.toUpperCase();
+ return methods.indexOf(upcased) > -1 ? upcased : method;
+ }
- // `AdvanceStringIndex` abstract operation
- // https://tc39.es/ecma262/#sec-advancestringindex
- var advanceStringIndex$3 = function (S, index, unicode) {
- return index + (unicode ? charAt(S, index).length : 1);
- };
+ function Request(input, options) {
+ if (!(this instanceof Request)) {
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
+ }
- var toObject$6 = toObject$i;
+ options = options || {};
+ var body = options.body;
- var floor$1 = Math.floor;
- var replace = ''.replace;
- var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d{1,2}|<[^>]*>)/g;
- var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&'`]|\d{1,2})/g;
+ if (input instanceof Request) {
+ if (input.bodyUsed) {
+ throw new TypeError('Already read');
+ }
- // `GetSubstitution` abstract operation
- // https://tc39.es/ecma262/#sec-getsubstitution
- var getSubstitution$1 = function (matched, str, position, captures, namedCaptures, replacement) {
- var tailPos = position + matched.length;
- var m = captures.length;
- var symbols = SUBSTITUTION_SYMBOLS_NO_NAMED;
- if (namedCaptures !== undefined) {
- namedCaptures = toObject$6(namedCaptures);
- symbols = SUBSTITUTION_SYMBOLS;
- }
- return replace.call(replacement, symbols, function (match, ch) {
- var capture;
- switch (ch.charAt(0)) {
- case '$': return '$';
- case '&': return matched;
- case '`': return str.slice(0, position);
- case "'": return str.slice(tailPos);
- case '<':
- capture = namedCaptures[ch.slice(1, -1)];
- break;
- default: // \d\d?
- var n = +ch;
- if (n === 0) return match;
- if (n > m) {
- var f = floor$1(n / 10);
- if (f === 0) return match;
- if (f <= m) return captures[f - 1] === undefined ? ch.charAt(1) : captures[f - 1] + ch.charAt(1);
- return match;
- }
- capture = captures[n - 1];
+ this.url = input.url;
+ this.credentials = input.credentials;
+
+ if (!options.headers) {
+ this.headers = new Headers(input.headers);
}
- return capture === undefined ? '' : capture;
- });
- };
- var classof$3 = classofRaw$1;
- var regexpExec$1 = regexpExec$3;
+ this.method = input.method;
+ this.mode = input.mode;
+ this.signal = input.signal;
- // `RegExpExec` abstract operation
- // https://tc39.es/ecma262/#sec-regexpexec
- var regexpExecAbstract = function (R, S) {
- var exec = R.exec;
- if (typeof exec === 'function') {
- var result = exec.call(R, S);
- if (typeof result !== 'object') {
- throw TypeError('RegExp exec method returned something other than an Object or null');
+ if (!body && input._bodyInit != null) {
+ body = input._bodyInit;
+ input.bodyUsed = true;
}
- return result;
+ } else {
+ this.url = String(input);
}
- if (classof$3(R) !== 'RegExp') {
- throw TypeError('RegExp#exec called on incompatible receiver');
- }
+ this.credentials = options.credentials || this.credentials || 'same-origin';
- return regexpExec$1.call(R, S);
- };
+ if (options.headers || !this.headers) {
+ this.headers = new Headers(options.headers);
+ }
- var fixRegExpWellKnownSymbolLogic$3 = fixRegexpWellKnownSymbolLogic;
- var fails$l = fails$N;
- var anObject$5 = anObject$m;
- var toLength$8 = toLength$q;
- var toInteger$3 = toInteger$b;
- var requireObjectCoercible$a = requireObjectCoercible$e;
- var advanceStringIndex$2 = advanceStringIndex$3;
- var getSubstitution = getSubstitution$1;
- var regExpExec$2 = regexpExecAbstract;
- var wellKnownSymbol$4 = wellKnownSymbol$s;
+ this.method = normalizeMethod(options.method || this.method || 'GET');
+ this.mode = options.mode || this.mode || null;
+ this.signal = options.signal || this.signal;
+ this.referrer = null;
- var REPLACE = wellKnownSymbol$4('replace');
- var max$2 = Math.max;
- var min$5 = Math.min;
+ if ((this.method === 'GET' || this.method === 'HEAD') && body) {
+ throw new TypeError('Body not allowed for GET or HEAD requests');
+ }
- var maybeToString = function (it) {
- return it === undefined ? it : String(it);
- };
+ this._initBody(body);
- // IE <= 11 replaces $0 with the whole match, as if it was $&
- // https://stackoverflow.com/questions/6024666/getting-ie-to-replace-a-regex-with-the-literal-string-0
- var REPLACE_KEEPS_$0 = (function () {
- // eslint-disable-next-line regexp/prefer-escape-replacement-dollar-char -- required for testing
- return 'a'.replace(/./, '$0') === '$0';
- })();
+ if (this.method === 'GET' || this.method === 'HEAD') {
+ if (options.cache === 'no-store' || options.cache === 'no-cache') {
+ // Search for a '_' parameter in the query string
+ var reParamSearch = /([?&])_=[^&]*/;
- // Safari <= 13.0.3(?) substitutes nth capture where n>m with an empty string
- var REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE = (function () {
- if (/./[REPLACE]) {
- return /./[REPLACE]('a', '$0') === '';
+ if (reParamSearch.test(this.url)) {
+ // If it already exists then set the value with the current time
+ this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime());
+ } else {
+ // Otherwise add a new '_' parameter to the end with the current time
+ var reQueryString = /\?/;
+ this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime();
+ }
+ }
}
- return false;
- })();
-
- var REPLACE_SUPPORTS_NAMED_GROUPS = !fails$l(function () {
- var re = /./;
- re.exec = function () {
- var result = [];
- result.groups = { a: '7' };
- return result;
- };
- return ''.replace(re, '$<a>') !== '7';
- });
+ }
- // @@replace logic
- fixRegExpWellKnownSymbolLogic$3('replace', function (_, nativeReplace, maybeCallNative) {
- var UNSAFE_SUBSTITUTE = REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE ? '$' : '$0';
+ Request.prototype.clone = function () {
+ return new Request(this, {
+ body: this._bodyInit
+ });
+ };
- return [
- // `String.prototype.replace` method
- // https://tc39.es/ecma262/#sec-string.prototype.replace
- function replace(searchValue, replaceValue) {
- var O = requireObjectCoercible$a(this);
- var replacer = searchValue == undefined ? undefined : searchValue[REPLACE];
- return replacer !== undefined
- ? replacer.call(searchValue, O, replaceValue)
- : nativeReplace.call(String(O), searchValue, replaceValue);
- },
- // `RegExp.prototype[@@replace]` method
- // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
- function (string, replaceValue) {
- if (
- typeof replaceValue === 'string' &&
- replaceValue.indexOf(UNSAFE_SUBSTITUTE) === -1 &&
- replaceValue.indexOf('$<') === -1
- ) {
- var res = maybeCallNative(nativeReplace, this, string, replaceValue);
- if (res.done) return res.value;
- }
+ function decode(body) {
+ var form = new FormData();
+ body.trim().split('&').forEach(function (bytes) {
+ if (bytes) {
+ var split = bytes.split('=');
+ var name = split.shift().replace(/\+/g, ' ');
+ var value = split.join('=').replace(/\+/g, ' ');
+ form.append(decodeURIComponent(name), decodeURIComponent(value));
+ }
+ });
+ return form;
+ }
- var rx = anObject$5(this);
- var S = String(string);
+ function parseHeaders(rawHeaders) {
+ var headers = new Headers(); // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
+ // https://tools.ietf.org/html/rfc7230#section-3.2
- var functionalReplace = typeof replaceValue === 'function';
- if (!functionalReplace) replaceValue = String(replaceValue);
+ var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill
+ // https://github.com/github/fetch/issues/748
+ // https://github.com/zloirock/core-js/issues/751
- var global = rx.global;
- if (global) {
- var fullUnicode = rx.unicode;
- rx.lastIndex = 0;
- }
- var results = [];
- while (true) {
- var result = regExpExec$2(rx, S);
- if (result === null) break;
+ preProcessedHeaders.split('\r').map(function (header) {
+ return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header;
+ }).forEach(function (line) {
+ var parts = line.split(':');
+ var key = parts.shift().trim();
- results.push(result);
- if (!global) break;
+ if (key) {
+ var value = parts.join(':').trim();
+ headers.append(key, value);
+ }
+ });
+ return headers;
+ }
- var matchStr = String(result[0]);
- if (matchStr === '') rx.lastIndex = advanceStringIndex$2(S, toLength$8(rx.lastIndex), fullUnicode);
- }
+ Body.call(Request.prototype);
+ function Response(bodyInit, options) {
+ if (!(this instanceof Response)) {
+ throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
+ }
- var accumulatedResult = '';
- var nextSourcePosition = 0;
- for (var i = 0; i < results.length; i++) {
- result = results[i];
+ if (!options) {
+ options = {};
+ }
- var matched = String(result[0]);
- var position = max$2(min$5(toInteger$3(result.index), S.length), 0);
- var captures = [];
- // NOTE: This is equivalent to
- // captures = result.slice(1).map(maybeToString)
- // but for some reason `nativeSlice.call(result, 1, result.length)` (called in
- // the slice polyfill when slicing native arrays) "doesn't work" in safari 9 and
- // causes a crash (https://pastebin.com/N21QzeQA) when trying to debug it.
- for (var j = 1; j < result.length; j++) captures.push(maybeToString(result[j]));
- var namedCaptures = result.groups;
- if (functionalReplace) {
- var replacerArgs = [matched].concat(captures, position, S);
- if (namedCaptures !== undefined) replacerArgs.push(namedCaptures);
- var replacement = String(replaceValue.apply(undefined, replacerArgs));
- } else {
- replacement = getSubstitution(matched, S, position, captures, namedCaptures, replaceValue);
- }
- if (position >= nextSourcePosition) {
- accumulatedResult += S.slice(nextSourcePosition, position) + replacement;
- nextSourcePosition = position + matched.length;
- }
- }
- return accumulatedResult + S.slice(nextSourcePosition);
- }
- ];
- }, !REPLACE_SUPPORTS_NAMED_GROUPS || !REPLACE_KEEPS_$0 || REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE);
+ this.type = 'default';
+ this.status = options.status === undefined ? 200 : options.status;
+ this.ok = this.status >= 200 && this.status < 300;
+ this.statusText = options.statusText === undefined ? '' : '' + options.statusText;
+ this.headers = new Headers(options.headers);
+ this.url = options.url || '';
- var isObject$b = isObject$r;
- var classof$2 = classofRaw$1;
- var wellKnownSymbol$3 = wellKnownSymbol$s;
+ this._initBody(bodyInit);
+ }
+ Body.call(Response.prototype);
- var MATCH$2 = wellKnownSymbol$3('match');
+ Response.prototype.clone = function () {
+ return new Response(this._bodyInit, {
+ status: this.status,
+ statusText: this.statusText,
+ headers: new Headers(this.headers),
+ url: this.url
+ });
+ };
- // `IsRegExp` abstract operation
- // https://tc39.es/ecma262/#sec-isregexp
- var isRegexp = function (it) {
- var isRegExp;
- return isObject$b(it) && ((isRegExp = it[MATCH$2]) !== undefined ? !!isRegExp : classof$2(it) == 'RegExp');
+ Response.error = function () {
+ var response = new Response(null, {
+ status: 0,
+ statusText: ''
+ });
+ response.type = 'error';
+ return response;
};
- var fixRegExpWellKnownSymbolLogic$2 = fixRegexpWellKnownSymbolLogic;
- var isRegExp$2 = isRegexp;
- var anObject$4 = anObject$m;
- var requireObjectCoercible$9 = requireObjectCoercible$e;
- var speciesConstructor$1 = speciesConstructor$8;
- var advanceStringIndex$1 = advanceStringIndex$3;
- var toLength$7 = toLength$q;
- var callRegExpExec = regexpExecAbstract;
- var regexpExec = regexpExec$3;
- var stickyHelpers$1 = regexpStickyHelpers;
- var fails$k = fails$N;
+ var redirectStatuses = [301, 302, 303, 307, 308];
- var UNSUPPORTED_Y$1 = stickyHelpers$1.UNSUPPORTED_Y;
- var arrayPush = [].push;
- var min$4 = Math.min;
- var MAX_UINT32 = 0xFFFFFFFF;
+ Response.redirect = function (url, status) {
+ if (redirectStatuses.indexOf(status) === -1) {
+ throw new RangeError('Invalid status code');
+ }
- // Chrome 51 has a buggy "split" implementation when RegExp#exec !== nativeExec
- // Weex JS has frozen built-in prototypes, so use try / catch wrapper
- var SPLIT_WORKS_WITH_OVERWRITTEN_EXEC = !fails$k(function () {
- // eslint-disable-next-line regexp/no-empty-group -- required for testing
- var re = /(?:)/;
- var originalExec = re.exec;
- re.exec = function () { return originalExec.apply(this, arguments); };
- var result = 'ab'.split(re);
- return result.length !== 2 || result[0] !== 'a' || result[1] !== 'b';
- });
+ return new Response(null, {
+ status: status,
+ headers: {
+ location: url
+ }
+ });
+ };
- // @@split logic
- fixRegExpWellKnownSymbolLogic$2('split', function (SPLIT, nativeSplit, maybeCallNative) {
- var internalSplit;
- if (
- 'abbc'.split(/(b)*/)[1] == 'c' ||
- // eslint-disable-next-line regexp/no-empty-group -- required for testing
- 'test'.split(/(?:)/, -1).length != 4 ||
- 'ab'.split(/(?:ab)*/).length != 2 ||
- '.'.split(/(.?)(.?)/).length != 4 ||
- // eslint-disable-next-line regexp/no-assertion-capturing-group, regexp/no-empty-group -- required for testing
- '.'.split(/()()/).length > 1 ||
- ''.split(/.?/).length
- ) {
- // based on es5-shim implementation, need to rework it
- internalSplit = function (separator, limit) {
- var string = String(requireObjectCoercible$9(this));
- var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;
- if (lim === 0) return [];
- if (separator === undefined) return [string];
- // If `separator` is not a regex, use native split
- if (!isRegExp$2(separator)) {
- return nativeSplit.call(string, separator, lim);
- }
- var output = [];
- var flags = (separator.ignoreCase ? 'i' : '') +
- (separator.multiline ? 'm' : '') +
- (separator.unicode ? 'u' : '') +
- (separator.sticky ? 'y' : '');
- var lastLastIndex = 0;
- // Make `global` and avoid `lastIndex` issues by working with a copy
- var separatorCopy = new RegExp(separator.source, flags + 'g');
- var match, lastIndex, lastLength;
- while (match = regexpExec.call(separatorCopy, string)) {
- lastIndex = separatorCopy.lastIndex;
- if (lastIndex > lastLastIndex) {
- output.push(string.slice(lastLastIndex, match.index));
- if (match.length > 1 && match.index < string.length) arrayPush.apply(output, match.slice(1));
- lastLength = match[0].length;
- lastLastIndex = lastIndex;
- if (output.length >= lim) break;
- }
- if (separatorCopy.lastIndex === match.index) separatorCopy.lastIndex++; // Avoid an infinite loop
- }
- if (lastLastIndex === string.length) {
- if (lastLength || !separatorCopy.test('')) output.push('');
- } else output.push(string.slice(lastLastIndex));
- return output.length > lim ? output.slice(0, lim) : output;
- };
- // Chakra, V8
- } else if ('0'.split(undefined, 0).length) {
- internalSplit = function (separator, limit) {
- return separator === undefined && limit === 0 ? [] : nativeSplit.call(this, separator, limit);
- };
- } else internalSplit = nativeSplit;
+ var DOMException$1 = global$k.DOMException;
- return [
- // `String.prototype.split` method
- // https://tc39.es/ecma262/#sec-string.prototype.split
- function split(separator, limit) {
- var O = requireObjectCoercible$9(this);
- var splitter = separator == undefined ? undefined : separator[SPLIT];
- return splitter !== undefined
- ? splitter.call(separator, O, limit)
- : internalSplit.call(String(O), separator, limit);
- },
- // `RegExp.prototype[@@split]` method
- // https://tc39.es/ecma262/#sec-regexp.prototype-@@split
- //
- // NOTE: This cannot be properly polyfilled in engines that don't support
- // the 'y' flag.
- function (string, limit) {
- var res = maybeCallNative(internalSplit, this, string, limit, internalSplit !== nativeSplit);
- if (res.done) return res.value;
+ try {
+ new DOMException$1();
+ } catch (err) {
+ DOMException$1 = function DOMException(message, name) {
+ this.message = message;
+ this.name = name;
+ var error = Error(message);
+ this.stack = error.stack;
+ };
- var rx = anObject$4(this);
- var S = String(string);
- var C = speciesConstructor$1(rx, RegExp);
+ DOMException$1.prototype = Object.create(Error.prototype);
+ DOMException$1.prototype.constructor = DOMException$1;
+ }
- var unicodeMatching = rx.unicode;
- var flags = (rx.ignoreCase ? 'i' : '') +
- (rx.multiline ? 'm' : '') +
- (rx.unicode ? 'u' : '') +
- (UNSUPPORTED_Y$1 ? 'g' : 'y');
+ function fetch$1(input, init) {
+ return new Promise(function (resolve, reject) {
+ var request = new Request(input, init);
- // ^(? + rx + ) is needed, in combination with some S slicing, to
- // simulate the 'y' flag.
- var splitter = new C(UNSUPPORTED_Y$1 ? '^(?:' + rx.source + ')' : rx, flags);
- var lim = limit === undefined ? MAX_UINT32 : limit >>> 0;
- if (lim === 0) return [];
- if (S.length === 0) return callRegExpExec(splitter, S) === null ? [S] : [];
- var p = 0;
- var q = 0;
- var A = [];
- while (q < S.length) {
- splitter.lastIndex = UNSUPPORTED_Y$1 ? 0 : q;
- var z = callRegExpExec(splitter, UNSUPPORTED_Y$1 ? S.slice(q) : S);
- var e;
- if (
- z === null ||
- (e = min$4(toLength$7(splitter.lastIndex + (UNSUPPORTED_Y$1 ? q : 0)), S.length)) === p
- ) {
- q = advanceStringIndex$1(S, q, unicodeMatching);
- } else {
- A.push(S.slice(p, q));
- if (A.length === lim) return A;
- for (var i = 1; i <= z.length - 1; i++) {
- A.push(z[i]);
- if (A.length === lim) return A;
- }
- q = p = e;
- }
- }
- A.push(S.slice(p));
- return A;
+ if (request.signal && request.signal.aborted) {
+ return reject(new DOMException$1('Aborted', 'AbortError'));
}
- ];
- }, !SPLIT_WORKS_WITH_OVERWRITTEN_EXEC, UNSUPPORTED_Y$1);
- // a string of all valid unicode whitespaces
- var whitespaces$4 = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' +
- '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF';
+ var xhr = new XMLHttpRequest();
- var requireObjectCoercible$8 = requireObjectCoercible$e;
- var whitespaces$3 = whitespaces$4;
+ function abortXhr() {
+ xhr.abort();
+ }
- var whitespace = '[' + whitespaces$3 + ']';
- var ltrim = RegExp('^' + whitespace + whitespace + '*');
- var rtrim$2 = RegExp(whitespace + whitespace + '*$');
+ xhr.onload = function () {
+ var options = {
+ status: xhr.status,
+ statusText: xhr.statusText,
+ headers: parseHeaders(xhr.getAllResponseHeaders() || '')
+ };
+ options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
+ var body = 'response' in xhr ? xhr.response : xhr.responseText;
+ setTimeout(function () {
+ resolve(new Response(body, options));
+ }, 0);
+ };
- // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation
- var createMethod$2 = function (TYPE) {
- return function ($this) {
- var string = String(requireObjectCoercible$8($this));
- if (TYPE & 1) string = string.replace(ltrim, '');
- if (TYPE & 2) string = string.replace(rtrim$2, '');
- return string;
- };
- };
+ xhr.onerror = function () {
+ setTimeout(function () {
+ reject(new TypeError('Network request failed'));
+ }, 0);
+ };
- var stringTrim = {
- // `String.prototype.{ trimLeft, trimStart }` methods
- // https://tc39.es/ecma262/#sec-string.prototype.trimstart
- start: createMethod$2(1),
- // `String.prototype.{ trimRight, trimEnd }` methods
- // https://tc39.es/ecma262/#sec-string.prototype.trimend
- end: createMethod$2(2),
- // `String.prototype.trim` method
- // https://tc39.es/ecma262/#sec-string.prototype.trim
- trim: createMethod$2(3)
- };
+ xhr.ontimeout = function () {
+ setTimeout(function () {
+ reject(new TypeError('Network request failed'));
+ }, 0);
+ };
- var fails$j = fails$N;
- var whitespaces$2 = whitespaces$4;
+ xhr.onabort = function () {
+ setTimeout(function () {
+ reject(new DOMException$1('Aborted', 'AbortError'));
+ }, 0);
+ };
- var non = '\u200B\u0085\u180E';
+ function fixUrl(url) {
+ try {
+ return url === '' && global$k.location.href ? global$k.location.href : url;
+ } catch (e) {
+ return url;
+ }
+ }
- // check that a method works with the correct list
- // of whitespaces and has a correct name
- var stringTrimForced = function (METHOD_NAME) {
- return fails$j(function () {
- return !!whitespaces$2[METHOD_NAME]() || non[METHOD_NAME]() != non || whitespaces$2[METHOD_NAME].name !== METHOD_NAME;
- });
- };
+ xhr.open(request.method, fixUrl(request.url), true);
- var $$O = _export;
- var $trim = stringTrim.trim;
- var forcedStringTrimMethod = stringTrimForced;
+ if (request.credentials === 'include') {
+ xhr.withCredentials = true;
+ } else if (request.credentials === 'omit') {
+ xhr.withCredentials = false;
+ }
- // `String.prototype.trim` method
- // https://tc39.es/ecma262/#sec-string.prototype.trim
- $$O({ target: 'String', proto: true, forced: forcedStringTrimMethod('trim') }, {
- trim: function trim() {
- return $trim(this);
- }
- });
+ if ('responseType' in xhr) {
+ if (support.blob) {
+ xhr.responseType = 'blob';
+ } else if (support.arrayBuffer && request.headers.get('Content-Type') && request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1) {
+ xhr.responseType = 'arraybuffer';
+ }
+ }
- var DESCRIPTORS$9 = descriptors;
- var defineProperty$4 = objectDefineProperty.f;
+ if (init && _typeof(init.headers) === 'object' && !(init.headers instanceof Headers)) {
+ Object.getOwnPropertyNames(init.headers).forEach(function (name) {
+ xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
+ });
+ } else {
+ request.headers.forEach(function (value, name) {
+ xhr.setRequestHeader(name, value);
+ });
+ }
- var FunctionPrototype = Function.prototype;
- var FunctionPrototypeToString = FunctionPrototype.toString;
- var nameRE = /^\s*function ([^ (]*)/;
- var NAME = 'name';
+ if (request.signal) {
+ request.signal.addEventListener('abort', abortXhr);
- // Function instances `.name` property
- // https://tc39.es/ecma262/#sec-function-instances-name
- if (DESCRIPTORS$9 && !(NAME in FunctionPrototype)) {
- defineProperty$4(FunctionPrototype, NAME, {
- configurable: true,
- get: function () {
- try {
- return FunctionPrototypeToString.call(this).match(nameRE)[1];
- } catch (error) {
- return '';
- }
+ xhr.onreadystatechange = function () {
+ // DONE (success or failure)
+ if (xhr.readyState === 4) {
+ request.signal.removeEventListener('abort', abortXhr);
+ }
+ };
}
+
+ xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
});
}
+ fetch$1.polyfill = true;
- var $$N = _export;
- var DESCRIPTORS$8 = descriptors;
- var create$6 = objectCreate;
+ if (!global$k.fetch) {
+ global$k.fetch = fetch$1;
+ global$k.Headers = Headers;
+ global$k.Request = Request;
+ global$k.Response = Response;
+ }
- // `Object.create` method
- // https://tc39.es/ecma262/#sec-object.create
- $$N({ target: 'Object', stat: true, sham: !DESCRIPTORS$8 }, {
- create: create$6
+ var $$T = _export;
+ var DESCRIPTORS$9 = descriptors;
+ var objectDefinePropertyModile = objectDefineProperty;
+
+ // `Object.defineProperty` method
+ // https://tc39.es/ecma262/#sec-object.defineproperty
+ $$T({ target: 'Object', stat: true, forced: !DESCRIPTORS$9, sham: !DESCRIPTORS$9 }, {
+ defineProperty: objectDefinePropertyModile.f
});
- var $$M = _export;
- var global$9 = global$F;
- var userAgent = engineUserAgent;
+ var $$S = _export;
+ var setPrototypeOf = objectSetPrototypeOf;
- var slice$3 = [].slice;
- var MSIE = /MSIE .\./.test(userAgent); // <- dirty ie9- check
+ // `Object.setPrototypeOf` method
+ // https://tc39.es/ecma262/#sec-object.setprototypeof
+ $$S({ target: 'Object', stat: true }, {
+ setPrototypeOf: setPrototypeOf
+ });
- var wrap$1 = function (scheduler) {
- return function (handler, timeout /* , ...arguments */) {
- var boundArgs = arguments.length > 2;
- var args = boundArgs ? slice$3.call(arguments, 2) : undefined;
- return scheduler(boundArgs ? function () {
- // eslint-disable-next-line no-new-func -- spec requirement
- (typeof handler == 'function' ? handler : Function(handler)).apply(this, args);
- } : handler, timeout);
- };
- };
+ var $$R = _export;
+ var fails$n = fails$S;
+ var toObject$8 = toObject$j;
+ var nativeGetPrototypeOf = objectGetPrototypeOf;
+ var CORRECT_PROTOTYPE_GETTER = correctPrototypeGetter;
- // ie9- setTimeout & setInterval additional parameters fix
- // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
- $$M({ global: true, bind: true, forced: MSIE }, {
- // `setTimeout` method
- // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout
- setTimeout: wrap$1(global$9.setTimeout),
- // `setInterval` method
- // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval
- setInterval: wrap$1(global$9.setInterval)
+ var FAILS_ON_PRIMITIVES$4 = fails$n(function () { nativeGetPrototypeOf(1); });
+
+ // `Object.getPrototypeOf` method
+ // https://tc39.es/ecma262/#sec-object.getprototypeof
+ $$R({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$4, sham: !CORRECT_PROTOTYPE_GETTER }, {
+ getPrototypeOf: function getPrototypeOf(it) {
+ return nativeGetPrototypeOf(toObject$8(it));
+ }
});
- var global$8 = typeof globalThis !== 'undefined' && globalThis || typeof self !== 'undefined' && self || typeof global$8 !== 'undefined' && global$8;
- var support = {
- searchParams: 'URLSearchParams' in global$8,
- iterable: 'Symbol' in global$8 && 'iterator' in Symbol,
- blob: 'FileReader' in global$8 && 'Blob' in global$8 && function () {
- try {
- new Blob();
- return true;
- } catch (e) {
- return false;
- }
- }(),
- formData: 'FormData' in global$8,
- arrayBuffer: 'ArrayBuffer' in global$8
- };
+ var global$j = global$1m;
+ var uncurryThis$m = functionUncurryThis;
+ var aCallable$2 = aCallable$a;
+ var isObject$b = isObject$s;
+ var hasOwn$5 = hasOwnProperty_1;
+ var arraySlice$2 = arraySlice$c;
- function isDataView(obj) {
- return obj && DataView.prototype.isPrototypeOf(obj);
- }
+ var Function$1 = global$j.Function;
+ var concat$1 = uncurryThis$m([].concat);
+ var join$3 = uncurryThis$m([].join);
+ var factories = {};
- if (support.arrayBuffer) {
- var viewClasses = ['[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]'];
+ var construct = function (C, argsLength, args) {
+ if (!hasOwn$5(factories, argsLength)) {
+ for (var list = [], i = 0; i < argsLength; i++) list[i] = 'a[' + i + ']';
+ factories[argsLength] = Function$1('C,a', 'return new C(' + join$3(list, ',') + ')');
+ } return factories[argsLength](C, args);
+ };
- var isArrayBufferView = ArrayBuffer.isView || function (obj) {
- return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
+ // `Function.prototype.bind` method implementation
+ // https://tc39.es/ecma262/#sec-function.prototype.bind
+ var functionBind = Function$1.bind || function bind(that /* , ...args */) {
+ var F = aCallable$2(this);
+ var Prototype = F.prototype;
+ var partArgs = arraySlice$2(arguments, 1);
+ var boundFunction = function bound(/* args... */) {
+ var args = concat$1(partArgs, arraySlice$2(arguments));
+ return this instanceof boundFunction ? construct(F, args.length, args) : F.apply(that, args);
};
- }
-
- function normalizeName(name) {
- if (typeof name !== 'string') {
- name = String(name);
- }
+ if (isObject$b(Prototype)) boundFunction.prototype = Prototype;
+ return boundFunction;
+ };
- if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') {
- throw new TypeError('Invalid character in header field name: "' + name + '"');
- }
+ var $$Q = _export;
+ var getBuiltIn$1 = getBuiltIn$b;
+ var apply = functionApply;
+ var bind$7 = functionBind;
+ var aConstructor = aConstructor$3;
+ var anObject$4 = anObject$n;
+ var isObject$a = isObject$s;
+ var create$4 = objectCreate;
+ var fails$m = fails$S;
- return name.toLowerCase();
- }
+ var nativeConstruct = getBuiltIn$1('Reflect', 'construct');
+ var ObjectPrototype = Object.prototype;
+ var push$4 = [].push;
- function normalizeValue(value) {
- if (typeof value !== 'string') {
- value = String(value);
- }
+ // `Reflect.construct` method
+ // https://tc39.es/ecma262/#sec-reflect.construct
+ // MS Edge supports only 2 arguments and argumentsList argument is optional
+ // FF Nightly sets third argument as `new.target`, but does not create `this` from it
+ var NEW_TARGET_BUG = fails$m(function () {
+ function F() { /* empty */ }
+ return !(nativeConstruct(function () { /* empty */ }, [], F) instanceof F);
+ });
- return value;
- } // Build a destructive iterator for the value list
+ var ARGS_BUG = !fails$m(function () {
+ nativeConstruct(function () { /* empty */ });
+ });
+ var FORCED$c = NEW_TARGET_BUG || ARGS_BUG;
- function iteratorFor(items) {
- var iterator = {
- next: function next() {
- var value = items.shift();
- return {
- done: value === undefined,
- value: value
- };
+ $$Q({ target: 'Reflect', stat: true, forced: FORCED$c, sham: FORCED$c }, {
+ construct: function construct(Target, args /* , newTarget */) {
+ aConstructor(Target);
+ anObject$4(args);
+ var newTarget = arguments.length < 3 ? Target : aConstructor(arguments[2]);
+ if (ARGS_BUG && !NEW_TARGET_BUG) return nativeConstruct(Target, args, newTarget);
+ if (Target == newTarget) {
+ // w/o altered newTarget, optimization for 0-4 arguments
+ switch (args.length) {
+ case 0: return new Target();
+ case 1: return new Target(args[0]);
+ case 2: return new Target(args[0], args[1]);
+ case 3: return new Target(args[0], args[1], args[2]);
+ case 4: return new Target(args[0], args[1], args[2], args[3]);
+ }
+ // w/o altered newTarget, lot of arguments case
+ var $args = [null];
+ apply(push$4, $args, args);
+ return new (apply(bind$7, Target, $args))();
}
- };
-
- if (support.iterable) {
- iterator[Symbol.iterator] = function () {
- return iterator;
- };
+ // with altered newTarget, not support built-in constructors
+ var proto = newTarget.prototype;
+ var instance = create$4(isObject$a(proto) ? proto : ObjectPrototype);
+ var result = apply(Target, instance, args);
+ return isObject$a(result) ? result : instance;
}
+ });
- return iterator;
- }
+ var hasOwn$4 = hasOwnProperty_1;
- function Headers(headers) {
- this.map = {};
+ var isDataDescriptor$1 = function (descriptor) {
+ return descriptor !== undefined && (hasOwn$4(descriptor, 'value') || hasOwn$4(descriptor, 'writable'));
+ };
- if (headers instanceof Headers) {
- headers.forEach(function (value, name) {
- this.append(name, value);
- }, this);
- } else if (Array.isArray(headers)) {
- headers.forEach(function (header) {
- this.append(header[0], header[1]);
- }, this);
- } else if (headers) {
- Object.getOwnPropertyNames(headers).forEach(function (name) {
- this.append(name, headers[name]);
- }, this);
- }
+ var $$P = _export;
+ var call$6 = functionCall;
+ var isObject$9 = isObject$s;
+ var anObject$3 = anObject$n;
+ var isDataDescriptor = isDataDescriptor$1;
+ var getOwnPropertyDescriptorModule = objectGetOwnPropertyDescriptor;
+ var getPrototypeOf = objectGetPrototypeOf;
+
+ // `Reflect.get` method
+ // https://tc39.es/ecma262/#sec-reflect.get
+ function get$3(target, propertyKey /* , receiver */) {
+ var receiver = arguments.length < 3 ? target : arguments[2];
+ var descriptor, prototype;
+ if (anObject$3(target) === receiver) return target[propertyKey];
+ descriptor = getOwnPropertyDescriptorModule.f(target, propertyKey);
+ if (descriptor) return isDataDescriptor(descriptor)
+ ? descriptor.value
+ : descriptor.get === undefined ? undefined : call$6(descriptor.get, receiver);
+ if (isObject$9(prototype = getPrototypeOf(target))) return get$3(prototype, propertyKey, receiver);
}
- Headers.prototype.append = function (name, value) {
- name = normalizeName(name);
- value = normalizeValue(value);
- var oldValue = this.map[name];
- this.map[name] = oldValue ? oldValue + ', ' + value : value;
- };
+ $$P({ target: 'Reflect', stat: true }, {
+ get: get$3
+ });
- Headers.prototype['delete'] = function (name) {
- delete this.map[normalizeName(name)];
- };
+ var $$O = _export;
+ var fails$l = fails$S;
+ var toIndexedObject$1 = toIndexedObject$c;
+ var nativeGetOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
+ var DESCRIPTORS$8 = descriptors;
- Headers.prototype.get = function (name) {
- name = normalizeName(name);
- return this.has(name) ? this.map[name] : null;
- };
+ var FAILS_ON_PRIMITIVES$3 = fails$l(function () { nativeGetOwnPropertyDescriptor(1); });
+ var FORCED$b = !DESCRIPTORS$8 || FAILS_ON_PRIMITIVES$3;
- Headers.prototype.has = function (name) {
- return this.map.hasOwnProperty(normalizeName(name));
- };
+ // `Object.getOwnPropertyDescriptor` method
+ // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
+ $$O({ target: 'Object', stat: true, forced: FORCED$b, sham: !DESCRIPTORS$8 }, {
+ getOwnPropertyDescriptor: function getOwnPropertyDescriptor(it, key) {
+ return nativeGetOwnPropertyDescriptor(toIndexedObject$1(it), key);
+ }
+ });
- Headers.prototype.set = function (name, value) {
- this.map[normalizeName(name)] = normalizeValue(value);
- };
+ var $$N = _export;
+ var global$i = global$1m;
+ var toAbsoluteIndex$1 = toAbsoluteIndex$8;
+ var toIntegerOrInfinity$2 = toIntegerOrInfinity$b;
+ var lengthOfArrayLike$5 = lengthOfArrayLike$g;
+ var toObject$7 = toObject$j;
+ var arraySpeciesCreate$2 = arraySpeciesCreate$4;
+ var createProperty$2 = createProperty$4;
+ var arrayMethodHasSpeciesSupport$2 = arrayMethodHasSpeciesSupport$5;
- Headers.prototype.forEach = function (callback, thisArg) {
- for (var name in this.map) {
- if (this.map.hasOwnProperty(name)) {
- callback.call(thisArg, this.map[name], name, this);
+ var HAS_SPECIES_SUPPORT$1 = arrayMethodHasSpeciesSupport$2('splice');
+
+ var TypeError$6 = global$i.TypeError;
+ var max$1 = Math.max;
+ var min$3 = Math.min;
+ var MAX_SAFE_INTEGER$1 = 0x1FFFFFFFFFFFFF;
+ var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded';
+
+ // `Array.prototype.splice` method
+ // https://tc39.es/ecma262/#sec-array.prototype.splice
+ // with adding support of @@species
+ $$N({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$1 }, {
+ splice: function splice(start, deleteCount /* , ...items */) {
+ var O = toObject$7(this);
+ var len = lengthOfArrayLike$5(O);
+ var actualStart = toAbsoluteIndex$1(start, len);
+ var argumentsLength = arguments.length;
+ var insertCount, actualDeleteCount, A, k, from, to;
+ if (argumentsLength === 0) {
+ insertCount = actualDeleteCount = 0;
+ } else if (argumentsLength === 1) {
+ insertCount = 0;
+ actualDeleteCount = len - actualStart;
+ } else {
+ insertCount = argumentsLength - 2;
+ actualDeleteCount = min$3(max$1(toIntegerOrInfinity$2(deleteCount), 0), len - actualStart);
+ }
+ if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER$1) {
+ throw TypeError$6(MAXIMUM_ALLOWED_LENGTH_EXCEEDED);
+ }
+ A = arraySpeciesCreate$2(O, actualDeleteCount);
+ for (k = 0; k < actualDeleteCount; k++) {
+ from = actualStart + k;
+ if (from in O) createProperty$2(A, k, O[from]);
+ }
+ A.length = actualDeleteCount;
+ if (insertCount < actualDeleteCount) {
+ for (k = actualStart; k < len - actualDeleteCount; k++) {
+ from = k + actualDeleteCount;
+ to = k + insertCount;
+ if (from in O) O[to] = O[from];
+ else delete O[to];
+ }
+ for (k = len; k > len - actualDeleteCount + insertCount; k--) delete O[k - 1];
+ } else if (insertCount > actualDeleteCount) {
+ for (k = len - actualDeleteCount; k > actualStart; k--) {
+ from = k + actualDeleteCount - 1;
+ to = k + insertCount - 1;
+ if (from in O) O[to] = O[from];
+ else delete O[to];
+ }
+ }
+ for (k = 0; k < insertCount; k++) {
+ O[k + actualStart] = arguments[k + 2];
}
+ O.length = len - actualDeleteCount + insertCount;
+ return A;
}
- };
+ });
- Headers.prototype.keys = function () {
- var items = [];
- this.forEach(function (value, name) {
- items.push(name);
- });
- return iteratorFor(items);
- };
+ var defineWellKnownSymbol$1 = defineWellKnownSymbol$4;
- Headers.prototype.values = function () {
- var items = [];
- this.forEach(function (value) {
- items.push(value);
- });
- return iteratorFor(items);
- };
+ // `Symbol.toStringTag` well-known symbol
+ // https://tc39.es/ecma262/#sec-symbol.tostringtag
+ defineWellKnownSymbol$1('toStringTag');
- Headers.prototype.entries = function () {
- var items = [];
- this.forEach(function (value, name) {
- items.push([name, value]);
- });
- return iteratorFor(items);
- };
+ var global$h = global$1m;
+ var setToStringTag$3 = setToStringTag$a;
- if (support.iterable) {
- Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
- }
+ // JSON[@@toStringTag] property
+ // https://tc39.es/ecma262/#sec-json-@@tostringtag
+ setToStringTag$3(global$h.JSON, 'JSON', true);
- function consumed(body) {
- if (body.bodyUsed) {
- return Promise.reject(new TypeError('Already read'));
- }
+ var setToStringTag$2 = setToStringTag$a;
- body.bodyUsed = true;
- }
+ // Math[@@toStringTag] property
+ // https://tc39.es/ecma262/#sec-math-@@tostringtag
+ setToStringTag$2(Math, 'Math', true);
- function fileReaderReady(reader) {
- return new Promise(function (resolve, reject) {
- reader.onload = function () {
- resolve(reader.result);
- };
+ (function (factory) {
+ factory();
+ })(function () {
- reader.onerror = function () {
- reject(reader.error);
- };
- });
- }
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
- function readBlobAsArrayBuffer(blob) {
- var reader = new FileReader();
- var promise = fileReaderReady(reader);
- reader.readAsArrayBuffer(blob);
- return promise;
- }
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
- function readBlobAsText(blob) {
- var reader = new FileReader();
- var promise = fileReaderReady(reader);
- reader.readAsText(blob);
- return promise;
- }
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+ }
- function readArrayBufferAsText(buf) {
- var view = new Uint8Array(buf);
- var chars = new Array(view.length);
+ function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
- for (var i = 0; i < view.length; i++) {
- chars[i] = String.fromCharCode(view[i]);
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
}
- return chars.join('');
- }
-
- function bufferClone(buf) {
- if (buf.slice) {
- return buf.slice(0);
- } else {
- var view = new Uint8Array(buf.byteLength);
- view.set(new Uint8Array(buf));
- return view.buffer;
+ function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
}
- }
- function Body() {
- this.bodyUsed = false;
+ function _setPrototypeOf(o, p) {
+ _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
- this._initBody = function (body) {
- /*
- fetch-mock wraps the Response object in an ES6 Proxy to
- provide useful test harness features such as flush. However, on
- ES5 browsers without fetch or Proxy support pollyfills must be used;
- the proxy-pollyfill is unable to proxy an attribute unless it exists
- on the object before the Proxy is created. This change ensures
- Response.bodyUsed exists on the instance, while maintaining the
- semantic of setting Request.bodyUsed in the constructor before
- _initBody is called.
- */
- this.bodyUsed = this.bodyUsed;
- this._bodyInit = body;
+ return _setPrototypeOf(o, p);
+ }
- if (!body) {
- this._bodyText = '';
- } else if (typeof body === 'string') {
- this._bodyText = body;
- } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
- this._bodyBlob = body;
- } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
- this._bodyFormData = body;
- } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
- this._bodyText = body.toString();
- } else if (support.arrayBuffer && support.blob && isDataView(body)) {
- this._bodyArrayBuffer = bufferClone(body.buffer); // IE 10-11 can't handle a DataView body.
+ function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
- this._bodyInit = new Blob([this._bodyArrayBuffer]);
- } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
- this._bodyArrayBuffer = bufferClone(body);
- } else {
- this._bodyText = body = Object.prototype.toString.call(body);
+ try {
+ Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
+ return true;
+ } catch (e) {
+ return false;
}
+ }
- if (!this.headers.get('content-type')) {
- if (typeof body === 'string') {
- this.headers.set('content-type', 'text/plain;charset=UTF-8');
- } else if (this._bodyBlob && this._bodyBlob.type) {
- this.headers.set('content-type', this._bodyBlob.type);
- } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
- this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
- }
+ function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
- };
- if (support.blob) {
- this.blob = function () {
- var rejected = consumed(this);
+ return self;
+ }
- if (rejected) {
- return rejected;
- }
+ function _possibleConstructorReturn(self, call) {
+ if (call && (_typeof(call) === "object" || typeof call === "function")) {
+ return call;
+ }
- if (this._bodyBlob) {
- return Promise.resolve(this._bodyBlob);
- } else if (this._bodyArrayBuffer) {
- return Promise.resolve(new Blob([this._bodyArrayBuffer]));
- } else if (this._bodyFormData) {
- throw new Error('could not read FormData body as blob');
- } else {
- return Promise.resolve(new Blob([this._bodyText]));
- }
- };
+ return _assertThisInitialized(self);
+ }
- this.arrayBuffer = function () {
- if (this._bodyArrayBuffer) {
- var isConsumed = consumed(this);
+ function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
- if (isConsumed) {
- return isConsumed;
- }
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
- if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
- return Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset, this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength));
- } else {
- return Promise.resolve(this._bodyArrayBuffer);
- }
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+
+ result = Reflect.construct(Super, arguments, NewTarget);
} else {
- return this.blob().then(readBlobAsArrayBuffer);
+ result = Super.apply(this, arguments);
}
+
+ return _possibleConstructorReturn(this, result);
};
}
- this.text = function () {
- var rejected = consumed(this);
-
- if (rejected) {
- return rejected;
- }
-
- if (this._bodyBlob) {
- return readBlobAsText(this._bodyBlob);
- } else if (this._bodyArrayBuffer) {
- return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
- } else if (this._bodyFormData) {
- throw new Error('could not read FormData body as text');
- } else {
- return Promise.resolve(this._bodyText);
+ function _superPropBase(object, property) {
+ while (!Object.prototype.hasOwnProperty.call(object, property)) {
+ object = _getPrototypeOf(object);
+ if (object === null) break;
}
- };
- if (support.formData) {
- this.formData = function () {
- return this.text().then(decode);
- };
+ return object;
}
- this.json = function () {
- return this.text().then(JSON.parse);
- };
-
- return this;
- } // HTTP methods whose capitalization should be normalized
+ function _get(target, property, receiver) {
+ if (typeof Reflect !== "undefined" && Reflect.get) {
+ _get = Reflect.get;
+ } else {
+ _get = function _get(target, property, receiver) {
+ var base = _superPropBase(target, property);
+ if (!base) return;
+ var desc = Object.getOwnPropertyDescriptor(base, property);
- var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];
+ if (desc.get) {
+ return desc.get.call(receiver);
+ }
- function normalizeMethod(method) {
- var upcased = method.toUpperCase();
- return methods.indexOf(upcased) > -1 ? upcased : method;
- }
+ return desc.value;
+ };
+ }
- function Request(input, options) {
- if (!(this instanceof Request)) {
- throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
+ return _get(target, property, receiver || target);
}
- options = options || {};
- var body = options.body;
+ var Emitter = /*#__PURE__*/function () {
+ function Emitter() {
+ _classCallCheck(this, Emitter);
- if (input instanceof Request) {
- if (input.bodyUsed) {
- throw new TypeError('Already read');
+ Object.defineProperty(this, 'listeners', {
+ value: {},
+ writable: true,
+ configurable: true
+ });
}
- this.url = input.url;
- this.credentials = input.credentials;
-
- if (!options.headers) {
- this.headers = new Headers(input.headers);
- }
-
- this.method = input.method;
- this.mode = input.mode;
- this.signal = input.signal;
+ _createClass(Emitter, [{
+ key: "addEventListener",
+ value: function addEventListener(type, callback, options) {
+ if (!(type in this.listeners)) {
+ this.listeners[type] = [];
+ }
- if (!body && input._bodyInit != null) {
- body = input._bodyInit;
- input.bodyUsed = true;
- }
- } else {
- this.url = String(input);
- }
+ this.listeners[type].push({
+ callback: callback,
+ options: options
+ });
+ }
+ }, {
+ key: "removeEventListener",
+ value: function removeEventListener(type, callback) {
+ if (!(type in this.listeners)) {
+ return;
+ }
- this.credentials = options.credentials || this.credentials || 'same-origin';
+ var stack = this.listeners[type];
- if (options.headers || !this.headers) {
- this.headers = new Headers(options.headers);
- }
+ for (var i = 0, l = stack.length; i < l; i++) {
+ if (stack[i].callback === callback) {
+ stack.splice(i, 1);
+ return;
+ }
+ }
+ }
+ }, {
+ key: "dispatchEvent",
+ value: function dispatchEvent(event) {
+ if (!(event.type in this.listeners)) {
+ return;
+ }
- this.method = normalizeMethod(options.method || this.method || 'GET');
- this.mode = options.mode || this.mode || null;
- this.signal = options.signal || this.signal;
- this.referrer = null;
+ var stack = this.listeners[event.type];
+ var stackToCall = stack.slice();
- if ((this.method === 'GET' || this.method === 'HEAD') && body) {
- throw new TypeError('Body not allowed for GET or HEAD requests');
- }
+ for (var i = 0, l = stackToCall.length; i < l; i++) {
+ var listener = stackToCall[i];
- this._initBody(body);
+ try {
+ listener.callback.call(this, event);
+ } catch (e) {
+ Promise.resolve().then(function () {
+ throw e;
+ });
+ }
- if (this.method === 'GET' || this.method === 'HEAD') {
- if (options.cache === 'no-store' || options.cache === 'no-cache') {
- // Search for a '_' parameter in the query string
- var reParamSearch = /([?&])_=[^&]*/;
+ if (listener.options && listener.options.once) {
+ this.removeEventListener(event.type, listener.callback);
+ }
+ }
- if (reParamSearch.test(this.url)) {
- // If it already exists then set the value with the current time
- this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime());
- } else {
- // Otherwise add a new '_' parameter to the end with the current time
- var reQueryString = /\?/;
- this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime();
+ return !event.defaultPrevented;
}
- }
- }
- }
-
- Request.prototype.clone = function () {
- return new Request(this, {
- body: this._bodyInit
- });
- };
+ }]);
- function decode(body) {
- var form = new FormData();
- body.trim().split('&').forEach(function (bytes) {
- if (bytes) {
- var split = bytes.split('=');
- var name = split.shift().replace(/\+/g, ' ');
- var value = split.join('=').replace(/\+/g, ' ');
- form.append(decodeURIComponent(name), decodeURIComponent(value));
- }
- });
- return form;
- }
+ return Emitter;
+ }();
- function parseHeaders(rawHeaders) {
- var headers = new Headers(); // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
- // https://tools.ietf.org/html/rfc7230#section-3.2
+ var AbortSignal = /*#__PURE__*/function (_Emitter) {
+ _inherits(AbortSignal, _Emitter);
- var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill
- // https://github.com/github/fetch/issues/748
- // https://github.com/zloirock/core-js/issues/751
+ var _super = _createSuper(AbortSignal);
- preProcessedHeaders.split('\r').map(function (header) {
- return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header;
- }).forEach(function (line) {
- var parts = line.split(':');
- var key = parts.shift().trim();
+ function AbortSignal() {
+ var _this;
- if (key) {
- var value = parts.join(':').trim();
- headers.append(key, value);
- }
- });
- return headers;
- }
+ _classCallCheck(this, AbortSignal);
- Body.call(Request.prototype);
- function Response(bodyInit, options) {
- if (!(this instanceof Response)) {
- throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
- }
+ _this = _super.call(this); // Some versions of babel does not transpile super() correctly for IE <= 10, if the parent
+ // constructor has failed to run, then "this.listeners" will still be undefined and then we call
+ // the parent constructor directly instead as a workaround. For general details, see babel bug:
+ // https://github.com/babel/babel/issues/3041
+ // This hack was added as a fix for the issue described here:
+ // https://github.com/Financial-Times/polyfill-library/pull/59#issuecomment-477558042
- if (!options) {
- options = {};
- }
+ if (!_this.listeners) {
+ Emitter.call(_assertThisInitialized(_this));
+ } // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
+ // we want Object.keys(new AbortController().signal) to be [] for compat with the native impl
- this.type = 'default';
- this.status = options.status === undefined ? 200 : options.status;
- this.ok = this.status >= 200 && this.status < 300;
- this.statusText = options.statusText === undefined ? '' : '' + options.statusText;
- this.headers = new Headers(options.headers);
- this.url = options.url || '';
- this._initBody(bodyInit);
- }
- Body.call(Response.prototype);
+ Object.defineProperty(_assertThisInitialized(_this), 'aborted', {
+ value: false,
+ writable: true,
+ configurable: true
+ });
+ Object.defineProperty(_assertThisInitialized(_this), 'onabort', {
+ value: null,
+ writable: true,
+ configurable: true
+ });
+ return _this;
+ }
- Response.prototype.clone = function () {
- return new Response(this._bodyInit, {
- status: this.status,
- statusText: this.statusText,
- headers: new Headers(this.headers),
- url: this.url
- });
- };
+ _createClass(AbortSignal, [{
+ key: "toString",
+ value: function toString() {
+ return '[object AbortSignal]';
+ }
+ }, {
+ key: "dispatchEvent",
+ value: function dispatchEvent(event) {
+ if (event.type === 'abort') {
+ this.aborted = true;
- Response.error = function () {
- var response = new Response(null, {
- status: 0,
- statusText: ''
- });
- response.type = 'error';
- return response;
- };
+ if (typeof this.onabort === 'function') {
+ this.onabort.call(this, event);
+ }
+ }
- var redirectStatuses = [301, 302, 303, 307, 308];
+ _get(_getPrototypeOf(AbortSignal.prototype), "dispatchEvent", this).call(this, event);
+ }
+ }]);
- Response.redirect = function (url, status) {
- if (redirectStatuses.indexOf(status) === -1) {
- throw new RangeError('Invalid status code');
- }
+ return AbortSignal;
+ }(Emitter);
- return new Response(null, {
- status: status,
- headers: {
- location: url
- }
- });
- };
+ var AbortController = /*#__PURE__*/function () {
+ function AbortController() {
+ _classCallCheck(this, AbortController); // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
+ // we want Object.keys(new AbortController()) to be [] for compat with the native impl
- var DOMException$1 = global$8.DOMException;
- try {
- new DOMException$1();
- } catch (err) {
- DOMException$1 = function DOMException(message, name) {
- this.message = message;
- this.name = name;
- var error = Error(message);
- this.stack = error.stack;
- };
+ Object.defineProperty(this, 'signal', {
+ value: new AbortSignal(),
+ writable: true,
+ configurable: true
+ });
+ }
- DOMException$1.prototype = Object.create(Error.prototype);
- DOMException$1.prototype.constructor = DOMException$1;
- }
+ _createClass(AbortController, [{
+ key: "abort",
+ value: function abort() {
+ var event;
- function fetch$1(input, init) {
- return new Promise(function (resolve, reject) {
- var request = new Request(input, init);
+ try {
+ event = new Event('abort');
+ } catch (e) {
+ if (typeof document !== 'undefined') {
+ if (!document.createEvent) {
+ // For Internet Explorer 8:
+ event = document.createEventObject();
+ event.type = 'abort';
+ } else {
+ // For Internet Explorer 11:
+ event = document.createEvent('Event');
+ event.initEvent('abort', false, false);
+ }
+ } else {
+ // Fallback where document isn't available:
+ event = {
+ type: 'abort',
+ bubbles: false,
+ cancelable: false
+ };
+ }
+ }
- if (request.signal && request.signal.aborted) {
- return reject(new DOMException$1('Aborted', 'AbortError'));
- }
+ this.signal.dispatchEvent(event);
+ }
+ }, {
+ key: "toString",
+ value: function toString() {
+ return '[object AbortController]';
+ }
+ }]);
- var xhr = new XMLHttpRequest();
+ return AbortController;
+ }();
- function abortXhr() {
- xhr.abort();
- }
+ if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+ // These are necessary to make sure that we get correct output for:
+ // Object.prototype.toString.call(new AbortController())
+ AbortController.prototype[Symbol.toStringTag] = 'AbortController';
+ AbortSignal.prototype[Symbol.toStringTag] = 'AbortSignal';
+ }
- xhr.onload = function () {
- var options = {
- status: xhr.status,
- statusText: xhr.statusText,
- headers: parseHeaders(xhr.getAllResponseHeaders() || '')
- };
- options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
- var body = 'response' in xhr ? xhr.response : xhr.responseText;
- setTimeout(function () {
- resolve(new Response(body, options));
- }, 0);
- };
+ function polyfillNeeded(self) {
+ if (self.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
+ console.log('__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL=true is set, will force install polyfill');
+ return true;
+ } // Note that the "unfetch" minimal fetch polyfill defines fetch() without
+ // defining window.Request, and this polyfill need to work on top of unfetch
+ // so the below feature detection needs the !self.AbortController part.
+ // The Request.prototype check is also needed because Safari versions 11.1.2
+ // up to and including 12.1.x has a window.AbortController present but still
+ // does NOT correctly implement abortable fetch:
+ // https://bugs.webkit.org/show_bug.cgi?id=174980#c2
- xhr.onerror = function () {
- setTimeout(function () {
- reject(new TypeError('Network request failed'));
- }, 0);
- };
- xhr.ontimeout = function () {
- setTimeout(function () {
- reject(new TypeError('Network request failed'));
- }, 0);
- };
+ return typeof self.Request === 'function' && !self.Request.prototype.hasOwnProperty('signal') || !self.AbortController;
+ }
+ /**
+ * Note: the "fetch.Request" default value is available for fetch imported from
+ * the "node-fetch" package and not in browsers. This is OK since browsers
+ * will be importing umd-polyfill.js from that path "self" is passed the
+ * decorator so the default value will not be used (because browsers that define
+ * fetch also has Request). One quirky setup where self.fetch exists but
+ * self.Request does not is when the "unfetch" minimal fetch polyfill is used
+ * on top of IE11; for this case the browser will try to use the fetch.Request
+ * default value which in turn will be undefined but then then "if (Request)"
+ * will ensure that you get a patched fetch but still no Request (as expected).
+ * @param {fetch, Request = fetch.Request}
+ * @returns {fetch: abortableFetch, Request: AbortableRequest}
+ */
- xhr.onabort = function () {
- setTimeout(function () {
- reject(new DOMException$1('Aborted', 'AbortError'));
- }, 0);
- };
- function fixUrl(url) {
- try {
- return url === '' && global$8.location.href ? global$8.location.href : url;
- } catch (e) {
- return url;
- }
+ function abortableFetchDecorator(patchTargets) {
+ if ('function' === typeof patchTargets) {
+ patchTargets = {
+ fetch: patchTargets
+ };
}
- xhr.open(request.method, fixUrl(request.url), true);
+ var _patchTargets = patchTargets,
+ fetch = _patchTargets.fetch,
+ _patchTargets$Request = _patchTargets.Request,
+ NativeRequest = _patchTargets$Request === void 0 ? fetch.Request : _patchTargets$Request,
+ NativeAbortController = _patchTargets.AbortController,
+ _patchTargets$__FORCE = _patchTargets.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL,
+ __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL = _patchTargets$__FORCE === void 0 ? false : _patchTargets$__FORCE;
- if (request.credentials === 'include') {
- xhr.withCredentials = true;
- } else if (request.credentials === 'omit') {
- xhr.withCredentials = false;
+ if (!polyfillNeeded({
+ fetch: fetch,
+ Request: NativeRequest,
+ AbortController: NativeAbortController,
+ __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL: __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL
+ })) {
+ return {
+ fetch: fetch,
+ Request: Request
+ };
}
- if ('responseType' in xhr) {
- if (support.blob) {
- xhr.responseType = 'blob';
- } else if (support.arrayBuffer && request.headers.get('Content-Type') && request.headers.get('Content-Type').indexOf('application/octet-stream') !== -1) {
- xhr.responseType = 'arraybuffer';
- }
- }
+ var Request = NativeRequest; // Note that the "unfetch" minimal fetch polyfill defines fetch() without
+ // defining window.Request, and this polyfill need to work on top of unfetch
+ // hence we only patch it if it's available. Also we don't patch it if signal
+ // is already available on the Request prototype because in this case support
+ // is present and the patching below can cause a crash since it assigns to
+ // request.signal which is technically a read-only property. This latter error
+ // happens when you run the main5.js node-fetch example in the repo
+ // "abortcontroller-polyfill-examples". The exact error is:
+ // request.signal = init.signal;
+ // ^
+ // TypeError: Cannot set property signal of #<Request> which has only a getter
- if (init && _typeof(init.headers) === 'object' && !(init.headers instanceof Headers)) {
- Object.getOwnPropertyNames(init.headers).forEach(function (name) {
- xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
- });
- } else {
- request.headers.forEach(function (value, name) {
- xhr.setRequestHeader(name, value);
- });
- }
+ if (Request && !Request.prototype.hasOwnProperty('signal') || __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
+ Request = function Request(input, init) {
+ var signal;
- if (request.signal) {
- request.signal.addEventListener('abort', abortXhr);
+ if (init && init.signal) {
+ signal = init.signal; // Never pass init.signal to the native Request implementation when the polyfill has
+ // been installed because if we're running on top of a browser with a
+ // working native AbortController (i.e. the polyfill was installed due to
+ // __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL being set), then passing our
+ // fake AbortSignal to the native fetch will trigger:
+ // TypeError: Failed to construct 'Request': member signal is not of type AbortSignal.
- xhr.onreadystatechange = function () {
- // DONE (success or failure)
- if (xhr.readyState === 4) {
- request.signal.removeEventListener('abort', abortXhr);
+ delete init.signal;
}
- };
- }
- xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);
- });
- }
- fetch$1.polyfill = true;
+ var request = new NativeRequest(input, init);
- if (!global$8.fetch) {
- global$8.fetch = fetch$1;
- global$8.Headers = Headers;
- global$8.Request = Request;
- global$8.Response = Response;
- }
+ if (signal) {
+ Object.defineProperty(request, 'signal', {
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ value: signal
+ });
+ }
- var $$L = _export;
- var DESCRIPTORS$7 = descriptors;
- var objectDefinePropertyModile = objectDefineProperty;
+ return request;
+ };
- // `Object.defineProperty` method
- // https://tc39.es/ecma262/#sec-object.defineproperty
- $$L({ target: 'Object', stat: true, forced: !DESCRIPTORS$7, sham: !DESCRIPTORS$7 }, {
- defineProperty: objectDefinePropertyModile.f
- });
+ Request.prototype = NativeRequest.prototype;
+ }
- var $$K = _export;
- var setPrototypeOf = objectSetPrototypeOf;
+ var realFetch = fetch;
- // `Object.setPrototypeOf` method
- // https://tc39.es/ecma262/#sec-object.setprototypeof
- $$K({ target: 'Object', stat: true }, {
- setPrototypeOf: setPrototypeOf
- });
+ var abortableFetch = function abortableFetch(input, init) {
+ var signal = Request && Request.prototype.isPrototypeOf(input) ? input.signal : init ? init.signal : undefined;
- var $$J = _export;
- var fails$i = fails$N;
- var toObject$5 = toObject$i;
- var nativeGetPrototypeOf = objectGetPrototypeOf;
- var CORRECT_PROTOTYPE_GETTER = correctPrototypeGetter;
+ if (signal) {
+ var abortError;
- var FAILS_ON_PRIMITIVES$3 = fails$i(function () { nativeGetPrototypeOf(1); });
+ try {
+ abortError = new DOMException('Aborted', 'AbortError');
+ } catch (err) {
+ // IE 11 does not support calling the DOMException constructor, use a
+ // regular error object on it instead.
+ abortError = new Error('Aborted');
+ abortError.name = 'AbortError';
+ } // Return early if already aborted, thus avoiding making an HTTP request
- // `Object.getPrototypeOf` method
- // https://tc39.es/ecma262/#sec-object.getprototypeof
- $$J({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$3, sham: !CORRECT_PROTOTYPE_GETTER }, {
- getPrototypeOf: function getPrototypeOf(it) {
- return nativeGetPrototypeOf(toObject$5(it));
- }
- });
- var aFunction$2 = aFunction$9;
- var isObject$a = isObject$r;
+ if (signal.aborted) {
+ return Promise.reject(abortError);
+ } // Turn an event into a promise, reject it once `abort` is dispatched
- var slice$2 = [].slice;
- var factories = {};
- var construct = function (C, argsLength, args) {
- if (!(argsLength in factories)) {
- for (var list = [], i = 0; i < argsLength; i++) list[i] = 'a[' + i + ']';
- // eslint-disable-next-line no-new-func -- we have no proper alternatives, IE8- only
- factories[argsLength] = Function('C,a', 'return new C(' + list.join(',') + ')');
- } return factories[argsLength](C, args);
- };
+ var cancellation = new Promise(function (_, reject) {
+ signal.addEventListener('abort', function () {
+ return reject(abortError);
+ }, {
+ once: true
+ });
+ });
- // `Function.prototype.bind` method implementation
- // https://tc39.es/ecma262/#sec-function.prototype.bind
- var functionBind = Function.bind || function bind(that /* , ...args */) {
- var fn = aFunction$2(this);
- var partArgs = slice$2.call(arguments, 1);
- var boundFunction = function bound(/* args... */) {
- var args = partArgs.concat(slice$2.call(arguments));
- return this instanceof boundFunction ? construct(fn, args.length, args) : fn.apply(that, args);
- };
- if (isObject$a(fn.prototype)) boundFunction.prototype = fn.prototype;
- return boundFunction;
- };
+ if (init && init.signal) {
+ // Never pass .signal to the native implementation when the polyfill has
+ // been installed because if we're running on top of a browser with a
+ // working native AbortController (i.e. the polyfill was installed due to
+ // __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL being set), then passing our
+ // fake AbortSignal to the native fetch will trigger:
+ // TypeError: Failed to execute 'fetch' on 'Window': member signal is not of type AbortSignal.
+ delete init.signal;
+ } // Return the fastest promise (don't need to wait for request to finish)
- var $$I = _export;
- var getBuiltIn$1 = getBuiltIn$9;
- var aFunction$1 = aFunction$9;
- var anObject$3 = anObject$m;
- var isObject$9 = isObject$r;
- var create$5 = objectCreate;
- var bind$4 = functionBind;
- var fails$h = fails$N;
- var nativeConstruct = getBuiltIn$1('Reflect', 'construct');
+ return Promise.race([cancellation, realFetch(input, init)]);
+ }
- // `Reflect.construct` method
- // https://tc39.es/ecma262/#sec-reflect.construct
- // MS Edge supports only 2 arguments and argumentsList argument is optional
- // FF Nightly sets third argument as `new.target`, but does not create `this` from it
- var NEW_TARGET_BUG = fails$h(function () {
- function F() { /* empty */ }
- return !(nativeConstruct(function () { /* empty */ }, [], F) instanceof F);
- });
- var ARGS_BUG = !fails$h(function () {
- nativeConstruct(function () { /* empty */ });
- });
- var FORCED$a = NEW_TARGET_BUG || ARGS_BUG;
+ return realFetch(input, init);
+ };
- $$I({ target: 'Reflect', stat: true, forced: FORCED$a, sham: FORCED$a }, {
- construct: function construct(Target, args /* , newTarget */) {
- aFunction$1(Target);
- anObject$3(args);
- var newTarget = arguments.length < 3 ? Target : aFunction$1(arguments[2]);
- if (ARGS_BUG && !NEW_TARGET_BUG) return nativeConstruct(Target, args, newTarget);
- if (Target == newTarget) {
- // w/o altered newTarget, optimization for 0-4 arguments
- switch (args.length) {
- case 0: return new Target();
- case 1: return new Target(args[0]);
- case 2: return new Target(args[0], args[1]);
- case 3: return new Target(args[0], args[1], args[2]);
- case 4: return new Target(args[0], args[1], args[2], args[3]);
- }
- // w/o altered newTarget, lot of arguments case
- var $args = [null];
- $args.push.apply($args, args);
- return new (bind$4.apply(Target, $args))();
- }
- // with altered newTarget, not support built-in constructors
- var proto = newTarget.prototype;
- var instance = create$5(isObject$9(proto) ? proto : Object.prototype);
- var result = Function.apply.call(Target, instance, args);
- return isObject$9(result) ? result : instance;
+ return {
+ fetch: abortableFetch,
+ Request: Request
+ };
}
- });
- var $$H = _export;
- var isObject$8 = isObject$r;
- var anObject$2 = anObject$m;
- var has$3 = has$j;
- var getOwnPropertyDescriptorModule = objectGetOwnPropertyDescriptor;
- var getPrototypeOf = objectGetPrototypeOf;
+ (function (self) {
+ if (!polyfillNeeded(self)) {
+ return;
+ }
- // `Reflect.get` method
- // https://tc39.es/ecma262/#sec-reflect.get
- function get$3(target, propertyKey /* , receiver */) {
- var receiver = arguments.length < 3 ? target : arguments[2];
- var descriptor, prototype;
- if (anObject$2(target) === receiver) return target[propertyKey];
- if (descriptor = getOwnPropertyDescriptorModule.f(target, propertyKey)) return has$3(descriptor, 'value')
- ? descriptor.value
- : descriptor.get === undefined
- ? undefined
- : descriptor.get.call(receiver);
- if (isObject$8(prototype = getPrototypeOf(target))) return get$3(prototype, propertyKey, receiver);
- }
+ if (!self.fetch) {
+ console.warn('fetch() is not available, cannot install abortcontroller-polyfill');
+ return;
+ }
- $$H({ target: 'Reflect', stat: true }, {
- get: get$3
+ var _abortableFetch = abortableFetchDecorator(self),
+ fetch = _abortableFetch.fetch,
+ Request = _abortableFetch.Request;
+
+ self.fetch = fetch;
+ self.Request = Request;
+ Object.defineProperty(self, 'AbortController', {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ value: AbortController
+ });
+ Object.defineProperty(self, 'AbortSignal', {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ value: AbortSignal
+ });
+ })(typeof self !== 'undefined' ? self : commonjsGlobal);
});
- var $$G = _export;
- var fails$g = fails$N;
- var toIndexedObject$1 = toIndexedObject$b;
- var nativeGetOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
- var DESCRIPTORS$6 = descriptors;
+ function actionAddEntity(way) {
+ return function (graph) {
+ return graph.replace(way);
+ };
+ }
+
+ var $$M = _export;
+ var global$g = global$1m;
+ var fails$k = fails$S;
+ var isArray$3 = isArray$8;
+ var isObject$8 = isObject$s;
+ var toObject$6 = toObject$j;
+ var lengthOfArrayLike$4 = lengthOfArrayLike$g;
+ var createProperty$1 = createProperty$4;
+ var arraySpeciesCreate$1 = arraySpeciesCreate$4;
+ var arrayMethodHasSpeciesSupport$1 = arrayMethodHasSpeciesSupport$5;
+ var wellKnownSymbol$2 = wellKnownSymbol$t;
+ var V8_VERSION = engineV8Version;
- var FAILS_ON_PRIMITIVES$2 = fails$g(function () { nativeGetOwnPropertyDescriptor(1); });
- var FORCED$9 = !DESCRIPTORS$6 || FAILS_ON_PRIMITIVES$2;
+ var IS_CONCAT_SPREADABLE = wellKnownSymbol$2('isConcatSpreadable');
+ var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF;
+ var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded';
+ var TypeError$5 = global$g.TypeError;
- // `Object.getOwnPropertyDescriptor` method
- // https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
- $$G({ target: 'Object', stat: true, forced: FORCED$9, sham: !DESCRIPTORS$6 }, {
- getOwnPropertyDescriptor: function getOwnPropertyDescriptor(it, key) {
- return nativeGetOwnPropertyDescriptor(toIndexedObject$1(it), key);
- }
+ // We can't use this feature detection in V8 since it causes
+ // deoptimization and serious performance degradation
+ // https://github.com/zloirock/core-js/issues/679
+ var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails$k(function () {
+ var array = [];
+ array[IS_CONCAT_SPREADABLE] = false;
+ return array.concat()[0] !== array;
});
- var $$F = _export;
- var toAbsoluteIndex$1 = toAbsoluteIndex$8;
- var toInteger$2 = toInteger$b;
- var toLength$6 = toLength$q;
- var toObject$4 = toObject$i;
- var arraySpeciesCreate$1 = arraySpeciesCreate$3;
- var createProperty$1 = createProperty$4;
- var arrayMethodHasSpeciesSupport$2 = arrayMethodHasSpeciesSupport$5;
+ var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport$1('concat');
- var HAS_SPECIES_SUPPORT$1 = arrayMethodHasSpeciesSupport$2('splice');
+ var isConcatSpreadable = function (O) {
+ if (!isObject$8(O)) return false;
+ var spreadable = O[IS_CONCAT_SPREADABLE];
+ return spreadable !== undefined ? !!spreadable : isArray$3(O);
+ };
- var max$1 = Math.max;
- var min$3 = Math.min;
- var MAX_SAFE_INTEGER$1 = 0x1FFFFFFFFFFFFF;
- var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded';
+ var FORCED$a = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT;
- // `Array.prototype.splice` method
- // https://tc39.es/ecma262/#sec-array.prototype.splice
- // with adding support of @@species
- $$F({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$1 }, {
- splice: function splice(start, deleteCount /* , ...items */) {
- var O = toObject$4(this);
- var len = toLength$6(O.length);
- var actualStart = toAbsoluteIndex$1(start, len);
- var argumentsLength = arguments.length;
- var insertCount, actualDeleteCount, A, k, from, to;
- if (argumentsLength === 0) {
- insertCount = actualDeleteCount = 0;
- } else if (argumentsLength === 1) {
- insertCount = 0;
- actualDeleteCount = len - actualStart;
- } else {
- insertCount = argumentsLength - 2;
- actualDeleteCount = min$3(max$1(toInteger$2(deleteCount), 0), len - actualStart);
- }
- if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER$1) {
- throw TypeError(MAXIMUM_ALLOWED_LENGTH_EXCEEDED);
- }
- A = arraySpeciesCreate$1(O, actualDeleteCount);
- for (k = 0; k < actualDeleteCount; k++) {
- from = actualStart + k;
- if (from in O) createProperty$1(A, k, O[from]);
- }
- A.length = actualDeleteCount;
- if (insertCount < actualDeleteCount) {
- for (k = actualStart; k < len - actualDeleteCount; k++) {
- from = k + actualDeleteCount;
- to = k + insertCount;
- if (from in O) O[to] = O[from];
- else delete O[to];
- }
- for (k = len; k > len - actualDeleteCount + insertCount; k--) delete O[k - 1];
- } else if (insertCount > actualDeleteCount) {
- for (k = len - actualDeleteCount; k > actualStart; k--) {
- from = k + actualDeleteCount - 1;
- to = k + insertCount - 1;
- if (from in O) O[to] = O[from];
- else delete O[to];
+ // `Array.prototype.concat` method
+ // https://tc39.es/ecma262/#sec-array.prototype.concat
+ // with adding support of @@isConcatSpreadable and @@species
+ $$M({ target: 'Array', proto: true, forced: FORCED$a }, {
+ // eslint-disable-next-line no-unused-vars -- required for `.length`
+ concat: function concat(arg) {
+ var O = toObject$6(this);
+ var A = arraySpeciesCreate$1(O, 0);
+ var n = 0;
+ var i, k, length, len, E;
+ for (i = -1, length = arguments.length; i < length; i++) {
+ E = i === -1 ? O : arguments[i];
+ if (isConcatSpreadable(E)) {
+ len = lengthOfArrayLike$4(E);
+ if (n + len > MAX_SAFE_INTEGER) throw TypeError$5(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
+ for (k = 0; k < len; k++, n++) if (k in E) createProperty$1(A, n, E[k]);
+ } else {
+ if (n >= MAX_SAFE_INTEGER) throw TypeError$5(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
+ createProperty$1(A, n++, E);
}
}
- for (k = 0; k < insertCount; k++) {
- O[k + actualStart] = arguments[k + 2];
- }
- O.length = len - actualDeleteCount + insertCount;
+ A.length = n;
return A;
}
});
- var defineWellKnownSymbol$1 = defineWellKnownSymbol$4;
-
- // `Symbol.toStringTag` well-known symbol
- // https://tc39.es/ecma262/#sec-symbol.tostringtag
- defineWellKnownSymbol$1('toStringTag');
+ var DESCRIPTORS$7 = descriptors;
+ var uncurryThis$l = functionUncurryThis;
+ var call$5 = functionCall;
+ var fails$j = fails$S;
+ var objectKeys$1 = objectKeys$4;
+ var getOwnPropertySymbolsModule = objectGetOwnPropertySymbols;
+ var propertyIsEnumerableModule = objectPropertyIsEnumerable;
+ var toObject$5 = toObject$j;
+ var IndexedObject = indexedObject;
- var global$7 = global$F;
- var setToStringTag$2 = setToStringTag$a;
+ // eslint-disable-next-line es/no-object-assign -- safe
+ var $assign = Object.assign;
+ // eslint-disable-next-line es/no-object-defineproperty -- required for testing
+ var defineProperty$4 = Object.defineProperty;
+ var concat = uncurryThis$l([].concat);
- // JSON[@@toStringTag] property
- // https://tc39.es/ecma262/#sec-json-@@tostringtag
- setToStringTag$2(global$7.JSON, 'JSON', true);
+ // `Object.assign` method
+ // https://tc39.es/ecma262/#sec-object.assign
+ var objectAssign = !$assign || fails$j(function () {
+ // should have correct order of operations (Edge bug)
+ if (DESCRIPTORS$7 && $assign({ b: 1 }, $assign(defineProperty$4({}, 'a', {
+ enumerable: true,
+ get: function () {
+ defineProperty$4(this, 'b', {
+ value: 3,
+ enumerable: false
+ });
+ }
+ }), { b: 2 })).b !== 1) return true;
+ // should work with symbols and should have deterministic property order (V8 bug)
+ var A = {};
+ var B = {};
+ // eslint-disable-next-line es/no-symbol -- safe
+ var symbol = Symbol();
+ var alphabet = 'abcdefghijklmnopqrst';
+ A[symbol] = 7;
+ alphabet.split('').forEach(function (chr) { B[chr] = chr; });
+ return $assign({}, A)[symbol] != 7 || objectKeys$1($assign({}, B)).join('') != alphabet;
+ }) ? function assign(target, source) { // eslint-disable-line no-unused-vars -- required for `.length`
+ var T = toObject$5(target);
+ var argumentsLength = arguments.length;
+ var index = 1;
+ var getOwnPropertySymbols = getOwnPropertySymbolsModule.f;
+ var propertyIsEnumerable = propertyIsEnumerableModule.f;
+ while (argumentsLength > index) {
+ var S = IndexedObject(arguments[index++]);
+ var keys = getOwnPropertySymbols ? concat(objectKeys$1(S), getOwnPropertySymbols(S)) : objectKeys$1(S);
+ var length = keys.length;
+ var j = 0;
+ var key;
+ while (length > j) {
+ key = keys[j++];
+ if (!DESCRIPTORS$7 || call$5(propertyIsEnumerable, S, key)) T[key] = S[key];
+ }
+ } return T;
+ } : $assign;
- var setToStringTag$1 = setToStringTag$a;
+ var $$L = _export;
+ var assign$2 = objectAssign;
- // Math[@@toStringTag] property
- // https://tc39.es/ecma262/#sec-math-@@tostringtag
- setToStringTag$1(Math, 'Math', true);
+ // `Object.assign` method
+ // https://tc39.es/ecma262/#sec-object.assign
+ // eslint-disable-next-line es/no-object-assign -- required for testing
+ $$L({ target: 'Object', stat: true, forced: Object.assign !== assign$2 }, {
+ assign: assign$2
+ });
- (function (factory) {
- factory();
- })(function () {
+ var $$K = _export;
+ var $filter = arrayIteration.filter;
+ var arrayMethodHasSpeciesSupport = arrayMethodHasSpeciesSupport$5;
- function _classCallCheck(instance, Constructor) {
- if (!(instance instanceof Constructor)) {
- throw new TypeError("Cannot call a class as a function");
- }
- }
+ var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('filter');
- function _defineProperties(target, props) {
- for (var i = 0; i < props.length; i++) {
- var descriptor = props[i];
- descriptor.enumerable = descriptor.enumerable || false;
- descriptor.configurable = true;
- if ("value" in descriptor) descriptor.writable = true;
- Object.defineProperty(target, descriptor.key, descriptor);
- }
+ // `Array.prototype.filter` method
+ // https://tc39.es/ecma262/#sec-array.prototype.filter
+ // with adding support of @@species
+ $$K({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, {
+ filter: function filter(callbackfn /* , thisArg */) {
+ return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
+ });
- function _createClass(Constructor, protoProps, staticProps) {
- if (protoProps) _defineProperties(Constructor.prototype, protoProps);
- if (staticProps) _defineProperties(Constructor, staticProps);
- return Constructor;
- }
+ var $$J = _export;
+ var toObject$4 = toObject$j;
+ var nativeKeys = objectKeys$4;
+ var fails$i = fails$S;
- function _inherits(subClass, superClass) {
- if (typeof superClass !== "function" && superClass !== null) {
- throw new TypeError("Super expression must either be null or a function");
- }
+ var FAILS_ON_PRIMITIVES$2 = fails$i(function () { nativeKeys(1); });
- subClass.prototype = Object.create(superClass && superClass.prototype, {
- constructor: {
- value: subClass,
- writable: true,
- configurable: true
- }
- });
- if (superClass) _setPrototypeOf(subClass, superClass);
+ // `Object.keys` method
+ // https://tc39.es/ecma262/#sec-object.keys
+ $$J({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$2 }, {
+ keys: function keys(it) {
+ return nativeKeys(toObject$4(it));
}
+ });
- function _getPrototypeOf(o) {
- _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
- return o.__proto__ || Object.getPrototypeOf(o);
- };
- return _getPrototypeOf(o);
- }
+ var $$I = _export;
+ var uncurryThis$k = functionUncurryThis;
+ var isArray$2 = isArray$8;
- function _setPrototypeOf(o, p) {
- _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
- o.__proto__ = p;
- return o;
- };
+ var un$Reverse = uncurryThis$k([].reverse);
+ var test$1 = [1, 2];
- return _setPrototypeOf(o, p);
+ // `Array.prototype.reverse` method
+ // https://tc39.es/ecma262/#sec-array.prototype.reverse
+ // fix for Safari 12.0 bug
+ // https://bugs.webkit.org/show_bug.cgi?id=188794
+ $$I({ target: 'Array', proto: true, forced: String(test$1) === String(test$1.reverse()) }, {
+ reverse: function reverse() {
+ // eslint-disable-next-line no-self-assign -- dirty hack
+ if (isArray$2(this)) this.length = this.length;
+ return un$Reverse(this);
}
+ });
- function _isNativeReflectConstruct() {
- if (typeof Reflect === "undefined" || !Reflect.construct) return false;
- if (Reflect.construct.sham) return false;
- if (typeof Proxy === "function") return true;
+ var global$f = global$1m;
+ var fails$h = fails$S;
+ var uncurryThis$j = functionUncurryThis;
+ var toString$c = toString$k;
+ var trim$4 = stringTrim.trim;
+ var whitespaces$1 = whitespaces$4;
- try {
- Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
- return true;
- } catch (e) {
- return false;
- }
- }
+ var charAt$2 = uncurryThis$j(''.charAt);
+ var n$ParseFloat = global$f.parseFloat;
+ var Symbol$2 = global$f.Symbol;
+ var ITERATOR$1 = Symbol$2 && Symbol$2.iterator;
+ var FORCED$9 = 1 / n$ParseFloat(whitespaces$1 + '-0') !== -Infinity
+ // MS Edge 18- broken with boxed symbols
+ || (ITERATOR$1 && !fails$h(function () { n$ParseFloat(Object(ITERATOR$1)); }));
- function _assertThisInitialized(self) {
- if (self === void 0) {
- throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
- }
+ // `parseFloat` method
+ // https://tc39.es/ecma262/#sec-parsefloat-string
+ var numberParseFloat = FORCED$9 ? function parseFloat(string) {
+ var trimmedString = trim$4(toString$c(string));
+ var result = n$ParseFloat(trimmedString);
+ return result === 0 && charAt$2(trimmedString, 0) == '-' ? -0 : result;
+ } : n$ParseFloat;
- return self;
- }
+ var $$H = _export;
+ var $parseFloat = numberParseFloat;
- function _possibleConstructorReturn(self, call) {
- if (call && (_typeof(call) === "object" || typeof call === "function")) {
- return call;
- }
+ // `parseFloat` method
+ // https://tc39.es/ecma262/#sec-parsefloat-string
+ $$H({ global: true, forced: parseFloat != $parseFloat }, {
+ parseFloat: $parseFloat
+ });
- return _assertThisInitialized(self);
- }
+ /*
+ Order the nodes of a way in reverse order and reverse any direction dependent tags
+ other than `oneway`. (We assume that correcting a backwards oneway is the primary
+ reason for reversing a way.)
- function _createSuper(Derived) {
- var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ In addition, numeric-valued `incline` tags are negated.
- return function _createSuperInternal() {
- var Super = _getPrototypeOf(Derived),
- result;
+ The JOSM implementation was used as a guide, but transformations that were of unclear benefit
+ or adjusted tags that don't seem to be used in practice were omitted.
- if (hasNativeReflectConstruct) {
- var NewTarget = _getPrototypeOf(this).constructor;
+ References:
+ http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right
+ http://wiki.openstreetmap.org/wiki/Key:direction#Steps
+ http://wiki.openstreetmap.org/wiki/Key:incline
+ http://wiki.openstreetmap.org/wiki/Route#Members
+ http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
+ http://wiki.openstreetmap.org/wiki/Tag:highway%3Dstop
+ http://wiki.openstreetmap.org/wiki/Key:traffic_sign#On_a_way_or_area
+ */
+ function actionReverse(entityID, options) {
+ var ignoreKey = /^.*(_|:)?(description|name|note|website|ref|source|comment|watch|attribution)(_|:)?/;
+ var numeric = /^([+\-]?)(?=[\d.])/;
+ var directionKey = /direction$/;
+ var turn_lanes = /^turn:lanes:?/;
+ var keyReplacements = [[/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'], [/:right:/, ':left:'], [/:left:/, ':right:'], [/:forward:/, ':backward:'], [/:backward:/, ':forward:']];
+ var valueReplacements = {
+ left: 'right',
+ right: 'left',
+ up: 'down',
+ down: 'up',
+ forward: 'backward',
+ backward: 'forward',
+ forwards: 'backward',
+ backwards: 'forward'
+ };
+ var roleReplacements = {
+ forward: 'backward',
+ backward: 'forward',
+ forwards: 'backward',
+ backwards: 'forward'
+ };
+ var onewayReplacements = {
+ yes: '-1',
+ '1': '-1',
+ '-1': 'yes'
+ };
+ var compassReplacements = {
+ N: 'S',
+ NNE: 'SSW',
+ NE: 'SW',
+ ENE: 'WSW',
+ E: 'W',
+ ESE: 'WNW',
+ SE: 'NW',
+ SSE: 'NNW',
+ S: 'N',
+ SSW: 'NNE',
+ SW: 'NE',
+ WSW: 'ENE',
+ W: 'E',
+ WNW: 'ESE',
+ NW: 'SE',
+ NNW: 'SSE'
+ };
- result = Reflect.construct(Super, arguments, NewTarget);
- } else {
- result = Super.apply(this, arguments);
+ function reverseKey(key) {
+ for (var i = 0; i < keyReplacements.length; ++i) {
+ var replacement = keyReplacements[i];
+
+ if (replacement[0].test(key)) {
+ return key.replace(replacement[0], replacement[1]);
}
+ }
- return _possibleConstructorReturn(this, result);
- };
+ return key;
}
- function _superPropBase(object, property) {
- while (!Object.prototype.hasOwnProperty.call(object, property)) {
- object = _getPrototypeOf(object);
- if (object === null) break;
+ function reverseValue(key, value, includeAbsolute) {
+ if (ignoreKey.test(key)) return value; // Turn lanes are left/right to key (not way) direction - #5674
+
+ if (turn_lanes.test(key)) {
+ return value;
+ } else if (key === 'incline' && numeric.test(value)) {
+ return value.replace(numeric, function (_, sign) {
+ return sign === '-' ? '' : '-';
+ });
+ } else if (options && options.reverseOneway && key === 'oneway') {
+ return onewayReplacements[value] || value;
+ } else if (includeAbsolute && directionKey.test(key)) {
+ if (compassReplacements[value]) return compassReplacements[value];
+ var degrees = parseFloat(value);
+
+ if (typeof degrees === 'number' && !isNaN(degrees)) {
+ if (degrees < 180) {
+ degrees += 180;
+ } else {
+ degrees -= 180;
+ }
+
+ return degrees.toString();
+ }
}
- return object;
- }
+ return valueReplacements[value] || value;
+ } // Reverse the direction of tags attached to the nodes - #3076
- function _get(target, property, receiver) {
- if (typeof Reflect !== "undefined" && Reflect.get) {
- _get = Reflect.get;
- } else {
- _get = function _get(target, property, receiver) {
- var base = _superPropBase(target, property);
- if (!base) return;
- var desc = Object.getOwnPropertyDescriptor(base, property);
+ function reverseNodeTags(graph, nodeIDs) {
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var node = graph.hasEntity(nodeIDs[i]);
+ if (!node || !Object.keys(node.tags).length) continue;
+ var tags = {};
- if (desc.get) {
- return desc.get.call(receiver);
- }
+ for (var key in node.tags) {
+ tags[reverseKey(key)] = reverseValue(key, node.tags[key], node.id === entityID);
+ }
- return desc.value;
- };
+ graph = graph.replace(node.update({
+ tags: tags
+ }));
}
- return _get(target, property, receiver || target);
+ return graph;
}
- var Emitter = /*#__PURE__*/function () {
- function Emitter() {
- _classCallCheck(this, Emitter);
+ function reverseWay(graph, way) {
+ var nodes = way.nodes.slice().reverse();
+ var tags = {};
+ var role;
- Object.defineProperty(this, 'listeners', {
- value: {},
- writable: true,
- configurable: true
- });
+ for (var key in way.tags) {
+ tags[reverseKey(key)] = reverseValue(key, way.tags[key]);
}
- _createClass(Emitter, [{
- key: "addEventListener",
- value: function addEventListener(type, callback, options) {
- if (!(type in this.listeners)) {
- this.listeners[type] = [];
+ graph.parentRelations(way).forEach(function (relation) {
+ relation.members.forEach(function (member, index) {
+ if (member.id === way.id && (role = roleReplacements[member.role])) {
+ relation = relation.updateMember({
+ role: role
+ }, index);
+ graph = graph.replace(relation);
}
+ });
+ }); // Reverse any associated directions on nodes on the way and then replace
+ // the way itself with the reversed node ids and updated way tags
- this.listeners[type].push({
- callback: callback,
- options: options
- });
- }
- }, {
- key: "removeEventListener",
- value: function removeEventListener(type, callback) {
- if (!(type in this.listeners)) {
- return;
- }
+ return reverseNodeTags(graph, nodes).replace(way.update({
+ nodes: nodes,
+ tags: tags
+ }));
+ }
- var stack = this.listeners[type];
+ var action = function action(graph) {
+ var entity = graph.entity(entityID);
- for (var i = 0, l = stack.length; i < l; i++) {
- if (stack[i].callback === callback) {
- stack.splice(i, 1);
- return;
- }
- }
- }
- }, {
- key: "dispatchEvent",
- value: function dispatchEvent(event) {
- if (!(event.type in this.listeners)) {
- return;
- }
-
- var stack = this.listeners[event.type];
- var stackToCall = stack.slice();
+ if (entity.type === 'way') {
+ return reverseWay(graph, entity);
+ }
- for (var i = 0, l = stackToCall.length; i < l; i++) {
- var listener = stackToCall[i];
+ return reverseNodeTags(graph, [entityID]);
+ };
- try {
- listener.callback.call(this, event);
- } catch (e) {
- Promise.resolve().then(function () {
- throw e;
- });
- }
+ action.disabled = function (graph) {
+ var entity = graph.hasEntity(entityID);
+ if (!entity || entity.type === 'way') return false;
- if (listener.options && listener.options.once) {
- this.removeEventListener(event.type, listener.callback);
- }
- }
+ for (var key in entity.tags) {
+ var value = entity.tags[key];
- return !event.defaultPrevented;
+ if (reverseKey(key) !== key || reverseValue(key, value, true) !== value) {
+ return false;
}
- }]);
-
- return Emitter;
- }();
-
- var AbortSignal = /*#__PURE__*/function (_Emitter) {
- _inherits(AbortSignal, _Emitter);
-
- var _super = _createSuper(AbortSignal);
+ }
- function AbortSignal() {
- var _this;
+ return 'nondirectional_node';
+ };
- _classCallCheck(this, AbortSignal);
+ action.entityID = function () {
+ return entityID;
+ };
- _this = _super.call(this); // Some versions of babel does not transpile super() correctly for IE <= 10, if the parent
- // constructor has failed to run, then "this.listeners" will still be undefined and then we call
- // the parent constructor directly instead as a workaround. For general details, see babel bug:
- // https://github.com/babel/babel/issues/3041
- // This hack was added as a fix for the issue described here:
- // https://github.com/Financial-Times/polyfill-library/pull/59#issuecomment-477558042
+ return action;
+ }
- if (!_this.listeners) {
- Emitter.call(_assertThisInitialized(_this));
- } // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
- // we want Object.keys(new AbortController().signal) to be [] for compat with the native impl
+ function osmIsInterestingTag(key) {
+ return key !== 'attribution' && key !== 'created_by' && key !== 'source' && key !== 'odbl' && key.indexOf('source:') !== 0 && key.indexOf('source_ref') !== 0 && // purposely exclude colon
+ key.indexOf('tiger:') !== 0;
+ }
+ var osmAreaKeys = {};
+ function osmSetAreaKeys(value) {
+ osmAreaKeys = value;
+ } // returns an object with the tag from `tags` that implies an area geometry, if any
+ function osmTagSuggestingArea(tags) {
+ if (tags.area === 'yes') return {
+ area: 'yes'
+ };
+ if (tags.area === 'no') return null; // `highway` and `railway` are typically linear features, but there
+ // are a few exceptions that should be treated as areas, even in the
+ // absence of a proper `area=yes` or `areaKeys` tag.. see #4194
- Object.defineProperty(_assertThisInitialized(_this), 'aborted', {
- value: false,
- writable: true,
- configurable: true
- });
- Object.defineProperty(_assertThisInitialized(_this), 'onabort', {
- value: null,
- writable: true,
- configurable: true
- });
- return _this;
+ var lineKeys = {
+ highway: {
+ rest_area: true,
+ services: true
+ },
+ railway: {
+ roundhouse: true,
+ station: true,
+ traverser: true,
+ turntable: true,
+ wash: true
}
+ };
+ var returnTags = {};
- _createClass(AbortSignal, [{
- key: "toString",
- value: function toString() {
- return '[object AbortSignal]';
- }
- }, {
- key: "dispatchEvent",
- value: function dispatchEvent(event) {
- if (event.type === 'abort') {
- this.aborted = true;
-
- if (typeof this.onabort === 'function') {
- this.onabort.call(this, event);
- }
- }
+ for (var key in tags) {
+ if (key in osmAreaKeys && !(tags[key] in osmAreaKeys[key])) {
+ returnTags[key] = tags[key];
+ return returnTags;
+ }
- _get(_getPrototypeOf(AbortSignal.prototype), "dispatchEvent", this).call(this, event);
- }
- }]);
+ if (key in lineKeys && tags[key] in lineKeys[key]) {
+ returnTags[key] = tags[key];
+ return returnTags;
+ }
+ }
- return AbortSignal;
- }(Emitter);
+ return null;
+ } // Tags that indicate a node can be a standalone point
+ // e.g. { amenity: { bar: true, parking: true, ... } ... }
- var AbortController = /*#__PURE__*/function () {
- function AbortController() {
- _classCallCheck(this, AbortController); // Compared to assignment, Object.defineProperty makes properties non-enumerable by default and
- // we want Object.keys(new AbortController()) to be [] for compat with the native impl
+ var osmPointTags = {};
+ function osmSetPointTags(value) {
+ osmPointTags = value;
+ } // Tags that indicate a node can be part of a way
+ // e.g. { amenity: { parking: true, ... }, highway: { stop: true ... } ... }
+ var osmVertexTags = {};
+ function osmSetVertexTags(value) {
+ osmVertexTags = value;
+ }
+ function osmNodeGeometriesForTags(nodeTags) {
+ var geometries = {};
- Object.defineProperty(this, 'signal', {
- value: new AbortSignal(),
- writable: true,
- configurable: true
- });
+ for (var key in nodeTags) {
+ if (osmPointTags[key] && (osmPointTags[key]['*'] || osmPointTags[key][nodeTags[key]])) {
+ geometries.point = true;
}
- _createClass(AbortController, [{
- key: "abort",
- value: function abort() {
- var event;
-
- try {
- event = new Event('abort');
- } catch (e) {
- if (typeof document !== 'undefined') {
- if (!document.createEvent) {
- // For Internet Explorer 8:
- event = document.createEventObject();
- event.type = 'abort';
- } else {
- // For Internet Explorer 11:
- event = document.createEvent('Event');
- event.initEvent('abort', false, false);
- }
- } else {
- // Fallback where document isn't available:
- event = {
- type: 'abort',
- bubbles: false,
- cancelable: false
- };
- }
- }
-
- this.signal.dispatchEvent(event);
- }
- }, {
- key: "toString",
- value: function toString() {
- return '[object AbortController]';
- }
- }]);
+ if (osmVertexTags[key] && (osmVertexTags[key]['*'] || osmVertexTags[key][nodeTags[key]])) {
+ geometries.vertex = true;
+ } // break early if both are already supported
- return AbortController;
- }();
- if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
- // These are necessary to make sure that we get correct output for:
- // Object.prototype.toString.call(new AbortController())
- AbortController.prototype[Symbol.toStringTag] = 'AbortController';
- AbortSignal.prototype[Symbol.toStringTag] = 'AbortSignal';
+ if (geometries.point && geometries.vertex) break;
}
- function polyfillNeeded(self) {
- if (self.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
- console.log('__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL=true is set, will force install polyfill');
- return true;
- } // Note that the "unfetch" minimal fetch polyfill defines fetch() without
- // defining window.Request, and this polyfill need to work on top of unfetch
- // so the below feature detection needs the !self.AbortController part.
- // The Request.prototype check is also needed because Safari versions 11.1.2
- // up to and including 12.1.x has a window.AbortController present but still
- // does NOT correctly implement abortable fetch:
- // https://bugs.webkit.org/show_bug.cgi?id=174980#c2
-
-
- return typeof self.Request === 'function' && !self.Request.prototype.hasOwnProperty('signal') || !self.AbortController;
+ return geometries;
+ }
+ var osmOneWayTags = {
+ 'aerialway': {
+ 'chair_lift': true,
+ 'drag_lift': true,
+ 'j-bar': true,
+ 'magic_carpet': true,
+ 'mixed_lift': true,
+ 'platter': true,
+ 'rope_tow': true,
+ 't-bar': true,
+ 'zip_line': true
+ },
+ 'highway': {
+ 'motorway': true
+ },
+ 'junction': {
+ 'circular': true,
+ 'roundabout': true
+ },
+ 'man_made': {
+ 'goods_conveyor': true,
+ 'piste:halfpipe': true
+ },
+ 'piste:type': {
+ 'downhill': true,
+ 'sled': true,
+ 'yes': true
+ },
+ 'seamark:type': {
+ 'separation_lane': true,
+ 'separation_roundabout': true
+ },
+ 'waterway': {
+ 'canal': true,
+ 'ditch': true,
+ 'drain': true,
+ 'fish_pass': true,
+ 'river': true,
+ 'stream': true,
+ 'tidal_channel': true
}
- /**
- * Note: the "fetch.Request" default value is available for fetch imported from
- * the "node-fetch" package and not in browsers. This is OK since browsers
- * will be importing umd-polyfill.js from that path "self" is passed the
- * decorator so the default value will not be used (because browsers that define
- * fetch also has Request). One quirky setup where self.fetch exists but
- * self.Request does not is when the "unfetch" minimal fetch polyfill is used
- * on top of IE11; for this case the browser will try to use the fetch.Request
- * default value which in turn will be undefined but then then "if (Request)"
- * will ensure that you get a patched fetch but still no Request (as expected).
- * @param {fetch, Request = fetch.Request}
- * @returns {fetch: abortableFetch, Request: AbortableRequest}
- */
+ }; // solid and smooth surfaces akin to the assumed default road surface in OSM
+ var osmPavedTags = {
+ 'surface': {
+ 'paved': true,
+ 'asphalt': true,
+ 'concrete': true,
+ 'concrete:lanes': true,
+ 'concrete:plates': true
+ },
+ 'tracktype': {
+ 'grade1': true
+ }
+ }; // solid, if somewhat uncommon surfaces with a high range of smoothness
- function abortableFetchDecorator(patchTargets) {
- if ('function' === typeof patchTargets) {
- patchTargets = {
- fetch: patchTargets
- };
- }
+ var osmSemipavedTags = {
+ 'surface': {
+ 'cobblestone': true,
+ 'cobblestone:flattened': true,
+ 'unhewn_cobblestone': true,
+ 'sett': true,
+ 'paving_stones': true,
+ 'metal': true,
+ 'wood': true
+ }
+ };
+ var osmRightSideIsInsideTags = {
+ 'natural': {
+ 'cliff': true,
+ 'coastline': 'coastline'
+ },
+ 'barrier': {
+ 'retaining_wall': true,
+ 'kerb': true,
+ 'guard_rail': true,
+ 'city_wall': true
+ },
+ 'man_made': {
+ 'embankment': true
+ },
+ 'waterway': {
+ 'weir': true
+ }
+ }; // "highway" tag values for pedestrian or vehicle right-of-ways that make up the routable network
+ // (does not include `raceway`)
- var _patchTargets = patchTargets,
- fetch = _patchTargets.fetch,
- _patchTargets$Request = _patchTargets.Request,
- NativeRequest = _patchTargets$Request === void 0 ? fetch.Request : _patchTargets$Request,
- NativeAbortController = _patchTargets.AbortController,
- _patchTargets$__FORCE = _patchTargets.__FORCE_INSTALL_ABORTCONTROLLER_POLYFILL,
- __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL = _patchTargets$__FORCE === void 0 ? false : _patchTargets$__FORCE;
+ var osmRoutableHighwayTagValues = {
+ motorway: true,
+ trunk: true,
+ primary: true,
+ secondary: true,
+ tertiary: true,
+ residential: true,
+ motorway_link: true,
+ trunk_link: true,
+ primary_link: true,
+ secondary_link: true,
+ tertiary_link: true,
+ unclassified: true,
+ road: true,
+ service: true,
+ track: true,
+ living_street: true,
+ bus_guideway: true,
+ path: true,
+ footway: true,
+ cycleway: true,
+ bridleway: true,
+ pedestrian: true,
+ corridor: true,
+ steps: true
+ }; // "highway" tag values that generally do not allow motor vehicles
- if (!polyfillNeeded({
- fetch: fetch,
- Request: NativeRequest,
- AbortController: NativeAbortController,
- __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL: __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL
- })) {
- return {
- fetch: fetch,
- Request: Request
- };
- }
+ var osmPathHighwayTagValues = {
+ path: true,
+ footway: true,
+ cycleway: true,
+ bridleway: true,
+ pedestrian: true,
+ corridor: true,
+ steps: true
+ }; // "railway" tag values representing existing railroad tracks (purposely does not include 'abandoned')
- var Request = NativeRequest; // Note that the "unfetch" minimal fetch polyfill defines fetch() without
- // defining window.Request, and this polyfill need to work on top of unfetch
- // hence we only patch it if it's available. Also we don't patch it if signal
- // is already available on the Request prototype because in this case support
- // is present and the patching below can cause a crash since it assigns to
- // request.signal which is technically a read-only property. This latter error
- // happens when you run the main5.js node-fetch example in the repo
- // "abortcontroller-polyfill-examples". The exact error is:
- // request.signal = init.signal;
- // ^
- // TypeError: Cannot set property signal of #<Request> which has only a getter
+ var osmRailwayTrackTagValues = {
+ rail: true,
+ light_rail: true,
+ tram: true,
+ subway: true,
+ monorail: true,
+ funicular: true,
+ miniature: true,
+ narrow_gauge: true,
+ disused: true,
+ preserved: true
+ }; // "waterway" tag values for line features representing water flow
- if (Request && !Request.prototype.hasOwnProperty('signal') || __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL) {
- Request = function Request(input, init) {
- var signal;
+ var osmFlowingWaterwayTagValues = {
+ canal: true,
+ ditch: true,
+ drain: true,
+ fish_pass: true,
+ river: true,
+ stream: true,
+ tidal_channel: true
+ };
- if (init && init.signal) {
- signal = init.signal; // Never pass init.signal to the native Request implementation when the polyfill has
- // been installed because if we're running on top of a browser with a
- // working native AbortController (i.e. the polyfill was installed due to
- // __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL being set), then passing our
- // fake AbortSignal to the native fetch will trigger:
- // TypeError: Failed to construct 'Request': member signal is not of type AbortSignal.
+ var global$e = global$1m;
+ var fails$g = fails$S;
+ var uncurryThis$i = functionUncurryThis;
+ var toString$b = toString$k;
+ var trim$3 = stringTrim.trim;
+ var whitespaces = whitespaces$4;
- delete init.signal;
- }
+ var $parseInt$1 = global$e.parseInt;
+ var Symbol$1 = global$e.Symbol;
+ var ITERATOR = Symbol$1 && Symbol$1.iterator;
+ var hex$2 = /^[+-]?0x/i;
+ var exec$3 = uncurryThis$i(hex$2.exec);
+ var FORCED$8 = $parseInt$1(whitespaces + '08') !== 8 || $parseInt$1(whitespaces + '0x16') !== 22
+ // MS Edge 18- broken with boxed symbols
+ || (ITERATOR && !fails$g(function () { $parseInt$1(Object(ITERATOR)); }));
- var request = new NativeRequest(input, init);
+ // `parseInt` method
+ // https://tc39.es/ecma262/#sec-parseint-string-radix
+ var numberParseInt = FORCED$8 ? function parseInt(string, radix) {
+ var S = trim$3(toString$b(string));
+ return $parseInt$1(S, (radix >>> 0) || (exec$3(hex$2, S) ? 16 : 10));
+ } : $parseInt$1;
- if (signal) {
- Object.defineProperty(request, 'signal', {
- writable: false,
- enumerable: false,
- configurable: true,
- value: signal
- });
- }
+ var $$G = _export;
+ var $parseInt = numberParseInt;
- return request;
- };
+ // `parseInt` method
+ // https://tc39.es/ecma262/#sec-parseint-string-radix
+ $$G({ global: true, forced: parseInt != $parseInt }, {
+ parseInt: $parseInt
+ });
- Request.prototype = NativeRequest.prototype;
- }
+ var internalMetadata = {exports: {}};
- var realFetch = fetch;
+ // FF26- bug: ArrayBuffers are non-extensible, but Object.isExtensible does not report it
+ var fails$f = fails$S;
- var abortableFetch = function abortableFetch(input, init) {
- var signal = Request && Request.prototype.isPrototypeOf(input) ? input.signal : init ? init.signal : undefined;
+ var arrayBufferNonExtensible = fails$f(function () {
+ if (typeof ArrayBuffer == 'function') {
+ var buffer = new ArrayBuffer(8);
+ // eslint-disable-next-line es/no-object-isextensible, es/no-object-defineproperty -- safe
+ if (Object.isExtensible(buffer)) Object.defineProperty(buffer, 'a', { value: 8 });
+ }
+ });
- if (signal) {
- var abortError;
+ var fails$e = fails$S;
+ var isObject$7 = isObject$s;
+ var classof = classofRaw$1;
+ var ARRAY_BUFFER_NON_EXTENSIBLE = arrayBufferNonExtensible;
- try {
- abortError = new DOMException('Aborted', 'AbortError');
- } catch (err) {
- // IE 11 does not support calling the DOMException constructor, use a
- // regular error object on it instead.
- abortError = new Error('Aborted');
- abortError.name = 'AbortError';
- } // Return early if already aborted, thus avoiding making an HTTP request
+ // eslint-disable-next-line es/no-object-isextensible -- safe
+ var $isExtensible = Object.isExtensible;
+ var FAILS_ON_PRIMITIVES$1 = fails$e(function () { $isExtensible(1); });
+ // `Object.isExtensible` method
+ // https://tc39.es/ecma262/#sec-object.isextensible
+ var objectIsExtensible = (FAILS_ON_PRIMITIVES$1 || ARRAY_BUFFER_NON_EXTENSIBLE) ? function isExtensible(it) {
+ if (!isObject$7(it)) return false;
+ if (ARRAY_BUFFER_NON_EXTENSIBLE && classof(it) == 'ArrayBuffer') return false;
+ return $isExtensible ? $isExtensible(it) : true;
+ } : $isExtensible;
- if (signal.aborted) {
- return Promise.reject(abortError);
- } // Turn an event into a promise, reject it once `abort` is dispatched
+ var fails$d = fails$S;
+ var freezing = !fails$d(function () {
+ // eslint-disable-next-line es/no-object-isextensible, es/no-object-preventextensions -- required for testing
+ return Object.isExtensible(Object.preventExtensions({}));
+ });
- var cancellation = new Promise(function (_, reject) {
- signal.addEventListener('abort', function () {
- return reject(abortError);
- }, {
- once: true
- });
- });
+ var $$F = _export;
+ var uncurryThis$h = functionUncurryThis;
+ var hiddenKeys = hiddenKeys$6;
+ var isObject$6 = isObject$s;
+ var hasOwn$3 = hasOwnProperty_1;
+ var defineProperty$3 = objectDefineProperty.f;
+ var getOwnPropertyNamesModule = objectGetOwnPropertyNames;
+ var getOwnPropertyNamesExternalModule = objectGetOwnPropertyNamesExternal;
+ var isExtensible = objectIsExtensible;
+ var uid = uid$5;
+ var FREEZING$1 = freezing;
- if (init && init.signal) {
- // Never pass .signal to the native implementation when the polyfill has
- // been installed because if we're running on top of a browser with a
- // working native AbortController (i.e. the polyfill was installed due to
- // __FORCE_INSTALL_ABORTCONTROLLER_POLYFILL being set), then passing our
- // fake AbortSignal to the native fetch will trigger:
- // TypeError: Failed to execute 'fetch' on 'Window': member signal is not of type AbortSignal.
- delete init.signal;
- } // Return the fastest promise (don't need to wait for request to finish)
+ var REQUIRED = false;
+ var METADATA = uid('meta');
+ var id$1 = 0;
+ var setMetadata = function (it) {
+ defineProperty$3(it, METADATA, { value: {
+ objectID: 'O' + id$1++, // object ID
+ weakData: {} // weak collections IDs
+ } });
+ };
- return Promise.race([cancellation, realFetch(input, init)]);
- }
+ var fastKey$1 = function (it, create) {
+ // return a primitive with prefix
+ if (!isObject$6(it)) return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
+ if (!hasOwn$3(it, METADATA)) {
+ // can't set metadata to uncaught frozen object
+ if (!isExtensible(it)) return 'F';
+ // not necessary to add metadata
+ if (!create) return 'E';
+ // add missing metadata
+ setMetadata(it);
+ // return object ID
+ } return it[METADATA].objectID;
+ };
- return realFetch(input, init);
- };
+ var getWeakData = function (it, create) {
+ if (!hasOwn$3(it, METADATA)) {
+ // can't set metadata to uncaught frozen object
+ if (!isExtensible(it)) return true;
+ // not necessary to add metadata
+ if (!create) return false;
+ // add missing metadata
+ setMetadata(it);
+ // return the store of weak collections IDs
+ } return it[METADATA].weakData;
+ };
- return {
- fetch: abortableFetch,
- Request: Request
+ // add metadata on freeze-family methods calling
+ var onFreeze$1 = function (it) {
+ if (FREEZING$1 && REQUIRED && isExtensible(it) && !hasOwn$3(it, METADATA)) setMetadata(it);
+ return it;
+ };
+
+ var enable = function () {
+ meta.enable = function () { /* empty */ };
+ REQUIRED = true;
+ var getOwnPropertyNames = getOwnPropertyNamesModule.f;
+ var splice = uncurryThis$h([].splice);
+ var test = {};
+ test[METADATA] = 1;
+
+ // prevent exposing of metadata key
+ if (getOwnPropertyNames(test).length) {
+ getOwnPropertyNamesModule.f = function (it) {
+ var result = getOwnPropertyNames(it);
+ for (var i = 0, length = result.length; i < length; i++) {
+ if (result[i] === METADATA) {
+ splice(result, i, 1);
+ break;
+ }
+ } return result;
};
+
+ $$F({ target: 'Object', stat: true, forced: true }, {
+ getOwnPropertyNames: getOwnPropertyNamesExternalModule.f
+ });
}
+ };
- (function (self) {
- if (!polyfillNeeded(self)) {
- return;
- }
+ var meta = internalMetadata.exports = {
+ enable: enable,
+ fastKey: fastKey$1,
+ getWeakData: getWeakData,
+ onFreeze: onFreeze$1
+ };
- if (!self.fetch) {
- console.warn('fetch() is not available, cannot install abortcontroller-polyfill');
- return;
- }
+ hiddenKeys[METADATA] = true;
- var _abortableFetch = abortableFetchDecorator(self),
- fetch = _abortableFetch.fetch,
- Request = _abortableFetch.Request;
+ var $$E = _export;
+ var global$d = global$1m;
+ var uncurryThis$g = functionUncurryThis;
+ var isForced$2 = isForced_1;
+ var redefine$4 = redefine$h.exports;
+ var InternalMetadataModule = internalMetadata.exports;
+ var iterate$1 = iterate$3;
+ var anInstance$2 = anInstance$7;
+ var isCallable$1 = isCallable$r;
+ var isObject$5 = isObject$s;
+ var fails$c = fails$S;
+ var checkCorrectnessOfIteration$1 = checkCorrectnessOfIteration$4;
+ var setToStringTag$1 = setToStringTag$a;
+ var inheritIfRequired$2 = inheritIfRequired$4;
- self.fetch = fetch;
- self.Request = Request;
- Object.defineProperty(self, 'AbortController', {
- writable: true,
- enumerable: false,
- configurable: true,
- value: AbortController
- });
- Object.defineProperty(self, 'AbortSignal', {
- writable: true,
- enumerable: false,
- configurable: true,
- value: AbortSignal
- });
- })(typeof self !== 'undefined' ? self : commonjsGlobal);
- });
+ var collection$2 = function (CONSTRUCTOR_NAME, wrapper, common) {
+ var IS_MAP = CONSTRUCTOR_NAME.indexOf('Map') !== -1;
+ var IS_WEAK = CONSTRUCTOR_NAME.indexOf('Weak') !== -1;
+ var ADDER = IS_MAP ? 'set' : 'add';
+ var NativeConstructor = global$d[CONSTRUCTOR_NAME];
+ var NativePrototype = NativeConstructor && NativeConstructor.prototype;
+ var Constructor = NativeConstructor;
+ var exported = {};
- function actionAddEntity(way) {
- return function (graph) {
- return graph.replace(way);
+ var fixMethod = function (KEY) {
+ var uncurriedNativeMethod = uncurryThis$g(NativePrototype[KEY]);
+ redefine$4(NativePrototype, KEY,
+ KEY == 'add' ? function add(value) {
+ uncurriedNativeMethod(this, value === 0 ? 0 : value);
+ return this;
+ } : KEY == 'delete' ? function (key) {
+ return IS_WEAK && !isObject$5(key) ? false : uncurriedNativeMethod(this, key === 0 ? 0 : key);
+ } : KEY == 'get' ? function get(key) {
+ return IS_WEAK && !isObject$5(key) ? undefined : uncurriedNativeMethod(this, key === 0 ? 0 : key);
+ } : KEY == 'has' ? function has(key) {
+ return IS_WEAK && !isObject$5(key) ? false : uncurriedNativeMethod(this, key === 0 ? 0 : key);
+ } : function set(key, value) {
+ uncurriedNativeMethod(this, key === 0 ? 0 : key, value);
+ return this;
+ }
+ );
};
- }
-
- var $$E = _export;
- var fails$f = fails$N;
- var isArray$1 = isArray$6;
- var isObject$7 = isObject$r;
- var toObject$3 = toObject$i;
- var toLength$5 = toLength$q;
- var createProperty = createProperty$4;
- var arraySpeciesCreate = arraySpeciesCreate$3;
- var arrayMethodHasSpeciesSupport$1 = arrayMethodHasSpeciesSupport$5;
- var wellKnownSymbol$2 = wellKnownSymbol$s;
- var V8_VERSION = engineV8Version;
- var IS_CONCAT_SPREADABLE = wellKnownSymbol$2('isConcatSpreadable');
- var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF;
- var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded';
+ var REPLACE = isForced$2(
+ CONSTRUCTOR_NAME,
+ !isCallable$1(NativeConstructor) || !(IS_WEAK || NativePrototype.forEach && !fails$c(function () {
+ new NativeConstructor().entries().next();
+ }))
+ );
- // We can't use this feature detection in V8 since it causes
- // deoptimization and serious performance degradation
- // https://github.com/zloirock/core-js/issues/679
- var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails$f(function () {
- var array = [];
- array[IS_CONCAT_SPREADABLE] = false;
- return array.concat()[0] !== array;
- });
+ if (REPLACE) {
+ // create collection constructor
+ Constructor = common.getConstructor(wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER);
+ InternalMetadataModule.enable();
+ } else if (isForced$2(CONSTRUCTOR_NAME, true)) {
+ var instance = new Constructor();
+ // early implementations not supports chaining
+ var HASNT_CHAINING = instance[ADDER](IS_WEAK ? {} : -0, 1) != instance;
+ // V8 ~ Chromium 40- weak-collections throws on primitives, but should return false
+ var THROWS_ON_PRIMITIVES = fails$c(function () { instance.has(1); });
+ // most early implementations doesn't supports iterables, most modern - not close it correctly
+ // eslint-disable-next-line no-new -- required for testing
+ var ACCEPT_ITERABLES = checkCorrectnessOfIteration$1(function (iterable) { new NativeConstructor(iterable); });
+ // for early implementations -0 and +0 not the same
+ var BUGGY_ZERO = !IS_WEAK && fails$c(function () {
+ // V8 ~ Chromium 42- fails only with 5+ elements
+ var $instance = new NativeConstructor();
+ var index = 5;
+ while (index--) $instance[ADDER](index, index);
+ return !$instance.has(-0);
+ });
- var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport$1('concat');
+ if (!ACCEPT_ITERABLES) {
+ Constructor = wrapper(function (dummy, iterable) {
+ anInstance$2(dummy, NativePrototype);
+ var that = inheritIfRequired$2(new NativeConstructor(), dummy, Constructor);
+ if (iterable != undefined) iterate$1(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
+ return that;
+ });
+ Constructor.prototype = NativePrototype;
+ NativePrototype.constructor = Constructor;
+ }
- var isConcatSpreadable = function (O) {
- if (!isObject$7(O)) return false;
- var spreadable = O[IS_CONCAT_SPREADABLE];
- return spreadable !== undefined ? !!spreadable : isArray$1(O);
- };
+ if (THROWS_ON_PRIMITIVES || BUGGY_ZERO) {
+ fixMethod('delete');
+ fixMethod('has');
+ IS_MAP && fixMethod('get');
+ }
- var FORCED$8 = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT;
+ if (BUGGY_ZERO || HASNT_CHAINING) fixMethod(ADDER);
- // `Array.prototype.concat` method
- // https://tc39.es/ecma262/#sec-array.prototype.concat
- // with adding support of @@isConcatSpreadable and @@species
- $$E({ target: 'Array', proto: true, forced: FORCED$8 }, {
- // eslint-disable-next-line no-unused-vars -- required for `.length`
- concat: function concat(arg) {
- var O = toObject$3(this);
- var A = arraySpeciesCreate(O, 0);
- var n = 0;
- var i, k, length, len, E;
- for (i = -1, length = arguments.length; i < length; i++) {
- E = i === -1 ? O : arguments[i];
- if (isConcatSpreadable(E)) {
- len = toLength$5(E.length);
- if (n + len > MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
- for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]);
- } else {
- if (n >= MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED);
- createProperty(A, n++, E);
- }
- }
- A.length = n;
- return A;
+ // weak collections should not contains .clear method
+ if (IS_WEAK && NativePrototype.clear) delete NativePrototype.clear;
}
- });
- var $$D = _export;
- var assign$1 = objectAssign;
+ exported[CONSTRUCTOR_NAME] = Constructor;
+ $$E({ global: true, forced: Constructor != NativeConstructor }, exported);
- // `Object.assign` method
- // https://tc39.es/ecma262/#sec-object.assign
- // eslint-disable-next-line es/no-object-assign -- required for testing
- $$D({ target: 'Object', stat: true, forced: Object.assign !== assign$1 }, {
- assign: assign$1
- });
+ setToStringTag$1(Constructor, CONSTRUCTOR_NAME);
- var $$C = _export;
- var $filter = arrayIteration.filter;
- var arrayMethodHasSpeciesSupport = arrayMethodHasSpeciesSupport$5;
+ if (!IS_WEAK) common.setStrong(Constructor, CONSTRUCTOR_NAME, IS_MAP);
- var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('filter');
+ return Constructor;
+ };
- // `Array.prototype.filter` method
- // https://tc39.es/ecma262/#sec-array.prototype.filter
- // with adding support of @@species
- $$C({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, {
- filter: function filter(callbackfn /* , thisArg */) {
- return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
- }
- });
+ var defineProperty$2 = objectDefineProperty.f;
+ var create$3 = objectCreate;
+ var redefineAll = redefineAll$4;
+ var bind$6 = functionBindContext;
+ var anInstance$1 = anInstance$7;
+ var iterate = iterate$3;
+ var defineIterator = defineIterator$3;
+ var setSpecies$1 = setSpecies$5;
+ var DESCRIPTORS$6 = descriptors;
+ var fastKey = internalMetadata.exports.fastKey;
+ var InternalStateModule$1 = internalState;
- var $$B = _export;
- var toObject$2 = toObject$i;
- var nativeKeys = objectKeys$4;
- var fails$e = fails$N;
+ var setInternalState$1 = InternalStateModule$1.set;
+ var internalStateGetterFor = InternalStateModule$1.getterFor;
- var FAILS_ON_PRIMITIVES$1 = fails$e(function () { nativeKeys(1); });
+ var collectionStrong$2 = {
+ getConstructor: function (wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER) {
+ var Constructor = wrapper(function (that, iterable) {
+ anInstance$1(that, Prototype);
+ setInternalState$1(that, {
+ type: CONSTRUCTOR_NAME,
+ index: create$3(null),
+ first: undefined,
+ last: undefined,
+ size: 0
+ });
+ if (!DESCRIPTORS$6) that.size = 0;
+ if (iterable != undefined) iterate(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
+ });
- // `Object.keys` method
- // https://tc39.es/ecma262/#sec-object.keys
- $$B({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES$1 }, {
- keys: function keys(it) {
- return nativeKeys(toObject$2(it));
- }
- });
+ var Prototype = Constructor.prototype;
- var $$A = _export;
- var isArray = isArray$6;
+ var getInternalState = internalStateGetterFor(CONSTRUCTOR_NAME);
- var nativeReverse = [].reverse;
- var test$1 = [1, 2];
+ var define = function (that, key, value) {
+ var state = getInternalState(that);
+ var entry = getEntry(that, key);
+ var previous, index;
+ // change existing entry
+ if (entry) {
+ entry.value = value;
+ // create new entry
+ } else {
+ state.last = entry = {
+ index: index = fastKey(key, true),
+ key: key,
+ value: value,
+ previous: previous = state.last,
+ next: undefined,
+ removed: false
+ };
+ if (!state.first) state.first = entry;
+ if (previous) previous.next = entry;
+ if (DESCRIPTORS$6) state.size++;
+ else that.size++;
+ // add to index
+ if (index !== 'F') state.index[index] = entry;
+ } return that;
+ };
- // `Array.prototype.reverse` method
- // https://tc39.es/ecma262/#sec-array.prototype.reverse
- // fix for Safari 12.0 bug
- // https://bugs.webkit.org/show_bug.cgi?id=188794
- $$A({ target: 'Array', proto: true, forced: String(test$1) === String(test$1.reverse()) }, {
- reverse: function reverse() {
- // eslint-disable-next-line no-self-assign -- dirty hack
- if (isArray(this)) this.length = this.length;
- return nativeReverse.call(this);
- }
- });
+ var getEntry = function (that, key) {
+ var state = getInternalState(that);
+ // fast case
+ var index = fastKey(key);
+ var entry;
+ if (index !== 'F') return state.index[index];
+ // frozen object case
+ for (entry = state.first; entry; entry = entry.next) {
+ if (entry.key == key) return entry;
+ }
+ };
- var global$6 = global$F;
- var trim$4 = stringTrim.trim;
- var whitespaces$1 = whitespaces$4;
+ redefineAll(Prototype, {
+ // `{ Map, Set }.prototype.clear()` methods
+ // https://tc39.es/ecma262/#sec-map.prototype.clear
+ // https://tc39.es/ecma262/#sec-set.prototype.clear
+ clear: function clear() {
+ var that = this;
+ var state = getInternalState(that);
+ var data = state.index;
+ var entry = state.first;
+ while (entry) {
+ entry.removed = true;
+ if (entry.previous) entry.previous = entry.previous.next = undefined;
+ delete data[entry.index];
+ entry = entry.next;
+ }
+ state.first = state.last = undefined;
+ if (DESCRIPTORS$6) state.size = 0;
+ else that.size = 0;
+ },
+ // `{ Map, Set }.prototype.delete(key)` methods
+ // https://tc39.es/ecma262/#sec-map.prototype.delete
+ // https://tc39.es/ecma262/#sec-set.prototype.delete
+ 'delete': function (key) {
+ var that = this;
+ var state = getInternalState(that);
+ var entry = getEntry(that, key);
+ if (entry) {
+ var next = entry.next;
+ var prev = entry.previous;
+ delete state.index[entry.index];
+ entry.removed = true;
+ if (prev) prev.next = next;
+ if (next) next.previous = prev;
+ if (state.first == entry) state.first = next;
+ if (state.last == entry) state.last = prev;
+ if (DESCRIPTORS$6) state.size--;
+ else that.size--;
+ } return !!entry;
+ },
+ // `{ Map, Set }.prototype.forEach(callbackfn, thisArg = undefined)` methods
+ // https://tc39.es/ecma262/#sec-map.prototype.foreach
+ // https://tc39.es/ecma262/#sec-set.prototype.foreach
+ forEach: function forEach(callbackfn /* , that = undefined */) {
+ var state = getInternalState(this);
+ var boundFunction = bind$6(callbackfn, arguments.length > 1 ? arguments[1] : undefined);
+ var entry;
+ while (entry = entry ? entry.next : state.first) {
+ boundFunction(entry.value, entry.key, this);
+ // revert to the last existing entry
+ while (entry && entry.removed) entry = entry.previous;
+ }
+ },
+ // `{ Map, Set}.prototype.has(key)` methods
+ // https://tc39.es/ecma262/#sec-map.prototype.has
+ // https://tc39.es/ecma262/#sec-set.prototype.has
+ has: function has(key) {
+ return !!getEntry(this, key);
+ }
+ });
- var $parseFloat = global$6.parseFloat;
- var FORCED$7 = 1 / $parseFloat(whitespaces$1 + '-0') !== -Infinity;
+ redefineAll(Prototype, IS_MAP ? {
+ // `Map.prototype.get(key)` method
+ // https://tc39.es/ecma262/#sec-map.prototype.get
+ get: function get(key) {
+ var entry = getEntry(this, key);
+ return entry && entry.value;
+ },
+ // `Map.prototype.set(key, value)` method
+ // https://tc39.es/ecma262/#sec-map.prototype.set
+ set: function set(key, value) {
+ return define(this, key === 0 ? 0 : key, value);
+ }
+ } : {
+ // `Set.prototype.add(value)` method
+ // https://tc39.es/ecma262/#sec-set.prototype.add
+ add: function add(value) {
+ return define(this, value = value === 0 ? 0 : value, value);
+ }
+ });
+ if (DESCRIPTORS$6) defineProperty$2(Prototype, 'size', {
+ get: function () {
+ return getInternalState(this).size;
+ }
+ });
+ return Constructor;
+ },
+ setStrong: function (Constructor, CONSTRUCTOR_NAME, IS_MAP) {
+ var ITERATOR_NAME = CONSTRUCTOR_NAME + ' Iterator';
+ var getInternalCollectionState = internalStateGetterFor(CONSTRUCTOR_NAME);
+ var getInternalIteratorState = internalStateGetterFor(ITERATOR_NAME);
+ // `{ Map, Set }.prototype.{ keys, values, entries, @@iterator }()` methods
+ // https://tc39.es/ecma262/#sec-map.prototype.entries
+ // https://tc39.es/ecma262/#sec-map.prototype.keys
+ // https://tc39.es/ecma262/#sec-map.prototype.values
+ // https://tc39.es/ecma262/#sec-map.prototype-@@iterator
+ // https://tc39.es/ecma262/#sec-set.prototype.entries
+ // https://tc39.es/ecma262/#sec-set.prototype.keys
+ // https://tc39.es/ecma262/#sec-set.prototype.values
+ // https://tc39.es/ecma262/#sec-set.prototype-@@iterator
+ defineIterator(Constructor, CONSTRUCTOR_NAME, function (iterated, kind) {
+ setInternalState$1(this, {
+ type: ITERATOR_NAME,
+ target: iterated,
+ state: getInternalCollectionState(iterated),
+ kind: kind,
+ last: undefined
+ });
+ }, function () {
+ var state = getInternalIteratorState(this);
+ var kind = state.kind;
+ var entry = state.last;
+ // revert to the last existing entry
+ while (entry && entry.removed) entry = entry.previous;
+ // get next entry
+ if (!state.target || !(state.last = entry = entry ? entry.next : state.state.first)) {
+ // or finish the iteration
+ state.target = undefined;
+ return { value: undefined, done: true };
+ }
+ // return step by kind
+ if (kind == 'keys') return { value: entry.key, done: false };
+ if (kind == 'values') return { value: entry.value, done: false };
+ return { value: [entry.key, entry.value], done: false };
+ }, IS_MAP ? 'entries' : 'values', !IS_MAP, true);
- // `parseFloat` method
- // https://tc39.es/ecma262/#sec-parsefloat-string
- var numberParseFloat = FORCED$7 ? function parseFloat(string) {
- var trimmedString = trim$4(String(string));
- var result = $parseFloat(trimmedString);
- return result === 0 && trimmedString.charAt(0) == '-' ? -0 : result;
- } : $parseFloat;
+ // `{ Map, Set }.prototype[@@species]` accessors
+ // https://tc39.es/ecma262/#sec-get-map-@@species
+ // https://tc39.es/ecma262/#sec-get-set-@@species
+ setSpecies$1(CONSTRUCTOR_NAME);
+ }
+ };
- var $$z = _export;
- var parseFloatImplementation = numberParseFloat;
+ var collection$1 = collection$2;
+ var collectionStrong$1 = collectionStrong$2;
- // `parseFloat` method
- // https://tc39.es/ecma262/#sec-parsefloat-string
- $$z({ global: true, forced: parseFloat != parseFloatImplementation }, {
- parseFloat: parseFloatImplementation
- });
+ // `Set` constructor
+ // https://tc39.es/ecma262/#sec-set-objects
+ collection$1('Set', function (init) {
+ return function Set() { return init(this, arguments.length ? arguments[0] : undefined); };
+ }, collectionStrong$1);
- /*
- Order the nodes of a way in reverse order and reverse any direction dependent tags
- other than `oneway`. (We assume that correcting a backwards oneway is the primary
- reason for reversing a way.)
+ function d3_ascending (a, b) {
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+ }
- In addition, numeric-valued `incline` tags are negated.
+ function d3_bisector (f) {
+ var delta = f;
+ var compare = f;
- The JOSM implementation was used as a guide, but transformations that were of unclear benefit
- or adjusted tags that don't seem to be used in practice were omitted.
+ if (f.length === 1) {
+ delta = function delta(d, x) {
+ return f(d) - x;
+ };
- References:
- http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right
- http://wiki.openstreetmap.org/wiki/Key:direction#Steps
- http://wiki.openstreetmap.org/wiki/Key:incline
- http://wiki.openstreetmap.org/wiki/Route#Members
- http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
- http://wiki.openstreetmap.org/wiki/Tag:highway%3Dstop
- http://wiki.openstreetmap.org/wiki/Key:traffic_sign#On_a_way_or_area
- */
- function actionReverse(entityID, options) {
- var ignoreKey = /^.*(_|:)?(description|name|note|website|ref|source|comment|watch|attribution)(_|:)?/;
- var numeric = /^([+\-]?)(?=[\d.])/;
- var directionKey = /direction$/;
- var turn_lanes = /^turn:lanes:?/;
- var keyReplacements = [[/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'], [/:right:/, ':left:'], [/:left:/, ':right:'], [/:forward:/, ':backward:'], [/:backward:/, ':forward:']];
- var valueReplacements = {
- left: 'right',
- right: 'left',
- up: 'down',
- down: 'up',
- forward: 'backward',
- backward: 'forward',
- forwards: 'backward',
- backwards: 'forward'
- };
- var roleReplacements = {
- forward: 'backward',
- backward: 'forward',
- forwards: 'backward',
- backwards: 'forward'
- };
- var onewayReplacements = {
- yes: '-1',
- '1': '-1',
- '-1': 'yes'
- };
- var compassReplacements = {
- N: 'S',
- NNE: 'SSW',
- NE: 'SW',
- ENE: 'WSW',
- E: 'W',
- ESE: 'WNW',
- SE: 'NW',
- SSE: 'NNW',
- S: 'N',
- SSW: 'NNE',
- SW: 'NE',
- WSW: 'ENE',
- W: 'E',
- WNW: 'ESE',
- NW: 'SE',
- NNW: 'SSE'
- };
+ compare = ascendingComparator(f);
+ }
- function reverseKey(key) {
- for (var i = 0; i < keyReplacements.length; ++i) {
- var replacement = keyReplacements[i];
+ function left(a, x, lo, hi) {
+ if (lo == null) lo = 0;
+ if (hi == null) hi = a.length;
- if (replacement[0].test(key)) {
- return key.replace(replacement[0], replacement[1]);
- }
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid;
}
- return key;
+ return lo;
}
- function reverseValue(key, value, includeAbsolute) {
- if (ignoreKey.test(key)) return value; // Turn lanes are left/right to key (not way) direction - #5674
-
- if (turn_lanes.test(key)) {
- return value;
- } else if (key === 'incline' && numeric.test(value)) {
- return value.replace(numeric, function (_, sign) {
- return sign === '-' ? '' : '-';
- });
- } else if (options && options.reverseOneway && key === 'oneway') {
- return onewayReplacements[value] || value;
- } else if (includeAbsolute && directionKey.test(key)) {
- if (compassReplacements[value]) return compassReplacements[value];
- var degrees = parseFloat(value);
-
- if (typeof degrees === 'number' && !isNaN(degrees)) {
- if (degrees < 180) {
- degrees += 180;
- } else {
- degrees -= 180;
- }
+ function right(a, x, lo, hi) {
+ if (lo == null) lo = 0;
+ if (hi == null) hi = a.length;
- return degrees.toString();
- }
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1;
}
- return valueReplacements[value] || value;
- } // Reverse the direction of tags attached to the nodes - #3076
-
+ return lo;
+ }
- function reverseNodeTags(graph, nodeIDs) {
- for (var i = 0; i < nodeIDs.length; i++) {
- var node = graph.hasEntity(nodeIDs[i]);
- if (!node || !Object.keys(node.tags).length) continue;
- var tags = {};
+ function center(a, x, lo, hi) {
+ if (lo == null) lo = 0;
+ if (hi == null) hi = a.length;
+ var i = left(a, x, lo, hi - 1);
+ return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
+ }
- for (var key in node.tags) {
- tags[reverseKey(key)] = reverseValue(key, node.tags[key], node.id === entityID);
- }
+ return {
+ left: left,
+ center: center,
+ right: right
+ };
+ }
- graph = graph.replace(node.update({
- tags: tags
- }));
- }
+ function ascendingComparator(f) {
+ return function (d, x) {
+ return d3_ascending(f(d), x);
+ };
+ }
- return graph;
- }
+ var defineWellKnownSymbol = defineWellKnownSymbol$4;
- function reverseWay(graph, way) {
- var nodes = way.nodes.slice().reverse();
- var tags = {};
- var role;
+ // `Symbol.asyncIterator` well-known symbol
+ // https://tc39.es/ecma262/#sec-symbol.asynciterator
+ defineWellKnownSymbol('asyncIterator');
- for (var key in way.tags) {
- tags[reverseKey(key)] = reverseValue(key, way.tags[key]);
- }
+ var runtime = {exports: {}};
- graph.parentRelations(way).forEach(function (relation) {
- relation.members.forEach(function (member, index) {
- if (member.id === way.id && (role = roleReplacements[member.role])) {
- relation = relation.updateMember({
- role: role
- }, index);
- graph = graph.replace(relation);
- }
- });
- }); // Reverse any associated directions on nodes on the way and then replace
- // the way itself with the reversed node ids and updated way tags
+ (function (module) {
+ var runtime = function (exports) {
- return reverseNodeTags(graph, nodes).replace(way.update({
- nodes: nodes,
- tags: tags
- }));
- }
+ var Op = Object.prototype;
+ var hasOwn = Op.hasOwnProperty;
+ var undefined$1; // More compressible than void 0.
- var action = function action(graph) {
- var entity = graph.entity(entityID);
+ var $Symbol = typeof Symbol === "function" ? Symbol : {};
+ var iteratorSymbol = $Symbol.iterator || "@@iterator";
+ var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
+ var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
- if (entity.type === 'way') {
- return reverseWay(graph, entity);
+ function define(obj, key, value) {
+ Object.defineProperty(obj, key, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ return obj[key];
}
- return reverseNodeTags(graph, [entityID]);
- };
+ try {
+ // IE 8 has a broken Object.defineProperty that only works on DOM objects.
+ define({}, "");
+ } catch (err) {
+ define = function define(obj, key, value) {
+ return obj[key] = value;
+ };
+ }
- action.disabled = function (graph) {
- var entity = graph.hasEntity(entityID);
- if (!entity || entity.type === 'way') return false;
+ function wrap(innerFn, outerFn, self, tryLocsList) {
+ // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
+ var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
+ var generator = Object.create(protoGenerator.prototype);
+ var context = new Context(tryLocsList || []); // The ._invoke method unifies the implementations of the .next,
+ // .throw, and .return methods.
- for (var key in entity.tags) {
- var value = entity.tags[key];
+ generator._invoke = makeInvokeMethod(innerFn, self, context);
+ return generator;
+ }
- if (reverseKey(key) !== key || reverseValue(key, value, true) !== value) {
- return false;
+ exports.wrap = wrap; // Try/catch helper to minimize deoptimizations. Returns a completion
+ // record like context.tryEntries[i].completion. This interface could
+ // have been (and was previously) designed to take a closure to be
+ // invoked without arguments, but in all the cases we care about we
+ // already have an existing method we want to call, so there's no need
+ // to create a new function object. We can even get away with assuming
+ // the method takes exactly one argument, since that happens to be true
+ // in every case, so we don't have to touch the arguments object. The
+ // only additional allocation required is the completion record, which
+ // has a stable shape and so hopefully should be cheap to allocate.
+
+ function tryCatch(fn, obj, arg) {
+ try {
+ return {
+ type: "normal",
+ arg: fn.call(obj, arg)
+ };
+ } catch (err) {
+ return {
+ type: "throw",
+ arg: err
+ };
}
}
- return 'nondirectional_node';
- };
+ var GenStateSuspendedStart = "suspendedStart";
+ var GenStateSuspendedYield = "suspendedYield";
+ var GenStateExecuting = "executing";
+ var GenStateCompleted = "completed"; // Returning this object from the innerFn has the same effect as
+ // breaking out of the dispatch switch statement.
- action.entityID = function () {
- return entityID;
- };
+ var ContinueSentinel = {}; // Dummy constructor functions that we use as the .constructor and
+ // .constructor.prototype properties for functions that return Generator
+ // objects. For full spec compliance, you may wish to configure your
+ // minifier not to mangle the names of these two functions.
- return action;
- }
+ function Generator() {}
- function osmIsInterestingTag(key) {
- return key !== 'attribution' && key !== 'created_by' && key !== 'source' && key !== 'odbl' && key.indexOf('source:') !== 0 && key.indexOf('source_ref') !== 0 && // purposely exclude colon
- key.indexOf('tiger:') !== 0;
- }
- var osmAreaKeys = {};
- function osmSetAreaKeys(value) {
- osmAreaKeys = value;
- } // returns an object with the tag from `tags` that implies an area geometry, if any
+ function GeneratorFunction() {}
- function osmTagSuggestingArea(tags) {
- if (tags.area === 'yes') return {
- area: 'yes'
- };
- if (tags.area === 'no') return null; // `highway` and `railway` are typically linear features, but there
- // are a few exceptions that should be treated as areas, even in the
- // absence of a proper `area=yes` or `areaKeys` tag.. see #4194
+ function GeneratorFunctionPrototype() {} // This is a polyfill for %IteratorPrototype% for environments that
+ // don't natively support it.
- var lineKeys = {
- highway: {
- rest_area: true,
- services: true
- },
- railway: {
- roundhouse: true,
- station: true,
- traverser: true,
- turntable: true,
- wash: true
- }
- };
- var returnTags = {};
- for (var key in tags) {
- if (key in osmAreaKeys && !(tags[key] in osmAreaKeys[key])) {
- returnTags[key] = tags[key];
- return returnTags;
- }
+ var IteratorPrototype = {};
+ define(IteratorPrototype, iteratorSymbol, function () {
+ return this;
+ });
+ var getProto = Object.getPrototypeOf;
+ var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
- if (key in lineKeys && tags[key] in lineKeys[key]) {
- returnTags[key] = tags[key];
- return returnTags;
+ if (NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
+ // This environment has a native %IteratorPrototype%; use it instead
+ // of the polyfill.
+ IteratorPrototype = NativeIteratorPrototype;
}
- }
- return null;
- } // Tags that indicate a node can be a standalone point
- // e.g. { amenity: { bar: true, parking: true, ... } ... }
+ var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype);
+ GeneratorFunction.prototype = GeneratorFunctionPrototype;
+ define(Gp, "constructor", GeneratorFunctionPrototype);
+ define(GeneratorFunctionPrototype, "constructor", GeneratorFunction);
+ GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"); // Helper for defining the .next, .throw, and .return methods of the
+ // Iterator interface in terms of a single ._invoke method.
- var osmPointTags = {};
- function osmSetPointTags(value) {
- osmPointTags = value;
- } // Tags that indicate a node can be part of a way
- // e.g. { amenity: { parking: true, ... }, highway: { stop: true ... } ... }
+ function defineIteratorMethods(prototype) {
+ ["next", "throw", "return"].forEach(function (method) {
+ define(prototype, method, function (arg) {
+ return this._invoke(method, arg);
+ });
+ });
+ }
- var osmVertexTags = {};
- function osmSetVertexTags(value) {
- osmVertexTags = value;
- }
- function osmNodeGeometriesForTags(nodeTags) {
- var geometries = {};
+ exports.isGeneratorFunction = function (genFun) {
+ var ctor = typeof genFun === "function" && genFun.constructor;
+ return ctor ? ctor === GeneratorFunction || // For the native GeneratorFunction constructor, the best we can
+ // do is to check its .name property.
+ (ctor.displayName || ctor.name) === "GeneratorFunction" : false;
+ };
- for (var key in nodeTags) {
- if (osmPointTags[key] && (osmPointTags[key]['*'] || osmPointTags[key][nodeTags[key]])) {
- geometries.point = true;
- }
+ exports.mark = function (genFun) {
+ if (Object.setPrototypeOf) {
+ Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
+ } else {
+ genFun.__proto__ = GeneratorFunctionPrototype;
+ define(genFun, toStringTagSymbol, "GeneratorFunction");
+ }
- if (osmVertexTags[key] && (osmVertexTags[key]['*'] || osmVertexTags[key][nodeTags[key]])) {
- geometries.vertex = true;
- } // break early if both are already supported
+ genFun.prototype = Object.create(Gp);
+ return genFun;
+ }; // Within the body of any async function, `await x` is transformed to
+ // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
+ // `hasOwn.call(value, "__await")` to determine if the yielded value is
+ // meant to be awaited.
- if (geometries.point && geometries.vertex) break;
- }
+ exports.awrap = function (arg) {
+ return {
+ __await: arg
+ };
+ };
- return geometries;
- }
- var osmOneWayTags = {
- 'aerialway': {
- 'chair_lift': true,
- 'drag_lift': true,
- 'j-bar': true,
- 'magic_carpet': true,
- 'mixed_lift': true,
- 'platter': true,
- 'rope_tow': true,
- 't-bar': true,
- 'zip_line': true
- },
- 'highway': {
- 'motorway': true
- },
- 'junction': {
- 'circular': true,
- 'roundabout': true
- },
- 'man_made': {
- 'goods_conveyor': true,
- 'piste:halfpipe': true
- },
- 'piste:type': {
- 'downhill': true,
- 'sled': true,
- 'yes': true
- },
- 'waterway': {
- 'canal': true,
- 'ditch': true,
- 'drain': true,
- 'fish_pass': true,
- 'river': true,
- 'stream': true,
- 'tidal_channel': true
- }
- }; // solid and smooth surfaces akin to the assumed default road surface in OSM
+ function AsyncIterator(generator, PromiseImpl) {
+ function invoke(method, arg, resolve, reject) {
+ var record = tryCatch(generator[method], generator, arg);
- var osmPavedTags = {
- 'surface': {
- 'paved': true,
- 'asphalt': true,
- 'concrete': true,
- 'concrete:lanes': true,
- 'concrete:plates': true
- },
- 'tracktype': {
- 'grade1': true
- }
- }; // solid, if somewhat uncommon surfaces with a high range of smoothness
+ if (record.type === "throw") {
+ reject(record.arg);
+ } else {
+ var result = record.arg;
+ var value = result.value;
- var osmSemipavedTags = {
- 'surface': {
- 'cobblestone': true,
- 'cobblestone:flattened': true,
- 'unhewn_cobblestone': true,
- 'sett': true,
- 'paving_stones': true,
- 'metal': true,
- 'wood': true
- }
- };
- var osmRightSideIsInsideTags = {
- 'natural': {
- 'cliff': true,
- 'coastline': 'coastline'
- },
- 'barrier': {
- 'retaining_wall': true,
- 'kerb': true,
- 'guard_rail': true,
- 'city_wall': true
- },
- 'man_made': {
- 'embankment': true
- },
- 'waterway': {
- 'weir': true
- }
- }; // "highway" tag values for pedestrian or vehicle right-of-ways that make up the routable network
- // (does not include `raceway`)
+ if (value && _typeof(value) === "object" && hasOwn.call(value, "__await")) {
+ return PromiseImpl.resolve(value.__await).then(function (value) {
+ invoke("next", value, resolve, reject);
+ }, function (err) {
+ invoke("throw", err, resolve, reject);
+ });
+ }
- var osmRoutableHighwayTagValues = {
- motorway: true,
- trunk: true,
- primary: true,
- secondary: true,
- tertiary: true,
- residential: true,
- motorway_link: true,
- trunk_link: true,
- primary_link: true,
- secondary_link: true,
- tertiary_link: true,
- unclassified: true,
- road: true,
- service: true,
- track: true,
- living_street: true,
- bus_guideway: true,
- path: true,
- footway: true,
- cycleway: true,
- bridleway: true,
- pedestrian: true,
- corridor: true,
- steps: true
- }; // "highway" tag values that generally do not allow motor vehicles
+ return PromiseImpl.resolve(value).then(function (unwrapped) {
+ // When a yielded Promise is resolved, its final value becomes
+ // the .value of the Promise<{value,done}> result for the
+ // current iteration.
+ result.value = unwrapped;
+ resolve(result);
+ }, function (error) {
+ // If a rejected Promise was yielded, throw the rejection back
+ // into the async generator function so it can be handled there.
+ return invoke("throw", error, resolve, reject);
+ });
+ }
+ }
- var osmPathHighwayTagValues = {
- path: true,
- footway: true,
- cycleway: true,
- bridleway: true,
- pedestrian: true,
- corridor: true,
- steps: true
- }; // "railway" tag values representing existing railroad tracks (purposely does not include 'abandoned')
+ var previousPromise;
- var osmRailwayTrackTagValues = {
- rail: true,
- light_rail: true,
- tram: true,
- subway: true,
- monorail: true,
- funicular: true,
- miniature: true,
- narrow_gauge: true,
- disused: true,
- preserved: true
- }; // "waterway" tag values for line features representing water flow
+ function enqueue(method, arg) {
+ function callInvokeWithMethodAndArg() {
+ return new PromiseImpl(function (resolve, reject) {
+ invoke(method, arg, resolve, reject);
+ });
+ }
- var osmFlowingWaterwayTagValues = {
- canal: true,
- ditch: true,
- drain: true,
- fish_pass: true,
- river: true,
- stream: true,
- tidal_channel: true
- };
+ return previousPromise = // If enqueue has been called before, then we want to wait until
+ // all previous Promises have been resolved before calling invoke,
+ // so that results are always delivered in the correct order. If
+ // enqueue has not been called before, then it is important to
+ // call invoke immediately, without waiting on a callback to fire,
+ // so that the async generator function has the opportunity to do
+ // any necessary setup in a predictable way. This predictability
+ // is why the Promise constructor synchronously invokes its
+ // executor callback, and why async functions synchronously
+ // execute code before the first await. Since we implement simple
+ // async functions in terms of async generators, it is especially
+ // important to get this right, even though it requires care.
+ previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, // Avoid propagating failures to Promises returned by later
+ // invocations of the iterator.
+ callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();
+ } // Define the unified helper method that is used to implement .next,
+ // .throw, and .return (see defineIteratorMethods).
- var global$5 = global$F;
- var trim$3 = stringTrim.trim;
- var whitespaces = whitespaces$4;
- var $parseInt = global$5.parseInt;
- var hex$2 = /^[+-]?0[Xx]/;
- var FORCED$6 = $parseInt(whitespaces + '08') !== 8 || $parseInt(whitespaces + '0x16') !== 22;
+ this._invoke = enqueue;
+ }
- // `parseInt` method
- // https://tc39.es/ecma262/#sec-parseint-string-radix
- var numberParseInt = FORCED$6 ? function parseInt(string, radix) {
- var S = trim$3(String(string));
- return $parseInt(S, (radix >>> 0) || (hex$2.test(S) ? 16 : 10));
- } : $parseInt;
+ defineIteratorMethods(AsyncIterator.prototype);
+ define(AsyncIterator.prototype, asyncIteratorSymbol, function () {
+ return this;
+ });
+ exports.AsyncIterator = AsyncIterator; // Note that simple async functions are implemented on top of
+ // AsyncIterator objects; they just return a Promise for the value of
+ // the final result produced by the iterator.
- var $$y = _export;
- var parseIntImplementation = numberParseInt;
+ exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) {
+ if (PromiseImpl === void 0) PromiseImpl = Promise;
+ var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl);
+ return exports.isGeneratorFunction(outerFn) ? iter // If outerFn is a generator, return the full iterator.
+ : iter.next().then(function (result) {
+ return result.done ? result.value : iter.next();
+ });
+ };
- // `parseInt` method
- // https://tc39.es/ecma262/#sec-parseint-string-radix
- $$y({ global: true, forced: parseInt != parseIntImplementation }, {
- parseInt: parseIntImplementation
- });
+ function makeInvokeMethod(innerFn, self, context) {
+ var state = GenStateSuspendedStart;
+ return function invoke(method, arg) {
+ if (state === GenStateExecuting) {
+ throw new Error("Generator is already running");
+ }
- var internalMetadata = {exports: {}};
+ if (state === GenStateCompleted) {
+ if (method === "throw") {
+ throw arg;
+ } // Be forgiving, per 25.3.3.3.3 of the spec:
+ // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
- var fails$d = fails$N;
- var freezing = !fails$d(function () {
- // eslint-disable-next-line es/no-object-isextensible, es/no-object-preventextensions -- required for testing
- return Object.isExtensible(Object.preventExtensions({}));
- });
+ return doneResult();
+ }
- var hiddenKeys = hiddenKeys$6;
- var isObject$6 = isObject$r;
- var has$2 = has$j;
- var defineProperty$3 = objectDefineProperty.f;
- var uid = uid$5;
- var FREEZING$1 = freezing;
+ context.method = method;
+ context.arg = arg;
- var METADATA = uid('meta');
- var id$1 = 0;
+ while (true) {
+ var delegate = context.delegate;
- // eslint-disable-next-line es/no-object-isextensible -- safe
- var isExtensible = Object.isExtensible || function () {
- return true;
- };
+ if (delegate) {
+ var delegateResult = maybeInvokeDelegate(delegate, context);
- var setMetadata = function (it) {
- defineProperty$3(it, METADATA, { value: {
- objectID: 'O' + ++id$1, // object ID
- weakData: {} // weak collections IDs
- } });
- };
+ if (delegateResult) {
+ if (delegateResult === ContinueSentinel) continue;
+ return delegateResult;
+ }
+ }
- var fastKey$1 = function (it, create) {
- // return a primitive with prefix
- if (!isObject$6(it)) return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
- if (!has$2(it, METADATA)) {
- // can't set metadata to uncaught frozen object
- if (!isExtensible(it)) return 'F';
- // not necessary to add metadata
- if (!create) return 'E';
- // add missing metadata
- setMetadata(it);
- // return object ID
- } return it[METADATA].objectID;
- };
+ if (context.method === "next") {
+ // Setting context._sent for legacy support of Babel's
+ // function.sent implementation.
+ context.sent = context._sent = context.arg;
+ } else if (context.method === "throw") {
+ if (state === GenStateSuspendedStart) {
+ state = GenStateCompleted;
+ throw context.arg;
+ }
- var getWeakData = function (it, create) {
- if (!has$2(it, METADATA)) {
- // can't set metadata to uncaught frozen object
- if (!isExtensible(it)) return true;
- // not necessary to add metadata
- if (!create) return false;
- // add missing metadata
- setMetadata(it);
- // return the store of weak collections IDs
- } return it[METADATA].weakData;
- };
+ context.dispatchException(context.arg);
+ } else if (context.method === "return") {
+ context.abrupt("return", context.arg);
+ }
- // add metadata on freeze-family methods calling
- var onFreeze$1 = function (it) {
- if (FREEZING$1 && meta.REQUIRED && isExtensible(it) && !has$2(it, METADATA)) setMetadata(it);
- return it;
- };
+ state = GenStateExecuting;
+ var record = tryCatch(innerFn, self, context);
- var meta = internalMetadata.exports = {
- REQUIRED: false,
- fastKey: fastKey$1,
- getWeakData: getWeakData,
- onFreeze: onFreeze$1
- };
+ if (record.type === "normal") {
+ // If an exception is thrown from innerFn, we leave state ===
+ // GenStateExecuting and loop back for another invocation.
+ state = context.done ? GenStateCompleted : GenStateSuspendedYield;
- hiddenKeys[METADATA] = true;
+ if (record.arg === ContinueSentinel) {
+ continue;
+ }
- var $$x = _export;
- var global$4 = global$F;
- var isForced$2 = isForced_1;
- var redefine$3 = redefine$g.exports;
- var InternalMetadataModule = internalMetadata.exports;
- var iterate$1 = iterate$3;
- var anInstance$1 = anInstance$7;
- var isObject$5 = isObject$r;
- var fails$c = fails$N;
- var checkCorrectnessOfIteration$1 = checkCorrectnessOfIteration$4;
- var setToStringTag = setToStringTag$a;
- var inheritIfRequired$2 = inheritIfRequired$4;
+ return {
+ value: record.arg,
+ done: context.done
+ };
+ } else if (record.type === "throw") {
+ state = GenStateCompleted; // Dispatch the exception by looping back around to the
+ // context.dispatchException(context.arg) call above.
- var collection$2 = function (CONSTRUCTOR_NAME, wrapper, common) {
- var IS_MAP = CONSTRUCTOR_NAME.indexOf('Map') !== -1;
- var IS_WEAK = CONSTRUCTOR_NAME.indexOf('Weak') !== -1;
- var ADDER = IS_MAP ? 'set' : 'add';
- var NativeConstructor = global$4[CONSTRUCTOR_NAME];
- var NativePrototype = NativeConstructor && NativeConstructor.prototype;
- var Constructor = NativeConstructor;
- var exported = {};
+ context.method = "throw";
+ context.arg = record.arg;
+ }
+ }
+ };
+ } // Call delegate.iterator[context.method](context.arg) and handle the
+ // result, either by returning a { value, done } result from the
+ // delegate iterator, or by modifying context.method and context.arg,
+ // setting context.delegate to null, and returning the ContinueSentinel.
- var fixMethod = function (KEY) {
- var nativeMethod = NativePrototype[KEY];
- redefine$3(NativePrototype, KEY,
- KEY == 'add' ? function add(value) {
- nativeMethod.call(this, value === 0 ? 0 : value);
- return this;
- } : KEY == 'delete' ? function (key) {
- return IS_WEAK && !isObject$5(key) ? false : nativeMethod.call(this, key === 0 ? 0 : key);
- } : KEY == 'get' ? function get(key) {
- return IS_WEAK && !isObject$5(key) ? undefined : nativeMethod.call(this, key === 0 ? 0 : key);
- } : KEY == 'has' ? function has(key) {
- return IS_WEAK && !isObject$5(key) ? false : nativeMethod.call(this, key === 0 ? 0 : key);
- } : function set(key, value) {
- nativeMethod.call(this, key === 0 ? 0 : key, value);
- return this;
- }
- );
- };
- var REPLACE = isForced$2(
- CONSTRUCTOR_NAME,
- typeof NativeConstructor != 'function' || !(IS_WEAK || NativePrototype.forEach && !fails$c(function () {
- new NativeConstructor().entries().next();
- }))
- );
+ function maybeInvokeDelegate(delegate, context) {
+ var method = delegate.iterator[context.method];
- if (REPLACE) {
- // create collection constructor
- Constructor = common.getConstructor(wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER);
- InternalMetadataModule.REQUIRED = true;
- } else if (isForced$2(CONSTRUCTOR_NAME, true)) {
- var instance = new Constructor();
- // early implementations not supports chaining
- var HASNT_CHAINING = instance[ADDER](IS_WEAK ? {} : -0, 1) != instance;
- // V8 ~ Chromium 40- weak-collections throws on primitives, but should return false
- var THROWS_ON_PRIMITIVES = fails$c(function () { instance.has(1); });
- // most early implementations doesn't supports iterables, most modern - not close it correctly
- // eslint-disable-next-line no-new -- required for testing
- var ACCEPT_ITERABLES = checkCorrectnessOfIteration$1(function (iterable) { new NativeConstructor(iterable); });
- // for early implementations -0 and +0 not the same
- var BUGGY_ZERO = !IS_WEAK && fails$c(function () {
- // V8 ~ Chromium 42- fails only with 5+ elements
- var $instance = new NativeConstructor();
- var index = 5;
- while (index--) $instance[ADDER](index, index);
- return !$instance.has(-0);
- });
+ if (method === undefined$1) {
+ // A .throw or .return when the delegate iterator has no .throw
+ // method always terminates the yield* loop.
+ context.delegate = null;
- if (!ACCEPT_ITERABLES) {
- Constructor = wrapper(function (dummy, iterable) {
- anInstance$1(dummy, Constructor, CONSTRUCTOR_NAME);
- var that = inheritIfRequired$2(new NativeConstructor(), dummy, Constructor);
- if (iterable != undefined) iterate$1(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
- return that;
- });
- Constructor.prototype = NativePrototype;
- NativePrototype.constructor = Constructor;
- }
+ if (context.method === "throw") {
+ // Note: ["return"] must be used for ES3 parsing compatibility.
+ if (delegate.iterator["return"]) {
+ // If the delegate iterator has a return method, give it a
+ // chance to clean up.
+ context.method = "return";
+ context.arg = undefined$1;
+ maybeInvokeDelegate(delegate, context);
- if (THROWS_ON_PRIMITIVES || BUGGY_ZERO) {
- fixMethod('delete');
- fixMethod('has');
- IS_MAP && fixMethod('get');
- }
+ if (context.method === "throw") {
+ // If maybeInvokeDelegate(context) changed context.method from
+ // "return" to "throw", let that override the TypeError below.
+ return ContinueSentinel;
+ }
+ }
- if (BUGGY_ZERO || HASNT_CHAINING) fixMethod(ADDER);
+ context.method = "throw";
+ context.arg = new TypeError("The iterator does not provide a 'throw' method");
+ }
- // weak collections should not contains .clear method
- if (IS_WEAK && NativePrototype.clear) delete NativePrototype.clear;
- }
+ return ContinueSentinel;
+ }
- exported[CONSTRUCTOR_NAME] = Constructor;
- $$x({ global: true, forced: Constructor != NativeConstructor }, exported);
+ var record = tryCatch(method, delegate.iterator, context.arg);
- setToStringTag(Constructor, CONSTRUCTOR_NAME);
+ if (record.type === "throw") {
+ context.method = "throw";
+ context.arg = record.arg;
+ context.delegate = null;
+ return ContinueSentinel;
+ }
- if (!IS_WEAK) common.setStrong(Constructor, CONSTRUCTOR_NAME, IS_MAP);
+ var info = record.arg;
- return Constructor;
- };
+ if (!info) {
+ context.method = "throw";
+ context.arg = new TypeError("iterator result is not an object");
+ context.delegate = null;
+ return ContinueSentinel;
+ }
- var defineProperty$2 = objectDefineProperty.f;
- var create$4 = objectCreate;
- var redefineAll = redefineAll$4;
- var bind$3 = functionBindContext;
- var anInstance = anInstance$7;
- var iterate = iterate$3;
- var defineIterator = defineIterator$3;
- var setSpecies$1 = setSpecies$5;
- var DESCRIPTORS$5 = descriptors;
- var fastKey = internalMetadata.exports.fastKey;
- var InternalStateModule = internalState;
+ if (info.done) {
+ // Assign the result of the finished delegate to the temporary
+ // variable specified by delegate.resultName (see delegateYield).
+ context[delegate.resultName] = info.value; // Resume execution at the desired location (see delegateYield).
- var setInternalState = InternalStateModule.set;
- var internalStateGetterFor = InternalStateModule.getterFor;
+ context.next = delegate.nextLoc; // If context.method was "throw" but the delegate handled the
+ // exception, let the outer generator proceed normally. If
+ // context.method was "next", forget context.arg since it has been
+ // "consumed" by the delegate iterator. If context.method was
+ // "return", allow the original .return call to continue in the
+ // outer generator.
- var collectionStrong$2 = {
- getConstructor: function (wrapper, CONSTRUCTOR_NAME, IS_MAP, ADDER) {
- var C = wrapper(function (that, iterable) {
- anInstance(that, C, CONSTRUCTOR_NAME);
- setInternalState(that, {
- type: CONSTRUCTOR_NAME,
- index: create$4(null),
- first: undefined,
- last: undefined,
- size: 0
- });
- if (!DESCRIPTORS$5) that.size = 0;
- if (iterable != undefined) iterate(iterable, that[ADDER], { that: that, AS_ENTRIES: IS_MAP });
- });
+ if (context.method !== "return") {
+ context.method = "next";
+ context.arg = undefined$1;
+ }
+ } else {
+ // Re-yield the result returned by the delegate method.
+ return info;
+ } // The delegate iterator is finished, so forget it and continue with
+ // the outer generator.
- var getInternalState = internalStateGetterFor(CONSTRUCTOR_NAME);
- var define = function (that, key, value) {
- var state = getInternalState(that);
- var entry = getEntry(that, key);
- var previous, index;
- // change existing entry
- if (entry) {
- entry.value = value;
- // create new entry
- } else {
- state.last = entry = {
- index: index = fastKey(key, true),
- key: key,
- value: value,
- previous: previous = state.last,
- next: undefined,
- removed: false
- };
- if (!state.first) state.first = entry;
- if (previous) previous.next = entry;
- if (DESCRIPTORS$5) state.size++;
- else that.size++;
- // add to index
- if (index !== 'F') state.index[index] = entry;
- } return that;
- };
+ context.delegate = null;
+ return ContinueSentinel;
+ } // Define Generator.prototype.{next,throw,return} in terms of the
+ // unified ._invoke helper method.
- var getEntry = function (that, key) {
- var state = getInternalState(that);
- // fast case
- var index = fastKey(key);
- var entry;
- if (index !== 'F') return state.index[index];
- // frozen object case
- for (entry = state.first; entry; entry = entry.next) {
- if (entry.key == key) return entry;
- }
- };
- redefineAll(C.prototype, {
- // `{ Map, Set }.prototype.clear()` methods
- // https://tc39.es/ecma262/#sec-map.prototype.clear
- // https://tc39.es/ecma262/#sec-set.prototype.clear
- clear: function clear() {
- var that = this;
- var state = getInternalState(that);
- var data = state.index;
- var entry = state.first;
- while (entry) {
- entry.removed = true;
- if (entry.previous) entry.previous = entry.previous.next = undefined;
- delete data[entry.index];
- entry = entry.next;
- }
- state.first = state.last = undefined;
- if (DESCRIPTORS$5) state.size = 0;
- else that.size = 0;
- },
- // `{ Map, Set }.prototype.delete(key)` methods
- // https://tc39.es/ecma262/#sec-map.prototype.delete
- // https://tc39.es/ecma262/#sec-set.prototype.delete
- 'delete': function (key) {
- var that = this;
- var state = getInternalState(that);
- var entry = getEntry(that, key);
- if (entry) {
- var next = entry.next;
- var prev = entry.previous;
- delete state.index[entry.index];
- entry.removed = true;
- if (prev) prev.next = next;
- if (next) next.previous = prev;
- if (state.first == entry) state.first = next;
- if (state.last == entry) state.last = prev;
- if (DESCRIPTORS$5) state.size--;
- else that.size--;
- } return !!entry;
- },
- // `{ Map, Set }.prototype.forEach(callbackfn, thisArg = undefined)` methods
- // https://tc39.es/ecma262/#sec-map.prototype.foreach
- // https://tc39.es/ecma262/#sec-set.prototype.foreach
- forEach: function forEach(callbackfn /* , that = undefined */) {
- var state = getInternalState(this);
- var boundFunction = bind$3(callbackfn, arguments.length > 1 ? arguments[1] : undefined, 3);
- var entry;
- while (entry = entry ? entry.next : state.first) {
- boundFunction(entry.value, entry.key, this);
- // revert to the last existing entry
- while (entry && entry.removed) entry = entry.previous;
- }
- },
- // `{ Map, Set}.prototype.has(key)` methods
- // https://tc39.es/ecma262/#sec-map.prototype.has
- // https://tc39.es/ecma262/#sec-set.prototype.has
- has: function has(key) {
- return !!getEntry(this, key);
- }
- });
+ defineIteratorMethods(Gp);
+ define(Gp, toStringTagSymbol, "Generator"); // A Generator should always return itself as the iterator object when the
+ // @@iterator function is called on it. Some browsers' implementations of the
+ // iterator prototype chain incorrectly implement this, causing the Generator
+ // object to not be returned from this call. This ensures that doesn't happen.
+ // See https://github.com/facebook/regenerator/issues/274 for more details.
- redefineAll(C.prototype, IS_MAP ? {
- // `Map.prototype.get(key)` method
- // https://tc39.es/ecma262/#sec-map.prototype.get
- get: function get(key) {
- var entry = getEntry(this, key);
- return entry && entry.value;
- },
- // `Map.prototype.set(key, value)` method
- // https://tc39.es/ecma262/#sec-map.prototype.set
- set: function set(key, value) {
- return define(this, key === 0 ? 0 : key, value);
- }
- } : {
- // `Set.prototype.add(value)` method
- // https://tc39.es/ecma262/#sec-set.prototype.add
- add: function add(value) {
- return define(this, value = value === 0 ? 0 : value, value);
- }
+ define(Gp, iteratorSymbol, function () {
+ return this;
});
- if (DESCRIPTORS$5) defineProperty$2(C.prototype, 'size', {
- get: function () {
- return getInternalState(this).size;
- }
+ define(Gp, "toString", function () {
+ return "[object Generator]";
});
- return C;
- },
- setStrong: function (C, CONSTRUCTOR_NAME, IS_MAP) {
- var ITERATOR_NAME = CONSTRUCTOR_NAME + ' Iterator';
- var getInternalCollectionState = internalStateGetterFor(CONSTRUCTOR_NAME);
- var getInternalIteratorState = internalStateGetterFor(ITERATOR_NAME);
- // `{ Map, Set }.prototype.{ keys, values, entries, @@iterator }()` methods
- // https://tc39.es/ecma262/#sec-map.prototype.entries
- // https://tc39.es/ecma262/#sec-map.prototype.keys
- // https://tc39.es/ecma262/#sec-map.prototype.values
- // https://tc39.es/ecma262/#sec-map.prototype-@@iterator
- // https://tc39.es/ecma262/#sec-set.prototype.entries
- // https://tc39.es/ecma262/#sec-set.prototype.keys
- // https://tc39.es/ecma262/#sec-set.prototype.values
- // https://tc39.es/ecma262/#sec-set.prototype-@@iterator
- defineIterator(C, CONSTRUCTOR_NAME, function (iterated, kind) {
- setInternalState(this, {
- type: ITERATOR_NAME,
- target: iterated,
- state: getInternalCollectionState(iterated),
- kind: kind,
- last: undefined
- });
- }, function () {
- var state = getInternalIteratorState(this);
- var kind = state.kind;
- var entry = state.last;
- // revert to the last existing entry
- while (entry && entry.removed) entry = entry.previous;
- // get next entry
- if (!state.target || !(state.last = entry = entry ? entry.next : state.state.first)) {
- // or finish the iteration
- state.target = undefined;
- return { value: undefined, done: true };
- }
- // return step by kind
- if (kind == 'keys') return { value: entry.key, done: false };
- if (kind == 'values') return { value: entry.value, done: false };
- return { value: [entry.key, entry.value], done: false };
- }, IS_MAP ? 'entries' : 'values', !IS_MAP, true);
-
- // `{ Map, Set }.prototype[@@species]` accessors
- // https://tc39.es/ecma262/#sec-get-map-@@species
- // https://tc39.es/ecma262/#sec-get-set-@@species
- setSpecies$1(CONSTRUCTOR_NAME);
- }
- };
-
- var collection$1 = collection$2;
- var collectionStrong$1 = collectionStrong$2;
-
- // `Set` constructor
- // https://tc39.es/ecma262/#sec-set-objects
- collection$1('Set', function (init) {
- return function Set() { return init(this, arguments.length ? arguments[0] : undefined); };
- }, collectionStrong$1);
-
- function d3_ascending (a, b) {
- return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
- }
- function d3_bisector (f) {
- var delta = f;
- var compare = f;
-
- if (f.length === 1) {
- delta = function delta(d, x) {
- return f(d) - x;
- };
+ function pushTryEntry(locs) {
+ var entry = {
+ tryLoc: locs[0]
+ };
- compare = ascendingComparator(f);
- }
+ if (1 in locs) {
+ entry.catchLoc = locs[1];
+ }
- function left(a, x, lo, hi) {
- if (lo == null) lo = 0;
- if (hi == null) hi = a.length;
+ if (2 in locs) {
+ entry.finallyLoc = locs[2];
+ entry.afterLoc = locs[3];
+ }
- while (lo < hi) {
- var mid = lo + hi >>> 1;
- if (compare(a[mid], x) < 0) lo = mid + 1;else hi = mid;
+ this.tryEntries.push(entry);
}
- return lo;
- }
-
- function right(a, x, lo, hi) {
- if (lo == null) lo = 0;
- if (hi == null) hi = a.length;
+ function resetTryEntry(entry) {
+ var record = entry.completion || {};
+ record.type = "normal";
+ delete record.arg;
+ entry.completion = record;
+ }
- while (lo < hi) {
- var mid = lo + hi >>> 1;
- if (compare(a[mid], x) > 0) hi = mid;else lo = mid + 1;
+ function Context(tryLocsList) {
+ // The root entry object (effectively a try statement without a catch
+ // or a finally block) gives us a place to store values thrown from
+ // locations where there is no enclosing try statement.
+ this.tryEntries = [{
+ tryLoc: "root"
+ }];
+ tryLocsList.forEach(pushTryEntry, this);
+ this.reset(true);
}
- return lo;
- }
+ exports.keys = function (object) {
+ var keys = [];
- function center(a, x, lo, hi) {
- if (lo == null) lo = 0;
- if (hi == null) hi = a.length;
- var i = left(a, x, lo, hi - 1);
- return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
- }
+ for (var key in object) {
+ keys.push(key);
+ }
- return {
- left: left,
- center: center,
- right: right
- };
- }
+ keys.reverse(); // Rather than returning an object with a next method, we keep
+ // things simple and return the next function itself.
- function ascendingComparator(f) {
- return function (d, x) {
- return d3_ascending(f(d), x);
- };
- }
+ return function next() {
+ while (keys.length) {
+ var key = keys.pop();
- var defineWellKnownSymbol = defineWellKnownSymbol$4;
+ if (key in object) {
+ next.value = key;
+ next.done = false;
+ return next;
+ }
+ } // To avoid creating an additional object, we just hang the .value
+ // and .done properties off the next function object itself. This
+ // also ensures that the minifier will not anonymize the function.
- // `Symbol.asyncIterator` well-known symbol
- // https://tc39.es/ecma262/#sec-symbol.asynciterator
- defineWellKnownSymbol('asyncIterator');
- var runtime = {exports: {}};
+ next.done = true;
+ return next;
+ };
+ };
- (function (module) {
- var runtime = function (exports) {
+ function values(iterable) {
+ if (iterable) {
+ var iteratorMethod = iterable[iteratorSymbol];
- var Op = Object.prototype;
- var hasOwn = Op.hasOwnProperty;
- var undefined$1; // More compressible than void 0.
-
- var $Symbol = typeof Symbol === "function" ? Symbol : {};
- var iteratorSymbol = $Symbol.iterator || "@@iterator";
- var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
- var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
-
- function define(obj, key, value) {
- Object.defineProperty(obj, key, {
- value: value,
- enumerable: true,
- configurable: true,
- writable: true
- });
- return obj[key];
- }
-
- try {
- // IE 8 has a broken Object.defineProperty that only works on DOM objects.
- define({}, "");
- } catch (err) {
- define = function define(obj, key, value) {
- return obj[key] = value;
- };
- }
-
- function wrap(innerFn, outerFn, self, tryLocsList) {
- // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
- var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
- var generator = Object.create(protoGenerator.prototype);
- var context = new Context(tryLocsList || []); // The ._invoke method unifies the implementations of the .next,
- // .throw, and .return methods.
-
- generator._invoke = makeInvokeMethod(innerFn, self, context);
- return generator;
- }
-
- exports.wrap = wrap; // Try/catch helper to minimize deoptimizations. Returns a completion
- // record like context.tryEntries[i].completion. This interface could
- // have been (and was previously) designed to take a closure to be
- // invoked without arguments, but in all the cases we care about we
- // already have an existing method we want to call, so there's no need
- // to create a new function object. We can even get away with assuming
- // the method takes exactly one argument, since that happens to be true
- // in every case, so we don't have to touch the arguments object. The
- // only additional allocation required is the completion record, which
- // has a stable shape and so hopefully should be cheap to allocate.
-
- function tryCatch(fn, obj, arg) {
- try {
- return {
- type: "normal",
- arg: fn.call(obj, arg)
- };
- } catch (err) {
- return {
- type: "throw",
- arg: err
- };
- }
- }
-
- var GenStateSuspendedStart = "suspendedStart";
- var GenStateSuspendedYield = "suspendedYield";
- var GenStateExecuting = "executing";
- var GenStateCompleted = "completed"; // Returning this object from the innerFn has the same effect as
- // breaking out of the dispatch switch statement.
-
- var ContinueSentinel = {}; // Dummy constructor functions that we use as the .constructor and
- // .constructor.prototype properties for functions that return Generator
- // objects. For full spec compliance, you may wish to configure your
- // minifier not to mangle the names of these two functions.
-
- function Generator() {}
-
- function GeneratorFunction() {}
-
- function GeneratorFunctionPrototype() {} // This is a polyfill for %IteratorPrototype% for environments that
- // don't natively support it.
-
-
- var IteratorPrototype = {};
-
- IteratorPrototype[iteratorSymbol] = function () {
- return this;
- };
-
- var getProto = Object.getPrototypeOf;
- var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
-
- if (NativeIteratorPrototype && NativeIteratorPrototype !== Op && hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
- // This environment has a native %IteratorPrototype%; use it instead
- // of the polyfill.
- IteratorPrototype = NativeIteratorPrototype;
- }
-
- var Gp = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(IteratorPrototype);
- GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
- GeneratorFunctionPrototype.constructor = GeneratorFunction;
- GeneratorFunction.displayName = define(GeneratorFunctionPrototype, toStringTagSymbol, "GeneratorFunction"); // Helper for defining the .next, .throw, and .return methods of the
- // Iterator interface in terms of a single ._invoke method.
-
- function defineIteratorMethods(prototype) {
- ["next", "throw", "return"].forEach(function (method) {
- define(prototype, method, function (arg) {
- return this._invoke(method, arg);
- });
- });
- }
-
- exports.isGeneratorFunction = function (genFun) {
- var ctor = typeof genFun === "function" && genFun.constructor;
- return ctor ? ctor === GeneratorFunction || // For the native GeneratorFunction constructor, the best we can
- // do is to check its .name property.
- (ctor.displayName || ctor.name) === "GeneratorFunction" : false;
- };
-
- exports.mark = function (genFun) {
- if (Object.setPrototypeOf) {
- Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
- } else {
- genFun.__proto__ = GeneratorFunctionPrototype;
- define(genFun, toStringTagSymbol, "GeneratorFunction");
- }
-
- genFun.prototype = Object.create(Gp);
- return genFun;
- }; // Within the body of any async function, `await x` is transformed to
- // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
- // `hasOwn.call(value, "__await")` to determine if the yielded value is
- // meant to be awaited.
-
-
- exports.awrap = function (arg) {
- return {
- __await: arg
- };
- };
-
- function AsyncIterator(generator, PromiseImpl) {
- function invoke(method, arg, resolve, reject) {
- var record = tryCatch(generator[method], generator, arg);
-
- if (record.type === "throw") {
- reject(record.arg);
- } else {
- var result = record.arg;
- var value = result.value;
-
- if (value && _typeof(value) === "object" && hasOwn.call(value, "__await")) {
- return PromiseImpl.resolve(value.__await).then(function (value) {
- invoke("next", value, resolve, reject);
- }, function (err) {
- invoke("throw", err, resolve, reject);
- });
- }
-
- return PromiseImpl.resolve(value).then(function (unwrapped) {
- // When a yielded Promise is resolved, its final value becomes
- // the .value of the Promise<{value,done}> result for the
- // current iteration.
- result.value = unwrapped;
- resolve(result);
- }, function (error) {
- // If a rejected Promise was yielded, throw the rejection back
- // into the async generator function so it can be handled there.
- return invoke("throw", error, resolve, reject);
- });
- }
- }
-
- var previousPromise;
-
- function enqueue(method, arg) {
- function callInvokeWithMethodAndArg() {
- return new PromiseImpl(function (resolve, reject) {
- invoke(method, arg, resolve, reject);
- });
- }
-
- return previousPromise = // If enqueue has been called before, then we want to wait until
- // all previous Promises have been resolved before calling invoke,
- // so that results are always delivered in the correct order. If
- // enqueue has not been called before, then it is important to
- // call invoke immediately, without waiting on a callback to fire,
- // so that the async generator function has the opportunity to do
- // any necessary setup in a predictable way. This predictability
- // is why the Promise constructor synchronously invokes its
- // executor callback, and why async functions synchronously
- // execute code before the first await. Since we implement simple
- // async functions in terms of async generators, it is especially
- // important to get this right, even though it requires care.
- previousPromise ? previousPromise.then(callInvokeWithMethodAndArg, // Avoid propagating failures to Promises returned by later
- // invocations of the iterator.
- callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg();
- } // Define the unified helper method that is used to implement .next,
- // .throw, and .return (see defineIteratorMethods).
-
-
- this._invoke = enqueue;
- }
-
- defineIteratorMethods(AsyncIterator.prototype);
-
- AsyncIterator.prototype[asyncIteratorSymbol] = function () {
- return this;
- };
-
- exports.AsyncIterator = AsyncIterator; // Note that simple async functions are implemented on top of
- // AsyncIterator objects; they just return a Promise for the value of
- // the final result produced by the iterator.
-
- exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) {
- if (PromiseImpl === void 0) PromiseImpl = Promise;
- var iter = new AsyncIterator(wrap(innerFn, outerFn, self, tryLocsList), PromiseImpl);
- return exports.isGeneratorFunction(outerFn) ? iter // If outerFn is a generator, return the full iterator.
- : iter.next().then(function (result) {
- return result.done ? result.value : iter.next();
- });
- };
-
- function makeInvokeMethod(innerFn, self, context) {
- var state = GenStateSuspendedStart;
- return function invoke(method, arg) {
- if (state === GenStateExecuting) {
- throw new Error("Generator is already running");
- }
-
- if (state === GenStateCompleted) {
- if (method === "throw") {
- throw arg;
- } // Be forgiving, per 25.3.3.3.3 of the spec:
- // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
-
-
- return doneResult();
- }
-
- context.method = method;
- context.arg = arg;
-
- while (true) {
- var delegate = context.delegate;
-
- if (delegate) {
- var delegateResult = maybeInvokeDelegate(delegate, context);
-
- if (delegateResult) {
- if (delegateResult === ContinueSentinel) continue;
- return delegateResult;
- }
- }
-
- if (context.method === "next") {
- // Setting context._sent for legacy support of Babel's
- // function.sent implementation.
- context.sent = context._sent = context.arg;
- } else if (context.method === "throw") {
- if (state === GenStateSuspendedStart) {
- state = GenStateCompleted;
- throw context.arg;
- }
-
- context.dispatchException(context.arg);
- } else if (context.method === "return") {
- context.abrupt("return", context.arg);
- }
-
- state = GenStateExecuting;
- var record = tryCatch(innerFn, self, context);
-
- if (record.type === "normal") {
- // If an exception is thrown from innerFn, we leave state ===
- // GenStateExecuting and loop back for another invocation.
- state = context.done ? GenStateCompleted : GenStateSuspendedYield;
-
- if (record.arg === ContinueSentinel) {
- continue;
- }
-
- return {
- value: record.arg,
- done: context.done
- };
- } else if (record.type === "throw") {
- state = GenStateCompleted; // Dispatch the exception by looping back around to the
- // context.dispatchException(context.arg) call above.
-
- context.method = "throw";
- context.arg = record.arg;
- }
- }
- };
- } // Call delegate.iterator[context.method](context.arg) and handle the
- // result, either by returning a { value, done } result from the
- // delegate iterator, or by modifying context.method and context.arg,
- // setting context.delegate to null, and returning the ContinueSentinel.
-
-
- function maybeInvokeDelegate(delegate, context) {
- var method = delegate.iterator[context.method];
-
- if (method === undefined$1) {
- // A .throw or .return when the delegate iterator has no .throw
- // method always terminates the yield* loop.
- context.delegate = null;
-
- if (context.method === "throw") {
- // Note: ["return"] must be used for ES3 parsing compatibility.
- if (delegate.iterator["return"]) {
- // If the delegate iterator has a return method, give it a
- // chance to clean up.
- context.method = "return";
- context.arg = undefined$1;
- maybeInvokeDelegate(delegate, context);
-
- if (context.method === "throw") {
- // If maybeInvokeDelegate(context) changed context.method from
- // "return" to "throw", let that override the TypeError below.
- return ContinueSentinel;
- }
- }
-
- context.method = "throw";
- context.arg = new TypeError("The iterator does not provide a 'throw' method");
- }
-
- return ContinueSentinel;
- }
-
- var record = tryCatch(method, delegate.iterator, context.arg);
-
- if (record.type === "throw") {
- context.method = "throw";
- context.arg = record.arg;
- context.delegate = null;
- return ContinueSentinel;
- }
-
- var info = record.arg;
-
- if (!info) {
- context.method = "throw";
- context.arg = new TypeError("iterator result is not an object");
- context.delegate = null;
- return ContinueSentinel;
- }
-
- if (info.done) {
- // Assign the result of the finished delegate to the temporary
- // variable specified by delegate.resultName (see delegateYield).
- context[delegate.resultName] = info.value; // Resume execution at the desired location (see delegateYield).
-
- context.next = delegate.nextLoc; // If context.method was "throw" but the delegate handled the
- // exception, let the outer generator proceed normally. If
- // context.method was "next", forget context.arg since it has been
- // "consumed" by the delegate iterator. If context.method was
- // "return", allow the original .return call to continue in the
- // outer generator.
-
- if (context.method !== "return") {
- context.method = "next";
- context.arg = undefined$1;
- }
- } else {
- // Re-yield the result returned by the delegate method.
- return info;
- } // The delegate iterator is finished, so forget it and continue with
- // the outer generator.
-
-
- context.delegate = null;
- return ContinueSentinel;
- } // Define Generator.prototype.{next,throw,return} in terms of the
- // unified ._invoke helper method.
-
-
- defineIteratorMethods(Gp);
- define(Gp, toStringTagSymbol, "Generator"); // A Generator should always return itself as the iterator object when the
- // @@iterator function is called on it. Some browsers' implementations of the
- // iterator prototype chain incorrectly implement this, causing the Generator
- // object to not be returned from this call. This ensures that doesn't happen.
- // See https://github.com/facebook/regenerator/issues/274 for more details.
-
- Gp[iteratorSymbol] = function () {
- return this;
- };
-
- Gp.toString = function () {
- return "[object Generator]";
- };
-
- function pushTryEntry(locs) {
- var entry = {
- tryLoc: locs[0]
- };
-
- if (1 in locs) {
- entry.catchLoc = locs[1];
- }
-
- if (2 in locs) {
- entry.finallyLoc = locs[2];
- entry.afterLoc = locs[3];
- }
-
- this.tryEntries.push(entry);
- }
-
- function resetTryEntry(entry) {
- var record = entry.completion || {};
- record.type = "normal";
- delete record.arg;
- entry.completion = record;
- }
-
- function Context(tryLocsList) {
- // The root entry object (effectively a try statement without a catch
- // or a finally block) gives us a place to store values thrown from
- // locations where there is no enclosing try statement.
- this.tryEntries = [{
- tryLoc: "root"
- }];
- tryLocsList.forEach(pushTryEntry, this);
- this.reset(true);
- }
-
- exports.keys = function (object) {
- var keys = [];
-
- for (var key in object) {
- keys.push(key);
- }
-
- keys.reverse(); // Rather than returning an object with a next method, we keep
- // things simple and return the next function itself.
-
- return function next() {
- while (keys.length) {
- var key = keys.pop();
-
- if (key in object) {
- next.value = key;
- next.done = false;
- return next;
- }
- } // To avoid creating an additional object, we just hang the .value
- // and .done properties off the next function object itself. This
- // also ensures that the minifier will not anonymize the function.
-
-
- next.done = true;
- return next;
- };
- };
-
- function values(iterable) {
- if (iterable) {
- var iteratorMethod = iterable[iteratorSymbol];
-
- if (iteratorMethod) {
- return iteratorMethod.call(iterable);
- }
+ if (iteratorMethod) {
+ return iteratorMethod.call(iterable);
+ }
if (typeof iterable.next === "function") {
return iterable;
} catch (accidentalStrictMode) {
// This module should not be running in strict mode, so the above
// assignment should always work unless something is misconfigured. Just
- // in case runtime.js accidentally runs in strict mode, we can escape
+ // in case runtime.js accidentally runs in strict mode, in modern engines
+ // we can explicitly access globalThis. In older engines we can escape
// strict mode using a global Function call. This could conceivably fail
// if a Content Security Policy forbids using Function, but in that case
// the proper solution is to fix the accidental strict mode problem. If
// you've misconfigured your bundler to force strict mode and applied a
// CSP to forbid Function, and you're not willing to fix either of those
// problems, please detail your unique predicament in a GitHub issue.
- Function("r", "regeneratorRuntime = r")(runtime);
+ if ((typeof globalThis === "undefined" ? "undefined" : _typeof(globalThis)) === "object") {
+ globalThis.regeneratorRuntime = runtime;
+ } else {
+ Function("r", "regeneratorRuntime = r")(runtime);
+ }
}
})(runtime);
var bisectRight = ascendingBisect.right;
d3_bisector(number$1).center;
- var $$w = _export;
+ var anObject$2 = anObject$n;
+ var iteratorClose = iteratorClose$2;
+
+ // call something on iterator step with safe closing on error
+ var callWithSafeIterationClosing$1 = function (iterator, fn, value, ENTRIES) {
+ try {
+ return ENTRIES ? fn(anObject$2(value)[0], value[1]) : fn(value);
+ } catch (error) {
+ iteratorClose(iterator, 'throw', error);
+ }
+ };
+
+ var global$c = global$1m;
+ var bind$5 = functionBindContext;
+ var call$4 = functionCall;
+ var toObject$3 = toObject$j;
+ var callWithSafeIterationClosing = callWithSafeIterationClosing$1;
+ var isArrayIteratorMethod = isArrayIteratorMethod$3;
+ var isConstructor = isConstructor$4;
+ var lengthOfArrayLike$3 = lengthOfArrayLike$g;
+ var createProperty = createProperty$4;
+ var getIterator = getIterator$4;
+ var getIteratorMethod = getIteratorMethod$5;
+
+ var Array$1 = global$c.Array;
+
+ // `Array.from` method implementation
+ // https://tc39.es/ecma262/#sec-array.from
+ var arrayFrom$1 = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) {
+ var O = toObject$3(arrayLike);
+ var IS_CONSTRUCTOR = isConstructor(this);
+ var argumentsLength = arguments.length;
+ var mapfn = argumentsLength > 1 ? arguments[1] : undefined;
+ var mapping = mapfn !== undefined;
+ if (mapping) mapfn = bind$5(mapfn, argumentsLength > 2 ? arguments[2] : undefined);
+ var iteratorMethod = getIteratorMethod(O);
+ var index = 0;
+ var length, result, step, iterator, next, value;
+ // if the target is not iterable or it's an array with the default iterator - use a simple case
+ if (iteratorMethod && !(this == Array$1 && isArrayIteratorMethod(iteratorMethod))) {
+ iterator = getIterator(O, iteratorMethod);
+ next = iterator.next;
+ result = IS_CONSTRUCTOR ? new this() : [];
+ for (;!(step = call$4(next, iterator)).done; index++) {
+ value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value;
+ createProperty(result, index, value);
+ }
+ } else {
+ length = lengthOfArrayLike$3(O);
+ result = IS_CONSTRUCTOR ? new this(length) : Array$1(length);
+ for (;length > index; index++) {
+ value = mapping ? mapfn(O[index], index) : O[index];
+ createProperty(result, index, value);
+ }
+ }
+ result.length = index;
+ return result;
+ };
+
+ var $$D = _export;
var from = arrayFrom$1;
var checkCorrectnessOfIteration = checkCorrectnessOfIteration$4;
// `Array.from` method
// https://tc39.es/ecma262/#sec-array.from
- $$w({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, {
+ $$D({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, {
from: from
});
- var $$v = _export;
+ var $$C = _export;
var fill = arrayFill$1;
- var addToUnscopables$3 = addToUnscopables$5;
+ var addToUnscopables$4 = addToUnscopables$6;
// `Array.prototype.fill` method
// https://tc39.es/ecma262/#sec-array.prototype.fill
- $$v({ target: 'Array', proto: true }, {
+ $$C({ target: 'Array', proto: true }, {
fill: fill
});
// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
- addToUnscopables$3('fill');
+ addToUnscopables$4('fill');
- var $$u = _export;
+ var $$B = _export;
var $some = arrayIteration.some;
- var arrayMethodIsStrict$3 = arrayMethodIsStrict$8;
+ var arrayMethodIsStrict$4 = arrayMethodIsStrict$9;
- var STRICT_METHOD$3 = arrayMethodIsStrict$3('some');
+ var STRICT_METHOD$4 = arrayMethodIsStrict$4('some');
// `Array.prototype.some` method
// https://tc39.es/ecma262/#sec-array.prototype.some
- $$u({ target: 'Array', proto: true, forced: !STRICT_METHOD$3 }, {
+ $$B({ target: 'Array', proto: true, forced: !STRICT_METHOD$4 }, {
some: function some(callbackfn /* , thisArg */) {
return $some(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
return Adder;
}();
- var $$t = _export;
- var DESCRIPTORS$4 = descriptors;
- var defineProperties = objectDefineProperties;
+ var $$A = _export;
+ var DESCRIPTORS$5 = descriptors;
+ var defineProperties$1 = objectDefineProperties;
// `Object.defineProperties` method
// https://tc39.es/ecma262/#sec-object.defineproperties
- $$t({ target: 'Object', stat: true, forced: !DESCRIPTORS$4, sham: !DESCRIPTORS$4 }, {
- defineProperties: defineProperties
+ $$A({ target: 'Object', stat: true, forced: !DESCRIPTORS$5, sham: !DESCRIPTORS$5 }, {
+ defineProperties: defineProperties$1
});
var collection = collection$2;
return function Map() { return init(this, arguments.length ? arguments[0] : undefined); };
}, collectionStrong);
- var $$s = _export;
- var aFunction = aFunction$9;
- var toObject$1 = toObject$i;
- var toLength$4 = toLength$q;
- var fails$b = fails$N;
- var internalSort = arraySort;
- var arrayMethodIsStrict$2 = arrayMethodIsStrict$8;
+ var $$z = _export;
+ var uncurryThis$f = functionUncurryThis;
+ var aCallable$1 = aCallable$a;
+ var toObject$2 = toObject$j;
+ var lengthOfArrayLike$2 = lengthOfArrayLike$g;
+ var toString$a = toString$k;
+ var fails$b = fails$S;
+ var internalSort = arraySort$1;
+ var arrayMethodIsStrict$3 = arrayMethodIsStrict$9;
var FF = engineFfVersion;
var IE_OR_EDGE = engineIsIeOrEdge;
var V8 = engineV8Version;
var WEBKIT = engineWebkitVersion;
var test = [];
- var nativeSort = test.sort;
+ var un$Sort = uncurryThis$f(test.sort);
+ var push$3 = uncurryThis$f(test.push);
// IE8-
var FAILS_ON_UNDEFINED = fails$b(function () {
test.sort(null);
});
// Old WebKit
- var STRICT_METHOD$2 = arrayMethodIsStrict$2('sort');
+ var STRICT_METHOD$3 = arrayMethodIsStrict$3('sort');
var STABLE_SORT = !fails$b(function () {
// feature detection can be too slow, so check engines versions
return result !== 'DGBEFHACIJK';
});
- var FORCED$5 = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD$2 || !STABLE_SORT;
+ var FORCED$7 = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD$3 || !STABLE_SORT;
var getSortCompare = function (comparefn) {
return function (x, y) {
if (y === undefined) return -1;
if (x === undefined) return 1;
if (comparefn !== undefined) return +comparefn(x, y) || 0;
- return String(x) > String(y) ? 1 : -1;
+ return toString$a(x) > toString$a(y) ? 1 : -1;
};
};
// `Array.prototype.sort` method
// https://tc39.es/ecma262/#sec-array.prototype.sort
- $$s({ target: 'Array', proto: true, forced: FORCED$5 }, {
+ $$z({ target: 'Array', proto: true, forced: FORCED$7 }, {
sort: function sort(comparefn) {
- if (comparefn !== undefined) aFunction(comparefn);
+ if (comparefn !== undefined) aCallable$1(comparefn);
- var array = toObject$1(this);
+ var array = toObject$2(this);
- if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn);
+ if (STABLE_SORT) return comparefn === undefined ? un$Sort(array) : un$Sort(array, comparefn);
var items = [];
- var arrayLength = toLength$4(array.length);
+ var arrayLength = lengthOfArrayLike$2(array);
var itemsLength, index;
for (index = 0; index < arrayLength; index++) {
- if (index in array) items.push(array[index]);
+ if (index in array) push$3(items, array[index]);
}
- items = internalSort(items, getSortCompare(comparefn));
+ internalSort(items, getSortCompare(comparefn));
+
itemsLength = items.length;
index = 0;
return x === y ? x !== 0 || 1 / x === 1 / y : x != x && y != y;
};
- var $$r = _export;
+ var $$y = _export;
// eslint-disable-next-line es/no-math-hypot -- required for testing
var $hypot = Math.hypot;
// `Math.hypot` method
// https://tc39.es/ecma262/#sec-math.hypot
- $$r({ target: 'Math', stat: true, forced: BUGGY }, {
+ $$y({ target: 'Math', stat: true, forced: BUGGY }, {
// eslint-disable-next-line no-unused-vars -- required for `.length`
hypot: function hypot(value1, value2) {
var sum = 0;
return (x = +x) == 0 || x != x ? x : x < 0 ? -1 : 1;
};
- var $$q = _export;
+ var $$x = _export;
var sign$1 = mathSign;
// `Math.sign` method
// https://tc39.es/ecma262/#sec-math.sign
- $$q({ target: 'Math', stat: true }, {
+ $$x({ target: 'Math', stat: true }, {
sign: sign$1
});
}
});
- var $$p = _export;
+ var $$w = _export;
var $every = arrayIteration.every;
- var arrayMethodIsStrict$1 = arrayMethodIsStrict$8;
+ var arrayMethodIsStrict$2 = arrayMethodIsStrict$9;
- var STRICT_METHOD$1 = arrayMethodIsStrict$1('every');
+ var STRICT_METHOD$2 = arrayMethodIsStrict$2('every');
// `Array.prototype.every` method
// https://tc39.es/ecma262/#sec-array.prototype.every
- $$p({ target: 'Array', proto: true, forced: !STRICT_METHOD$1 }, {
+ $$w({ target: 'Array', proto: true, forced: !STRICT_METHOD$2 }, {
every: function every(callbackfn /* , thisArg */) {
return $every(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
- var $$o = _export;
+ var $$v = _export;
var $reduce = arrayReduce.left;
- var arrayMethodIsStrict = arrayMethodIsStrict$8;
- var CHROME_VERSION = engineV8Version;
- var IS_NODE = engineIsNode;
+ var arrayMethodIsStrict$1 = arrayMethodIsStrict$9;
+ var CHROME_VERSION$1 = engineV8Version;
+ var IS_NODE$1 = engineIsNode;
- var STRICT_METHOD = arrayMethodIsStrict('reduce');
+ var STRICT_METHOD$1 = arrayMethodIsStrict$1('reduce');
// Chrome 80-82 has a critical bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
- var CHROME_BUG = !IS_NODE && CHROME_VERSION > 79 && CHROME_VERSION < 83;
+ var CHROME_BUG$1 = !IS_NODE$1 && CHROME_VERSION$1 > 79 && CHROME_VERSION$1 < 83;
// `Array.prototype.reduce` method
// https://tc39.es/ecma262/#sec-array.prototype.reduce
- $$o({ target: 'Array', proto: true, forced: !STRICT_METHOD || CHROME_BUG }, {
+ $$v({ target: 'Array', proto: true, forced: !STRICT_METHOD$1 || CHROME_BUG$1 }, {
reduce: function reduce(callbackfn /* , initialValue */) {
- return $reduce(this, callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined);
+ var length = arguments.length;
+ return $reduce(this, callbackfn, length, length > 1 ? arguments[1] : undefined);
}
});
return new Selection$1(subgroups, parents);
}
- var $$n = _export;
+ var $$u = _export;
var $find = arrayIteration.find;
- var addToUnscopables$2 = addToUnscopables$5;
+ var addToUnscopables$3 = addToUnscopables$6;
var FIND = 'find';
var SKIPS_HOLES$1 = true;
// `Array.prototype.find` method
// https://tc39.es/ecma262/#sec-array.prototype.find
- $$n({ target: 'Array', proto: true, forced: SKIPS_HOLES$1 }, {
+ $$u({ target: 'Array', proto: true, forced: SKIPS_HOLES$1 }, {
find: function find(callbackfn /* , that = undefined */) {
return $find(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
- addToUnscopables$2(FIND);
+ addToUnscopables$3(FIND);
function matcher (selector) {
return function () {
}
function defaultView (node) {
- return node.ownerDocument && node.ownerDocument.defaultView || // node is a Node
- node.document && node // node is a Window
+ return node.ownerDocument && node.ownerDocument.defaultView // node is a Node
+ || node.document && node // node is a Window
|| node.defaultView; // node is a Document
}
return drag;
}
- var DESCRIPTORS$3 = descriptors;
- var global$3 = global$F;
+ var DESCRIPTORS$4 = descriptors;
+ var global$b = global$1m;
+ var uncurryThis$e = functionUncurryThis;
var isForced$1 = isForced_1;
var inheritIfRequired$1 = inheritIfRequired$4;
- var createNonEnumerableProperty = createNonEnumerableProperty$e;
+ var createNonEnumerableProperty = createNonEnumerableProperty$b;
var defineProperty$1 = objectDefineProperty.f;
var getOwnPropertyNames$1 = objectGetOwnPropertyNames.f;
+ var isPrototypeOf$1 = objectIsPrototypeOf;
var isRegExp$1 = isRegexp;
- var getFlags = regexpFlags$1;
+ var toString$9 = toString$k;
+ var regExpFlags$1 = regexpFlags$1;
var stickyHelpers = regexpStickyHelpers;
- var redefine$2 = redefine$g.exports;
- var fails$a = fails$N;
- var has$1 = has$j;
+ var redefine$3 = redefine$h.exports;
+ var fails$a = fails$S;
+ var hasOwn$2 = hasOwnProperty_1;
var enforceInternalState = internalState.enforce;
var setSpecies = setSpecies$5;
- var wellKnownSymbol$1 = wellKnownSymbol$s;
+ var wellKnownSymbol$1 = wellKnownSymbol$t;
var UNSUPPORTED_DOT_ALL = regexpUnsupportedDotAll;
var UNSUPPORTED_NCG = regexpUnsupportedNcg;
var MATCH$1 = wellKnownSymbol$1('match');
- var NativeRegExp = global$3.RegExp;
- var RegExpPrototype = NativeRegExp.prototype;
+ var NativeRegExp = global$b.RegExp;
+ var RegExpPrototype$1 = NativeRegExp.prototype;
+ var SyntaxError$1 = global$b.SyntaxError;
+ var getFlags = uncurryThis$e(regExpFlags$1);
+ var exec$2 = uncurryThis$e(RegExpPrototype$1.exec);
+ var charAt$1 = uncurryThis$e(''.charAt);
+ var replace$3 = uncurryThis$e(''.replace);
+ var stringIndexOf$1 = uncurryThis$e(''.indexOf);
+ var stringSlice$4 = uncurryThis$e(''.slice);
// TODO: Use only propper RegExpIdentifierName
var IS_NCG = /^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/;
var re1 = /a/g;
var UNSUPPORTED_Y = stickyHelpers.UNSUPPORTED_Y;
- var BASE_FORCED = DESCRIPTORS$3 &&
+ var BASE_FORCED = DESCRIPTORS$4 &&
(!CORRECT_NEW || UNSUPPORTED_Y || UNSUPPORTED_DOT_ALL || UNSUPPORTED_NCG || fails$a(function () {
re2[MATCH$1] = false;
// RegExp constructor can alter flags and IsRegExp works correct with @@match
var brackets = false;
var chr;
for (; index <= length; index++) {
- chr = string.charAt(index);
+ chr = charAt$1(string, index);
if (chr === '\\') {
- result += chr + string.charAt(++index);
+ result += chr + charAt$1(string, ++index);
continue;
}
if (!brackets && chr === '.') {
var groupname = '';
var chr;
for (; index <= length; index++) {
- chr = string.charAt(index);
+ chr = charAt$1(string, index);
if (chr === '\\') {
- chr = chr + string.charAt(++index);
+ chr = chr + charAt$1(string, ++index);
} else if (chr === ']') {
brackets = false;
} else if (!brackets) switch (true) {
brackets = true;
break;
case chr === '(':
- if (IS_NCG.test(string.slice(index + 1))) {
+ if (exec$2(IS_NCG, stringSlice$4(string, index + 1))) {
index += 2;
ncg = true;
}
groupid++;
continue;
case chr === '>' && ncg:
- if (groupname === '' || has$1(names, groupname)) {
- throw new SyntaxError('Invalid capture group name');
+ if (groupname === '' || hasOwn$2(names, groupname)) {
+ throw new SyntaxError$1('Invalid capture group name');
}
names[groupname] = true;
- named.push([groupname, groupid]);
+ named[named.length] = [groupname, groupid];
ncg = false;
groupname = '';
continue;
// https://tc39.es/ecma262/#sec-regexp-constructor
if (isForced$1('RegExp', BASE_FORCED)) {
var RegExpWrapper = function RegExp(pattern, flags) {
- var thisIsRegExp = this instanceof RegExpWrapper;
+ var thisIsRegExp = isPrototypeOf$1(RegExpPrototype$1, this);
var patternIsRegExp = isRegExp$1(pattern);
var flagsAreUndefined = flags === undefined;
var groups = [];
- var rawPattern, rawFlags, dotAll, sticky, handled, result, state;
+ var rawPattern = pattern;
+ var rawFlags, dotAll, sticky, handled, result, state;
- if (!thisIsRegExp && patternIsRegExp && pattern.constructor === RegExpWrapper && flagsAreUndefined) {
+ if (!thisIsRegExp && patternIsRegExp && flagsAreUndefined && pattern.constructor === RegExpWrapper) {
return pattern;
}
- if (CORRECT_NEW) {
- if (patternIsRegExp && !flagsAreUndefined) pattern = pattern.source;
- } else if (pattern instanceof RegExpWrapper) {
- if (flagsAreUndefined) flags = getFlags.call(pattern);
+ if (patternIsRegExp || isPrototypeOf$1(RegExpPrototype$1, pattern)) {
pattern = pattern.source;
+ if (flagsAreUndefined) flags = 'flags' in rawPattern ? rawPattern.flags : getFlags(rawPattern);
}
- pattern = pattern === undefined ? '' : String(pattern);
- flags = flags === undefined ? '' : String(flags);
+ pattern = pattern === undefined ? '' : toString$9(pattern);
+ flags = flags === undefined ? '' : toString$9(flags);
rawPattern = pattern;
if (UNSUPPORTED_DOT_ALL && 'dotAll' in re1) {
- dotAll = !!flags && flags.indexOf('s') > -1;
- if (dotAll) flags = flags.replace(/s/g, '');
+ dotAll = !!flags && stringIndexOf$1(flags, 's') > -1;
+ if (dotAll) flags = replace$3(flags, /s/g, '');
}
rawFlags = flags;
if (UNSUPPORTED_Y && 'sticky' in re1) {
- sticky = !!flags && flags.indexOf('y') > -1;
- if (sticky) flags = flags.replace(/y/g, '');
+ sticky = !!flags && stringIndexOf$1(flags, 'y') > -1;
+ if (sticky) flags = replace$3(flags, /y/g, '');
}
if (UNSUPPORTED_NCG) {
groups = handled[1];
}
- result = inheritIfRequired$1(
- CORRECT_NEW ? new NativeRegExp(pattern, flags) : NativeRegExp(pattern, flags),
- thisIsRegExp ? this : RegExpPrototype,
- RegExpWrapper
- );
+ result = inheritIfRequired$1(NativeRegExp(pattern, flags), thisIsRegExp ? this : RegExpPrototype$1, RegExpWrapper);
if (dotAll || sticky || groups.length) {
state = enforceInternalState(result);
proxy(keys$1[index$1++]);
}
- RegExpPrototype.constructor = RegExpWrapper;
- RegExpWrapper.prototype = RegExpPrototype;
- redefine$2(global$3, 'RegExp', RegExpWrapper);
+ RegExpPrototype$1.constructor = RegExpWrapper;
+ RegExpWrapper.prototype = RegExpPrototype$1;
+ redefine$3(global$b, 'RegExp', RegExpWrapper);
}
// https://tc39.es/ecma262/#sec-get-regexp-@@species
};
} // General case.
else {
- var d1 = Math.sqrt(d2),
- b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
- b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
- r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
- r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
- S = (r1 - r0) / rho;
-
- i = function i(t) {
- var s = t * S,
- coshr0 = cosh(r0),
- u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
- return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(rho * s + r0)];
- };
- }
+ var d1 = Math.sqrt(d2),
+ b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
+ b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
+ r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
+ r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
+ S = (r1 - r0) / rho;
+
+ i = function i(t) {
+ var s = t * S,
+ coshr0 = cosh(r0),
+ u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
+ return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(rho * s + r0)];
+ };
+ }
i.duration = S * 1000 * rho / Math.SQRT2;
return i;
return samples;
}
- var $$m = _export;
- var bind$2 = functionBind;
+ var $$t = _export;
+ var bind$4 = functionBind;
// `Function.prototype.bind` method
// https://tc39.es/ecma262/#sec-function.prototype.bind
- $$m({ target: 'Function', proto: true }, {
- bind: bind$2
+ $$t({ target: 'Function', proto: true }, {
+ bind: bind$4
});
var frame = 0,
function schedule (node, name, id, index, group, timing) {
var schedules = node.__transition;
if (!schedules) node.__transition = {};else if (id in schedules) return;
- create$3(node, id, {
+ create$2(node, id, {
name: name,
index: index,
// For context during callback.
return schedule;
}
- function create$3(node, id, self) {
+ function create$2(node, id, self) {
var schedules = node.__transition,
tween; // Initialize the self timer when the transition is created.
// Note the actual delay is not known until the first callback!
delete schedules[i];
} // Cancel any pre-empted transitions.
else if (+i < id) {
- o.state = ENDED;
- o.timer.stop();
- o.on.call("cancel", node, node.__data__, o.index, o.group);
- delete schedules[i];
- }
+ o.state = ENDED;
+ o.timer.stop();
+ o.on.call("cancel", node, node.__data__, o.index, o.group);
+ delete schedules[i];
+ }
} // Defer the first tick to end of the current frame; see d3/d3#1576.
// Note the transition may be canceled after start and before the first tick!
// Note this must be scheduled before the start event; see d3/d3-transition#16!
return function (t) {
if (t === 1) t = b; // Avoid rounding error on end.
else {
- var l = i(t),
- k = w / l[2];
- t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k);
- }
+ var l = i(t),
+ k = w / l[2];
+ t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k);
+ }
g.zoom(null, t);
};
});
clearTimeout(g.wheel);
} // If this wheel event won’t trigger a transform change, ignore it.
else if (t.k === k) return; // Otherwise, capture the mouse point and location at the start.
- else {
- g.mouse = [p, t.invert(p)];
- interrupt(this);
- g.start();
- }
+ else {
+ g.mouse = [p, t.invert(p)];
+ interrupt(this);
+ g.start();
+ }
noevent(event);
g.wheel = setTimeout(wheelidled, wheelDelay);
return score;
}
- var $$l = _export;
+ var call$3 = functionCall;
+ var fixRegExpWellKnownSymbolLogic$1 = fixRegexpWellKnownSymbolLogic;
+ var anObject$1 = anObject$n;
+ var toLength$3 = toLength$c;
+ var toString$8 = toString$k;
+ var requireObjectCoercible$7 = requireObjectCoercible$e;
+ var getMethod$1 = getMethod$7;
+ var advanceStringIndex = advanceStringIndex$3;
+ var regExpExec$1 = regexpExecAbstract;
+
+ // @@match logic
+ fixRegExpWellKnownSymbolLogic$1('match', function (MATCH, nativeMatch, maybeCallNative) {
+ return [
+ // `String.prototype.match` method
+ // https://tc39.es/ecma262/#sec-string.prototype.match
+ function match(regexp) {
+ var O = requireObjectCoercible$7(this);
+ var matcher = regexp == undefined ? undefined : getMethod$1(regexp, MATCH);
+ return matcher ? call$3(matcher, regexp, O) : new RegExp(regexp)[MATCH](toString$8(O));
+ },
+ // `RegExp.prototype[@@match]` method
+ // https://tc39.es/ecma262/#sec-regexp.prototype-@@match
+ function (string) {
+ var rx = anObject$1(this);
+ var S = toString$8(string);
+ var res = maybeCallNative(nativeMatch, rx, S);
+
+ if (res.done) return res.value;
+
+ if (!rx.global) return regExpExec$1(rx, S);
+
+ var fullUnicode = rx.unicode;
+ rx.lastIndex = 0;
+ var A = [];
+ var n = 0;
+ var result;
+ while ((result = regExpExec$1(rx, S)) !== null) {
+ var matchStr = toString$8(result[0]);
+ A[n] = matchStr;
+ if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength$3(rx.lastIndex), fullUnicode);
+ n++;
+ }
+ return n === 0 ? null : A;
+ }
+ ];
+ });
+
+ var $$s = _export;
var FREEZING = freezing;
- var fails$9 = fails$N;
- var isObject$4 = isObject$r;
+ var fails$9 = fails$S;
+ var isObject$4 = isObject$s;
var onFreeze = internalMetadata.exports.onFreeze;
// eslint-disable-next-line es/no-object-freeze -- safe
// `Object.freeze` method
// https://tc39.es/ecma262/#sec-object.freeze
- $$l({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES, sham: !FREEZING }, {
+ $$s({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES, sham: !FREEZING }, {
freeze: function freeze(it) {
return $freeze && isObject$4(it) ? $freeze(onFreeze(it)) : it;
}
}, []);
}
- var DESCRIPTORS$2 = descriptors;
- var global$2 = global$F;
+ var uncurryThis$d = functionUncurryThis;
+
+ // `thisNumberValue` abstract operation
+ // https://tc39.es/ecma262/#sec-thisnumbervalue
+ var thisNumberValue$3 = uncurryThis$d(1.0.valueOf);
+
+ var DESCRIPTORS$3 = descriptors;
+ var global$a = global$1m;
+ var uncurryThis$c = functionUncurryThis;
var isForced = isForced_1;
- var redefine$1 = redefine$g.exports;
- var has = has$j;
- var classof$1 = classofRaw$1;
+ var redefine$2 = redefine$h.exports;
+ var hasOwn$1 = hasOwnProperty_1;
var inheritIfRequired = inheritIfRequired$4;
- var toPrimitive$1 = toPrimitive$7;
- var fails$8 = fails$N;
- var create$2 = objectCreate;
+ var isPrototypeOf = objectIsPrototypeOf;
+ var isSymbol$1 = isSymbol$6;
+ var toPrimitive$1 = toPrimitive$3;
+ var fails$8 = fails$S;
var getOwnPropertyNames = objectGetOwnPropertyNames.f;
var getOwnPropertyDescriptor$2 = objectGetOwnPropertyDescriptor.f;
var defineProperty = objectDefineProperty.f;
+ var thisNumberValue$2 = thisNumberValue$3;
var trim$2 = stringTrim.trim;
var NUMBER = 'Number';
- var NativeNumber = global$2[NUMBER];
+ var NativeNumber = global$a[NUMBER];
var NumberPrototype = NativeNumber.prototype;
-
- // Opera ~12 has broken Object#toString
- var BROKEN_CLASSOF = classof$1(create$2(NumberPrototype)) == NUMBER;
+ var TypeError$4 = global$a.TypeError;
+ var arraySlice$1 = uncurryThis$c(''.slice);
+ var charCodeAt$1 = uncurryThis$c(''.charCodeAt);
+
+ // `ToNumeric` abstract operation
+ // https://tc39.es/ecma262/#sec-tonumeric
+ var toNumeric = function (value) {
+ var primValue = toPrimitive$1(value, 'number');
+ return typeof primValue == 'bigint' ? primValue : toNumber$1(primValue);
+ };
// `ToNumber` abstract operation
// https://tc39.es/ecma262/#sec-tonumber
var toNumber$1 = function (argument) {
- var it = toPrimitive$1(argument, false);
+ var it = toPrimitive$1(argument, 'number');
var first, third, radix, maxCode, digits, length, index, code;
+ if (isSymbol$1(it)) throw TypeError$4('Cannot convert a Symbol value to a number');
if (typeof it == 'string' && it.length > 2) {
it = trim$2(it);
- first = it.charCodeAt(0);
+ first = charCodeAt$1(it, 0);
if (first === 43 || first === 45) {
- third = it.charCodeAt(2);
+ third = charCodeAt$1(it, 2);
if (third === 88 || third === 120) return NaN; // Number('+0x1') should be NaN, old V8 fix
} else if (first === 48) {
- switch (it.charCodeAt(1)) {
+ switch (charCodeAt$1(it, 1)) {
case 66: case 98: radix = 2; maxCode = 49; break; // fast equal of /^0b[01]+$/i
case 79: case 111: radix = 8; maxCode = 55; break; // fast equal of /^0o[0-7]+$/i
default: return +it;
}
- digits = it.slice(2);
+ digits = arraySlice$1(it, 2);
length = digits.length;
for (index = 0; index < length; index++) {
- code = digits.charCodeAt(index);
+ code = charCodeAt$1(digits, index);
// parseInt parses a string to a first unavailable symbol
// but ToNumber should return NaN if a string contains unavailable symbols
if (code < 48 || code > maxCode) return NaN;
// https://tc39.es/ecma262/#sec-number-constructor
if (isForced(NUMBER, !NativeNumber(' 0o1') || !NativeNumber('0b1') || NativeNumber('+0x1'))) {
var NumberWrapper = function Number(value) {
- var it = arguments.length < 1 ? 0 : value;
+ var n = arguments.length < 1 ? 0 : NativeNumber(toNumeric(value));
var dummy = this;
- return dummy instanceof NumberWrapper
- // check on 1..constructor(foo) case
- && (BROKEN_CLASSOF ? fails$8(function () { NumberPrototype.valueOf.call(dummy); }) : classof$1(dummy) != NUMBER)
- ? inheritIfRequired(new NativeNumber(toNumber$1(it)), dummy, NumberWrapper) : toNumber$1(it);
+ // check on 1..constructor(foo) case
+ return isPrototypeOf(NumberPrototype, dummy) && fails$8(function () { thisNumberValue$2(dummy); })
+ ? inheritIfRequired(Object(n), dummy, NumberWrapper) : n;
};
- for (var keys = DESCRIPTORS$2 ? getOwnPropertyNames(NativeNumber) : (
+ for (var keys = DESCRIPTORS$3 ? getOwnPropertyNames(NativeNumber) : (
// ES3:
'MAX_VALUE,MIN_VALUE,NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,' +
// ES2015 (in case, if modules with ES2015 Number statics required before):
- 'EPSILON,isFinite,isInteger,isNaN,isSafeInteger,MAX_SAFE_INTEGER,' +
- 'MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger,' +
+ 'EPSILON,MAX_SAFE_INTEGER,MIN_SAFE_INTEGER,isFinite,isInteger,isNaN,isSafeInteger,parseFloat,parseInt,' +
// ESNext
'fromString,range'
).split(','), j$1 = 0, key; keys.length > j$1; j$1++) {
- if (has(NativeNumber, key = keys[j$1]) && !has(NumberWrapper, key)) {
+ if (hasOwn$1(NativeNumber, key = keys[j$1]) && !hasOwn$1(NumberWrapper, key)) {
defineProperty(NumberWrapper, key, getOwnPropertyDescriptor$2(NativeNumber, key));
}
}
NumberWrapper.prototype = NumberPrototype;
NumberPrototype.constructor = NumberWrapper;
- redefine$1(global$2, NUMBER, NumberWrapper);
+ redefine$2(global$a, NUMBER, NumberWrapper);
}
- var fixRegExpWellKnownSymbolLogic$1 = fixRegexpWellKnownSymbolLogic;
- var anObject$1 = anObject$m;
- var toLength$3 = toLength$q;
- var requireObjectCoercible$7 = requireObjectCoercible$e;
- var advanceStringIndex = advanceStringIndex$3;
- var regExpExec$1 = regexpExecAbstract;
-
- // @@match logic
- fixRegExpWellKnownSymbolLogic$1('match', function (MATCH, nativeMatch, maybeCallNative) {
- return [
- // `String.prototype.match` method
- // https://tc39.es/ecma262/#sec-string.prototype.match
- function match(regexp) {
- var O = requireObjectCoercible$7(this);
- var matcher = regexp == undefined ? undefined : regexp[MATCH];
- return matcher !== undefined ? matcher.call(regexp, O) : new RegExp(regexp)[MATCH](String(O));
- },
- // `RegExp.prototype[@@match]` method
- // https://tc39.es/ecma262/#sec-regexp.prototype-@@match
- function (string) {
- var res = maybeCallNative(nativeMatch, this, string);
- if (res.done) return res.value;
-
- var rx = anObject$1(this);
- var S = String(string);
-
- if (!rx.global) return regExpExec$1(rx, S);
-
- var fullUnicode = rx.unicode;
- rx.lastIndex = 0;
- var A = [];
- var n = 0;
- var result;
- while ((result = regExpExec$1(rx, S)) !== null) {
- var matchStr = String(result[0]);
- A[n] = matchStr;
- if (matchStr === '') rx.lastIndex = advanceStringIndex(S, toLength$3(rx.lastIndex), fullUnicode);
- n++;
- }
- return n === 0 ? null : A;
- }
- ];
- });
-
var diacritics = {};
var remove$6 = diacritics.remove = removeDiacritics;
} else if (reference_1$1.tashkeel.indexOf(word[w]) > -1) {
// tashkeel - add without changing state
output += word[w];
- } else if (nextLetter === ' ' || // last Arabic letter in this word
- reference_1$1.lineBreakers.indexOf(word[w]) > -1) {
+ } else if (nextLetter === ' ' // last Arabic letter in this word
+ || reference_1$1.lineBreakers.indexOf(word[w]) > -1) {
// the current letter is known to break lines
output += CharShaper_1$1.CharShaper(word[w], state === 'initial' ? 'isolated' : 'final');
state = 'initial';
return ret;
}
- var DESCRIPTORS$1 = descriptors;
+ var DESCRIPTORS$2 = descriptors;
+ var uncurryThis$b = functionUncurryThis;
var objectKeys = objectKeys$4;
- var toIndexedObject = toIndexedObject$b;
- var propertyIsEnumerable = objectPropertyIsEnumerable.f;
+ var toIndexedObject = toIndexedObject$c;
+ var $propertyIsEnumerable = objectPropertyIsEnumerable.f;
+
+ var propertyIsEnumerable = uncurryThis$b($propertyIsEnumerable);
+ var push$2 = uncurryThis$b([].push);
// `Object.{ entries, values }` methods implementation
var createMethod$1 = function (TO_ENTRIES) {
var key;
while (length > i) {
key = keys[i++];
- if (!DESCRIPTORS$1 || propertyIsEnumerable.call(O, key)) {
- result.push(TO_ENTRIES ? [key, O[key]] : O[key]);
+ if (!DESCRIPTORS$2 || propertyIsEnumerable(O, key)) {
+ push$2(result, TO_ENTRIES ? [key, O[key]] : O[key]);
}
}
return result;
values: createMethod$1(false)
};
- var $$k = _export;
+ var $$r = _export;
var $values = objectToArray.values;
// `Object.values` method
// https://tc39.es/ecma262/#sec-object.values
- $$k({ target: 'Object', stat: true }, {
+ $$r({ target: 'Object', stat: true }, {
values: function values(O) {
return $values(O);
}
return delete s[k];
}
};
- }(); //
+ }();
+
+ var _listeners = {}; //
// corePreferences is an interface for persisting basic key-value strings
// within and between iD sessions on the same site.
//
* @returns {boolean} true if the action succeeded
*/
-
function corePreferences(k, v) {
try {
- if (arguments.length === 1) return _storage.getItem(k);else if (v === null) _storage.removeItem(k);else _storage.setItem(k, v);
+ if (v === undefined) return _storage.getItem(k);else if (v === null) _storage.removeItem(k);else _storage.setItem(k, v);
+
+ if (_listeners[k]) {
+ _listeners[k].forEach(function (handler) {
+ return handler(v);
+ });
+ }
+
return true;
} catch (e) {
/* eslint-disable no-console */
return false;
}
- }
+ } // adds an event listener which is triggered whenever
+
+
+ corePreferences.onChange = function (k, handler) {
+ _listeners[k] = _listeners[k] || [];
+
+ _listeners[k].push(handler);
+ };
var vparse = {exports: {}};
var parseVersion = vparse.exports;
var name = "iD";
- var version = "2.20.2";
+ var version = "2.20.3";
var description = "A friendly editor for OpenStreetMap";
var main = "dist/iD.min.js";
var repository = "github:openstreetmap/iD";
var bugs = "https://github.com/openstreetmap/iD/issues";
var keywords = ["editor","openstreetmap"];
var license = "ISC";
- var scripts = {all:"npm-run-all -s clean build build:legacy dist",build:"npm-run-all -s build:css build:data build:dev","build:css":"node scripts/build_css.js","build:data":"shx mkdir -p dist/data && node scripts/build_data.js","build:dev":"rollup --config config/rollup.config.dev.js","build:legacy":"rollup --config config/rollup.config.legacy.js","build:stats":"rollup --config config/rollup.config.stats.js",clean:"shx rm -f dist/*.js dist/*.map dist/*.css dist/img/*.svg",dist:"npm-run-all -p dist:**","dist:mapillary":"shx mkdir -p dist/mapillary-js && shx cp -R node_modules/mapillary-js/dist/* dist/mapillary-js/","dist:pannellum":"shx mkdir -p dist/pannellum-streetside && shx cp -R node_modules/pannellum/build/* dist/pannellum-streetside/","dist:min:iD":"uglifyjs dist/iD.legacy.js --compress --mangle --output dist/iD.min.js","dist:svg:iD":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"iD-%s\" --symbol-sprite dist/img/iD-sprite.svg \"svg/iD-sprite/**/*.svg\"","dist:svg:community":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"community-%s\" --symbol-sprite dist/img/community-sprite.svg node_modules/osm-community-index/dist/img/*.svg","dist:svg:fa":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/fa-sprite.svg svg/fontawesome/*.svg","dist:svg:maki":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"maki-%s\" --symbol-sprite dist/img/maki-sprite.svg node_modules/@mapbox/maki/icons/*.svg","dist:svg:mapillary:signs":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/mapillary-sprite.svg node_modules/mapillary_sprite_source/package_signs/*.svg","dist:svg:mapillary:objects":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/mapillary-object-sprite.svg node_modules/mapillary_sprite_source/package_objects/*.svg","dist:svg:temaki":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"temaki-%s\" --symbol-sprite dist/img/temaki-sprite.svg node_modules/@ideditor/temaki/icons/*.svg",imagery:"node scripts/update_imagery.js",lint:"eslint scripts test/spec modules","lint:fix":"eslint scripts test/spec modules --fix",start:"npm-run-all -s build start:server",quickstart:"npm-run-all -s build:dev start:server","start:server":"node scripts/server.js",test:"npm-run-all -s lint build:css build:data build:legacy test:spec","test:spec":"phantomjs --web-security=no node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js test/index.html spec",translations:"node scripts/update_locales.js"};
+ var scripts = {all:"npm-run-all -s clean build build:legacy dist",build:"npm-run-all -s build:css build:data build:dev","build:css":"node scripts/build_css.js","build:data":"shx mkdir -p dist/data && node scripts/build_data.js","build:dev":"rollup --config config/rollup.config.dev.js","build:legacy":"rollup --config config/rollup.config.legacy.js","build:stats":"rollup --config config/rollup.config.stats.js",clean:"shx rm -f dist/*.js dist/*.map dist/*.css dist/img/*.svg",dist:"npm-run-all -p dist:**","dist:mapillary":"shx mkdir -p dist/mapillary-js && shx cp -R node_modules/mapillary-js/dist/* dist/mapillary-js/","dist:pannellum":"shx mkdir -p dist/pannellum-streetside && shx cp -R node_modules/pannellum/build/* dist/pannellum-streetside/","dist:min:iD":"uglifyjs dist/iD.legacy.js --compress --mangle --output dist/iD.min.js","dist:svg:iD":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"iD-%s\" --symbol-sprite dist/img/iD-sprite.svg \"svg/iD-sprite/**/*.svg\"","dist:svg:community":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"community-%s\" --symbol-sprite dist/img/community-sprite.svg node_modules/osm-community-index/dist/img/*.svg","dist:svg:fa":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/fa-sprite.svg svg/fontawesome/*.svg","dist:svg:maki":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"maki-%s\" --symbol-sprite dist/img/maki-sprite.svg node_modules/@mapbox/maki/icons/*.svg","dist:svg:mapillary:signs":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/mapillary-sprite.svg node_modules/mapillary_sprite_source/package_signs/*.svg","dist:svg:mapillary:objects":"svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/mapillary-object-sprite.svg node_modules/mapillary_sprite_source/package_objects/*.svg","dist:svg:temaki":"svg-sprite --symbol --symbol-dest . --shape-id-generator \"temaki-%s\" --symbol-sprite dist/img/temaki-sprite.svg node_modules/@ideditor/temaki/icons/*.svg",imagery:"node scripts/update_imagery.js",lint:"eslint scripts test/spec modules","lint:fix":"eslint scripts test/spec modules --fix",start:"npm-run-all -s build start:server",quickstart:"npm-run-all -s build:dev start:server","start:server":"node scripts/server.js",test:"npm-run-all -s lint build test:spec","test:spec":"karma start karma.conf.js",translations:"node scripts/update_locales.js"};
var dependencies = {"@ideditor/country-coder":"~5.0.3","@ideditor/location-conflation":"~1.0.2","@mapbox/geojson-area":"^0.2.2","@mapbox/sexagesimal":"1.2.0","@mapbox/vector-tile":"^1.3.1","@tmcw/togeojson":"^4.5.0","@turf/bbox-clip":"^6.0.0","abortcontroller-polyfill":"^1.4.0","aes-js":"^3.1.2","alif-toolkit":"^1.2.9","core-js":"^3.6.5",diacritics:"1.3.0","fast-deep-equal":"~3.1.1","fast-json-stable-stringify":"2.1.0","lodash-es":"~4.17.15",marked:"~2.0.0","node-diff3":"2.1.0","osm-auth":"1.1.0",pannellum:"2.5.6",pbf:"^3.2.1","polygon-clipping":"~0.15.1",rbush:"3.0.1","whatwg-fetch":"^3.4.1","which-polygon":"2.2.0"};
- var devDependencies = {"@babel/core":"^7.11.6","@babel/preset-env":"^7.11.5","@fortawesome/fontawesome-svg-core":"^1.2.32","@fortawesome/free-brands-svg-icons":"~5.15.1","@fortawesome/free-regular-svg-icons":"~5.15.1","@fortawesome/free-solid-svg-icons":"~5.15.1","@ideditor/temaki":"~4.4.0","@mapbox/maki":"^6.0.0","@rollup/plugin-babel":"^5.2.1","@rollup/plugin-commonjs":"^21.0.0","@rollup/plugin-json":"^4.0.1","@rollup/plugin-node-resolve":"~13.0.5",autoprefixer:"^10.0.1",btoa:"^1.2.1",chai:"^4.1.0","cldr-core":"37.0.0","cldr-localenames-full":"37.0.0",colors:"^1.1.2","concat-files":"^0.1.1",d3:"~6.6.0","editor-layer-index":"github:osmlab/editor-layer-index#gh-pages",eslint:"^7.1.0",gaze:"^1.1.3",glob:"^7.1.0",happen:"^0.3.1","js-yaml":"^4.0.0","json-stringify-pretty-compact":"^3.0.0",mapillary_sprite_source:"^1.8.0","mapillary-js":"4.0.0",minimist:"^1.2.3",mocha:"^7.0.1","mocha-phantomjs-core":"^2.1.0","name-suggestion-index":"~6.0","node-fetch":"^2.6.1","npm-run-all":"^4.0.0","object-inspect":"1.10.3","osm-community-index":"~5.1.0","phantomjs-prebuilt":"~2.1.16",postcss:"^8.1.1","postcss-selector-prepend":"^0.5.0",rollup:"~2.52.8","rollup-plugin-includepaths":"~0.2.3","rollup-plugin-progress":"^1.1.1","rollup-plugin-visualizer":"~4.2.0",shelljs:"^0.8.0",shx:"^0.3.0",sinon:"7.5.0","sinon-chai":"^3.3.0",smash:"0.0","static-server":"^2.2.1","svg-sprite":"1.5.1","uglify-js":"~3.13.0",vparse:"~1.1.0"};
+ var devDependencies = {"@babel/core":"^7.11.6","@babel/preset-env":"^7.11.5","@fortawesome/fontawesome-svg-core":"^1.2.32","@fortawesome/free-brands-svg-icons":"~5.15.1","@fortawesome/free-regular-svg-icons":"~5.15.1","@fortawesome/free-solid-svg-icons":"~5.15.1","@ideditor/temaki":"~5.0.0","@mapbox/maki":"^6.0.0","@rollup/plugin-babel":"^5.2.1","@rollup/plugin-commonjs":"^21.0.0","@rollup/plugin-json":"^4.0.1","@rollup/plugin-node-resolve":"~13.0.5",autoprefixer:"^10.0.1",btoa:"^1.2.1",chai:"^4.3.4","cldr-core":"37.0.0","cldr-localenames-full":"37.0.0",chalk:"^4.1.2","concat-files":"^0.1.1",d3:"~6.6.0","editor-layer-index":"github:osmlab/editor-layer-index#gh-pages",eslint:"^7.1.0","fetch-mock":"^9.11.0",gaze:"^1.1.3",glob:"^7.1.0",happen:"^0.3.2","js-yaml":"^4.0.0","json-stringify-pretty-compact":"^3.0.0",karma:"^6.3.5","karma-chrome-launcher":"^3.1.0","karma-coverage":"^2.0.3","karma-mocha":"^2.0.1","karma-remap-istanbul":"^0.6.0",mapillary_sprite_source:"^1.8.0","mapillary-js":"4.0.0",minimist:"^1.2.3",mocha:"^8.4.0","name-suggestion-index":"~6.0","node-fetch":"^2.6.1","npm-run-all":"^4.0.0","object-inspect":"1.10.3","osm-community-index":"~5.1.0",postcss:"^8.1.1","postcss-selector-prepend":"^0.5.0",rollup:"~2.52.8","rollup-plugin-includepaths":"~0.2.3","rollup-plugin-progress":"^1.1.1","rollup-plugin-visualizer":"~4.2.0",shelljs:"^0.8.0",shx:"^0.3.0",sinon:"^11.1.2","sinon-chai":"^3.7.0",smash:"0.0","static-server":"^2.2.1","svg-sprite":"1.5.1","uglify-js":"~3.13.0",vparse:"~1.1.0"};
var engines = {node:">=10"};
var browserslist = ["> 0.2%, last 6 major versions, Firefox ESR, IE 11, maintained node versions"];
var packageJSON = {
return _this;
}
- var classof = classofRaw$1;
-
- // `thisNumberValue` abstract operation
- // https://tc39.es/ecma262/#sec-thisnumbervalue
- var thisNumberValue$2 = function (value) {
- if (typeof value != 'number' && classof(value) != 'Number') {
- throw TypeError('Incorrect invocation');
- }
- return +value;
- };
-
- var toInteger$1 = toInteger$b;
+ var global$9 = global$1m;
+ var toIntegerOrInfinity$1 = toIntegerOrInfinity$b;
+ var toString$7 = toString$k;
var requireObjectCoercible$6 = requireObjectCoercible$e;
+ var RangeError$5 = global$9.RangeError;
+
// `String.prototype.repeat` method implementation
// https://tc39.es/ecma262/#sec-string.prototype.repeat
var stringRepeat = function repeat(count) {
- var str = String(requireObjectCoercible$6(this));
+ var str = toString$7(requireObjectCoercible$6(this));
var result = '';
- var n = toInteger$1(count);
- if (n < 0 || n == Infinity) throw RangeError('Wrong number of repetitions');
+ var n = toIntegerOrInfinity$1(count);
+ if (n < 0 || n == Infinity) throw RangeError$5('Wrong number of repetitions');
for (;n > 0; (n >>>= 1) && (str += str)) if (n & 1) result += str;
return result;
};
- var $$j = _export;
- var toInteger = toInteger$b;
- var thisNumberValue$1 = thisNumberValue$2;
- var repeat$2 = stringRepeat;
- var fails$7 = fails$N;
-
- var nativeToFixed = 1.0.toFixed;
- var floor = Math.floor;
+ var $$q = _export;
+ var global$8 = global$1m;
+ var uncurryThis$a = functionUncurryThis;
+ var toIntegerOrInfinity = toIntegerOrInfinity$b;
+ var thisNumberValue$1 = thisNumberValue$3;
+ var $repeat$1 = stringRepeat;
+ var fails$7 = fails$S;
+
+ var RangeError$4 = global$8.RangeError;
+ var String$1 = global$8.String;
+ var floor$2 = Math.floor;
+ var repeat$2 = uncurryThis$a($repeat$1);
+ var stringSlice$3 = uncurryThis$a(''.slice);
+ var un$ToFixed = uncurryThis$a(1.0.toFixed);
- var pow = function (x, n, acc) {
- return n === 0 ? acc : n % 2 === 1 ? pow(x, n - 1, acc * x) : pow(x * x, n / 2, acc);
+ var pow$1 = function (x, n, acc) {
+ return n === 0 ? acc : n % 2 === 1 ? pow$1(x, n - 1, acc * x) : pow$1(x * x, n / 2, acc);
};
var log = function (x) {
while (++index < 6) {
c2 += n * data[index];
data[index] = c2 % 1e7;
- c2 = floor(c2 / 1e7);
+ c2 = floor$2(c2 / 1e7);
}
};
var c = 0;
while (--index >= 0) {
c += data[index];
- data[index] = floor(c / n);
+ data[index] = floor$2(c / n);
c = (c % n) * 1e7;
}
};
var s = '';
while (--index >= 0) {
if (s !== '' || index === 0 || data[index] !== 0) {
- var t = String(data[index]);
- s = s === '' ? t : s + repeat$2.call('0', 7 - t.length) + t;
+ var t = String$1(data[index]);
+ s = s === '' ? t : s + repeat$2('0', 7 - t.length) + t;
}
} return s;
};
- var FORCED$4 = nativeToFixed && (
- 0.00008.toFixed(3) !== '0.000' ||
- 0.9.toFixed(0) !== '1' ||
- 1.255.toFixed(2) !== '1.25' ||
- 1000000000000000128.0.toFixed(0) !== '1000000000000000128'
- ) || !fails$7(function () {
+ var FORCED$6 = fails$7(function () {
+ return un$ToFixed(0.00008, 3) !== '0.000' ||
+ un$ToFixed(0.9, 0) !== '1' ||
+ un$ToFixed(1.255, 2) !== '1.25' ||
+ un$ToFixed(1000000000000000128.0, 0) !== '1000000000000000128';
+ }) || !fails$7(function () {
// V8 ~ Android 4.3-
- nativeToFixed.call({});
+ un$ToFixed({});
});
// `Number.prototype.toFixed` method
// https://tc39.es/ecma262/#sec-number.prototype.tofixed
- $$j({ target: 'Number', proto: true, forced: FORCED$4 }, {
+ $$q({ target: 'Number', proto: true, forced: FORCED$6 }, {
toFixed: function toFixed(fractionDigits) {
var number = thisNumberValue$1(this);
- var fractDigits = toInteger(fractionDigits);
+ var fractDigits = toIntegerOrInfinity(fractionDigits);
var data = [0, 0, 0, 0, 0, 0];
var sign = '';
var result = '0';
var e, z, j, k;
- if (fractDigits < 0 || fractDigits > 20) throw RangeError('Incorrect fraction digits');
+ if (fractDigits < 0 || fractDigits > 20) throw RangeError$4('Incorrect fraction digits');
// eslint-disable-next-line no-self-compare -- NaN check
if (number != number) return 'NaN';
- if (number <= -1e21 || number >= 1e21) return String(number);
+ if (number <= -1e21 || number >= 1e21) return String$1(number);
if (number < 0) {
sign = '-';
number = -number;
}
if (number > 1e-21) {
- e = log(number * pow(2, 69, 1)) - 69;
- z = e < 0 ? number * pow(2, -e, 1) : number / pow(2, e, 1);
+ e = log(number * pow$1(2, 69, 1)) - 69;
+ z = e < 0 ? number * pow$1(2, -e, 1) : number / pow$1(2, e, 1);
z *= 0x10000000000000;
e = 52 - e;
if (e > 0) {
multiply(data, 1e7, 0);
j -= 7;
}
- multiply(data, pow(10, j, 1), 0);
+ multiply(data, pow$1(10, j, 1), 0);
j = e - 1;
while (j >= 23) {
divide(data, 1 << 23);
} else {
multiply(data, 0, z);
multiply(data, 1 << -e, 0);
- result = dataToString(data) + repeat$2.call('0', fractDigits);
+ result = dataToString(data) + repeat$2('0', fractDigits);
}
}
if (fractDigits > 0) {
k = result.length;
result = sign + (k <= fractDigits
- ? '0.' + repeat$2.call('0', fractDigits - k) + result
- : result.slice(0, k - fractDigits) + '.' + result.slice(k - fractDigits));
+ ? '0.' + repeat$2('0', fractDigits - k) + result
+ : stringSlice$3(result, 0, k - fractDigits) + '.' + stringSlice$3(result, k - fractDigits));
} else {
result = sign + result;
} return result;
}
});
- var global$1 = global$F;
+ var global$7 = global$1m;
- var globalIsFinite = global$1.isFinite;
+ var globalIsFinite = global$7.isFinite;
// `Number.isFinite` method
// https://tc39.es/ecma262/#sec-number.isfinite
return typeof it == 'number' && globalIsFinite(it);
};
- var $$i = _export;
+ var $$p = _export;
var numberIsFinite = numberIsFinite$1;
// `Number.isFinite` method
// https://tc39.es/ecma262/#sec-number.isfinite
- $$i({ target: 'Number', stat: true }, { isFinite: numberIsFinite });
+ $$p({ target: 'Number', stat: true }, { isFinite: numberIsFinite });
- var $$h = _export;
+ var $$o = _export;
+ var global$6 = global$1m;
+ var uncurryThis$9 = functionUncurryThis;
var toAbsoluteIndex = toAbsoluteIndex$8;
- var fromCharCode = String.fromCharCode;
+ var RangeError$3 = global$6.RangeError;
+ var fromCharCode$1 = String.fromCharCode;
// eslint-disable-next-line es/no-string-fromcodepoint -- required for testing
var $fromCodePoint = String.fromCodePoint;
+ var join$2 = uncurryThis$9([].join);
// length should be 1, old FF problem
var INCORRECT_LENGTH = !!$fromCodePoint && $fromCodePoint.length != 1;
// `String.fromCodePoint` method
// https://tc39.es/ecma262/#sec-string.fromcodepoint
- $$h({ target: 'String', stat: true, forced: INCORRECT_LENGTH }, {
+ $$o({ target: 'String', stat: true, forced: INCORRECT_LENGTH }, {
// eslint-disable-next-line no-unused-vars -- required for `.length`
fromCodePoint: function fromCodePoint(x) {
var elements = [];
var code;
while (length > i) {
code = +arguments[i++];
- if (toAbsoluteIndex(code, 0x10FFFF) !== code) throw RangeError(code + ' is not a valid code point');
- elements.push(code < 0x10000
- ? fromCharCode(code)
- : fromCharCode(((code -= 0x10000) >> 10) + 0xD800, code % 0x400 + 0xDC00)
- );
- } return elements.join('');
+ if (toAbsoluteIndex(code, 0x10FFFF) !== code) throw RangeError$3(code + ' is not a valid code point');
+ elements[i] = code < 0x10000
+ ? fromCharCode$1(code)
+ : fromCharCode$1(((code -= 0x10000) >> 10) + 0xD800, code % 0x400 + 0xDC00);
+ } return join$2(elements, '');
}
});
+ var call$2 = functionCall;
var fixRegExpWellKnownSymbolLogic = fixRegexpWellKnownSymbolLogic;
- var anObject = anObject$m;
+ var anObject = anObject$n;
var requireObjectCoercible$5 = requireObjectCoercible$e;
var sameValue = sameValue$1;
+ var toString$6 = toString$k;
+ var getMethod = getMethod$7;
var regExpExec = regexpExecAbstract;
// @@search logic
// https://tc39.es/ecma262/#sec-string.prototype.search
function search(regexp) {
var O = requireObjectCoercible$5(this);
- var searcher = regexp == undefined ? undefined : regexp[SEARCH];
- return searcher !== undefined ? searcher.call(regexp, O) : new RegExp(regexp)[SEARCH](String(O));
+ var searcher = regexp == undefined ? undefined : getMethod(regexp, SEARCH);
+ return searcher ? call$2(searcher, regexp, O) : new RegExp(regexp)[SEARCH](toString$6(O));
},
// `RegExp.prototype[@@search]` method
// https://tc39.es/ecma262/#sec-regexp.prototype-@@search
function (string) {
- var res = maybeCallNative(nativeSearch, this, string);
- if (res.done) return res.value;
-
var rx = anObject(this);
- var S = String(string);
+ var S = toString$6(string);
+ var res = maybeCallNative(nativeSearch, rx, S);
+
+ if (res.done) return res.value;
var previousLastIndex = rx.lastIndex;
if (!sameValue(previousLastIndex, 0)) rx.lastIndex = 0;
var inputValidation = {};
- var $$g = _export;
+ var $$n = _export;
var $includes = arrayIncludes.includes;
- var addToUnscopables$1 = addToUnscopables$5;
+ var addToUnscopables$2 = addToUnscopables$6;
// `Array.prototype.includes` method
// https://tc39.es/ecma262/#sec-array.prototype.includes
- $$g({ target: 'Array', proto: true }, {
+ $$n({ target: 'Array', proto: true }, {
includes: function includes(el /* , fromIndex = 0 */) {
return $includes(this, el, arguments.length > 1 ? arguments[1] : undefined);
}
});
// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
- addToUnscopables$1('includes');
+ addToUnscopables$2('includes');
var validateCenter$1 = {};
return argument === null || argument === undefined;
}
- var $$f = _export;
+ var $$m = _export;
// `Number.EPSILON` constant
// https://tc39.es/ecma262/#sec-number.epsilon
- $$f({ target: 'Number', stat: true }, {
+ $$m({ target: 'Number', stat: true }, {
EPSILON: Math.pow(2, -52)
});
+ var uncurryThis$8 = functionUncurryThis;
var requireObjectCoercible$4 = requireObjectCoercible$e;
+ var toString$5 = toString$k;
var quot = /"/g;
+ var replace$2 = uncurryThis$8(''.replace);
// `CreateHTML` abstract operation
// https://tc39.es/ecma262/#sec-createhtml
var createHtml = function (string, tag, attribute, value) {
- var S = String(requireObjectCoercible$4(string));
+ var S = toString$5(requireObjectCoercible$4(string));
var p1 = '<' + tag;
- if (attribute !== '') p1 += ' ' + attribute + '="' + String(value).replace(quot, '"') + '"';
+ if (attribute !== '') p1 += ' ' + attribute + '="' + replace$2(toString$5(value), quot, '"') + '"';
return p1 + '>' + S + '</' + tag + '>';
};
- var fails$6 = fails$N;
+ var fails$6 = fails$S;
// check the existence of a method, lowercase
// of a tag and escaping quotes in arguments
});
};
- var $$e = _export;
+ var $$l = _export;
var createHTML = createHtml;
var forcedStringHTMLMethod = stringHtmlForced;
// `String.prototype.link` method
// https://tc39.es/ecma262/#sec-string.prototype.link
- $$e({ target: 'String', proto: true, forced: forcedStringHTMLMethod('link') }, {
+ $$l({ target: 'String', proto: true, forced: forcedStringHTMLMethod('link') }, {
link: function link(url) {
return createHTML(this, 'a', 'href', url);
}
return node;
}
- function split(key, v, comparator) {
+ function split$2(key, v, comparator) {
var left = null;
var right = null;
Tree.prototype.update = function (key, newKey, newData) {
var comparator = this._comparator;
- var _a = split(key, this._root, comparator),
+ var _a = split$2(key, this._root, comparator),
left = _a.left,
right = _a.right;
};
Tree.prototype.split = function (key) {
- return split(key, this._root, this._comparator);
+ return split$2(key, this._root, this._comparator);
};
return Tree;
var precision = geojsonPrecision.exports;
- var $$d = _export;
- var fails$5 = fails$N;
- var toObject = toObject$i;
- var toPrimitive = toPrimitive$7;
+ var $$k = _export;
+ var fails$5 = fails$S;
+ var toObject$1 = toObject$j;
+ var toPrimitive = toPrimitive$3;
- var FORCED$3 = fails$5(function () {
+ var FORCED$5 = fails$5(function () {
return new Date(NaN).toJSON() !== null
|| Date.prototype.toJSON.call({ toISOString: function () { return 1; } }) !== 1;
});
// `Date.prototype.toJSON` method
// https://tc39.es/ecma262/#sec-date.prototype.tojson
- $$d({ target: 'Date', proto: true, forced: FORCED$3 }, {
+ $$k({ target: 'Date', proto: true, forced: FORCED$5 }, {
// eslint-disable-next-line no-unused-vars -- required for `.length`
toJSON: function toJSON(key) {
- var O = toObject(this);
- var pv = toPrimitive(O);
+ var O = toObject$1(this);
+ var pv = toPrimitive(O, 'number');
return typeof pv == 'number' && !isFinite(pv) ? null : O.toISOString();
}
});
- var $$c = _export;
+ var $$j = _export;
+ var call$1 = functionCall;
// `URL.prototype.toJSON` method
// https://url.spec.whatwg.org/#dom-url-tojson
- $$c({ target: 'URL', proto: true, enumerable: true }, {
+ $$j({ target: 'URL', proto: true, enumerable: true }, {
toJSON: function toJSON() {
- return URL.prototype.toString.call(this);
+ return call$1(URL.prototype.toString, this);
}
});
return aRank > bRank ? 1 : aRank < bRank ? -1 : a.id.localeCompare(b.id);
}
- var $$b = _export;
+ var $$i = _export;
// `Number.MAX_SAFE_INTEGER` constant
// https://tc39.es/ecma262/#sec-number.max_safe_integer
- $$b({ target: 'Number', stat: true }, {
+ $$i({ target: 'Number', stat: true }, {
MAX_SAFE_INTEGER: 0x1FFFFFFFFFFFFF
});
// Like selection.property('value', ...), but avoids no-op value sets,
// which can result in layout/repaint thrashing in some situations.
+
+ /** @returns {string} */
function utilGetSetValue(selection, value) {
function d3_selection_value(value) {
function valueNull() {
return _this;
}
- var $$a = _export;
+ var $$h = _export;
var $findIndex = arrayIteration.findIndex;
- var addToUnscopables = addToUnscopables$5;
+ var addToUnscopables$1 = addToUnscopables$6;
var FIND_INDEX = 'findIndex';
var SKIPS_HOLES = true;
// `Array.prototype.findIndex` method
// https://tc39.es/ecma262/#sec-array.prototype.findindex
- $$a({ target: 'Array', proto: true, forced: SKIPS_HOLES }, {
+ $$h({ target: 'Array', proto: true, forced: SKIPS_HOLES }, {
findIndex: function findIndex(callbackfn /* , that = undefined */) {
return $findIndex(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
});
// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
- addToUnscopables(FIND_INDEX);
+ addToUnscopables$1(FIND_INDEX);
+ var global$5 = global$1m;
var isRegExp = isRegexp;
+ var TypeError$3 = global$5.TypeError;
+
var notARegexp = function (it) {
if (isRegExp(it)) {
- throw TypeError("The method doesn't accept regular expressions");
+ throw TypeError$3("The method doesn't accept regular expressions");
} return it;
};
- var wellKnownSymbol = wellKnownSymbol$s;
+ var wellKnownSymbol = wellKnownSymbol$t;
var MATCH = wellKnownSymbol('match');
} return false;
};
- var $$9 = _export;
+ var $$g = _export;
+ var uncurryThis$7 = functionUncurryThis;
var notARegExp$2 = notARegexp;
var requireObjectCoercible$3 = requireObjectCoercible$e;
+ var toString$4 = toString$k;
var correctIsRegExpLogic$2 = correctIsRegexpLogic;
+ var stringIndexOf = uncurryThis$7(''.indexOf);
+
// `String.prototype.includes` method
// https://tc39.es/ecma262/#sec-string.prototype.includes
- $$9({ target: 'String', proto: true, forced: !correctIsRegExpLogic$2('includes') }, {
+ $$g({ target: 'String', proto: true, forced: !correctIsRegExpLogic$2('includes') }, {
includes: function includes(searchString /* , position = 0 */) {
- return !!~String(requireObjectCoercible$3(this))
- .indexOf(notARegExp$2(searchString), arguments.length > 1 ? arguments[1] : undefined);
+ return !!~stringIndexOf(
+ toString$4(requireObjectCoercible$3(this)),
+ toString$4(notARegExp$2(searchString)),
+ arguments.length > 1 ? arguments[1] : undefined
+ );
}
});
- var _mainLocalizer = coreLocalizer(); // singleton
+ /** Detect free variable `global` from Node.js. */
+ var freeGlobal = (typeof global === "undefined" ? "undefined" : _typeof(global)) == 'object' && global && global.Object === Object && global;
+ /** Detect free variable `self`. */
- var _t = _mainLocalizer.t;
- // coreLocalizer manages language and locale parameters including translated strings
- //
+ var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self;
+ /** Used as a reference to the global object. */
- function coreLocalizer() {
- var localizer = {};
- var _dataLanguages = {}; // `_dataLocales` is an object containing all _supported_ locale codes -> language info.
- // * `rtl` - right-to-left or left-to-right text direction
- // * `pct` - the percent of strings translated; 1 = 100%, full coverage
- //
- // {
- // en: { rtl: false, pct: {…} },
- // de: { rtl: false, pct: {…} },
- // …
- // }
+ var root = freeGlobal || freeSelf || Function('return this')();
- var _dataLocales = {}; // `localeStrings` is an object containing all _loaded_ locale codes -> string data.
- // {
- // en: { icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, … },
- // de: { icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, … },
- // …
- // }
+ /** Built-in value references. */
- var _localeStrings = {}; // the current locale
+ var _Symbol = root.Symbol;
- var _localeCode = 'en-US'; // `_localeCodes` must contain `_localeCode` first, optionally followed by fallbacks
+ /** Used for built-in method references. */
- var _localeCodes = ['en-US', 'en'];
- var _languageCode = 'en';
- var _textDirection = 'ltr';
- var _usesMetric = false;
- var _languageNames = {};
- var _scriptNames = {}; // getters for the current locale parameters
+ var objectProto$1 = Object.prototype;
+ /** Used to check objects for own properties. */
- localizer.localeCode = function () {
- return _localeCode;
- };
+ var hasOwnProperty$2 = objectProto$1.hasOwnProperty;
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
- localizer.localeCodes = function () {
- return _localeCodes;
- };
+ var nativeObjectToString$1 = objectProto$1.toString;
+ /** Built-in value references. */
- localizer.languageCode = function () {
- return _languageCode;
- };
+ var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined;
+ /**
+ * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the raw `toStringTag`.
+ */
- localizer.textDirection = function () {
- return _textDirection;
- };
+ function getRawTag(value) {
+ var isOwn = hasOwnProperty$2.call(value, symToStringTag$1),
+ tag = value[symToStringTag$1];
- localizer.usesMetric = function () {
- return _usesMetric;
- };
+ try {
+ value[symToStringTag$1] = undefined;
+ var unmasked = true;
+ } catch (e) {}
- localizer.languageNames = function () {
- return _languageNames;
- };
+ var result = nativeObjectToString$1.call(value);
- localizer.scriptNames = function () {
- return _scriptNames;
- }; // The client app may want to manually set the locale, regardless of the
- // settings provided by the browser
+ if (unmasked) {
+ if (isOwn) {
+ value[symToStringTag$1] = tag;
+ } else {
+ delete value[symToStringTag$1];
+ }
+ }
+ return result;
+ }
- var _preferredLocaleCodes = [];
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
- localizer.preferredLocaleCodes = function (codes) {
- if (!arguments.length) return _preferredLocaleCodes;
+ var nativeObjectToString = objectProto.toString;
+ /**
+ * Converts `value` to a string using `Object.prototype.toString`.
+ *
+ * @private
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ */
- if (typeof codes === 'string') {
- // be generous and accept delimited strings as input
- _preferredLocaleCodes = codes.split(/,|;| /gi).filter(Boolean);
- } else {
- _preferredLocaleCodes = codes;
- }
+ function objectToString(value) {
+ return nativeObjectToString.call(value);
+ }
- return localizer;
- };
+ /** `Object#toString` result references. */
- var _loadPromise;
+ var nullTag = '[object Null]',
+ undefinedTag = '[object Undefined]';
+ /** Built-in value references. */
- localizer.ensureLoaded = function () {
- if (_loadPromise) return _loadPromise;
- var filesToFetch = ['languages', // load the list of languages
- 'locales' // load the list of supported locales
- ];
- var localeDirs = {
- general: 'locales',
- tagging: 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/translations'
- };
- var fileMap = _mainFileFetcher.fileMap();
+ var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined;
+ /**
+ * The base implementation of `getTag` without fallbacks for buggy environments.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
- for (var scopeId in localeDirs) {
- var key = "locales_index_".concat(scopeId);
+ function baseGetTag(value) {
+ if (value == null) {
+ return value === undefined ? undefinedTag : nullTag;
+ }
- if (!fileMap[key]) {
- fileMap[key] = localeDirs[scopeId] + '/index.min.json';
- }
+ return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
+ }
- filesToFetch.push(key);
- }
+ /**
+ * 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
+ */
+ function isObjectLike(value) {
+ return value != null && _typeof(value) == 'object';
+ }
- return _loadPromise = Promise.all(filesToFetch.map(function (key) {
- return _mainFileFetcher.get(key);
- })).then(function (results) {
- _dataLanguages = results[0];
- _dataLocales = results[1];
- var indexes = results.slice(2);
+ /** `Object#toString` result references. */
- var requestedLocales = (_preferredLocaleCodes || []).concat(utilDetect().browserLocales) // List of locales preferred by the browser in priority order.
- .concat(['en']); // fallback to English since it's the only guaranteed complete language
+ var symbolTag = '[object Symbol]';
+ /**
+ * 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 isSymbol(value) {
+ return _typeof(value) == 'symbol' || isObjectLike(value) && baseGetTag(value) == symbolTag;
+ }
- _localeCodes = localesToUseFrom(requestedLocales);
- _localeCode = _localeCodes[0]; // Run iD in the highest-priority locale; the rest are fallbacks
+ /**
+ * 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.
+ */
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ result = Array(length);
- var loadStringsPromises = [];
- indexes.forEach(function (index, i) {
- // Will always return the index for `en` if nothing else
- var fullCoverageIndex = _localeCodes.findIndex(function (locale) {
- return index[locale] && index[locale].pct === 1;
- }); // We only need to load locales up until we find one with full coverage
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
+ }
+ return result;
+ }
- _localeCodes.slice(0, fullCoverageIndex + 1).forEach(function (code) {
- var scopeId = Object.keys(localeDirs)[i];
- var directory = Object.values(localeDirs)[i];
- if (index[code]) loadStringsPromises.push(localizer.loadLocale(code, scopeId, directory));
- });
- });
- return Promise.all(loadStringsPromises);
- }).then(function () {
- updateForCurrentLocale();
- })["catch"](function (err) {
- return console.error(err);
- }); // eslint-disable-line
- }; // Returns the locales from `requestedLocales` supported by iD that we should use
+ /**
+ * 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
+ */
+ var isArray$1 = Array.isArray;
+ /** Used as references for various `Number` constants. */
- function localesToUseFrom(requestedLocales) {
- var supportedLocales = _dataLocales;
- var toUse = [];
+ var INFINITY = 1 / 0;
+ /** Used to convert symbols to primitives and strings. */
- for (var i in requestedLocales) {
- var locale = requestedLocales[i];
- if (supportedLocales[locale]) toUse.push(locale);
+ var symbolProto = _Symbol ? _Symbol.prototype : undefined,
+ symbolToString = symbolProto ? symbolProto.toString : undefined;
+ /**
+ * 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.
+ */
- if (locale.includes('-')) {
- // Full locale ('es-ES'), add fallback to the base ('es')
- var langPart = locale.split('-')[0];
- if (supportedLocales[langPart]) toUse.push(langPart);
- }
- } // remove duplicates
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+ if (isArray$1(value)) {
+ // Recursively convert values (susceptible to call stack limits).
+ return arrayMap(value, baseToString) + '';
+ }
- return utilArrayUniq(toUse);
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
}
- function updateForCurrentLocale() {
- if (!_localeCode) return;
- _languageCode = _localeCode.split('-')[0];
- var currentData = _dataLocales[_localeCode] || _dataLocales[_languageCode];
- var hash = utilStringQs(window.location.hash);
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
+ }
- if (hash.rtl === 'true') {
- _textDirection = 'rtl';
- } else if (hash.rtl === 'false') {
- _textDirection = 'ltr';
- } else {
- _textDirection = currentData && currentData.rtl ? 'rtl' : 'ltr';
- }
+ /** Used to match a single whitespace character. */
+ var reWhitespace = /\s/;
+ /**
+ * 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.
+ */
- var locale = _localeCode;
- if (locale.toLowerCase() === 'en-us') locale = 'en';
- _languageNames = _localeStrings.general[locale].languageNames;
- _scriptNames = _localeStrings.general[locale].scriptNames;
- _usesMetric = _localeCode.slice(-3).toLowerCase() !== '-us';
- }
- /* Locales */
- // Returns a Promise to load the strings for the requested locale
+ function trimmedEndIndex(string) {
+ var index = string.length;
+ while (index-- && reWhitespace.test(string.charAt(index))) {}
- localizer.loadLocale = function (locale, scopeId, directory) {
- // US English is the default
- if (locale.toLowerCase() === 'en-us') locale = 'en';
+ return index;
+ }
- if (_localeStrings[scopeId] && _localeStrings[scopeId][locale]) {
- // already loaded
- return Promise.resolve(locale);
- }
+ /** Used to match leading whitespace. */
- var fileMap = _mainFileFetcher.fileMap();
- var key = "locale_".concat(scopeId, "_").concat(locale);
+ var reTrimStart = /^\s+/;
+ /**
+ * The base implementation of `_.trim`.
+ *
+ * @private
+ * @param {string} string The string to trim.
+ * @returns {string} Returns the trimmed string.
+ */
- if (!fileMap[key]) {
- fileMap[key] = "".concat(directory, "/").concat(locale, ".min.json");
- }
+ function baseTrim(string) {
+ return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string;
+ }
- return _mainFileFetcher.get(key).then(function (d) {
- if (!_localeStrings[scopeId]) _localeStrings[scopeId] = {};
- _localeStrings[scopeId][locale] = d[locale];
- return locale;
- });
- };
+ /**
+ * 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
+ */
+ function isObject$2(value) {
+ var type = _typeof(value);
- localizer.pluralRule = function (number) {
- return pluralRule(number, _localeCode);
- }; // Returns the plural rule for the given `number` with the given `localeCode`.
- // One of: `zero`, `one`, `two`, `few`, `many`, `other`
+ return value != null && (type == 'object' || type == 'function');
+ }
+ /** Used as references for various `Number` constants. */
- function pluralRule(number, localeCode) {
- // modern browsers have this functionality built-in
- var rules = 'Intl' in window && Intl.PluralRules && new Intl.PluralRules(localeCode);
+ var NAN = 0 / 0;
+ /** Used to detect bad signed hexadecimal string values. */
- if (rules) {
- return rules.select(number);
- } // fallback to basic one/other, as in English
+ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+ /** Used to detect binary string values. */
+ var reIsBinary = /^0b[01]+$/i;
+ /** Used to detect octal string values. */
- if (number === 1) return 'one';
- return 'other';
+ var reIsOctal = /^0o[0-7]+$/i;
+ /** Built-in method references without a dependency on `root`. */
+
+ var freeParseInt = parseInt;
+ /**
+ * 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 toNumber(value) {
+ if (typeof value == 'number') {
+ return value;
}
- /**
- * Try to find that string in `locale` or the current `_localeCode` matching
- * the given `stringId`. If no string can be found in the requested locale,
- * we'll recurse down all the `_localeCodes` until one is found.
- *
- * @param {string} stringId string identifier
- * @param {object?} replacements token replacements and default string
- * @param {string?} locale locale to use (defaults to currentLocale)
- * @return {string?} localized string
- */
+ if (isSymbol(value)) {
+ return NAN;
+ }
- localizer.tInfo = function (origStringId, replacements, locale) {
- var stringId = origStringId.trim();
- var scopeId = 'general';
+ if (isObject$2(value)) {
+ var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+ value = isObject$2(other) ? other + '' : other;
+ }
- if (stringId[0] === '_') {
- var split = stringId.split('.');
- scopeId = split[0].slice(1);
- stringId = split.slice(1).join('.');
- }
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
+ }
- locale = locale || _localeCode;
- var path = stringId.split('.').map(function (s) {
- return s.replace(/<TX_DOT>/g, '.');
- }).reverse();
- var stringsKey = locale; // US English is the default
+ 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;
+ }
- if (stringsKey.toLowerCase() === 'en-us') stringsKey = 'en';
- var result = _localeStrings && _localeStrings[scopeId] && _localeStrings[scopeId][stringsKey];
+ /**
+ * 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'
+ */
- while (result !== undefined && path.length) {
- result = result[path.pop()];
- }
+ function toString$3(value) {
+ return value == null ? '' : baseToString(value);
+ }
- 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';
- });
+ /**
+ * 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 basePropertyOf(object) {
+ return function (key) {
+ return object == null ? undefined : object[key];
+ };
+ }
- if (number !== undefined) {
- var rule = pluralRule(number, locale);
+ /**
+ * 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.
+ */
- 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];
- }
- }
- }
+ var now = function now() {
+ return root.Date.now();
+ };
- if (typeof result === 'string') {
- for (var key in replacements) {
- var value = replacements[key];
+ /** Error message constants. */
- 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();
- }
- }
+ var FUNC_ERROR_TEXT$1 = 'Expected a function';
+ /* Built-in method references for those with the same name as other `lodash` methods. */
- var token = "{".concat(key, "}");
- var regex = new RegExp(token, 'g');
- result = result.replace(regex, value);
- }
- }
- }
+ var nativeMax = Math.max,
+ nativeMin = Math.min;
+ /**
+ * 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 result === 'string') {
- // found a localized string!
- return {
- text: result,
- locale: locale
- };
- }
- } // no localized string found...
- // attempt to fallback to a lower-priority language
+ function debounce(func, wait, options) {
+ var lastArgs,
+ lastThis,
+ maxWait,
+ result,
+ timerId,
+ lastCallTime,
+ lastInvokeTime = 0,
+ leading = false,
+ maxing = false,
+ trailing = true;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT$1);
+ }
- var index = _localeCodes.indexOf(locale);
+ wait = toNumber(wait) || 0;
- 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);
- }
+ if (isObject$2(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 (replacements && 'default' in replacements) {
- // Fallback to a default value if one is specified in `replacements`
- return {
- text: replacements["default"],
- locale: null
- };
- }
+ function invokeFunc(time) {
+ var args = lastArgs,
+ thisArg = lastThis;
+ lastArgs = lastThis = undefined;
+ lastInvokeTime = time;
+ result = func.apply(thisArg, args);
+ return result;
+ }
- var missing = "Missing ".concat(locale, " translation: ").concat(origStringId);
- if (typeof console !== 'undefined') console.error(missing); // eslint-disable-line
+ function leadingEdge(time) {
+ // Reset any `maxWait` timer.
+ lastInvokeTime = time; // Start the timer for the trailing edge.
- return {
- text: missing,
- locale: 'en'
- };
- };
+ timerId = setTimeout(timerExpired, wait); // Invoke the leading edge.
- localizer.hasTextForStringId = function (stringId) {
- return !!localizer.tInfo(stringId, {
- "default": 'nothing found'
- }).locale;
- }; // Returns only the localized text, discarding the locale info
+ return leading ? invokeFunc(time) : result;
+ }
+ function remainingWait(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime,
+ timeWaiting = wait - timeSinceLastCall;
+ return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
+ }
- 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
+ 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.
+ return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
+ }
- localizer.t.html = function (stringId, replacements, locale) {
- var info = localizer.tInfo(stringId, replacements, locale); // text may be empty or undefined if `replacements.default` is
+ function timerExpired() {
+ var time = now();
- return info.text ? localizer.htmlForLocalizedText(info.text, info.locale) : '';
- };
+ if (shouldInvoke(time)) {
+ return trailingEdge(time);
+ } // Restart the timer.
- localizer.htmlForLocalizedText = function (text, localeCode) {
- return "<span class=\"localized-text\" lang=\"".concat(localeCode || 'unknown', "\">").concat(text, "</span>");
- };
- 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
+ timerId = setTimeout(timerExpired, remainingWait(time));
+ }
+ function trailingEdge(time) {
+ timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been
+ // debounced at least once.
- if (options && options.localOnly) return null;
- var langInfo = _dataLanguages[code];
+ if (trailing && lastArgs) {
+ return invokeFunc(time);
+ }
- 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
+ lastArgs = lastThis = undefined;
+ return result;
+ }
- if (_languageNames[base]) {
- // base language name in locale language
- var scriptCode = langInfo.script;
- var script = _scriptNames[scriptCode] || scriptCode; // e.g. "Serbian (Cyrillic)"
+ function cancel() {
+ if (timerId !== undefined) {
+ clearTimeout(timerId);
+ }
- 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
- });
- }
+ lastInvokeTime = 0;
+ lastArgs = lastCallTime = lastThis = timerId = undefined;
+ }
+
+ function flush() {
+ return timerId === undefined ? result : trailingEdge(now());
+ }
+
+ function debounced() {
+ var time = now(),
+ isInvoking = shouldInvoke(time);
+ lastArgs = arguments;
+ lastThis = this;
+ lastCallTime = time;
+
+ if (isInvoking) {
+ if (timerId === undefined) {
+ return leadingEdge(lastCallTime);
+ }
+
+ if (maxing) {
+ // Handle invocations in a tight loop.
+ clearTimeout(timerId);
+ timerId = setTimeout(timerExpired, wait);
+ return invokeFunc(lastCallTime);
}
}
- return code; // if not found, use the code
- };
+ if (timerId === undefined) {
+ timerId = setTimeout(timerExpired, wait);
+ }
- return localizer;
+ return result;
+ }
+
+ debounced.cancel = cancel;
+ debounced.flush = flush;
+ return debounced;
}
- // `presetCollection` is a wrapper around an `Array` of presets `collection`,
- // and decorated with some extra methods for searching and matching geometry
- //
+ /** Used to map characters to HTML entities. */
- function presetCollection(collection) {
- var MAXRESULTS = 50;
- var _this = {};
- var _memo = {};
- _this.collection = collection;
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+ /**
+ * Used by `_.escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
- _this.item = function (id) {
- if (_memo[id]) return _memo[id];
+ var escapeHtmlChar = basePropertyOf(htmlEscapes);
- var found = _this.collection.find(function (d) {
- return d.id === id;
- });
+ /** Used to match HTML entities and HTML characters. */
- if (found) _memo[id] = found;
- return found;
- };
+ var reUnescapedHtml = /[&<>"']/g,
+ reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+ /**
+ * 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'
+ */
- _this.index = function (id) {
- return _this.collection.findIndex(function (d) {
- return d.id === id;
- });
- };
+ function escape$4(string) {
+ string = toString$3(string);
+ return string && reHasUnescapedHtml.test(string) ? string.replace(reUnescapedHtml, escapeHtmlChar) : string;
+ }
- _this.matchGeometry = function (geometry) {
- return presetCollection(_this.collection.filter(function (d) {
- return d.matchGeometry(geometry);
- }));
- };
+ /** Error message constants. */
- _this.matchAllGeometry = function (geometries) {
- return presetCollection(_this.collection.filter(function (d) {
- return d && d.matchAllGeometry(geometries);
- }));
- };
+ var FUNC_ERROR_TEXT = 'Expected a function';
+ /**
+ * 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);
+ */
- _this.matchAnyGeometry = function (geometries) {
- return presetCollection(_this.collection.filter(function (d) {
- return geometries.some(function (geom) {
- return d.matchGeometry(geom);
- });
- }));
- };
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
- _this.fallback = function (geometry) {
- var id = geometry;
- if (id === 'vertex') id = 'point';
- return _this.item(id);
- };
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- _this.search = function (value, geometry, loc) {
- if (!value) return _this; // don't remove diacritical characters since we're assuming the user is being intentional
+ if (isObject$2(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
- value = value.toLowerCase().trim(); // match at name beginning or just after a space (e.g. "office" -> match "Law Office")
+ return debounce(func, wait, {
+ 'leading': leading,
+ 'maxWait': wait,
+ 'trailing': trailing
+ });
+ }
- function leading(a) {
- var index = a.indexOf(value);
- return index === 0 || a[index - 1] === ' ';
- } // match at name beginning only
+ var $$f = _export;
+ var lastIndexOf = arrayLastIndexOf;
+ // `Array.prototype.lastIndexOf` method
+ // https://tc39.es/ecma262/#sec-array.prototype.lastindexof
+ // eslint-disable-next-line es/no-array-prototype-lastindexof -- required for testing
+ $$f({ target: 'Array', proto: true, forced: lastIndexOf !== [].lastIndexOf }, {
+ lastIndexOf: lastIndexOf
+ });
- function leadingStrict(a) {
- var index = a.indexOf(value);
- return index === 0;
- }
+ var global$4 = global$1m;
+ var isArray = isArray$8;
+ var lengthOfArrayLike$1 = lengthOfArrayLike$g;
+ var bind$3 = functionBindContext;
- 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
+ var TypeError$2 = global$4.TypeError;
- if (value === aCompare) return -1;
- if (value === bCompare) return 1; // priority for higher matchScore
+ // `FlattenIntoArray` abstract operation
+ // https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray
+ var flattenIntoArray$1 = function (target, original, source, sourceLen, start, depth, mapper, thisArg) {
+ var targetIndex = start;
+ var sourceIndex = 0;
+ var mapFn = mapper ? bind$3(mapper, thisArg) : false;
+ var element, elementLen;
- var i = b.originalScore - a.originalScore;
- if (i !== 0) return i; // priority if search string appears earlier in preset name
+ while (sourceIndex < sourceLen) {
+ if (sourceIndex in source) {
+ element = mapFn ? mapFn(source[sourceIndex], sourceIndex, original) : source[sourceIndex];
- i = aCompare.indexOf(value) - bCompare.indexOf(value);
- if (i !== 0) return i; // priority for shorter preset names
+ if (depth > 0 && isArray(element)) {
+ elementLen = lengthOfArrayLike$1(element);
+ targetIndex = flattenIntoArray$1(target, original, element, elementLen, targetIndex, depth - 1) - 1;
+ } else {
+ if (targetIndex >= 0x1FFFFFFFFFFFFF) throw TypeError$2('Exceed the acceptable array length');
+ target[targetIndex] = element;
+ }
- return aCompare.length - bCompare.length;
- };
+ targetIndex++;
}
+ sourceIndex++;
+ }
+ return targetIndex;
+ };
- var pool = _this.collection;
-
- if (Array.isArray(loc)) {
- var validLocations = _mainLocations.locationsAt(loc);
- pool = pool.filter(function (a) {
- return !a.locationSetID || validLocations[a.locationSetID];
- });
- }
+ var flattenIntoArray_1 = flattenIntoArray$1;
- 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 $$e = _export;
+ var flattenIntoArray = flattenIntoArray_1;
+ var aCallable = aCallable$a;
+ var toObject = toObject$j;
+ var lengthOfArrayLike = lengthOfArrayLike$g;
+ var arraySpeciesCreate = arraySpeciesCreate$4;
+
+ // `Array.prototype.flatMap` method
+ // https://tc39.es/ecma262/#sec-array.prototype.flatmap
+ $$e({ target: 'Array', proto: true }, {
+ flatMap: function flatMap(callbackfn /* , thisArg */) {
+ var O = toObject(this);
+ var sourceLen = lengthOfArrayLike(O);
+ var A;
+ aCallable(callbackfn);
+ A = arraySpeciesCreate(O, 0);
+ A.length = flattenIntoArray(A, O, O, sourceLen, 0, 1, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
+ return A;
+ }
+ });
- var leadingNames = searchable.filter(function (a) {
- return leading(a.searchName());
- }).sort(sortPresets('searchName')); // matches value to preset suggestion name
+ // this method was added to unscopables after implementation
+ // in popular engines, so it's moved to a separate module
+ var addToUnscopables = addToUnscopables$6;
- 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
+ // https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
+ addToUnscopables('flatMap');
- 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 $$d = _export;
+ var uncurryThis$6 = functionUncurryThis;
+ var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f;
+ var toLength$2 = toLength$c;
+ var toString$2 = toString$k;
+ var notARegExp$1 = notARegexp;
+ var requireObjectCoercible$2 = requireObjectCoercible$e;
+ var correctIsRegExpLogic$1 = correctIsRegexpLogic;
- 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
+ // eslint-disable-next-line es/no-string-prototype-endswith -- safe
+ var un$EndsWith = uncurryThis$6(''.endsWith);
+ var slice$2 = uncurryThis$6(''.slice);
+ var min$1 = Math.min;
- 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 CORRECT_IS_REGEXP_LOGIC$1 = correctIsRegExpLogic$1('endsWith');
+ // https://github.com/zloirock/core-js/pull/702
+ var MDN_POLYFILL_BUG$1 = !CORRECT_IS_REGEXP_LOGIC$1 && !!function () {
+ var descriptor = getOwnPropertyDescriptor$1(String.prototype, 'endsWith');
+ return descriptor && !descriptor.writable;
+ }();
- 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
+ // `String.prototype.endsWith` method
+ // https://tc39.es/ecma262/#sec-string.prototype.endswith
+ $$d({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG$1 && !CORRECT_IS_REGEXP_LOGIC$1 }, {
+ endsWith: function endsWith(searchString /* , endPosition = @length */) {
+ var that = toString$2(requireObjectCoercible$2(this));
+ notARegExp$1(searchString);
+ var endPosition = arguments.length > 1 ? arguments[1] : undefined;
+ var len = that.length;
+ var end = endPosition === undefined ? len : min$1(toLength$2(endPosition), len);
+ var search = toString$2(searchString);
+ return un$EndsWith
+ ? un$EndsWith(that, search, end)
+ : slice$2(that, end - search.length, end) === search;
+ }
+ });
- 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);
+ // https://github.com/tc39/proposal-string-pad-start-end
+ var uncurryThis$5 = functionUncurryThis;
+ var toLength$1 = toLength$c;
+ var toString$1 = toString$k;
+ var $repeat = stringRepeat;
+ var requireObjectCoercible$1 = requireObjectCoercible$e;
- if (geometry) {
- if (typeof geometry === 'string') {
- results.push(_this.fallback(geometry));
- } else {
- geometry.forEach(function (geom) {
- return results.push(_this.fallback(geom));
- });
- }
- }
+ var repeat$1 = uncurryThis$5($repeat);
+ var stringSlice$2 = uncurryThis$5(''.slice);
+ var ceil = Math.ceil;
- return presetCollection(utilArrayUniq(results));
+ // `String.prototype.{ padStart, padEnd }` methods implementation
+ var createMethod = function (IS_END) {
+ return function ($this, maxLength, fillString) {
+ var S = toString$1(requireObjectCoercible$1($this));
+ var intMaxLength = toLength$1(maxLength);
+ var stringLength = S.length;
+ var fillStr = fillString === undefined ? ' ' : toString$1(fillString);
+ var fillLen, stringFiller;
+ if (intMaxLength <= stringLength || fillStr == '') return S;
+ fillLen = intMaxLength - stringLength;
+ stringFiller = repeat$1(fillStr, ceil(fillLen / fillStr.length));
+ if (stringFiller.length > fillLen) stringFiller = stringSlice$2(stringFiller, 0, fillLen);
+ return IS_END ? S + stringFiller : stringFiller + S;
};
+ };
- return _this;
- }
-
- // `presetCategory` builds a `presetCollection` of member presets,
- // decorated with some extra methods for searching and matching geometry
- //
+ 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)
+ };
- function presetCategory(categoryID, category, allPresets) {
- var _this = Object.assign({}, category); // shallow copy
+ // https://github.com/zloirock/core-js/issues/280
+ var userAgent = engineUserAgent;
+ var stringPadWebkitBug = /Version\/10(?:\.\d+){1,2}(?: [\w./]+)?(?: Mobile\/\w+)? Safari\//.test(userAgent);
- var _searchName; // cache
+ var $$c = _export;
+ var $padEnd = stringPad.end;
+ var WEBKIT_BUG$1 = stringPadWebkitBug;
+ // `String.prototype.padEnd` method
+ // https://tc39.es/ecma262/#sec-string.prototype.padend
+ $$c({ target: 'String', proto: true, forced: WEBKIT_BUG$1 }, {
+ padEnd: function padEnd(maxLength /* , fillString = ' ' */) {
+ return $padEnd(this, maxLength, arguments.length > 1 ? arguments[1] : undefined);
+ }
+ });
- var _searchNameStripped; // cache
+ var $$b = _export;
+ var $padStart = stringPad.start;
+ var WEBKIT_BUG = stringPadWebkitBug;
+ // `String.prototype.padStart` method
+ // https://tc39.es/ecma262/#sec-string.prototype.padstart
+ $$b({ target: 'String', proto: true, forced: WEBKIT_BUG }, {
+ padStart: function padStart(maxLength /* , fillString = ' ' */) {
+ return $padStart(this, maxLength, arguments.length > 1 ? arguments[1] : undefined);
+ }
+ });
- _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];
+ var $$a = _export;
+ var $reduceRight = arrayReduce.right;
+ var arrayMethodIsStrict = arrayMethodIsStrict$9;
+ var CHROME_VERSION = engineV8Version;
+ var IS_NODE = engineIsNode;
- if (acc.indexOf(geometry) === -1) {
- acc.push(geometry);
- }
- }
+ var STRICT_METHOD = arrayMethodIsStrict('reduceRight');
+ // Chrome 80-82 has a critical bug
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
+ var CHROME_BUG = !IS_NODE && CHROME_VERSION > 79 && CHROME_VERSION < 83;
- return acc;
- }, []);
+ // `Array.prototype.reduceRight` method
+ // https://tc39.es/ecma262/#sec-array.prototype.reduceright
+ $$a({ target: 'Array', proto: true, forced: !STRICT_METHOD || CHROME_BUG }, {
+ reduceRight: function reduceRight(callbackfn /* , initialValue */) {
+ return $reduceRight(this, callbackfn, arguments.length, arguments.length > 1 ? arguments[1] : undefined);
+ }
+ });
- _this.matchGeometry = function (geom) {
- return _this.geometry.indexOf(geom) >= 0;
- };
+ var $$9 = _export;
+ var repeat = stringRepeat;
- _this.matchAllGeometry = function (geometries) {
- return _this.members.collection.some(function (preset) {
- return preset.matchAllGeometry(geometries);
- });
- };
+ // `String.prototype.repeat` method
+ // https://tc39.es/ecma262/#sec-string.prototype.repeat
+ $$9({ target: 'String', proto: true }, {
+ repeat: repeat
+ });
- _this.matchScore = function () {
- return -1;
- };
+ var $$8 = _export;
+ var uncurryThis$4 = functionUncurryThis;
+ var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
+ var toLength = toLength$c;
+ var toString = toString$k;
+ var notARegExp = notARegexp;
+ var requireObjectCoercible = requireObjectCoercible$e;
+ var correctIsRegExpLogic = correctIsRegexpLogic;
- _this.name = function () {
- return _t("_tagging.presets.categories.".concat(categoryID, ".name"), {
- 'default': categoryID
- });
- };
+ // eslint-disable-next-line es/no-string-prototype-startswith -- safe
+ var un$StartsWith = uncurryThis$4(''.startsWith);
+ var stringSlice$1 = uncurryThis$4(''.slice);
+ var min = Math.min;
- _this.nameLabel = function () {
- return _t.html("_tagging.presets.categories.".concat(categoryID, ".name"), {
- 'default': categoryID
- });
- };
+ var CORRECT_IS_REGEXP_LOGIC = correctIsRegExpLogic('startsWith');
+ // https://github.com/zloirock/core-js/pull/702
+ var MDN_POLYFILL_BUG = !CORRECT_IS_REGEXP_LOGIC && !!function () {
+ var descriptor = getOwnPropertyDescriptor(String.prototype, 'startsWith');
+ return descriptor && !descriptor.writable;
+ }();
- _this.terms = function () {
- return [];
- };
+ // `String.prototype.startsWith` method
+ // https://tc39.es/ecma262/#sec-string.prototype.startswith
+ $$8({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG && !CORRECT_IS_REGEXP_LOGIC }, {
+ startsWith: function startsWith(searchString /* , position = 0 */) {
+ var that = toString(requireObjectCoercible(this));
+ notARegExp(searchString);
+ var index = toLength(min(arguments.length > 1 ? arguments[1] : undefined, that.length));
+ var search = toString(searchString);
+ return un$StartsWith
+ ? un$StartsWith(that, search, index)
+ : stringSlice$1(that, index, index + search.length) === search;
+ }
+ });
- _this.searchName = function () {
- if (!_searchName) {
- _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
- }
+ var $$7 = _export;
+ var $trimEnd = stringTrim.end;
+ var forcedStringTrimMethod$1 = stringTrimForced;
+
+ var FORCED$4 = forcedStringTrimMethod$1('trimEnd');
+
+ var trimEnd = FORCED$4 ? function trimEnd() {
+ return $trimEnd(this);
+ // eslint-disable-next-line es/no-string-prototype-trimstart-trimend -- safe
+ } : ''.trimEnd;
+
+ // `String.prototype.{ trimEnd, trimRight }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimend
+ // https://tc39.es/ecma262/#String.prototype.trimright
+ $$7({ target: 'String', proto: true, name: 'trimEnd', forced: FORCED$4 }, {
+ trimEnd: trimEnd,
+ trimRight: trimEnd
+ });
- return _searchName;
- };
+ var $$6 = _export;
+ var $trimStart = stringTrim.start;
+ var forcedStringTrimMethod = stringTrimForced;
- _this.searchNameStripped = function () {
- if (!_searchNameStripped) {
- _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
+ var FORCED$3 = forcedStringTrimMethod('trimStart');
- if (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
+ var trimStart = FORCED$3 ? function trimStart() {
+ return $trimStart(this);
+ // eslint-disable-next-line es/no-string-prototype-trimstart-trimend -- safe
+ } : ''.trimStart;
- _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
- }
+ // `String.prototype.{ trimStart, trimLeft }` methods
+ // https://tc39.es/ecma262/#sec-string.prototype.trimstart
+ // https://tc39.es/ecma262/#String.prototype.trimleft
+ $$6({ target: 'String', proto: true, name: 'trimStart', forced: FORCED$3 }, {
+ trimStart: trimStart,
+ trimLeft: trimStart
+ });
- return _searchNameStripped;
- };
+ var _mainLocalizer = coreLocalizer(); // singleton
- return _this;
- }
- // `presetField` decorates a given `field` Object
- // with some extra methods for searching and matching geometry
+ var _t = _mainLocalizer.t;
+ // coreLocalizer manages language and locale parameters including translated strings
//
- function presetField(fieldID, field) {
- var _this = Object.assign({}, field); // shallow copy
+ function coreLocalizer() {
+ var localizer = {};
+ var _dataLanguages = {}; // `_dataLocales` is an object containing all _supported_ locale codes -> language info.
+ // * `rtl` - right-to-left or left-to-right text direction
+ // * `pct` - the percent of strings translated; 1 = 100%, full coverage
+ //
+ // {
+ // en: { rtl: false, pct: {…} },
+ // de: { rtl: false, pct: {…} },
+ // …
+ // }
+ var _dataLocales = {}; // `localeStrings` is an object containing all _loaded_ locale codes -> string data.
+ // {
+ // en: { icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, … },
+ // de: { icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, … },
+ // …
+ // }
- _this.id = fieldID; // for use in classes, element ids, css selectors
+ var _localeStrings = {}; // the current locale
- _this.safeid = utilSafeClassName(fieldID);
+ var _localeCode = 'en-US'; // `_localeCodes` must contain `_localeCode` first, optionally followed by fallbacks
- _this.matchGeometry = function (geom) {
- return !_this.geometry || _this.geometry.indexOf(geom) !== -1;
- };
+ var _localeCodes = ['en-US', 'en'];
+ var _languageCode = 'en';
+ var _textDirection = 'ltr';
+ var _usesMetric = false;
+ var _languageNames = {};
+ var _scriptNames = {}; // getters for the current locale parameters
- _this.matchAllGeometry = function (geometries) {
- return !_this.geometry || geometries.every(function (geom) {
- return _this.geometry.indexOf(geom) !== -1;
- });
+ localizer.localeCode = function () {
+ return _localeCode;
};
- _this.t = function (scope, options) {
- return _t("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ localizer.localeCodes = function () {
+ return _localeCodes;
};
- _this.t.html = function (scope, options) {
- return _t.html("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ localizer.languageCode = function () {
+ return _languageCode;
};
- _this.hasTextForStringId = function (scope) {
- return _mainLocalizer.hasTextForStringId("_tagging.presets.fields.".concat(fieldID, ".").concat(scope));
+ localizer.textDirection = function () {
+ return _textDirection;
};
- _this.title = function () {
- return _this.overrideLabel || _this.t('label', {
- 'default': fieldID
- });
+ localizer.usesMetric = function () {
+ return _usesMetric;
};
- _this.label = function () {
- return _this.overrideLabel || _this.t.html('label', {
- 'default': fieldID
- });
+ localizer.languageNames = function () {
+ return _languageNames;
};
- var _placeholder = _this.placeholder;
+ localizer.scriptNames = function () {
+ return _scriptNames;
+ }; // The client app may want to manually set the locale, regardless of the
+ // settings provided by the browser
- _this.placeholder = function () {
- return _this.t('placeholder', {
- 'default': _placeholder
- });
- };
- _this.originalTerms = (_this.terms || []).join();
+ var _preferredLocaleCodes = [];
- _this.terms = function () {
- return _this.t('terms', {
- 'default': _this.originalTerms
- }).toLowerCase().trim().split(/\s*,+\s*/);
- };
+ localizer.preferredLocaleCodes = function (codes) {
+ if (!arguments.length) return _preferredLocaleCodes;
- _this.increment = _this.type === 'number' ? _this.increment || 1 : undefined;
- return _this;
- }
+ if (typeof codes === 'string') {
+ // be generous and accept delimited strings as input
+ _preferredLocaleCodes = codes.split(/,|;| /gi).filter(Boolean);
+ } else {
+ _preferredLocaleCodes = codes;
+ }
- var $$8 = _export;
- var lastIndexOf = arrayLastIndexOf;
+ return localizer;
+ };
- // `Array.prototype.lastIndexOf` method
- // https://tc39.es/ecma262/#sec-array.prototype.lastindexof
- // eslint-disable-next-line es/no-array-prototype-lastindexof -- required for testing
- $$8({ target: 'Array', proto: true, forced: lastIndexOf !== [].lastIndexOf }, {
- lastIndexOf: lastIndexOf
- });
+ var _loadPromise;
- // `presetPreset` decorates a given `preset` Object
- // with some extra methods for searching and matching geometry
- //
+ localizer.ensureLoaded = function () {
+ if (_loadPromise) return _loadPromise;
+ var filesToFetch = ['languages', // load the list of languages
+ 'locales' // load the list of supported locales
+ ];
+ var localeDirs = {
+ general: 'locales',
+ tagging: 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/translations'
+ };
+ var fileMap = _mainFileFetcher.fileMap();
- function presetPreset(presetID, preset, addable, allFields, allPresets) {
- allFields = allFields || {};
- allPresets = allPresets || {};
+ for (var scopeId in localeDirs) {
+ var key = "locales_index_".concat(scopeId);
- var _this = Object.assign({}, preset); // shallow copy
+ if (!fileMap[key]) {
+ fileMap[key] = localeDirs[scopeId] + '/index.min.json';
+ }
+ filesToFetch.push(key);
+ }
- var _addable = addable || false;
+ return _loadPromise = Promise.all(filesToFetch.map(function (key) {
+ return _mainFileFetcher.get(key);
+ })).then(function (results) {
+ _dataLanguages = results[0];
+ _dataLocales = results[1];
+ var indexes = results.slice(2);
- var _resolvedFields; // cache
+ var requestedLocales = (_preferredLocaleCodes || []).concat(utilDetect().browserLocales) // List of locales preferred by the browser in priority order.
+ .concat(['en']); // fallback to English since it's the only guaranteed complete language
- var _resolvedMoreFields; // cache
+ _localeCodes = localesToUseFrom(requestedLocales);
+ _localeCode = _localeCodes[0]; // Run iD in the highest-priority locale; the rest are fallbacks
+ var loadStringsPromises = [];
+ indexes.forEach(function (index, i) {
+ // Will always return the index for `en` if nothing else
+ var fullCoverageIndex = _localeCodes.findIndex(function (locale) {
+ return index[locale] && index[locale].pct === 1;
+ }); // We only need to load locales up until we find one with full coverage
- var _searchName; // cache
+ _localeCodes.slice(0, fullCoverageIndex + 1).forEach(function (code) {
+ var scopeId = Object.keys(localeDirs)[i];
+ var directory = Object.values(localeDirs)[i];
+ if (index[code]) loadStringsPromises.push(localizer.loadLocale(code, scopeId, directory));
+ });
+ });
+ return Promise.all(loadStringsPromises);
+ }).then(function () {
+ updateForCurrentLocale();
+ })["catch"](function (err) {
+ return console.error(err);
+ }); // eslint-disable-line
+ }; // Returns the locales from `requestedLocales` supported by iD that we should use
- var _searchNameStripped; // cache
+ function localesToUseFrom(requestedLocales) {
+ var supportedLocales = _dataLocales;
+ var toUse = [];
- _this.id = presetID;
- _this.safeid = utilSafeClassName(presetID); // for use in css classes, selectors, element ids
+ for (var i in requestedLocales) {
+ var locale = requestedLocales[i];
+ if (supportedLocales[locale]) toUse.push(locale);
- _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.moreFields = function () {
- return _resolvedMoreFields || (_resolvedMoreFields = resolve('moreFields'));
- };
+ if (locale.includes('-')) {
+ // Full locale ('es-ES'), add fallback to the base ('es')
+ var langPart = locale.split('-')[0];
+ if (supportedLocales[langPart]) toUse.push(langPart);
+ }
+ } // remove duplicates
- _this.resetFields = function () {
- return _resolvedFields = _resolvedMoreFields = null;
- };
- _this.tags = _this.tags || {};
- _this.addTags = _this.addTags || _this.tags;
- _this.removeTags = _this.removeTags || _this.addTags;
- _this.geometry = _this.geometry || [];
+ return utilArrayUniq(toUse);
+ }
- _this.matchGeometry = function (geom) {
- return _this.geometry.indexOf(geom) >= 0;
- };
+ function updateForCurrentLocale() {
+ if (!_localeCode) return;
+ _languageCode = _localeCode.split('-')[0];
+ var currentData = _dataLocales[_localeCode] || _dataLocales[_languageCode];
+ var hash = utilStringQs(window.location.hash);
- _this.matchAllGeometry = function (geoms) {
- return geoms.every(_this.matchGeometry);
- };
+ if (hash.rtl === 'true') {
+ _textDirection = 'rtl';
+ } else if (hash.rtl === 'false') {
+ _textDirection = 'ltr';
+ } else {
+ _textDirection = currentData && currentData.rtl ? 'rtl' : 'ltr';
+ }
- _this.matchScore = function (entityTags) {
- var tags = _this.tags;
- var seen = {};
- var score = 0; // match on tags
+ var locale = _localeCode;
+ if (locale.toLowerCase() === 'en-us') locale = 'en';
+ _languageNames = _localeStrings.general[locale].languageNames;
+ _scriptNames = _localeStrings.general[locale].scriptNames;
+ _usesMetric = _localeCode.slice(-3).toLowerCase() !== '-us';
+ }
+ /* Locales */
+ // Returns a Promise to load the strings for the requested locale
- for (var k in tags) {
- seen[k] = true;
- 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
+ localizer.loadLocale = function (locale, scopeId, directory) {
+ // US English is the default
+ if (locale.toLowerCase() === 'en-us') locale = 'en';
+ if (_localeStrings[scopeId] && _localeStrings[scopeId][locale]) {
+ // already loaded
+ return Promise.resolve(locale);
+ }
- var addTags = _this.addTags;
+ var fileMap = _mainFileFetcher.fileMap();
+ var key = "locale_".concat(scopeId, "_").concat(locale);
- for (var _k in addTags) {
- if (!seen[_k] && entityTags[_k] === addTags[_k]) {
- score += _this.originalScore;
- }
+ if (!fileMap[key]) {
+ fileMap[key] = "".concat(directory, "/").concat(locale, ".min.json");
}
- return score;
+ return _mainFileFetcher.get(key).then(function (d) {
+ if (!_localeStrings[scopeId]) _localeStrings[scopeId] = {};
+ _localeStrings[scopeId][locale] = d[locale];
+ return locale;
+ });
};
- _this.t = function (scope, options) {
- var textID = "_tagging.presets.presets.".concat(presetID, ".").concat(scope);
- return _t(textID, options);
- };
+ localizer.pluralRule = function (number) {
+ return pluralRule(number, _localeCode);
+ }; // Returns the plural rule for the given `number` with the given `localeCode`.
+ // One of: `zero`, `one`, `two`, `few`, `many`, `other`
- _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
- });
- };
+ function pluralRule(number, localeCode) {
+ // modern browsers have this functionality built-in
+ var rules = 'Intl' in window && Intl.PluralRules && new Intl.PluralRules(localeCode);
- _this.nameLabel = function () {
- return _this.t.html('name', {
- 'default': _this.originalName
- });
- };
+ if (rules) {
+ return rules.select(number);
+ } // fallback to basic one/other, as in English
- _this.subtitle = function () {
- if (_this.suggestion) {
- var path = presetID.split('/');
- path.pop(); // remove brand name
- return _t('_tagging.presets.presets.' + path.join('/') + '.name');
- }
+ if (number === 1) return 'one';
+ return 'other';
+ }
+ /**
+ * Try to find that string in `locale` or the current `_localeCode` matching
+ * the given `stringId`. If no string can be found in the requested locale,
+ * we'll recurse down all the `_localeCodes` until one is found.
+ *
+ * @param {string} stringId string identifier
+ * @param {object?} replacements token replacements and default string
+ * @param {string?} locale locale to use (defaults to currentLocale)
+ * @return {string?} localized string
+ */
- return null;
- };
- _this.subtitleLabel = function () {
- if (_this.suggestion) {
- var path = presetID.split('/');
- path.pop(); // remove brand name
+ localizer.tInfo = function (origStringId, replacements, locale) {
+ var stringId = origStringId.trim();
+ var scopeId = 'general';
- return _t.html('_tagging.presets.presets.' + path.join('/') + '.name');
+ if (stringId[0] === '_') {
+ var split = stringId.split('.');
+ scopeId = split[0].slice(1);
+ stringId = split.slice(1).join('.');
}
- return null;
- };
+ locale = locale || _localeCode;
+ var path = stringId.split('.').map(function (s) {
+ return s.replace(/<TX_DOT>/g, '.');
+ }).reverse();
+ var stringsKey = locale; // US English is the default
- _this.terms = function () {
- return _this.t('terms', {
- 'default': _this.originalTerms
- }).toLowerCase().trim().split(/\s*,+\s*/);
- };
+ if (stringsKey.toLowerCase() === 'en-us') stringsKey = 'en';
+ var result = _localeStrings && _localeStrings[scopeId] && _localeStrings[scopeId][stringsKey];
- _this.searchName = function () {
- if (!_searchName) {
- _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
+ while (result !== undefined && path.length) {
+ result = result[path.pop()];
}
- return _searchName;
- };
-
- _this.searchNameStripped = function () {
- if (!_searchNameStripped) {
- _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
+ 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 (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
+ if (number !== undefined) {
+ var rule = pluralRule(number, locale);
- _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
- }
+ 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];
+ }
+ }
+ }
- return _searchNameStripped;
- };
+ if (typeof result === 'string') {
+ for (var key in replacements) {
+ var value = replacements[key];
- _this.isFallback = function () {
- var tagCount = Object.keys(_this.tags).length;
- return tagCount === 0 || tagCount === 1 && _this.tags.hasOwnProperty('area');
- };
+ 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();
+ }
+ }
- _this.addable = function (val) {
- if (!arguments.length) return _addable;
- _addable = val;
- return _this;
- };
+ var token = "{".concat(key, "}");
+ var regex = new RegExp(token, 'g');
+ result = result.replace(regex, value);
+ }
+ }
+ }
- _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'];
+ if (typeof result === 'string') {
+ // found a localized string!
+ return {
+ text: result,
+ locale: locale
+ };
+ }
+ } // no localized string found...
+ // attempt to fallback to a lower-priority language
- if (qid) {
- return {
- qid: qid
- };
- } // Lookup documentation on OSM Wikibase...
+ var index = _localeCodes.indexOf(locale);
- var key = _this.originalReference.key || Object.keys(utilObjectOmit(_this.tags, 'name'))[0];
- var value = _this.originalReference.value || _this.tags[key];
+ 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);
+ }
- if (value === '*') {
- return {
- key: key
- };
- } else {
+ if (replacements && 'default' in replacements) {
+ // Fallback to a default value if one is specified in `replacements`
return {
- key: key,
- value: value
+ text: replacements["default"],
+ locale: null
};
}
- };
-
- _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));
- if (geometry && !skipFieldDefaults) {
- _this.fields().forEach(function (field) {
- if (field.matchGeometry(geometry) && field.key && field["default"] === tags[field.key]) {
- delete tags[field.key];
- }
- });
- }
+ var missing = "Missing ".concat(locale, " translation: ").concat(origStringId);
+ if (typeof console !== 'undefined') console.error(missing); // eslint-disable-line
- delete tags.area;
- return tags;
+ return {
+ text: missing,
+ locale: 'en'
+ };
};
- _this.setTags = function (tags, geometry, skipFieldDefaults) {
- var addTags = _this.addTags;
- tags = Object.assign({}, tags); // shallow copy
+ localizer.hasTextForStringId = function (stringId) {
+ return !!localizer.tInfo(stringId, {
+ "default": 'nothing found'
+ }).locale;
+ }; // Returns only the localized text, discarding the locale info
- 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`)
+ 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
- if (!addTags.hasOwnProperty('area')) {
- delete tags.area;
+ /**
+ * @deprecated This method is considered deprecated. Instead, use the direct DOM manipulating
+ * method `t.append`.
+ */
- if (geometry === 'area') {
- var needsAreaTag = true;
- if (_this.geometry.indexOf('line') === -1) {
- for (var _k2 in addTags) {
- if (_k2 in osmAreaKeys) {
- needsAreaTag = false;
- break;
- }
- }
- }
+ 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);
- if (needsAreaTag) {
- tags.area = 'yes';
- }
+ for (var k in replacements) {
+ if (typeof replacements[k] === 'string') {
+ replacements[k] = escape$4(replacements[k]);
}
- }
- if (geometry && !skipFieldDefaults) {
- _this.fields().forEach(function (field) {
- if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field["default"]) {
- tags[field.key] = field["default"];
- }
- });
+ if (_typeof(replacements[k]) === 'object' && typeof replacements[k].html === 'string') {
+ replacements[k] = replacements[k].html;
+ }
}
- return tags;
- }; // For a preset without fields, use the fields of the parent preset.
- // Replace {preset} placeholders with the fields of the specified presets.
+ var info = localizer.tInfo(stringId, replacements, locale); // text may be empty or undefined if `replacements.default` is
+ if (info.text) {
+ return "<span class=\"localized-text\" lang=\"".concat(info.locale || 'und', "\">").concat(info.text, "</span>");
+ } else {
+ return '';
+ }
+ }; // Adds localized text wrapped as an HTML span element with locale info to the DOM
- function resolve(which) {
- var fieldIDs = which === 'fields' ? _this.originalFields : _this.originalMoreFields;
- var resolved = [];
- fieldIDs.forEach(function (fieldID) {
- var match = fieldID.match(/\{(.*)\}/);
- 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
+ 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 (!resolved.length) {
- var endIndex = _this.id.lastIndexOf('/');
+ 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
- var parentID = endIndex && _this.id.substring(0, endIndex);
- if (parentID) {
- resolved = inheritFields(parentID, which);
- }
- }
+ if (options && options.localOnly) return null;
+ var langInfo = _dataLanguages[code];
- return utilArrayUniq(resolved); // returns an array of fields to inherit from the given presetID, if found
+ 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
- function inheritFields(presetID, which) {
- var parent = allPresets[presetID];
- if (!parent) return [];
+ if (_languageNames[base]) {
+ // base language name in locale language
+ var scriptCode = langInfo.script;
+ var script = _scriptNames[scriptCode] || scriptCode; // e.g. "Serbian (Cyrillic)"
- if (which === 'fields') {
- return parent.fields().filter(shouldInherit);
- } else if (which === 'moreFields') {
- return parent.moreFields();
- } else {
- return [];
+ 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
+ });
+ }
}
- } // Skip `fields` for the keys which define the preset.
- // These are usually `typeCombo` fields like `shop=*`
-
-
- 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;
+ return code; // if not found, use the code
+ };
+
+ return localizer;
}
- var _mainPresetIndex = presetIndex(); // singleton
- // `presetIndex` wraps a `presetCollection`
- // with methods for loading new data and returning defaults
+ // `presetCollection` is a wrapper around an `Array` of presets `collection`,
+ // and decorated with some extra methods for searching and matching geometry
//
- function presetIndex() {
- var dispatch = dispatch$8('favoritePreset', 'recentsChange');
- var MAXRECENTS = 30; // seed the preset lists with geometry fallbacks
+ function presetCollection(collection) {
+ var MAXRESULTS = 50;
+ var _this = {};
+ var _memo = {};
+ _this.collection = collection;
- 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
- });
+ _this.item = function (id) {
+ if (_memo[id]) return _memo[id];
- var _this = presetCollection([POINT, LINE, AREA, RELATION]);
+ var found = _this.collection.find(function (d) {
+ return d.id === id;
+ });
- 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])
+ if (found) _memo[id] = found;
+ return found;
};
- var _fields = {};
- var _categories = {};
- var _universal = [];
- var _addablePresetIDs = null; // Set of preset IDs that the user can add
-
- var _recents;
- var _favorites; // Index of presets by (geometry, tag key).
+ _this.index = function (id) {
+ return _this.collection.findIndex(function (d) {
+ return d.id === id;
+ });
+ };
+ _this.matchGeometry = function (geometry) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return d.matchGeometry(geometry);
+ }));
+ };
- var _geometryIndex = {
- point: {},
- vertex: {},
- line: {},
- area: {},
- relation: {}
+ _this.matchAllGeometry = function (geometries) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return d && d.matchAllGeometry(geometries);
+ }));
};
- var _loadPromise;
-
- _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]
+ _this.matchAnyGeometry = function (geometries) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return geometries.some(function (geom) {
+ return d.matchGeometry(geom);
});
+ }));
+ };
- 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: {}
- //}
+ _this.fallback = function (geometry) {
+ var id = geometry;
+ if (id === 'vertex') id = 'point';
+ return _this.item(id);
+ };
+ _this.search = function (value, geometry, loc) {
+ if (!value) return _this; // don't remove diacritical characters since we're assuming the user is being intentional
- _this.merge = function (d) {
- var newLocationSets = []; // Merge Fields
+ value = value.toLowerCase().trim(); // match at name beginning or just after a space (e.g. "office" -> match "Law Office")
- if (d.fields) {
- Object.keys(d.fields).forEach(function (fieldID) {
- var f = d.fields[fieldID];
+ function leading(a) {
+ var index = a.indexOf(value);
+ return index === 0 || a[index - 1] === ' ';
+ } // match at name beginning only
- 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
+ function leadingStrict(a) {
+ var index = a.indexOf(value);
+ return index === 0;
+ }
- if (d.presets) {
- Object.keys(d.presets).forEach(function (presetID) {
- var p = d.presets[presetID];
+ 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
- if (p) {
- // add or replace
- var isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
+ if (value === aCompare) return -1;
+ if (value === bCompare) return 1; // priority for higher matchScore
- 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];
+ var i = b.originalScore - a.originalScore;
+ if (i !== 0) return i; // priority if search string appears earlier in preset name
- if (existing && !existing.isFallback()) {
- delete _presets[presetID];
- }
- }
- });
- } // Merge Categories
+ i = aCompare.indexOf(value) - bCompare.indexOf(value);
+ if (i !== 0) return i; // priority for shorter preset names
+ return aCompare.length - bCompare.length;
+ };
+ }
- if (d.categories) {
- Object.keys(d.categories).forEach(function (categoryID) {
- var c = d.categories[categoryID];
+ var pool = _this.collection;
- if (c) {
- // add or replace
- c = presetCategory(categoryID, c, _presets);
- if (c.locationSet) newLocationSets.push(c);
- _categories[categoryID] = c;
- } else {
- // remove
- delete _categories[categoryID];
- }
+ if (Array.isArray(loc)) {
+ var validLocations = _mainLocations.locationsAt(loc);
+ pool = pool.filter(function (a) {
+ return !a.locationSetID || validLocations[a.locationSetID];
});
- } // Rebuild _this.collection after changing presets and categories
-
-
- _this.collection = Object.values(_presets).concat(Object.values(_categories)); // Merge Defaults
+ }
- if (d.defaults) {
- Object.keys(d.defaults).forEach(function (geometry) {
- var def = d.defaults[geometry];
+ 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
- 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
+ var leadingNames = searchable.filter(function (a) {
+ return leading(a.searchName());
+ }).sort(sortPresets('searchName')); // matches value to preset suggestion name
+ 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
- _universal = Object.values(_fields).filter(function (field) {
- return field.universal;
- }); // Reset all the preset fields - they'll need to be resolved again
+ 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
- Object.values(_presets).forEach(function (preset) {
- return preset.resetFields();
- }); // Rebuild geometry index
+ 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
- _geometryIndex = {
- point: {},
- vertex: {},
- line: {},
- area: {},
- relation: {}
- };
+ 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
- _this.collection.forEach(function (preset) {
- (preset.geometry || []).forEach(function (geometry) {
- var g = _geometryIndex[geometry];
+ 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
- for (var key in preset.tags) {
- g[key] = g[key] || {};
- var value = preset.tags[key];
- (g[key][value] = g[key][value] || []).push(preset);
- }
+ var similarTerms = searchable.filter(function (a) {
+ return (a.terms() || []).some(function (b) {
+ return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
});
- }); // Merge Custom Features
-
-
- if (d.featureCollection && Array.isArray(d.featureCollection.features)) {
- _mainLocations.mergeCustomGeoJSON(d.featureCollection);
- } // Resolve all locationSet features.
-
+ });
+ var results = leadingNames.concat(leadingSuggestions, leadingNamesStripped, leadingSuggestionsStripped, leadingTerms, leadingSuggestionTerms, leadingTagValues, similarName, similarSuggestions, similarTerms).slice(0, MAXRESULTS - 1);
- if (newLocationSets.length) {
- _mainLocations.mergeLocationSets(newLocationSets);
+ if (geometry) {
+ if (typeof geometry === 'string') {
+ results.push(_this.fallback(geometry));
+ } else {
+ geometry.forEach(function (geom) {
+ return results.push(_this.fallback(geom));
+ });
+ }
}
- return _this;
+ return presetCollection(utilArrayUniq(results));
};
- _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 _this;
+ }
- if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
- geometry = 'point';
- }
+ // `presetCategory` builds a `presetCollection` of member presets,
+ // decorated with some extra methods for searching and matching geometry
+ //
- var entityExtent = entity.extent(resolver);
- return _this.matchTags(entity.tags, geometry, entityExtent.center());
- });
- };
+ function presetCategory(categoryID, category, allPresets) {
+ var _this = Object.assign({}, category); // shallow copy
- _this.matchTags = function (tags, geometry, loc) {
- var keyIndex = _geometryIndex[geometry];
- var bestScore = -1;
- var bestMatch;
- var matchCandidates = [];
- 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;
+ var _searchName; // cache
- for (var i = 0; i < indexMatches.length; i++) {
- var candidate = indexMatches[i];
- var score = candidate.matchScore(tags);
- if (score === -1) {
- continue;
- }
+ var _searchNameStripped; // cache
- matchCandidates.push({
- score: score,
- candidate: candidate
- });
- if (score > bestScore) {
- bestScore = score;
- bestMatch = candidate;
- }
+ _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);
}
}
- if (bestMatch && bestMatch.locationSetID && bestMatch.locationSetID !== '+[Q2]' && Array.isArray(loc)) {
- var validLocations = _mainLocations.locationsAt(loc);
+ return acc;
+ }, []);
- if (!validLocations[bestMatch.locationSetID]) {
- matchCandidates.sort(function (a, b) {
- return a.score < b.score ? 1 : -1;
- });
+ _this.matchGeometry = function (geom) {
+ return _this.geometry.indexOf(geom) >= 0;
+ };
- for (var _i = 0; _i < matchCandidates.length; _i++) {
- var candidateScore = matchCandidates[_i];
+ _this.matchAllGeometry = function (geometries) {
+ return _this.members.collection.some(function (preset) {
+ return preset.matchAllGeometry(geometries);
+ });
+ };
- 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
+ _this.matchScore = function () {
+ return -1;
+ };
+ _this.name = function () {
+ return _t("_tagging.presets.categories.".concat(categoryID, ".name"), {
+ 'default': categoryID
+ });
+ };
- if (!bestMatch || bestMatch.isFallback()) {
- for (var _k in tags) {
- if (/^addr:/.test(_k) && keyIndex['addr:*'] && keyIndex['addr:*']['*']) {
- bestMatch = keyIndex['addr:*']['*'][0];
- break;
- }
- }
+ _this.nameLabel = function () {
+ return _t.html("_tagging.presets.categories.".concat(categoryID, ".name"), {
+ 'default': categoryID
+ });
+ };
+
+ _this.terms = function () {
+ return [];
+ };
+
+ _this.searchName = function () {
+ if (!_searchName) {
+ _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
}
- return bestMatch || _this.fallback(geometry);
+ return _searchName;
};
- _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
+ _this.searchNameStripped = function () {
+ if (!_searchNameStripped) {
+ _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
- 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.
+ if (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
+ _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
+ }
- _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
+ return _searchNameStripped;
+ };
- var presets = _this.collection.filter(function (p) {
- return !p.suggestion && !p.replacement;
- }); // keeplist
+ return _this;
+ }
+ // `presetField` decorates a given `field` Object
+ // with some extra methods for searching and matching geometry
+ //
- presets.forEach(function (p) {
- var keys = p.tags && Object.keys(p.tags);
- var key = keys && keys.length && keys[0]; // pick the first tag
+ function presetField(fieldID, field) {
+ var _this = Object.assign({}, field); // shallow copy
- if (!key) return;
- if (ignore.indexOf(key) !== -1) return;
- if (p.geometry.indexOf('area') !== -1) {
- // probably an area..
- areaKeys[key] = areaKeys[key] || {};
- }
- }); // discardlist
+ _this.id = fieldID; // for use in classes, element ids, css selectors
- presets.forEach(function (p) {
- var key;
+ _this.safeid = utilSafeClassName(fieldID);
- 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 || _this.geometry.indexOf(geom) !== -1;
+ };
- 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.geometry || geometries.every(function (geom) {
+ return _this.geometry.indexOf(geom) !== -1;
});
- 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
+ _this.t = function (scope, options) {
+ return _t("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ };
- if (!key) return pointTags; // if this can be a point
+ _this.t.html = function (scope, options) {
+ return _t.html("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ };
- if (d.geometry.indexOf('point') !== -1) {
- pointTags[key] = pointTags[key] || {};
- pointTags[key][d.tags[key]] = true;
- }
+ _this.hasTextForStringId = function (scope) {
+ return _mainLocalizer.hasTextForStringId("_tagging.presets.fields.".concat(fieldID, ".").concat(scope));
+ };
- return pointTags;
- }, {});
+ _this.title = function () {
+ return _this.overrideLabel || _this.t('label', {
+ 'default': fieldID
+ });
};
- _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
+ _this.label = function () {
+ return _this.overrideLabel || _this.t.html('label', {
+ 'default': fieldID
+ });
+ };
- var keys = d.tags && Object.keys(d.tags);
- var key = keys && keys.length && keys[0]; // pick the first tag
+ var _placeholder = _this.placeholder;
- if (!key) return vertexTags; // if this can be a vertex
+ _this.placeholder = function () {
+ return _this.t('placeholder', {
+ 'default': _placeholder
+ });
+ };
- if (d.geometry.indexOf('vertex') !== -1) {
- vertexTags[key] = vertexTags[key] || {};
- vertexTags[key][d.tags[key]] = true;
- }
+ _this.originalTerms = (_this.terms || []).join();
- return vertexTags;
- }, {});
+ _this.terms = function () {
+ return _this.t('terms', {
+ 'default': _this.originalTerms
+ }).toLowerCase().trim().split(/\s*,+\s*/);
};
- _this.field = function (id) {
- return _fields[id];
- };
+ _this.increment = _this.type === 'number' ? _this.increment || 1 : undefined;
+ return _this;
+ }
- _this.universal = function () {
- return _universal;
- };
+ // `presetPreset` decorates a given `preset` Object
+ // with some extra methods for searching and matching geometry
+ //
- _this.defaults = function (geometry, n, startWithRecents, loc) {
- var recents = [];
+ function presetPreset(presetID, preset, addable, allFields, allPresets) {
+ allFields = allFields || {};
+ allPresets = allPresets || {};
- if (startWithRecents) {
- recents = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
- }
+ var _this = Object.assign({}, preset); // shallow copy
- var defaults;
- if (_addablePresetIDs) {
- defaults = Array.from(_addablePresetIDs).map(function (id) {
- var preset = _this.item(id);
+ var _addable = addable || false;
- if (preset && preset.matchGeometry(geometry)) return preset;
- return null;
- }).filter(Boolean);
- } else {
- defaults = _defaults[geometry].collection.concat(_this.fallback(geometry));
- }
+ var _resolvedFields; // cache
- 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];
- });
- }
+ var _resolvedMoreFields; // cache
- return result;
- }; // pass a Set of addable preset ids
+ var _searchName; // cache
- _this.addablePresetIDs = function (val) {
- if (!arguments.length) return _addablePresetIDs; // accept and convert arrays
- if (Array.isArray(val)) val = new Set(val);
- _addablePresetIDs = val;
+ var _searchNameStripped; // cache
- 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);
- });
- }
- return _this;
+ _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.recent = function () {
- return presetCollection(utilArrayUniq(_this.getRecents().map(function (d) {
- return d.preset;
- })));
+ _this.moreFields = function () {
+ return _resolvedMoreFields || (_resolvedMoreFields = resolve('moreFields'));
};
- function RibbonItem(preset, source) {
- var item = {};
- item.preset = preset;
- item.source = source;
+ _this.resetFields = function () {
+ return _resolvedFields = _resolvedMoreFields = null;
+ };
- item.isFavorite = function () {
- return item.source === 'favorite';
- };
+ _this.tags = _this.tags || {};
+ _this.addTags = _this.addTags || _this.tags;
+ _this.removeTags = _this.removeTags || _this.addTags;
+ _this.geometry = _this.geometry || [];
- item.isRecent = function () {
- return item.source === 'recent';
- };
+ _this.matchGeometry = function (geom) {
+ return _this.geometry.indexOf(geom) >= 0;
+ };
- item.matches = function (preset) {
- return item.preset.id === preset.id;
- };
+ _this.matchAllGeometry = function (geoms) {
+ return geoms.every(_this.matchGeometry);
+ };
- item.minified = function () {
- return {
- pID: item.preset.id
- };
- };
+ _this.matchScore = function (entityTags) {
+ var tags = _this.tags;
+ var seen = {};
+ var score = 0; // match on tags
- return item;
- }
+ for (var k in tags) {
+ seen[k] = true;
- function ribbonItemForMinified(d, source) {
- if (d && d.pID) {
- var preset = _this.item(d.pID);
+ 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 (!preset) return null;
- return RibbonItem(preset, source);
- }
- return null;
- }
+ var addTags = _this.addTags;
- _this.getGenericRibbonItems = function () {
- return ['point', 'line', 'area'].map(function (id) {
- return RibbonItem(_this.item(id), 'generic');
- });
+ for (var _k in addTags) {
+ if (!seen[_k] && entityTags[_k] === addTags[_k]) {
+ score += _this.originalScore;
+ }
+ }
+
+ return score;
};
- _this.getAddable = function () {
- if (!_addablePresetIDs) return [];
- return _addablePresetIDs.map(function (id) {
- var preset = _this.item(id);
+ _this.t = function (scope, options) {
+ var textID = "_tagging.presets.presets.".concat(presetID, ".").concat(scope);
+ return _t(textID, options);
+ };
- if (preset) return RibbonItem(preset, 'addable');
- return null;
- }).filter(Boolean);
+ _this.t.html = function (scope, options) {
+ var textID = "_tagging.presets.presets.".concat(presetID, ".").concat(scope);
+ return _t.html(textID, options);
};
- function setRecents(items) {
- _recents = items;
- var minifiedItems = items.map(function (d) {
- return d.minified();
+ _this.name = function () {
+ return _this.t('name', {
+ 'default': _this.originalName
});
- corePreferences('preset_recents', JSON.stringify(minifiedItems));
- dispatch.call('recentsChange');
- }
-
- _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;
- }, []);
- }
-
- return _recents;
};
- _this.addRecent = function (preset, besidePreset, after) {
- var recents = _this.getRecents();
-
- var beforeItem = _this.recentMatching(besidePreset);
-
- var toIndex = recents.indexOf(beforeItem);
- if (after) toIndex += 1;
- var newItem = RibbonItem(preset, 'recent');
- recents.splice(toIndex, 0, newItem);
- setRecents(recents);
+ _this.nameLabel = function () {
+ return _this.t.html('name', {
+ 'default': _this.originalName
+ });
};
- _this.removeRecent = function (preset) {
- var item = _this.recentMatching(preset);
-
- if (item) {
- var items = _this.getRecents();
+ _this.subtitle = function () {
+ if (_this.suggestion) {
+ var path = presetID.split('/');
+ path.pop(); // remove brand name
- items.splice(items.indexOf(item), 1);
- setRecents(items);
+ return _t('_tagging.presets.presets.' + path.join('/') + '.name');
}
+
+ return null;
};
- _this.recentMatching = function (preset) {
- var items = _this.getRecents();
+ _this.subtitleLabel = function () {
+ if (_this.suggestion) {
+ var path = presetID.split('/');
+ path.pop(); // remove brand name
- for (var i in items) {
- if (items[i].matches(preset)) {
- return items[i];
- }
+ return _t.html('_tagging.presets.presets.' + path.join('/') + '.name');
}
return null;
};
- _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.terms = function () {
+ return _this.t('terms', {
+ 'default': _this.originalTerms
+ }).toLowerCase().trim().split(/\s*,+\s*/);
};
- _this.moveRecent = function (item, beforeItem) {
- var recents = _this.getRecents();
-
- var fromIndex = recents.indexOf(item);
- var toIndex = recents.indexOf(beforeItem);
-
- var items = _this.moveItem(recents, fromIndex, toIndex);
+ _this.searchName = function () {
+ if (!_searchName) {
+ _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
+ }
- if (items) setRecents(items);
+ return _searchName;
};
- _this.setMostRecent = function (preset) {
- if (preset.searchable === false) return;
-
- var items = _this.getRecents();
-
- var item = _this.recentMatching(preset);
-
- if (item) {
- items.splice(items.indexOf(item), 1);
- } else {
- item = RibbonItem(preset, 'recent');
- } // remove the last recent (first in, first out)
-
+ _this.searchNameStripped = function () {
+ if (!_searchNameStripped) {
+ _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
- while (items.length >= MAXRECENTS) {
- items.pop();
- } // prepend array
+ if (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
+ _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
+ }
- items.unshift(item);
- setRecents(items);
+ return _searchNameStripped;
};
- function setFavorites(items) {
- _favorites = items;
- var minifiedItems = items.map(function (d) {
- return d.minified();
- });
- corePreferences('preset_favorites', JSON.stringify(minifiedItems)); // call update
-
- dispatch.call('favoritePreset');
- }
-
- _this.addFavorite = function (preset, besidePreset, after) {
- var favorites = _this.getFavorites();
-
- var beforeItem = _this.favoriteMatching(besidePreset);
+ _this.isFallback = function () {
+ var tagCount = Object.keys(_this.tags).length;
+ return tagCount === 0 || tagCount === 1 && _this.tags.hasOwnProperty('area');
+ };
- var toIndex = favorites.indexOf(beforeItem);
- if (after) toIndex += 1;
- var newItem = RibbonItem(preset, 'favorite');
- favorites.splice(toIndex, 0, newItem);
- setFavorites(favorites);
+ _this.addable = function (val) {
+ if (!arguments.length) return _addable;
+ _addable = val;
+ return _this;
};
- _this.toggleFavorite = function (preset) {
- var favs = _this.getFavorites();
+ _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'];
- var favorite = _this.favoriteMatching(preset);
+ if (qid) {
+ return {
+ qid: qid
+ };
+ } // Lookup documentation on OSM Wikibase...
- 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
+ var key = _this.originalReference.key || Object.keys(utilObjectOmit(_this.tags, 'name'))[0];
+ var value = _this.originalReference.value || _this.tags[key];
- favs.push(RibbonItem(preset, 'favorite'));
+ if (value === '*') {
+ return {
+ key: key
+ };
+ } else {
+ return {
+ key: key,
+ value: value
+ };
}
-
- setFavorites(favs);
};
- _this.removeFavorite = function (preset) {
- var item = _this.favoriteMatching(preset);
-
- if (item) {
- var items = _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));
- items.splice(items.indexOf(item), 1);
- setFavorites(items);
+ if (geometry && !skipFieldDefaults) {
+ _this.fields().forEach(function (field) {
+ if (field.matchGeometry(geometry) && field.key && field["default"] === tags[field.key]) {
+ delete tags[field.key];
+ }
+ });
}
+
+ delete tags.area;
+ return tags;
};
- _this.getFavorites = function () {
- if (!_favorites) {
- // fetch from local storage
- var rawFavorites = JSON.parse(corePreferences('preset_favorites'));
+ _this.setTags = function (tags, geometry, skipFieldDefaults) {
+ var addTags = _this.addTags;
+ tags = Object.assign({}, tags); // shallow copy
- if (!rawFavorites) {
- rawFavorites = [];
- corePreferences('preset_favorites', JSON.stringify(rawFavorites));
+ 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`)
- _favorites = rawFavorites.reduce(function (output, d) {
- var item = ribbonItemForMinified(d, 'favorite');
- if (item && item.preset.addable()) output.push(item);
- return output;
- }, []);
- }
- return _favorites;
- };
+ if (!addTags.hasOwnProperty('area')) {
+ delete tags.area;
- _this.favoriteMatching = function (preset) {
- var favs = _this.getFavorites();
+ if (geometry === 'area') {
+ var needsAreaTag = true;
- for (var index in favs) {
- if (favs[index].matches(preset)) {
- return favs[index];
+ if (_this.geometry.indexOf('line') === -1) {
+ for (var _k2 in addTags) {
+ if (_k2 in osmAreaKeys) {
+ needsAreaTag = false;
+ break;
+ }
+ }
+ }
+
+ if (needsAreaTag) {
+ tags.area = 'yes';
+ }
}
}
- return null;
- };
+ 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 utilRebind(_this, dispatch, 'on');
- }
+ 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 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 i = 0; i < array.length; i++) {
- val = array[i];
- entity = typeof val === 'string' ? graph.hasEntity(val) : val;
+ function resolve(which) {
+ var fieldIDs = which === 'fields' ? _this.originalFields : _this.originalMoreFields;
+ var resolved = [];
+ fieldIDs.forEach(function (fieldID) {
+ var match = fieldID.match(/\{(.*)\}/);
- if (entity) {
- extent._extend(entity.extent(graph));
- }
- }
+ 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
- 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 (!resolved.length) {
+ var endIndex = _this.id.lastIndexOf('/');
- if ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
- tagDiff.push({
- type: '-',
- key: k,
- oldVal: oldVal,
- newVal: newVal,
- display: '- ' + k + '=' + oldVal
- });
- }
+ var parentID = endIndex && _this.id.substring(0, endIndex);
- if ((newVal || newVal === '') && (oldVal === undefined || newVal !== oldVal)) {
- tagDiff.push({
- type: '+',
- key: k,
- oldVal: oldVal,
- newVal: newVal,
- display: '+ ' + k + '=' + newVal
- });
+ if (parentID) {
+ resolved = inheritFields(parentID, which);
+ }
}
- });
- 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));
+ return utilArrayUniq(resolved); // returns an array of fields to inherit from the given presetID, if found
- 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
+ function inheritFields(presetID, which) {
+ var parent = allPresets[presetID];
+ if (!parent) return [];
- 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 (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 utilEntityAndDeepMemberIDs(ids, graph) {
- var seen = new Set();
- ids.forEach(collectDeepDescendants);
- return Array.from(seen);
- 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
+ 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;
+ }
}
- } // returns an selector to select entity ids for:
- // - deep descendant entityIDs for any of those entities that are relations
- 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));
+ return _this;
+ }
- function collectDeepDescendants(id) {
- if (seen.has(id)) return;
- seen.add(id);
+ var _mainPresetIndex = presetIndex(); // singleton
+ // `presetIndex` wraps a `presetCollection`
+ // with methods for loading new data and returning defaults
+ //
- if (!idsSet.has(id)) {
- returners.add(id);
- }
+ function presetIndex() {
+ var dispatch = dispatch$8('favoritePreset', 'recentsChange');
+ var MAXRECENTS = 30; // seed the preset lists with geometry fallbacks
- 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
-
- 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 utilGetAllNodes(ids, graph) {
- var seen = new Set();
- var nodes = new Set();
- ids.forEach(collectNodes);
- return Array.from(nodes);
+ 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
+ });
- function collectNodes(id) {
- if (seen.has(id)) return;
- seen.add(id);
- var entity = graph.hasEntity(id);
- if (!entity) return;
+ var _this = presetCollection([POINT, LINE, AREA, RELATION]);
- 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 _presets = {
+ point: POINT,
+ line: LINE,
+ area: AREA,
+ relation: RELATION
};
- var keyComponents = [];
+ 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 (tags.network) {
- keyComponents.push('network');
- }
+ var _recents;
- if (tags.ref) {
- keyComponents.push('ref');
- } // Routes may need more disambiguation based on direction or destination
+ var _favorites; // Index of presets by (geometry, tag key).
- if (entity.tags.route) {
- if (tags.direction) {
- keyComponents.push('direction');
- } else if (tags.from && tags.to) {
- keyComponents.push('from');
- keyComponents.push('to');
+ var _geometryIndex = {
+ point: {},
+ vertex: {},
+ line: {},
+ area: {},
+ relation: {}
+ };
- if (tags.via) {
- keyComponents.push('via');
- }
- }
- }
+ var _loadPromise;
- if (keyComponents.length) {
- name = _t('inspector.display_name.' + keyComponents.join('_'), tags);
- }
+ _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]
+ });
- return name;
- }
- function utilDisplayNameForPath(entity) {
- var name = utilDisplayName(entity);
- var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;
- var isNewChromium = Number(utilDetect().version.split('.')[0]) >= 96.0;
+ 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 (!isFirefox && !isNewChromium && 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"
- //
+ _this.merge = function (d) {
+ var newLocationSets = []; // Merge Fields
- 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 (d.fields) {
+ Object.keys(d.fields).forEach(function (fieldID) {
+ var f = d.fields[fieldID];
- if (verbose) {
- result = [presetName, displayName].filter(Boolean).join(' ');
- } else {
- result = displayName || presetName;
- } // Fallback to the OSM type (node/way/relation)
+ 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
- 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 (d.presets) {
+ Object.keys(d.presets).forEach(function (presetID) {
+ var p = d.presets[presetID];
- 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
+ if (p) {
+ // add or replace
+ var isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
- 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`
+ 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 (!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 (existing && !existing.isFallback()) {
+ delete _presets[presetID];
}
+ }
+ });
+ } // Merge Categories
+
+
+ if (d.categories) {
+ Object.keys(d.categories).forEach(function (categoryID) {
+ var c = d.categories[categoryID];
+
+ if (c) {
+ // add or replace
+ c = presetCategory(categoryID, c, _presets);
+ if (c.locationSet) newLocationSets.push(c);
+ _categories[categoryID] = c;
} else {
- // type is array
- if (tags[key].indexOf(value) === -1) {
- // subsequent alternate value, add to array
- tags[key].push(value);
- }
+ // remove
+ delete _categories[categoryID];
}
- }
+ });
+ } // Rebuild _this.collection after changing presets and categories
- 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
+ _this.collection = Object.values(_presets).concat(Object.values(_categories)); // Merge Defaults
- tags[key] = tags[key].sort(function (val1, val2) {
- var key = key; // capture
+ if (d.defaults) {
+ Object.keys(d.defaults).forEach(function (geometry) {
+ var def = d.defaults[geometry];
- var count2 = tagCounts[key + '=' + val2];
- var count1 = tagCounts[key + '=' + val1];
+ 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 (count2 !== count1) {
- return count2 - count1;
- }
- if (val2 && val1) {
- return val1.localeCompare(val2);
- }
+ _universal = Object.values(_fields).filter(function (field) {
+ return field.universal;
+ }); // Reset all the preset fields - they'll need to be resolved again
- return val1 ? 1 : -1;
- });
- }
+ Object.values(_presets).forEach(function (preset) {
+ return preset.resetFields();
+ }); // Rebuild geometry index
- return tags;
- }
- function utilStringQs(str) {
- var i = 0; // advance past any leading '?' or '#' characters
+ _geometryIndex = {
+ point: {},
+ vertex: {},
+ line: {},
+ area: {},
+ relation: {}
+ };
- while (i < str.length && (str[i] === '?' || str[i] === '#')) {
- i++;
- }
+ _this.collection.forEach(function (preset) {
+ (preset.geometry || []).forEach(function (geometry) {
+ var g = _geometryIndex[geometry];
- str = str.slice(i);
- return str.split('&').reduce(function (obj, pair) {
- var parts = pair.split('=');
+ 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
- if (parts.length === 2) {
- obj[parts[0]] = null === parts[1] ? '' : decodeURIComponent(parts[1]);
- }
- 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 (d.featureCollection && Array.isArray(d.featureCollection.features)) {
+ _mainLocations.mergeCustomGeoJSON(d.featureCollection);
+ } // Resolve all locationSet features.
- 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;
+ if (newLocationSets.length) {
+ _mainLocations.mergeLocationSets(newLocationSets);
}
- }
- return false;
- }
- function utilPrefixCSSProperty(property) {
- var prefixes = ['webkit', 'ms', 'Moz', 'O'];
- var i = -1;
- var n = prefixes.length;
- var s = document.body.style;
+ return _this;
+ };
- if (property.toLowerCase() in s) {
- return property.toLowerCase();
- }
+ _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
- while (++i < n) {
- if (prefixes[i] + property in s) {
- return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
- }
- }
+ if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
+ geometry = 'point';
+ }
- 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.
+ var entityExtent = entity.extent(resolver);
+ return _this.matchTags(entity.tags, geometry, entityExtent.center());
+ });
+ };
- 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;
+ _this.matchTags = function (tags, geometry, loc) {
+ var keyIndex = _geometryIndex[geometry];
+ var bestScore = -1;
+ var bestMatch;
+ var matchCandidates = [];
- for (i = 0; i <= b.length; i++) {
- matrix[i] = [i];
- }
+ 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 (j = 0; j <= a.length; j++) {
- matrix[0][j] = j;
- }
+ for (var i = 0; i < indexMatches.length; i++) {
+ var candidate = indexMatches[i];
+ var score = candidate.matchScore(tags);
- 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 (score === -1) {
+ continue;
+ }
+
+ matchCandidates.push({
+ score: score,
+ candidate: candidate
+ });
+
+ if (score > bestScore) {
+ bestScore = score;
+ bestMatch = candidate;
+ }
}
}
- }
- 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
+ if (bestMatch && bestMatch.locationSetID && bestMatch.locationSetID !== '+[Q2]' && Array.isArray(loc)) {
+ var validLocations = _mainLocations.locationsAt(loc);
- 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 (!validLocations[bestMatch.locationSetID]) {
+ matchCandidates.sort(function (a, b) {
+ return a.score < b.score ? 1 : -1;
+ });
- function utilWrap(index, length) {
- if (index < 0) {
- index += Math.ceil(-index / length) * length;
- }
+ for (var _i = 0; _i < matchCandidates.length; _i++) {
+ var candidateScore = matchCandidates[_i];
- 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
- */
-
- 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/
+ 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 utilHashcode(str) {
- var hash = 0;
- if (str.length === 0) {
- return hash;
- }
+ if (!bestMatch || bestMatch.isFallback()) {
+ for (var _k in tags) {
+ if (/^addr:/.test(_k) && keyIndex['addr:*'] && keyIndex['addr:*']['*']) {
+ bestMatch = keyIndex['addr:*']['*'][0];
+ break;
+ }
+ }
+ }
- for (var i = 0; i < str.length; i++) {
- var _char = str.charCodeAt(i);
+ return bestMatch || _this.fallback(geometry);
+ };
- hash = (hash << 5) - hash + _char;
- hash = hash & hash; // Convert to 32bit integer
- }
+ _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 hash;
- } // Returns version of `str` with all runs of special characters replaced by `_`;
- // suitable for HTML ids, classes, selectors, etc.
+ 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.
- 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.
- 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.
+ _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
- 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.
+ var presets = _this.collection.filter(function (p) {
+ return !p.suggestion && !p.replacement;
+ }); // keeplist
- function utilUnicodeCharsTruncated(str, limit) {
- return Array.from(str).slice(0, limit).join('');
- }
- function osmEntity(attrs) {
- // For prototypal inheritance.
- if (this instanceof osmEntity) return; // Create the appropriate subtype.
+ presets.forEach(function (p) {
+ var keys = p.tags && Object.keys(p.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- 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 (!key) return;
+ if (ignore.indexOf(key) !== -1) return;
+ if (p.geometry.indexOf('area') !== -1) {
+ // probably an area..
+ areaKeys[key] = areaKeys[key] || {};
+ }
+ }); // discardlist
- return new osmEntity().initialize(arguments);
- }
+ presets.forEach(function (p) {
+ var key;
- osmEntity.id = function (type) {
- return osmEntity.id.fromOSM(type, osmEntity.id.next[type]--);
- };
+ 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];
- osmEntity.id.next = {
- changeset: -1,
- node: -1,
- way: -1,
- relation: -1
- };
+ if (key in areaKeys && // probably an area...
+ p.geometry.indexOf('line') !== -1 && // but sometimes a line
+ value !== '*') {
+ areaKeys[key][value] = true;
+ }
+ }
+ });
+ return areaKeys;
+ };
- osmEntity.id.fromOSM = function (type, id) {
- return type[0] + id;
- };
+ _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.id.toOSM = function (id) {
- return id.slice(1);
- };
+ var keys = d.tags && Object.keys(d.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- 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().
+ 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;
+ }
- osmEntity.key = function (entity) {
- return entity.id + 'v' + (entity.v || 0);
- };
+ return pointTags;
+ }, {});
+ };
- var _deprecatedTagValuesByKey;
+ _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
- osmEntity.deprecatedTagValuesByKey = function (dataDeprecated) {
- if (!_deprecatedTagValuesByKey) {
- _deprecatedTagValuesByKey = {};
- dataDeprecated.forEach(function (d) {
- var oldKeys = Object.keys(d.old);
+ var keys = d.tags && Object.keys(d.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- if (oldKeys.length === 1) {
- var oldKey = oldKeys[0];
- var oldValue = d.old[oldKey];
+ if (!key) return vertexTags; // if this can be a vertex
- if (oldValue !== '*') {
- if (!_deprecatedTagValuesByKey[oldKey]) {
- _deprecatedTagValuesByKey[oldKey] = [oldValue];
- } else {
- _deprecatedTagValuesByKey[oldKey].push(oldValue);
- }
- }
+ if (d.geometry.indexOf('vertex') !== -1) {
+ vertexTags[key] = vertexTags[key] || {};
+ vertexTags[key][d.tags[key]] = true;
}
- });
- }
-
- return _deprecatedTagValuesByKey;
- };
- osmEntity.prototype = {
- tags: {},
- initialize: function initialize(sources) {
- for (var i = 0; i < sources.length; ++i) {
- var source = sources[i];
+ return vertexTags;
+ }, {});
+ };
- for (var prop in source) {
- if (Object.prototype.hasOwnProperty.call(source, prop)) {
- if (source[prop] === undefined) {
- delete this[prop];
- } else {
- this[prop] = source[prop];
- }
- }
- }
- }
+ _this.field = function (id) {
+ return _fields[id];
+ };
- if (!this.id && this.type) {
- this.id = osmEntity.id(this.type);
- }
+ _this.universal = function () {
+ return _universal;
+ };
- if (!this.hasOwnProperty('visible')) {
- this.visible = true;
- }
+ _this.defaults = function (geometry, n, startWithRecents, loc) {
+ var recents = [];
- 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 (startWithRecents) {
+ recents = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
}
- 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
-
- var changed = false;
+ var defaults;
- for (var k in tags) {
- var t1 = merged[k];
- var t2 = tags[k];
+ if (_addablePresetIDs) {
+ defaults = Array.from(_addablePresetIDs).map(function (id) {
+ var preset = _this.item(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()
- );
- }
+ if (preset && preset.matchGeometry(geometry)) return preset;
+ return null;
+ }).filter(Boolean);
+ } else {
+ defaults = _defaults[geometry].collection.concat(_this.fallback(geometry));
}
- 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
+ var result = presetCollection(utilArrayUniq(recents.concat(defaults)).slice(0, n - 1));
- if (Object.keys(tags).length === 0) return [];
- var deprecated = [];
- dataDeprecated.forEach(function (d) {
- var oldKeys = Object.keys(d.old);
+ if (Array.isArray(loc)) {
+ var validLocations = _mainLocations.locationsAt(loc);
+ result.collection = result.collection.filter(function (a) {
+ return !a.locationSetID || validLocations[a.locationSetID];
+ });
+ }
- 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
+ return result;
+ }; // pass a Set of addable preset ids
- if (hasExistingValues) return;
- }
- 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);
+ _this.addablePresetIDs = function (val) {
+ if (!arguments.length) return _addablePresetIDs; // accept and convert arrays
- 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 (Array.isArray(val)) val = new Set(val);
+ _addablePresetIDs = val;
- return false;
+ 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 (matchesDeprecatedTags) {
- deprecated.push(d);
- }
- });
- return deprecated;
- }
- };
+ return _this;
+ };
- 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
+ _this.recent = function () {
+ return presetCollection(utilArrayUniq(_this.getRecents().map(function (d) {
+ return d.preset;
+ })));
+ };
- 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
+ function RibbonItem(preset, source) {
+ var item = {};
+ item.preset = preset;
+ item.source = source;
- 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
- };
- }
+ item.isFavorite = function () {
+ return item.source === 'favorite';
+ };
- function getLaneCount(tags, isOneWay) {
- var count;
+ item.isRecent = function () {
+ return item.source === 'recent';
+ };
- if (tags.lanes) {
- count = parseInt(tags.lanes, 10);
+ item.matches = function (preset) {
+ return item.preset.id === preset.id;
+ };
- if (count > 0) {
- return count;
- }
+ item.minified = function () {
+ return {
+ pID: item.preset.id
+ };
+ };
+
+ return item;
}
- switch (tags.highway) {
- case 'trunk':
- case 'motorway':
- count = isOneWay ? 2 : 4;
- break;
+ function ribbonItemForMinified(d, source) {
+ if (d && d.pID) {
+ var preset = _this.item(d.pID);
- default:
- count = isOneWay ? 1 : 2;
- break;
+ if (!preset) return null;
+ return RibbonItem(preset, source);
+ }
+
+ return null;
}
- return count;
- }
+ _this.getGenericRibbonItems = function () {
+ return ['point', 'line', 'area'].map(function (id) {
+ return RibbonItem(_this.item(id), 'generic');
+ });
+ };
- 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);
- }
+ _this.getAddable = function () {
+ if (!_addablePresetIDs) return [];
+ return _addablePresetIDs.map(function (id) {
+ var preset = _this.item(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;
+ if (preset) return RibbonItem(preset, 'addable');
+ return null;
+ }).filter(Boolean);
+ };
- 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;
+ function setRecents(items) {
+ _recents = items;
+ var minifiedItems = items.map(function (d) {
+ return d.minified();
+ });
+ corePreferences('preset_recents', JSON.stringify(minifiedItems));
+ dispatch.call('recentsChange');
+ }
+
+ _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;
+ }, []);
}
- forward = laneCount - bothways - backward;
- } else if (isNaN(backward)) {
- if (forward > laneCount - bothways) {
- forward = laneCount - bothways;
+ return _recents;
+ };
+
+ _this.addRecent = function (preset, besidePreset, after) {
+ var recents = _this.getRecents();
+
+ var beforeItem = _this.recentMatching(besidePreset);
+
+ 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 (item) {
+ var items = _this.getRecents();
+
+ items.splice(items.indexOf(item), 1);
+ setRecents(items);
}
+ };
- backward = laneCount - bothways - forward;
- }
+ _this.recentMatching = function (preset) {
+ var items = _this.getRecents();
- return {
- forward: forward,
- backward: backward,
- bothways: bothways
+ for (var i in items) {
+ if (items[i].matches(preset)) {
+ return items[i];
+ }
+ }
+
+ return null;
};
- }
- 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.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;
+ };
- 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.moveRecent = function (item, beforeItem) {
+ var recents = _this.getRecents();
- 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;
- });
- }
+ var fromIndex = recents.indexOf(item);
+ var toIndex = recents.indexOf(beforeItem);
- 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;
- });
- }
+ var items = _this.moveItem(recents, fromIndex, toIndex);
- 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 (items) setRecents(items);
+ };
- if (data.backward) {
- data.backward.forEach(function (l, i) {
- if (!lanesObj.backward[i]) lanesObj.backward[i] = {};
- lanesObj.backward[i][key] = l;
- });
- }
+ _this.setMostRecent = function (preset) {
+ if (preset.searchable === false) return;
- if (data.unspecified) {
- data.unspecified.forEach(function (l, i) {
- if (!lanesObj.unspecified[i]) lanesObj.unspecified[i] = {};
- lanesObj.unspecified[i][key] = l;
+ var items = _this.getRecents();
+
+ var item = _this.recentMatching(preset);
+
+ if (item) {
+ items.splice(items.indexOf(item), 1);
+ } else {
+ item = RibbonItem(preset, 'recent');
+ } // remove the last recent (first in, first out)
+
+
+ while (items.length >= MAXRECENTS) {
+ items.pop();
+ } // prepend array
+
+
+ items.unshift(item);
+ setRecents(items);
+ };
+
+ function setFavorites(items) {
+ _favorites = items;
+ var minifiedItems = items.map(function (d) {
+ return d.minified();
});
- }
- }
+ corePreferences('preset_favorites', JSON.stringify(minifiedItems)); // call update
- function osmWay() {
- if (!(this instanceof osmWay)) {
- return new osmWay().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
+ dispatch.call('favoritePreset');
}
- }
- 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();
- for (var i = 0; i < this.nodes.length; i++) {
- var node = resolver.hasEntity(this.nodes[i]);
+ _this.addFavorite = function (preset, besidePreset, after) {
+ var favorites = _this.getFavorites();
- if (node) {
- extent._extend(node.extent());
- }
- }
+ var beforeItem = _this.favoriteMatching(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 = 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();
- 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
- }
- };
+ var favorite = _this.favoriteMatching(preset);
- for (var key in averageWidths) {
- if (this.tags[key] && averageWidths[key][this.tags[key]]) {
- var width = averageWidths[key][this.tags[key]];
+ 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
- if (key === 'highway') {
- var laneCount = this.tags.lanes && parseInt(this.tags.lanes, 10);
- if (!laneCount) laneCount = this.isOneWay() ? 1 : 2;
- return width * laneCount;
- }
- return width;
- }
+ favs.push(RibbonItem(preset, 'favorite'));
}
- return null;
- },
- isOneWay: function isOneWay() {
- // explicit oneway tag..
- var values = {
- 'yes': true,
- '1': true,
- '-1': true,
- 'reversible': true,
- 'alternating': true,
- 'no': false,
- '0': false
- };
+ setFavorites(favs);
+ };
- if (values[this.tags.oneway] !== undefined) {
- return values[this.tags.oneway];
- } // implied oneway tag..
+ _this.removeFavorite = function (preset) {
+ var item = _this.favoriteMatching(preset);
+ if (item) {
+ var items = _this.getFavorites();
- for (var key in this.tags) {
- if (key in osmOneWayTags && this.tags[key] in osmOneWayTags[key]) {
- return true;
+ items.splice(items.indexOf(item), 1);
+ setFavorites(items);
+ }
+ };
+
+ _this.getFavorites = function () {
+ if (!_favorites) {
+ // fetch from local storage
+ var rawFavorites = JSON.parse(corePreferences('preset_favorites'));
+
+ 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;
+ }, []);
}
- 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];
+ return _favorites;
+ };
- 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.favoriteMatching = function (preset) {
+ var favs = _this.getFavorites();
+
+ for (var index in favs) {
+ if (favs[index].matches(preset)) {
+ return favs[index];
}
}
return null;
- },
- isSided: function isSided() {
- if (this.tags.two_sided === 'yes') {
- return false;
- }
+ };
- 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;
+ return utilRebind(_this, dispatch, 'on');
+ }
- 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;
+ 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;
- if (curr === 0) {
- continue;
- } else if (prev && curr !== prev) {
- return false;
- }
+ for (var i = 0; i < array.length; i++) {
+ val = array[i];
+ entity = typeof val === 'string' ? graph.hasEntity(val) : val;
- prev = curr;
+ if (entity) {
+ extent._extend(entity.extent(graph));
}
+ }
- 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;
- }
- }
+ 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];
- 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 ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
+ tagDiff.push({
+ type: '-',
+ key: k,
+ oldVal: oldVal,
+ newVal: newVal,
+ display: '- ' + k + '=' + oldVal
+ });
}
- return graph["transient"](this, 'segments', function () {
- var segments = [];
+ 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
- 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
- });
- }
+ function utilEntityOrMemberSelector(ids, graph) {
+ var seen = new Set(ids);
+ ids.forEach(collectShallowDescendants);
+ return utilEntitySelector(Array.from(seen));
- 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
+ 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);
});
- },
- // 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..
+ }
+ } // returns an selector to select entity ids for:
+ // - entityIDs passed in
+ // - deep descendant entityIDs for any of those entities that are relations
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- i = nodes.length - 1;
- }
+ 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
- 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;
+ function utilEntityAndDeepMemberIDs(ids, graph) {
+ var seen = new Set();
+ ids.forEach(collectDeepDescendants);
+ return Array.from(seen);
- if (index === undefined) {
- index = max;
- }
+ 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 (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..
+ 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));
+ function collectDeepDescendants(id) {
+ if (seen.has(id)) return;
+ seen.add(id);
- if (isClosed) {
- var connector = this.first(); // leading connectors..
+ if (!idsSet.has(id)) {
+ returners.add(id);
+ }
- var i = 1;
+ 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
- while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- } // trailing connectors..
+ 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 utilGetAllNodes(ids, graph) {
+ var seen = new Set();
+ var nodes = new Set();
+ ids.forEach(collectNodes);
+ return Array.from(nodes);
- i = nodes.length - 1;
+ function collectNodes(id) {
+ if (seen.has(id)) return;
+ seen.add(id);
+ var entity = graph.hasEntity(id);
+ if (!entity) return;
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- i = nodes.length - 1;
- }
+ 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 = [];
- nodes.splice(index, 0, id);
- nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+ if (tags.network) {
+ keyComponents.push('network');
+ }
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
- }
+ if (tags.ref) {
+ keyComponents.push('ref');
+ } // Routes may need more disambiguation based on direction or destination
- 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 (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 (entity.tags.route) {
+ if (tags.direction) {
+ keyComponents.push('direction');
+ } else if (tags.from && tags.to) {
+ keyComponents.push('from');
+ keyComponents.push('to');
+ if (tags.via) {
+ keyComponents.push('via');
+ }
+ }
+ }
- if (isClosed) {
- var connector = this.first(); // leading connectors..
+ if (keyComponents.length) {
+ name = _t('inspector.display_name.' + keyComponents.join('_'), tags);
+ }
- var i = 1;
+ return name;
+ }
+ function utilDisplayNameForPath(entity) {
+ var name = utilDisplayName(entity);
+ var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;
+ var isNewChromium = Number(utilDetect().version.split('.')[0]) >= 96.0;
- while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- } // trailing connectors..
+ if (!isFirefox && !isNewChromium && 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"
+ //
- i = nodes.length - 1;
+ 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());
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index === i) index = 0; // update leading connector instead
+ if (verbose) {
+ result = [presetName, displayName].filter(Boolean).join(' ');
+ } else {
+ result = displayName || presetName;
+ } // Fallback to the OSM type (node/way/relation)
- 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..
+ 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 (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
- }
+ 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 this.update({
- nodes: nodes
+ entities.forEach(function (entity) {
+ var keys = Object.keys(entity.tags).filter(Boolean);
+ keys.forEach(function (key) {
+ allKeys.add(key);
});
- },
- // 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();
+ });
+ entities.forEach(function (entity) {
+ allKeys.forEach(function (key) {
+ var value = entity.tags[key]; // purposely allow `undefined`
- for (var i = 0; i < nodes.length; i++) {
- if (nodes[i] === needleID) {
- nodes[i] = replacementID;
+ 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);
+ }
+ }
}
- }
-
- 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
- });
- },
- // 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..
-
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
- }
- return this.update({
- nodes: nodes
+ var tagHash = key + '=' + value;
+ if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
+ tagCounts[tagHash] += 1;
});
- },
- 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;
- }
+ for (var key in tags) {
+ if (!Array.isArray(tags[key])) continue; // sort values by frequency then alphabetically
- return r;
- },
- asGeoJSON: function asGeoJSON(resolver) {
- return resolver["transient"](this, 'GeoJSON', function () {
- var coordinates = resolver.childNodes(this).map(function (n) {
- return n.loc;
- });
+ tags[key] = tags[key].sort(function (val1, val2) {
+ var key = key; // capture
- if (this.isArea() && this.isClosed()) {
- return {
- type: 'Polygon',
- coordinates: [coordinates]
- };
- } else {
- return {
- type: 'LineString',
- coordinates: coordinates
- };
- }
- });
- },
- 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;
- })]
- };
+ var count2 = tagCounts[key + '=' + val2];
+ var count1 = tagCounts[key + '=' + val1];
- if (!this.isClosed() && nodes.length) {
- json.coordinates[0].push(nodes[0].loc);
+ if (count2 !== count1) {
+ return count2 - count1;
}
- 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);
+ if (val2 && val1) {
+ return val1.localeCompare(val2);
}
- return isNaN(area) ? 0 : area;
+ return val1 ? 1 : -1;
});
}
- }); // Filter function to eliminate consecutive duplicates.
- function noRepeatNodes(node, i, arr) {
- return i === 0 || node !== arr[i - 1];
+ return tags;
}
+ function utilStringQs(str) {
+ var i = 0; // advance past any leading '?' or '#' characters
- //
- // 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 osmOldMultipolygonOuterMemberOfRelation(entity, graph) {
- if (entity.type !== 'relation' || !entity.isMultipolygon() || Object.keys(entity.tags).filter(osmIsInterestingTag).length > 1) {
- return false;
+ while (i < str.length && (str[i] === '?' || str[i] === '#')) {
+ i++;
}
- var outerMember;
-
- for (var memberIndex in entity.members) {
- var member = entity.members[memberIndex];
-
- 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);
+ str = str.slice(i);
+ return str.split('&').reduce(function (obj, pair) {
+ var parts = pair.split('=');
- if (Object.keys(outerMember.tags).filter(osmIsInterestingTag).length === 0) {
- return false;
- }
+ if (parts.length === 2) {
+ obj[parts[0]] = null === parts[1] ? '' : decodeURIComponent(parts[1]);
}
+
+ 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 outerMember;
- } // For fixing up rendering of multipolygons with tags on the outer member.
- // https://github.com/openstreetmap/iD/issues/613
+ 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 osmIsOldMultipolygonOuterMember(entity, graph) {
- if (entity.type !== 'way' || Object.keys(entity.tags).filter(osmIsInterestingTag).length === 0) {
- return false;
+ while (++i < n) {
+ if (prefixes[i] + property in s) {
+ return prefixes[i] + property;
+ }
}
- var parents = graph.parentRelations(entity);
- if (parents.length !== 1) return false;
- var parent = parents[0];
+ return false;
+ }
+ function utilPrefixCSSProperty(property) {
+ var prefixes = ['webkit', 'ms', 'Moz', 'O'];
+ var i = -1;
+ var n = prefixes.length;
+ var s = document.body.style;
- if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
- return false;
+ if (property.toLowerCase() in s) {
+ return property.toLowerCase();
}
- var members = parent.members,
- member;
-
- 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;
- }
-
- if (member.id !== entity.id && (!member.role || member.role === 'outer')) {
- // Not a simple multipolygon
- return false;
+ while (++i < n) {
+ if (prefixes[i] + property in s) {
+ return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
}
}
- return parent;
+ return false;
}
- 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];
-
- if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
- 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.
- var members = parent.members,
- member,
- outerMember;
+ 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 (var i = 0; i < members.length; i++) {
- member = members[i];
+ for (i = 0; i <= b.length; i++) {
+ matrix[i] = [i];
+ }
- if (!member.role || member.role === 'outer') {
- if (outerMember) return false; // Not a simple multipolygon
+ for (j = 0; j <= a.length; j++) {
+ matrix[0][j] = j;
+ }
- outerMember = member;
+ 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 (!outerMember) return false;
- var outerEntity = graph.hasEntity(outerMember.id);
+ 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
- if (!outerEntity || !Object.keys(outerEntity.tags).filter(osmIsInterestingTag).length) {
- return false;
+ 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]
+
+ function utilWrap(index, length) {
+ if (index < 0) {
+ index += Math.ceil(-index / length) * length;
}
- 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.
- //
+ 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
+ */
- function osmJoinWays(toJoin, graph) {
- function resolve(member) {
- return graph.childNodes(graph.entity(member.id));
+ 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/
+
+ function utilHashcode(str) {
+ var hash = 0;
+
+ if (str.length === 0) {
+ return hash;
}
- 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
+ for (var i = 0; i < str.length; i++) {
+ var _char = str.charCodeAt(i);
+ hash = (hash << 5) - hash + _char;
+ hash = hash & hash; // Convert to 32bit integer
+ }
- 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)
+ return hash;
+ } // Returns version of `str` with all runs of special characters replaced by `_`;
+ // suitable for HTML ids, classes, selectors, etc.
- var i;
- var joinAsMembers = true;
+ 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.
- for (i = 0; i < toJoin.length; i++) {
- if (toJoin[i] instanceof osmWay) {
- joinAsMembers = false;
- break;
- }
- }
+ 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.
- var sequences = [];
- sequences.actions = [];
+ 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.
- while (toJoin.length) {
- // start a new sequence
- var item = toJoin.shift();
- var currWays = [item];
- var currNodes = resolve(item).slice(); // add to it
+ function utilUnicodeCharsTruncated(str, limit) {
+ return Array.from(str).slice(0, limit).join('');
+ }
- 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.
+ function toNumericID(id) {
+ var match = id.match(/^[cnwr](-?\d+)$/);
- 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 (match) {
+ return parseInt(match[1], 10);
+ }
- 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];
- }
+ return NaN;
+ }
- if (nodes[0] === end) {
- fn = currNodes.push; // join to end
+ 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.
- 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
+ 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.
- nodes = nodes.slice(0, -1);
- break;
- } else if (nodes[0] === start) {
- fn = currNodes.unshift; // join to beginning
+ function utilOldestID(ids) {
+ if (ids.length === 0) {
+ return undefined;
+ }
- nodes = nodes.slice(1).reverse();
- item = reverse(item);
- break;
- } else {
- fn = nodes = null;
- }
- }
+ var oldestIDIndex = 0;
+ var oldestID = toNumericID(ids[0]);
- if (!nodes) {
- // couldn't find a joinable way/member
- break;
- }
+ for (var i = 1; i < ids.length; i++) {
+ var num = toNumericID(ids[i]);
- fn.apply(currWays, [item]);
- fn.apply(currNodes, nodes);
- toJoin.splice(i, 1);
+ if (compareNumericIDs(oldestID, num) === 1) {
+ oldestIDIndex = i;
+ oldestID = num;
}
-
- currWays.nodes = currNodes;
- sequences.push(currWays);
}
- return sequences;
+ return ids[oldestIDIndex];
}
- 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.
+ function osmEntity(attrs) {
+ // For prototypal inheritance.
+ if (this instanceof osmEntity) return; // Create the appropriate subtype.
- var isPTv2 = /stop|platform/.test(member.role);
+ 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 ((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;
- }
- graph = graph.replace(relation.addMember(member, memberIndex));
- }
+ return new osmEntity().initialize(arguments);
+ }
- return graph;
- }; // Add a way member into the relation "wherever it makes sense".
- // In this situation we were not supplied a memberIndex.
+ osmEntity.id = function (type) {
+ return osmEntity.id.fromOSM(type, osmEntity.id.next[type]--);
+ };
- function addWayMember(relation, graph) {
- var groups, tempWay, item, i, j, k; // remove PTv2 stops and platforms before doing anything.
+ osmEntity.id.next = {
+ changeset: -1,
+ node: -1,
+ way: -1,
+ relation: -1
+ };
- var PTv2members = [];
- var members = [];
+ osmEntity.id.fromOSM = function (type, id) {
+ return type[0] + id;
+ };
- for (i = 0; i < relation.members.length; i++) {
- var m = relation.members[i];
+ osmEntity.id.toOSM = function (id) {
+ var match = id.match(/^[cnwr](-?\d+)$/);
- if (/stop|platform/.test(m.role)) {
- PTv2members.push(m);
- } else {
- members.push(m);
- }
- }
+ if (match) {
+ return match[1];
+ }
- relation = relation.update({
- members: members
- });
+ return '';
+ };
- 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);
- }
+ 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().
- 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
- 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
+ osmEntity.key = function (entity) {
+ return entity.id + 'v' + (entity.v || 0);
+ };
- for (j = 0; j < members.length; j++) {
- if (members[j].index === startIndex) {
- break;
- }
- } // k = each member in segment
+ var _deprecatedTagValuesByKey;
+ osmEntity.deprecatedTagValuesByKey = function (dataDeprecated) {
+ if (!_deprecatedTagValuesByKey) {
+ _deprecatedTagValuesByKey = {};
+ dataDeprecated.forEach(function (d) {
+ var oldKeys = Object.keys(d.old);
- 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
+ if (oldKeys.length === 1) {
+ var oldKey = oldKeys[0];
+ var oldValue = d.old[oldKey];
- 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
- }];
+ if (oldValue !== '*') {
+ if (!_deprecatedTagValuesByKey[oldKey]) {
+ _deprecatedTagValuesByKey[oldKey] = [oldValue];
} else {
- item.pair = [{
- id: insertPair.insertedID,
- type: 'way',
- role: item.role
- }, {
- id: insertPair.originalID,
- type: 'way',
- role: item.role
- }];
+ _deprecatedTagValuesByKey[oldKey].push(oldValue);
}
- } // reorder `members` if necessary
+ }
+ }
+ });
+ }
+ return _deprecatedTagValuesByKey;
+ };
- if (k > 0) {
- if (j + k >= members.length || item.index !== members[j + k].index) {
- moveMember(members, item.index, j + k);
+ osmEntity.prototype = {
+ tags: {},
+ initialize: function initialize(sources) {
+ for (var i = 0; i < sources.length; ++i) {
+ var source = sources[i];
+
+ for (var prop in source) {
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
+ if (source[prop] === undefined) {
+ delete this[prop];
+ } else {
+ this[prop] = source[prop];
}
}
-
- nodes.splice(0, way.nodes.length - 1);
}
}
- if (tempWay) {
- graph = graph.remove(tempWay);
- } // Final pass: skip dead items, split pairs, remove index properties
+ if (!this.id && this.type) {
+ this.id = osmEntity.id(this.type);
+ }
+ if (!this.hasOwnProperty('visible')) {
+ this.visible = true;
+ }
- var wayMembers = [];
+ 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);
+ }
- for (i = 0; i < members.length; i++) {
- item = members[i];
- if (item.index === -1) continue;
+ 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() {
+ 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 (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
+ var changed = false;
+ for (var k in tags) {
+ var t1 = merged[k];
+ var t2 = tags[k];
- 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
- //
+ 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()
+ );
+ }
+ }
- function moveMember(arr, findIndex, toIndex) {
- var i;
+ 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
- for (i = 0; i < arr.length; i++) {
- if (arr[i].index === findIndex) {
- break;
- }
+ if (Object.keys(tags).length === 0) return [];
+ var deprecated = [];
+ dataDeprecated.forEach(function (d) {
+ var oldKeys = Object.keys(d.old);
+
+ 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 (hasExistingValues) return;
}
- var item = Object.assign({}, arr[i]); // shallow copy
+ 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);
- arr[i].index = -1; // mark as dead
+ 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;
+ }
+ }
+ }
- 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
+ return false;
+ });
+ if (matchesDeprecatedTags) {
+ deprecated.push(d);
+ }
+ });
+ return deprecated;
+ }
+ };
- function withIndex(arr) {
- var result = new Array(arr.length);
+ 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
- for (var i = 0; i < arr.length; i++) {
- result[i] = Object.assign({}, arr[i]); // shallow copy
+ 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
- result[i].index = i;
- }
+ 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
+ };
+ }
- return result;
+ function getLaneCount(tags, isOneWay) {
+ var count;
+
+ if (tags.lanes) {
+ count = parseInt(tags.lanes, 10);
+
+ if (count > 0) {
+ return count;
}
}
+
+ switch (tags.highway) {
+ case 'trunk':
+ case 'motorway':
+ count = isOneWay ? 2 : 4;
+ break;
+
+ default:
+ count = isOneWay ? 1 : 2;
+ break;
+ }
+
+ return count;
}
- 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.
+ 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);
+ }
- return;
- }
- }
- });
- return graph;
+ 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;
+
+ 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;
+ }
+
+ forward = laneCount - bothways - backward;
+ } else if (isNaN(backward)) {
+ if (forward > laneCount - bothways) {
+ forward = laneCount - bothways;
+ }
+
+ backward = laneCount - bothways - forward;
+ }
+
+ return {
+ forward: forward,
+ backward: backward,
+ bothways: bothways
};
}
- // 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));
- };
+ 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;
+ });
+ });
}
- function actionChangeMember(relationId, member, memberIndex) {
- return function (graph) {
- return graph.replace(graph.entity(relationId).updateMember(member, memberIndex));
- };
+ 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;
+ });
}
- 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
+ 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 (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 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 actionChangeTags(entityId, tags) {
- return function (graph) {
- var entity = graph.entity(entityId);
- return graph.replace(entity.update({
- tags: tags
- }));
- };
+ 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;
+ });
+ }
+
+ if (data.unspecified) {
+ data.unspecified.forEach(function (l, i) {
+ if (!lanesObj.unspecified[i]) lanesObj.unspecified[i] = {};
+ lanesObj.unspecified[i][key] = l;
+ });
+ }
}
- function osmNode() {
- if (!(this instanceof osmNode)) {
- return new osmNode().initialize(arguments);
+ function osmWay() {
+ if (!(this instanceof osmWay)) {
+ return new osmWay().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';
+ 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;
},
- move: function move(loc) {
- return this.update({
- loc: loc
+ extent: function extent(resolver) {
+ return resolver["transient"](this, 'extent', function () {
+ var extent = geoExtent();
+
+ for (var i = 0; i < this.nodes.length; i++) {
+ var node = resolver.hasEntity(this.nodes[i]);
+
+ if (node) {
+ extent._extend(node.extent());
+ }
+ }
+
+ return extent;
});
},
- 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);
+ first: function first() {
+ return this.nodes[0];
},
- // 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?
+ 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 (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
- var re = /:direction$/i;
- var keys = Object.keys(this.tags);
+ 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
+ }
+ };
- for (i = 0; i < keys.length; i++) {
- if (re.test(keys[i])) {
- val = this.tags[keys[i]].toLowerCase();
- break;
+ 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;
}
+
+ return width;
}
}
- 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
+ return null;
+ },
+ isOneWay: function isOneWay() {
+ // explicit oneway tag..
+ var values = {
+ 'yes': true,
+ '1': true,
+ '-1': true,
+ 'reversible': true,
+ 'alternating': true,
+ 'no': false,
+ '0': false
};
- 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 (v !== '' && !isNaN(+v)) {
- results.push(+v);
- return;
- } // string direction - inspect parent ways
+ if (values[this.tags.oneway] !== undefined) {
+ return values[this.tags.oneway];
+ } // implied oneway tag..
- 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 (var key in this.tags) {
+ if (key in osmOneWayTags && this.tags[key] in osmOneWayTags[key]) {
+ return 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
- }
+ 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 (lookBackward && i < nodes.length - 1) {
- nodeIds[nodes[i + 1]] = true; // look ahead to next node
- }
- }
+ 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);
- 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 null;
},
- isCrossing: function isCrossing() {
- return this.tags.highway === 'crossing' || this.tags.railway && this.tags.railway.indexOf('crossing') !== -1;
+ isSided: function isSided() {
+ if (this.tags.two_sided === 'yes') {
+ return false;
+ }
+
+ return this.sidednessIdentifier() !== null;
},
- 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;
- });
+ lanes: function lanes() {
+ return osmLanes(this);
},
- 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();
-
- if (way.isClosed()) {
- nodes.pop();
- } // ignore connecting node if closed
- // return true if vertex appears multiple times (way is self intersecting)
+ 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 (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;
- return nodes.indexOf(this.id) !== nodes.lastIndexOf(this.id);
+ if (curr === 0) {
+ continue;
+ } else if (prev && curr !== prev) {
+ return false;
}
- return false;
- });
- },
- 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';
- });
- });
+ prev = curr;
+ }
+
+ return true;
},
- isIntersection: function isIntersection(resolver) {
- return this.parentIntersectionWays(resolver).length > 1;
+ // returns an object with the tag that implies this is an area, if any
+ tagSuggestingArea: function tagSuggestingArea() {
+ return osmTagSuggestingArea(this.tags);
},
- 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;
- });
+ isArea: function isArea() {
+ if (this.tags.area === 'yes') return true;
+ if (!this.isClosed() || this.tags.area === 'no') return false;
+ return this.tagSuggestingArea() !== null;
},
- 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;
- });
+ isDegenerate: function isDegenerate() {
+ return new Set(this.nodes).size < (this.isArea() ? 3 : 2);
},
- 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)
+ 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 (changeset_id) r.node['@changeset'] = changeset_id;
- return r;
+ }
+
+ return false;
},
- asGeoJSON: function asGeoJSON() {
- return {
- type: 'Point',
- coordinates: this.loc
- };
- }
- });
+ 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])]]);
+ }
- function actionCircularize(wayId, projection, maxAngle) {
- maxAngle = (maxAngle || 20) * Math.PI / 180;
+ return graph["transient"](this, 'segments', function () {
+ var segments = [];
- 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;
+ 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 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..
- if (!way.isConvex(graph)) {
- graph = action.makeConvex(graph);
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ i = nodes.length - 1;
}
- 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);
+ nodes = nodes.filter(noRepeatNodes);
+ return this.update({
+ nodes: nodes
});
- 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
+ },
+ // 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 (!keyNodes.length) {
- keyNodes = [nodes[0]];
- keyPoints = [points[0]];
+ if (index === undefined) {
+ index = max;
}
- 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 (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..
- 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 (isClosed) {
+ var connector = this.first(); // leading connectors..
- if (indexRange < 0) {
- indexRange += nodes.length;
- } // position this key node
+ var i = 1;
+ while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ } // trailing connectors..
- 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
- 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
+ i = nodes.length - 1;
- if (totalAngle * sign > 0) {
- totalAngle = -sign * (2 * Math.PI - Math.abs(totalAngle));
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ i = nodes.length - 1;
}
+ }
- do {
- numberNewPoints++;
- eachAngle = totalAngle / (indexRange + numberNewPoints);
- } while (Math.abs(eachAngle) > maxAngle); // move existing nodes
+ 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]);
+ }
- 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
+ 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 (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..
- 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;
+ if (isClosed) {
+ var connector = this.first(); // leading connectors..
- for (var nodeId in nearNodes) {
- var nearAngle = nearNodes[nodeId];
- var dist = Math.abs(nearAngle - angle);
+ var i = 1;
- if (dist < min) {
- min = dist;
- origNode = origNodes[nodeId];
- }
- }
+ while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ } // trailing connectors..
- 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..
+ i = nodes.length - 1;
- if (indexRange === 1 && inBetweenNodes.length) {
- var startIndex1 = way.nodes.lastIndexOf(startNode.id);
- var endIndex1 = way.nodes.lastIndexOf(endNode.id);
- var wayDirection1 = endIndex1 - startIndex1;
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index === i) index = 0; // update leading connector instead
- if (wayDirection1 < -1) {
- wayDirection1 = 1;
- }
+ i = nodes.length - 1;
+ }
+ }
- var parentWays = graph.parentWays(keyNodes[i]);
+ nodes.splice(index, 1, id);
+ nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
- for (j = 0; j < parentWays.length; j++) {
- var sharedWay = parentWays[j];
- if (sharedWay === way) continue;
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
- 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;
+ 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();
- if (wayDirection2 < -1) {
- wayDirection2 = 1;
- }
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i] === needleID) {
+ nodes[i] = replacementID;
+ }
+ }
- if (wayDirection1 !== wayDirection2) {
- inBetweenNodes.reverse();
- insertAt = startIndex2;
- }
+ nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
- for (k = 0; k < inBetweenNodes.length; k++) {
- sharedWay = sharedWay.addNode(inBetweenNodes[k], insertAt + k);
- }
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
- graph = graph.replace(sharedWay);
- }
- }
- }
- } // update the way to have all the new nodes
+ 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..
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
- 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 this.update({
+ nodes: nodes
});
- var sign = d3_polygonArea(points) > 0 ? 1 : -1;
- var hull = d3_polygonHull(points);
- var i, j; // D3 convex hulls go counterclockwise..
+ },
+ 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 (sign === -1) {
- nodes.reverse();
- points.reverse();
+ if (changeset_id) {
+ r.way['@changeset'] = changeset_id;
}
- 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..
-
+ return r;
+ },
+ asGeoJSON: function asGeoJSON(resolver) {
+ return resolver["transient"](this, 'GeoJSON', function () {
+ var coordinates = resolver.childNodes(this).map(function (n) {
+ return n.loc;
+ });
- 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);
+ if (this.isArea() && this.isClosed()) {
+ return {
+ type: 'Polygon',
+ coordinates: [coordinates]
+ };
+ } else {
+ return {
+ type: 'LineString',
+ coordinates: coordinates
+ };
}
- }
+ });
+ },
+ 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;
+ })]
+ };
- return graph;
- };
+ if (!this.isClosed() && nodes.length) {
+ json.coordinates[0].push(nodes[0].loc);
+ }
- action.disabled = function (graph) {
- if (!graph.entity(wayId).isClosed()) {
- return 'not_closed';
- } //disable when already circular
+ 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);
+ }
- var way = graph.entity(wayId);
- var nodes = utilArrayUniq(graph.childNodes(way));
- var points = nodes.map(function (n) {
- return projection(n.loc);
+ return isNaN(area) ? 0 : area;
});
- var hull = d3_polygonHull(points);
- var epsilonAngle = Math.PI / 180;
-
- if (hull.length !== points.length || hull.length < 3) {
- return false;
- }
-
- var centroid = d3_polygonCentroid(points);
- var radius = geoVecLengthSquare(centroid, points[0]);
- var i, actualPoint; // compare distances between centroid and points
+ }
+ }); // Filter function to eliminate consecutive duplicates.
- 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%)
+ function noRepeatNodes(node, i, arr) {
+ return i === 0 || node !== arr[i - 1];
+ }
- if (diff > 0.05 * radius) {
- return false;
- }
- } //check if central angles are smaller than maxAngle
+ //
+ // 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 osmOldMultipolygonOuterMemberOfRelation(entity, graph) {
+ if (entity.type !== 'relation' || !entity.isMultipolygon() || Object.keys(entity.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
+ }
- 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;
+ var outerMember;
- if (angle < 0) {
- angle = -angle;
- }
+ for (var memberIndex in entity.members) {
+ var member = entity.members[memberIndex];
- if (angle > Math.PI) {
- angle = 2 * Math.PI - angle;
- }
+ 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);
- if (angle > maxAngle + epsilonAngle) {
+ if (Object.keys(outerMember.tags).filter(osmIsInterestingTag).length === 0) {
return false;
}
}
+ }
- return 'already_circular';
- };
-
- action.transitionable = true;
- return action;
- }
-
- 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 (geometries.point) return false; // delete if this node only be a vertex
-
- 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 outerMember;
+ } // For fixing up rendering of multipolygons with tags on the outer member.
+ // https://github.com/openstreetmap/iD/issues/613
- return !node.hasInterestingTags();
+ function osmIsOldMultipolygonOuterMember(entity, graph) {
+ if (entity.type !== 'way' || Object.keys(entity.tags).filter(osmIsInterestingTag).length === 0) {
+ return false;
}
- var action = function action(graph) {
- var way = graph.entity(wayID);
- graph.parentRelations(way).forEach(function (parent) {
- parent = parent.removeMembersWithID(wayID);
- graph = graph.replace(parent);
+ var parents = graph.parentRelations(entity);
+ if (parents.length !== 1) return false;
+ var parent = parents[0];
- 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);
+ if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
+ }
- if (canDeleteNode(node, graph)) {
- graph = graph.remove(node);
- }
- });
- return graph.remove(way);
- };
+ var members = parent.members,
+ member;
- return action;
- }
+ for (var i = 0; i < members.length; i++) {
+ member = members[i];
- function actionDeleteMultiple(ids) {
- var actions = {
- way: actionDeleteWay,
- node: actionDeleteNode,
- relation: actionDeleteRelation
- };
+ if (member.id === entity.id && member.role && member.role !== 'outer') {
+ // Not outer member
+ return false;
+ }
- 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;
- };
+ if (member.id !== entity.id && (!member.role || member.role === 'outer')) {
+ // Not a simple multipolygon
+ return false;
+ }
+ }
- return action;
+ 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];
- function actionDeleteRelation(relationID, allowUntaggedMembers) {
- function canDeleteEntity(entity, graph) {
- return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags() && !allowUntaggedMembers;
+ if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
}
- var action = function action(graph) {
- var relation = graph.entity(relationID);
- graph.parentRelations(relation).forEach(function (parent) {
- parent = parent.removeMembersWithID(relationID);
- graph = graph.replace(parent);
-
- 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);
-
- if (canDeleteEntity(entity, graph)) {
- graph = actionDeleteMultiple([memberID])(graph);
- }
- });
- return graph.remove(relation);
- };
+ var members = parent.members,
+ member,
+ outerMember;
- return action;
- }
+ for (var i = 0; i < members.length; i++) {
+ member = members[i];
- 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 (!member.role || member.role === 'outer') {
+ if (outerMember) return false; // Not a simple multipolygon
- if (parent.isDegenerate()) {
- graph = actionDeleteWay(parent.id)(graph);
- }
- });
- graph.parentRelations(node).forEach(function (parent) {
- parent = parent.removeMembersWithID(nodeId);
- graph = graph.replace(parent);
+ outerMember = member;
+ }
+ }
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(graph);
- }
- });
- return graph.remove(node);
- };
+ if (!outerMember) return false;
+ var outerEntity = graph.hasEntity(outerMember.id);
- return action;
- }
+ if (!outerEntity || !Object.keys(outerEntity.tags).filter(osmIsInterestingTag).length) {
+ return false;
+ }
+ 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.
//
- // First choose a node to be the survivor, with preference given
- // to an existing (not new) node.
+ // The return value is a nested array. Each constituent array contains elements
+ // of `toJoin` which have been determined to connect.
//
- // Tags and relation memberships of of non-surviving nodes are merged
- // to the survivor.
+ // 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.
//
- // This is the inverse of `iD.actionDisconnect`.
+ // 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.
//
- // 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 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.
//
- 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
-
- 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.
-
+ function osmJoinWays(toJoin, graph) {
+ function resolve(member) {
+ return graph.childNodes(graph.entity(member.id));
+ }
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- if (node.id === survivor.id) continue;
- parents = graph.parentWays(node);
+ 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
- for (j = 0; j < parents.length; j++) {
- graph = graph.replace(parents[j].replaceNode(node.id, survivor.id));
- }
- parents = graph.parentRelations(node);
+ 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)
- for (j = 0; j < parents.length; j++) {
- graph = graph.replace(parents[j].replaceMember(node, survivor));
- }
+ var i;
+ var joinAsMembers = true;
- survivor = survivor.mergeTags(node.tags);
- graph = actionDeleteNode(node.id)(graph);
+ for (i = 0; i < toJoin.length; i++) {
+ if (toJoin[i] instanceof osmWay) {
+ joinAsMembers = false;
+ break;
}
+ }
- graph = graph.replace(survivor); // find and delete any degenerate ways created by connecting adjacent vertices
-
- parents = graph.parentWays(survivor);
+ var sequences = [];
+ sequences.actions = [];
- for (i = 0; i < parents.length; i++) {
- if (parents[i].isDegenerate()) {
- graph = actionDeleteWay(parents[i].id)(graph);
- }
- }
+ while (toJoin.length) {
+ // start a new sequence
+ var item = toJoin.shift();
+ var currWays = [item];
+ var currNodes = resolve(item).slice(); // add to it
- return graph;
- };
+ 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.
- 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
+ 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.
- 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 (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
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- relations = graph.parentRelations(node);
+ nodes = nodes.slice(1);
+ break;
+ } else if (nodes[nodes.length - 1] === end) {
+ fn = currNodes.push; // join to end
- 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
+ nodes = nodes.slice(0, -1).reverse();
+ item = reverse(item);
+ break;
+ } else if (nodes[nodes.length - 1] === start) {
+ fn = currNodes.unshift; // join to beginning
- if (relation.hasFromViaTo()) {
- restrictionIDs.push(relation.id);
- }
+ nodes = nodes.slice(0, -1);
+ break;
+ } else if (nodes[0] === start) {
+ fn = currNodes.unshift; // join to beginning
- if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
- return 'relation';
+ nodes = nodes.slice(1).reverse();
+ item = reverse(item);
+ break;
} else {
- seen[relation.id] = role;
+ fn = nodes = null;
}
}
- } // gather restrictions for parent ways
+ if (!nodes) {
+ // couldn't find a joinable way/member
+ break;
+ }
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- var parents = graph.parentWays(node);
+ fn.apply(currWays, [item]);
+ fn.apply(currNodes, nodes);
+ toJoin.splice(i, 1);
+ }
- for (j = 0; j < parents.length; j++) {
- var parent = parents[j];
- relations = graph.parentRelations(parent);
+ currWays.nodes = currNodes;
+ sequences.push(currWays);
+ }
- for (k = 0; k < relations.length; k++) {
- relation = relations[k];
+ return sequences;
+ }
- if (relation.hasFromViaTo()) {
- restrictionIDs.push(relation.id);
- }
- }
+ 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 isPTv2 = /stop|platform/.test(member.role);
+
+ 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;
}
- } // test restrictions
+ graph = graph.replace(relation.addMember(member, memberIndex));
+ }
- restrictionIDs = utilArrayUniq(restrictionIDs);
+ return graph;
+ }; // Add a way member into the relation "wherever it makes sense".
+ // In this situation we were not supplied a memberIndex.
- 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)
+ function addWayMember(relation, graph) {
+ var groups, tempWay, insertPairIsReversed, item, i, j, k; // remove PTv2 stops and platforms before doing anything.
- var nodes = {
- from: [],
- via: [],
- to: [],
- keyfrom: [],
- keyto: []
- };
+ var PTv2members = [];
+ var members = [];
- for (j = 0; j < relation.members.length; j++) {
- collectNodes(relation.members[j], nodes);
- }
+ for (i = 0; i < relation.members.length; i++) {
+ var m = relation.members[i];
- 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;
+ if (/stop|platform/.test(m.role)) {
+ PTv2members.push(m);
+ } else {
+ members.push(m);
+ }
+ }
- for (j = 0; j < nodeIDs.length; j++) {
- var n = nodeIDs[j];
+ relation = relation.update({
+ members: members
+ });
- if (nodes.from.indexOf(n) !== -1) {
- connectFrom = true;
- }
+ 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.)
- if (nodes.via.indexOf(n) !== -1) {
- connectVia = true;
- }
+ 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 (nodes.to.indexOf(n) !== -1) {
- connectTo = true;
- }
+ 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 (nodes.keyfrom.indexOf(n) !== -1) {
- connectKeyFrom = true;
- }
+ 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 (nodes.keyto.indexOf(n) !== -1) {
- connectKeyTo = true;
+ for (j = 0; j < members.length; j++) {
+ if (members[j].index === startIndex) {
+ break;
}
- }
-
- if (connectFrom && connectTo && !isUturn) {
- return 'restriction';
- }
-
- if (connectFrom && connectVia) {
- return 'restriction';
- }
-
- 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.
-
+ } // k = each member in segment
- if (connectKeyFrom || connectKeyTo) {
- if (nodeIDs.length !== 2) {
- return 'restriction';
- }
- var n0 = null;
- var n1 = null;
+ 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 (j = 0; j < memberWays.length; j++) {
- way = memberWays[j];
+ if (tempWay && item.id === tempWay.id) {
+ var reverse = nodes[0].id !== insertPair.nodes[0] ^ insertPairIsReversed;
- if (way.contains(nodeIDs[0])) {
- n0 = nodeIDs[0];
+ 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
- if (way.contains(nodeIDs[1])) {
- n1 = nodeIDs[1];
+
+ if (k > 0) {
+ if (j + k >= members.length || item.index !== members[j + k].index) {
+ moveMember(members, item.index, j + k);
}
}
- if (n0 && n1) {
- // both nodes are part of the restriction
- var ok = false;
+ nodes.splice(0, way.nodes.length - 1);
+ }
+ }
- for (j = 0; j < memberWays.length; j++) {
- way = memberWays[j];
+ if (tempWay) {
+ graph = graph.remove(tempWay);
+ } // Final pass: skip dead items, split pairs, remove index properties
- if (way.areAdjacent(n0, n1)) {
- ok = true;
- break;
- }
- }
- 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)
+ var wayMembers = [];
+ for (i = 0; i < members.length; i++) {
+ item = members[i];
+ if (item.index === -1) continue;
- for (j = 0; j < memberWays.length; j++) {
- way = memberWays[j].update({}); // make copy
+ 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
- for (k = 0; k < nodeIDs.length; k++) {
- if (nodeIDs[k] === survivor.id) continue;
- if (way.areAdjacent(nodeIDs[k], survivor.id)) {
- way = way.removeNode(nodeIDs[k]);
- } else {
- way = way.replaceNode(nodeIDs[k], survivor.id);
- }
- }
+ 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
+ //
- if (way.isDegenerate()) {
- return 'restriction';
+ function moveMember(arr, findIndex, toIndex) {
+ var i;
+
+ for (i = 0; i < arr.length; i++) {
+ if (arr[i].index === findIndex) {
+ break;
}
}
- }
-
- 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);
- }
- function keyNodeFilter(froms, tos) {
- return function (n) {
- return froms.indexOf(n) === -1 && tos.indexOf(n) === -1;
- };
- }
+ var item = Object.assign({}, arr[i]); // shallow copy
- function collectNodes(member, collection) {
- var entity = graph.hasEntity(member.id);
- if (!entity) return;
- var role = member.role || '';
+ arr[i].index = -1; // mark as dead
- if (!collection[role]) {
- collection[role] = [];
- }
+ 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 (member.type === 'node') {
- collection[role].push(member.id);
- 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);
+ function withIndex(arr) {
+ var result = new Array(arr.length);
- if (role === 'from' || role === 'via') {
- collection.keyfrom.push(entity.first());
- collection.keyfrom.push(entity.last());
- }
+ for (var i = 0; i < arr.length; i++) {
+ result[i] = Object.assign({}, arr[i]); // shallow copy
- if (role === 'to' || role === 'via') {
- collection.keyto.push(entity.first());
- collection.keyto.push(entity.last());
- }
+ result[i].index = i;
}
- }
- };
- return action;
+ return result;
+ }
+ }
}
- function actionCopyEntities(ids, fromGraph) {
- var _copies = {};
+ 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.
- var action = function action(graph) {
- ids.forEach(function (id) {
- fromGraph.entity(id).copy(fromGraph, _copies);
+ return;
+ }
+ }
});
-
- for (var id in _copies) {
- graph = graph.replace(_copies[id]);
- }
-
return graph;
};
+ }
- action.copies = function () {
- return _copies;
+ // 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 actionDeleteMember(relationId, memberIndex) {
+ function actionChangeMember(relationId, member, memberIndex) {
return function (graph) {
- var relation = graph.entity(relationId).removeMember(memberIndex);
- graph = graph.replace(relation);
+ return graph.replace(graph.entity(relationId).updateMember(member, memberIndex));
+ };
+ }
- if (relation.isDegenerate()) {
- graph = actionDeleteRelation(relation.id)(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
- return 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 actionDiscardTags(difference, discardTags) {
- discardTags = discardTags || {};
+ function actionChangeTags(entityId, tags) {
return function (graph) {
- difference.modified().forEach(checkTags);
- difference.created().forEach(checkTags);
- return graph;
+ var entity = graph.entity(entityId);
+ return graph.replace(entity.update({
+ tags: tags
+ }));
+ };
+ }
- function checkTags(entity) {
- var keys = Object.keys(entity.tags);
- var didDiscard = false;
- var tags = {};
+ 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?
- for (var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ 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
- if (discardTags[k] || !entity.tags[k]) {
- didDiscard = true;
- } else {
- tags[k] = entity.tags[k];
- }
- }
+ var re = /:direction$/i;
+ var keys = Object.keys(this.tags);
- if (didDiscard) {
- graph = graph.replace(entity.update({
- tags: tags
- }));
+ for (i = 0; i < keys.length; i++) {
+ if (re.test(keys[i])) {
+ val = this.tags[keys[i]].toLowerCase();
+ break;
+ }
}
}
- };
- }
- //
- // 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
- //
+ 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
- function actionDisconnect(nodeId, newNodeId) {
- var wayIds;
- 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);
+ if (v !== '' && !isNaN(+v)) {
+ results.push(+v);
+ return;
+ } // string direction - inspect parent 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;
+ 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 (var i = 0; i < parentWays.length; i++) {
- way = parentWays[i];
-
- if (wayIds && wayIds.indexOf(way.id) === -1) {
- keeping = true;
- continue;
- }
-
- 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;
+ 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
}
- candidates.push({
- wayID: way.id,
- index: j
- });
+ if (lookBackward && i < nodes.length - 1) {
+ nodeIds[nodes[i + 1]] = true; // look ahead to next node
+ }
}
}
- }
- }
-
- return keeping ? candidates : candidates.slice(1);
- };
+ }, 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);
- 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;
+ 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)
+
+
+ return nodes.indexOf(this.id) !== nodes.lastIndexOf(this.id);
+ }
+
+ return false;
+ });
+ },
+ 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';
});
});
- if (sharedRelation) return 'relation';
- };
+ },
+ 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.limitWays = function (val) {
- if (!arguments.length) return wayIds;
- wayIds = val;
- return action;
- };
+ 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;
+ });
- function actionExtract(entityID, projection) {
- var extractedNodeID;
+ if (!way.isConvex(graph)) {
+ graph = action.makeConvex(graph);
+ }
- var action = function action(graph) {
- var entity = graph.entity(entityID);
+ 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 (entity.type === 'node') {
- return extractFromNode(entity, graph);
+ if (!keyNodes.length) {
+ keyNodes = [nodes[0]];
+ keyPoints = [points[0]];
}
- return extractFromWayOrRelation(entity, graph);
- };
+ 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.
- function extractFromNode(node, graph) {
- extractedNodeID = node.id; // Create a new node to replace the one we will detach
- var replacement = osmNode({
- loc: node.loc
- });
- graph = graph.replace(replacement); // Process each way in turn, updating the graph as we go
+ 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 = graph.parentWays(node).reduce(function (accGraph, parentWay) {
- return accGraph.replace(parentWay.replaceNode(entityID, replacement.id));
- }, graph); // Process any relations too
+ if (indexRange < 0) {
+ indexRange += nodes.length;
+ } // position this key node
- return graph.parentRelations(node).reduce(function (accGraph, parentRel) {
- return accGraph.replace(parentRel.replaceMember(node, replacement));
- }, graph);
- }
- 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 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
- if (!extractedLoc || !isFinite(extractedLoc[0]) || !isFinite(extractedLoc[1])) {
- extractedLoc = entity.extent(graph).center();
- }
+ 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
- var indoorAreaValues = {
- area: true,
- corridor: true,
- elevator: true,
- level: true,
- room: true
- };
- 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
+ if (totalAngle * sign > 0) {
+ totalAngle = -sign * (2 * Math.PI - Math.abs(totalAngle));
+ }
- var pointTags = {};
+ do {
+ numberNewPoints++;
+ eachAngle = totalAngle / (indexRange + numberNewPoints);
+ } while (Math.abs(eachAngle) > maxAngle); // move existing nodes
- for (var key in entityTags) {
- if (entity.type === 'relation' && key === 'type') {
- continue;
- }
- if (keysToRetain.indexOf(key) !== -1) {
- continue;
- }
+ 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
- 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
+ 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
- if (isIndoorArea && key === 'indoor') {
- continue;
- } // copy the tag from the entity to the point
+ var min = Infinity;
+ for (var nodeId in nearNodes) {
+ var nearAngle = nearNodes[nodeId];
+ var dist = Math.abs(nearAngle - angle);
- pointTags[key] = entityTags[key]; // leave addresses and some other tags so they're on both features
+ if (dist < min) {
+ min = dist;
+ origNode = origNodes[nodeId];
+ }
+ }
- 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
+ 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..
- delete entityTags[key];
- }
+ if (indexRange === 1 && inBetweenNodes.length) {
+ var startIndex1 = way.nodes.lastIndexOf(startNode.id);
+ var endIndex1 = way.nodes.lastIndexOf(endNode.id);
+ var wayDirection1 = endIndex1 - startIndex1;
- if (!isBuilding && !isIndoorArea && fromGeometry === 'area') {
- // ensure that areas keep area geometry
- entityTags.area = 'yes';
- }
+ if (wayDirection1 < -1) {
+ wayDirection1 = 1;
+ }
- var replacement = osmNode({
- loc: extractedLoc,
- tags: pointTags
- });
- graph = graph.replace(replacement);
- extractedNodeID = replacement.id;
- return graph.replace(entity.update({
- tags: entityTags
- }));
- }
+ var parentWays = graph.parentWays(keyNodes[i]);
- action.getExtractedNodeID = function () {
- return extractedNodeID;
- };
+ for (j = 0; j < parentWays.length; j++) {
+ var sharedWay = parentWays[j];
+ if (sharedWay === way) continue;
- return action;
- }
+ 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;
- //
- // 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 (wayDirection2 < -1) {
+ wayDirection2 = 1;
+ }
- function actionJoin(ids) {
- function groupEntitiesByGeometry(graph) {
- var entities = ids.map(function (id) {
- return graph.entity(id);
- });
- return Object.assign({
- line: []
- }, utilArrayGroupBy(entities, function (entity) {
- return entity.geometry(graph);
- }));
- }
+ if (wayDirection1 !== wayDirection2) {
+ inBetweenNodes.reverse();
+ insertAt = startIndex2;
+ }
- var action = function action(graph) {
- var ways = ids.map(graph.entity, graph); // if any of the ways are sided (e.g. coastline, cliff, kerb)
- // sort them first so they establish the overall order - #6033
+ for (k = 0; k < inBetweenNodes.length; k++) {
+ sharedWay = sharedWay.addNode(inBetweenNodes[k], insertAt + k);
+ }
- 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 there are multiple existing ways, keep the oldest one
- // the oldest way is determined by the ID of the way
+ graph = graph.replace(sharedWay);
+ }
+ }
+ }
+ } // update the way to have all the new nodes
- var survivorID = (ways.filter(function (way) {
- return !way.isNew();
- }).sort(function (a, b) {
- return +a.osmId() - +b.osmId();
- })[0] || ways[0]).id;
- 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.
- 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;
- })
+ ids = 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
-
- 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
+ ids.push(ids[0]);
+ way = way.update({
+ nodes: ids
+ });
+ graph = graph.replace(way);
+ return graph;
+ };
- if (multipolygons.length !== 1) return;
- var multipolygon = multipolygons[0];
+ 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..
- 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;
- }
+ if (sign === -1) {
+ nodes.reverse();
+ points.reverse();
+ }
- survivor = survivor.mergeTags(multipolygon.tags);
- graph = graph.replace(survivor);
- graph = actionDeleteRelation(multipolygon.id, true
- /* allow untagged members */
- )(graph);
- var tags = Object.assign({}, survivor.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 (survivor.geometry(graph) !== 'area') {
- // ensure the feature persists as an area
- tags.area = 'yes';
- }
+ if (indexRange < 0) {
+ indexRange += nodes.length;
+ } // move interior nodes to the surface of the convex hull..
- delete tags.type; // remove type=multipolygon
- survivor = survivor.update({
- tags: tags
- });
- graph = graph.replace(survivor);
+ 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);
+ }
}
- checkForSimpleMultipolygon();
return graph;
- }; // Returns the number of nodes the resultant way is expected to have
-
-
- action.resultingWayNodesLength = function (graph) {
- return ids.reduce(function (count, id) {
- return count + graph.entity(id).nodes.length;
- }, 0) - ids.length - 1;
};
action.disabled = function (graph) {
- var geometries = groupEntitiesByGeometry(graph);
+ if (!graph.entity(wayId).isClosed()) {
+ return 'not_closed';
+ } //disable when already circular
- if (ids.length < 2 || ids.length !== geometries.line.length) {
- return 'not_eligible';
- }
- var joined = osmJoinWays(ids.map(graph.entity, graph), graph);
+ 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;
- if (joined.length > 1) {
- return 'not_adjacent';
+ if (hull.length !== points.length || hull.length < 3) {
+ return false;
}
- 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.
-
- 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;
- });
- };
-
- var relsA = sortedParentRelations(ids[0]);
+ var centroid = d3_polygonCentroid(points);
+ var radius = geoVecLengthSquare(centroid, points[0]);
+ var i, actualPoint; // compare distances between centroid and points
- for (i = 1; i < ids.length; i++) {
- var relsB = sortedParentRelations(ids[i]);
+ 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%)
- if (!utilArrayIdentical(relsA, relsB)) {
- return 'conflicting_relations';
+ if (diff > 0.05 * radius) {
+ return false;
}
- } // Loop through all combinations of path-pairs
- // to check potential intersections between all pairs
-
+ } //check if central angles are smaller than maxAngle
- 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
- var common = utilArrayIntersection(joined[0].nodes.map(function (n) {
- return n.loc.toString();
- }), intersections.map(function (n) {
- return n.toString();
- }));
+ 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 (common.length !== intersections.length) {
- return 'paths_intersect';
- }
+ if (angle < 0) {
+ angle = -angle;
}
- }
-
- 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;
- }
- });
- 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;
- }
+ if (angle > Math.PI) {
+ angle = 2 * Math.PI - angle;
}
- });
- if (relation) {
- return relation.isRestriction() ? 'restriction' : 'connectivity';
+ if (angle > maxAngle + epsilonAngle) {
+ return false;
+ }
}
- if (conflicting) {
- return 'conflicting_tags';
- }
+ return 'already_circular';
};
+ action.transitionable = true;
return action;
}
- 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);
- }));
+ 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 (geometries.point) return false; // delete if this node only be a vertex
+
+ 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 !node.hasInterestingTags();
}
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);
+ graph.parentRelations(way).forEach(function (parent) {
+ parent = parent.removeMembersWithID(wayID);
+ graph = graph.replace(parent);
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
+ 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);
- 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
+ if (canDeleteNode(node, graph)) {
+ graph = graph.remove(node);
+ }
+ });
+ return graph.remove(way);
+ };
+ return action;
+ }
- graph = graph.replace(point.update({
- tags: {},
- loc: node.loc
- }));
- target = target.replaceNode(node.id, point.id);
- graph = graph.replace(target);
- removeNode = node;
- break;
- }
+ function actionDeleteMultiple(ids) {
+ var actions = {
+ way: actionDeleteWay,
+ node: actionDeleteNode,
+ relation: actionDeleteRelation
+ };
- graph = graph.remove(removeNode);
+ 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;
+ };
- if (target.tags.area === 'yes') {
- var tags = Object.assign({}, target.tags); // shallow copy
+ return action;
+ }
- delete tags.area;
+ function actionDeleteRelation(relationID, allowUntaggedMembers) {
+ function canDeleteEntity(entity, graph) {
+ return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags() && !allowUntaggedMembers;
+ }
- if (osmTagSuggestingArea(tags)) {
- // remove the `area` tag if area geometry is now implied - #3851
- target = target.update({
- tags: tags
- });
- graph = graph.replace(target);
+ var action = function action(graph) {
+ var relation = graph.entity(relationID);
+ graph.parentRelations(relation).forEach(function (parent) {
+ parent = parent.removeMembersWithID(relationID);
+ graph = graph.replace(parent);
+
+ 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);
- return graph;
+ if (canDeleteEntity(entity, graph)) {
+ graph = actionDeleteMultiple([memberID])(graph);
+ }
+ });
+ return graph.remove(relation);
};
- action.disabled = function (graph) {
- var geometries = groupEntitiesByGeometry(graph);
+ return action;
+ }
- if (geometries.point.length === 0 || geometries.area.length + geometries.line.length !== 1 || geometries.relation.length !== 0) {
- return 'not_eligible';
- }
+ 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);
+
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
+ }
+ });
+ return graph.remove(node);
};
return action;
}
//
- // 1. move all the nodes to a common location
- // 2. `actionConnect` them
+ // 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
+ //
- 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;
+ 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.
- for (var i = 0; i < nodeIDs.length; i++) {
- var node = graph.entity(nodeIDs[i]);
+ nodeIDs.reverse();
+ var interestingIDs = [];
+
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
if (node.hasInterestingTags()) {
- interestingLoc = ++interestingCount === 1 ? node.loc : null;
+ if (!node.isNew()) {
+ interestingIDs.push(node.id);
+ }
}
-
- sum = geoVecAdd(sum, node.loc);
}
- return interestingLoc || geoVecScale(sum, 1 / nodeIDs.length);
- }
+ survivor = graph.entity(utilOldestID(interestingIDs.length > 0 ? interestingIDs : nodeIDs)); // Replace all non-surviving nodes with the survivor and merge tags.
- var action = function action(graph) {
- if (nodeIDs.length < 2) return graph;
- var toLoc = loc;
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ if (node.id === survivor.id) continue;
+ parents = graph.parentWays(node);
- if (!toLoc) {
- toLoc = chooseLoc(graph);
+ for (j = 0; j < parents.length; j++) {
+ graph = graph.replace(parents[j].replaceNode(node.id, survivor.id));
+ }
+
+ parents = graph.parentRelations(node);
+
+ for (j = 0; j < parents.length; j++) {
+ graph = graph.replace(parents[j].replaceMember(node, survivor));
+ }
+
+ survivor = survivor.mergeTags(node.tags);
+ graph = actionDeleteNode(node.id)(graph);
}
- for (var i = 0; i < nodeIDs.length; i++) {
- var node = graph.entity(nodeIDs[i]);
+ graph = graph.replace(survivor); // find and delete any degenerate ways created by connecting adjacent vertices
- if (node.loc !== toLoc) {
- graph = graph.replace(node.move(toLoc));
+ parents = graph.parentWays(survivor);
+
+ for (i = 0; i < parents.length; i++) {
+ if (parents[i].isDegenerate()) {
+ graph = actionDeleteWay(parents[i].id)(graph);
}
}
- return actionConnect(nodeIDs)(graph);
+ return graph;
};
action.disabled = function (graph) {
- if (nodeIDs.length < 2) return 'not_eligible';
+ 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.
- for (var i = 0; i < nodeIDs.length; i++) {
- var entity = graph.entity(nodeIDs[i]);
- if (entity.type !== 'node') return 'not_eligible';
- }
+ survivor = graph.entity(utilOldestID(nodeIDs)); // 1. disable if the nodes being connected have conflicting relation roles
- return actionConnect(nodeIDs).disabled(graph);
- };
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ relations = graph.parentRelations(node);
- return action;
- }
+ 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
- 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'
+ if (relation.hasFromViaTo()) {
+ restrictionIDs.push(relation.id);
+ }
+
+ if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
+ return 'relation';
+ } else {
+ seen[relation.id] = role;
}
}
- };
- },
- // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
- // XML. Returns a string.
- osmChangeJXON: function osmChangeJXON(changes) {
- var changeset_id = this.id;
+ } // gather restrictions for parent ways
- function nest(x, order) {
- var groups = {};
- 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 < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ var parents = graph.parentWays(node);
+
+ for (j = 0; j < parents.length; j++) {
+ var parent = parents[j];
+ relations = graph.parentRelations(parent);
+
+ for (k = 0; k < relations.length; k++) {
+ relation = relations[k];
+
+ if (relation.hasFromViaTo()) {
+ restrictionIDs.push(relation.id);
+ }
+ }
}
+ } // test restrictions
- var ordered = {};
- order.forEach(function (o) {
- if (groups[o]) ordered[o] = groups[o];
- });
- return ordered;
- } // sort relations in a changeset by dependencies
+ restrictionIDs = utilArrayUniq(restrictionIDs);
- 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
+ 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)
+ var nodes = {
+ from: [],
+ via: [],
+ to: [],
+ keyfrom: [],
+ keyto: []
+ };
- function isNew(item) {
- return !sorted[item['@id']] && !processing.find(function (proc) {
- return proc['@id'] === item['@id'];
- });
+ for (j = 0; j < relation.members.length; j++) {
+ collectNodes(relation.members[j], nodes);
}
- var processing = [];
- var sorted = {};
- var relations = changes.relation;
- if (!relations) return changes;
+ 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;
- for (var i = 0; i < relations.length; i++) {
- var relation = relations[i]; // skip relation if already sorted
+ for (j = 0; j < nodeIDs.length; j++) {
+ var n = nodeIDs[j];
- if (!sorted[relation['@id']]) {
- processing.push(relation);
+ if (nodes.from.indexOf(n) !== -1) {
+ connectFrom = true;
}
- while (processing.length > 0) {
- var next = processing[0],
- deps = next.member.map(resolve).filter(Boolean).filter(isNew);
+ if (nodes.via.indexOf(n) !== -1) {
+ connectVia = true;
+ }
- if (deps.length === 0) {
- sorted[next['@id']] = next;
- processing.shift();
- } else {
- processing = deps.concat(processing);
- }
+ if (nodes.to.indexOf(n) !== -1) {
+ connectTo = true;
}
- }
- changes.relation = Object.values(sorted);
- return changes;
- }
+ if (nodes.keyfrom.indexOf(n) !== -1) {
+ connectKeyFrom = true;
+ }
- function rep(entity) {
- return entity.asJXON(changeset_id);
- }
+ if (nodes.keyto.indexOf(n) !== -1) {
+ connectKeyTo = true;
+ }
+ }
- 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
- })
+ if (connectFrom && connectTo && !isUturn) {
+ return 'restriction';
}
- };
- },
- asGeoJSON: function asGeoJSON() {
- return {};
- }
- });
- function osmNote() {
- if (!(this instanceof osmNote)) {
- return new osmNote().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
- }
- }
+ if (connectFrom && connectVia) {
+ return 'restriction';
+ }
- osmNote.id = function () {
- return osmNote.id.next--;
- };
+ 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.
- 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];
- 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 (connectKeyFrom || connectKeyTo) {
+ if (nodeIDs.length !== 2) {
+ return 'restriction';
}
- }
- }
- if (!this.id) {
- this.id = osmNote.id().toString();
- }
-
- 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
- });
- }
- });
+ var n0 = null;
+ var n1 = null;
- 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);
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j];
- 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 (way.contains(nodeIDs[0])) {
+ n0 = nodeIDs[0];
+ }
- 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 (way.contains(nodeIDs[1])) {
+ n1 = nodeIDs[1];
+ }
+ }
- for (var i = 0; i < this.members.length; i++) {
- var member = resolver.hasEntity(this.members[i].id);
+ if (n0 && n1) {
+ // both nodes are part of the restriction
+ var ok = false;
- if (member) {
- extent._extend(member.extent(resolver, memo));
- }
- }
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j];
- 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);
+ if (way.areAdjacent(n0, n1)) {
+ ok = true;
+ break;
+ }
+ }
- for (var i = 0; i < this.members.length; i++) {
- result[i] = Object.assign({}, this.members[i], {
- index: i
- });
- }
+ 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)
- 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 (var i = 0; i < this.members.length; i++) {
- if (this.members[i].role === role) {
- result.push(Object.assign({}, this.members[i], {
- index: i
- }));
- }
- }
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j].update({}); // make copy
- 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 (k = 0; k < nodeIDs.length; k++) {
+ if (nodeIDs[k] === survivor.id) continue;
- for (var i = 0; i < this.members.length; i++) {
- var member = this.members[i];
+ if (way.areAdjacent(nodeIDs[k], survivor.id)) {
+ way = way.removeNode(nodeIDs[k]);
+ } else {
+ way = way.replaceNode(nodeIDs[k], survivor.id);
+ }
+ }
- 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 (way.isDegenerate()) {
+ return 'restriction';
+ }
}
}
- 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)
- }
- };
+ return false; // if a key node appears multiple times (indexOf !== lastIndexOf) it's a FROM-VIA or TO-VIA junction
- if (changeset_id) {
- r.relation['@changeset'] = changeset_id;
+ function hasDuplicates(n, i, arr) {
+ return arr.indexOf(n) !== arr.lastIndexOf(n);
}
- 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;
- }
+ function keyNodeFilter(froms, tos) {
+ return function (n) {
+ return froms.indexOf(n) === -1 && tos.indexOf(n) === -1;
+ };
}
- 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 collectNodes(member, collection) {
+ var entity = graph.hasEntity(member.id);
+ if (!entity) return;
+ var role = member.role || '';
- 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]);
+ if (!collection[role]) {
+ collection[role] = [];
}
- return sequence.nodes.map(function (node) {
- return node.loc;
- });
- };
-
- 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];
- });
-
- function findOuter(inner) {
- var o, outer;
-
- for (o = 0; o < outers.length; o++) {
- outer = outers[o];
+ if (member.type === 'node') {
+ collection[role].push(member.id);
- if (geoPolygonContainsPolygon(outer, inner)) {
- return o;
+ 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);
- for (o = 0; o < outers.length; o++) {
- outer = outers[o];
+ if (role === 'from' || role === 'via') {
+ collection.keyfrom.push(entity.first());
+ collection.keyfrom.push(entity.last());
+ }
- if (geoPolygonIntersectsPolygon(outer, inner, false)) {
- return o;
+ if (role === 'to' || role === 'via') {
+ collection.keyto.push(entity.first());
+ collection.keyto.push(entity.last());
}
}
}
+ };
- for (var i = 0; i < inners.length; i++) {
- var inner = inners[i];
+ return action;
+ }
- if (d3_geoArea({
- type: 'Polygon',
- coordinates: [inner]
- }) < 2 * Math.PI) {
- inner = inner.reverse();
- }
+ function actionCopyEntities(ids, fromGraph) {
+ var _copies = {};
- var o = findOuter(inners[i]);
+ var action = function action(graph) {
+ ids.forEach(function (id) {
+ fromGraph.entity(id).copy(fromGraph, _copies);
+ });
- if (o !== undefined) {
- result[o].push(inners[i]);
- } else {
- result.push([inners[i]]); // Invalid geometry
- }
+ for (var id in _copies) {
+ graph = graph.replace(_copies[id]);
}
- return result;
- }
- });
+ return graph;
+ };
- var QAItem = /*#__PURE__*/function () {
- function QAItem(loc, service, itemType, id, props) {
- _classCallCheck$1(this, QAItem);
+ action.copies = function () {
+ return _copies;
+ };
- // 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
+ return action;
+ }
- this.id = id ? id : "".concat(QAItem.id());
- this.update(props); // Some QA services have marker icons to differentiate issues
+ function actionDeleteMember(relationId, memberIndex) {
+ return function (graph) {
+ var relation = graph.entity(relationId).removeMember(memberIndex);
+ graph = graph.replace(relation);
- if (service && typeof service.getIcon === 'function') {
- this.icon = service.getIcon(itemType);
+ if (relation.isDegenerate()) {
+ graph = actionDeleteRelation(relation.id)(graph);
}
- }
- _createClass$1(QAItem, [{
- key: "update",
- value: function update(props) {
- var _this = this;
+ return graph;
+ };
+ }
- // 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 actionDiscardTags(difference, discardTags) {
+ discardTags = discardTags || {};
+ return function (graph) {
+ difference.modified().forEach(checkTags);
+ difference.created().forEach(checkTags);
+ return graph;
- }], [{
- key: "id",
- value: function id() {
- return this.nextId--;
- }
- }]);
+ function checkTags(entity) {
+ var keys = Object.keys(entity.tags);
+ var didDiscard = false;
+ var tags = {};
- return QAItem;
- }();
- QAItem.nextId = -1;
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
+
+ if (discardTags[k] || !entity.tags[k]) {
+ didDiscard = true;
+ } else {
+ tags[k] = entity.tags[k];
+ }
+ }
+
+ if (didDiscard) {
+ graph = graph.replace(entity.update({
+ tags: tags
+ }));
+ }
+ }
+ };
+ }
//
- // Optionally, split only the given ways, if multiple ways share
- // the given node.
- //
- // This is the inverse of `iD.actionJoin`.
+ // Optionally, disconnect only the given ways.
//
- // For testing convenience, accepts an ID to assign to the new way.
+ // 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/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
+ // 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
//
- function actionSplit(nodeIds, newWayIds) {
- // accept single ID for backwards-compatiblity
- if (typeof nodeIds === 'string') nodeIds = [nodeIds];
-
- var _wayIDs; // the strategy for picking which way will have a new version and which way is newly created
-
-
- var _keepHistoryOn = 'longest'; // 'longest', 'first'
- // The IDs of the ways actually created by running this action
-
- var _createdWayIDs = [];
-
- 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.
-
-
- function splitArea(nodes, idxA, graph) {
- var lengths = new Array(nodes.length);
- var length;
- var i;
- var best = 0;
- var idxB;
-
- function wrap(index) {
- return utilWrap(index, nodes.length);
- } // calculate lengths
-
-
- length = 0;
-
- for (i = wrap(idxA + 1); i !== idxA; i = wrap(i + 1)) {
- length += dist(graph, nodes[i], nodes[wrap(i - 1)]);
- lengths[i] = length;
- }
-
- length = 0;
+ function actionDisconnect(nodeId, newNodeId) {
+ var wayIds;
+ var disconnectableRelationTypes = {
+ 'associatedStreet': true,
+ 'enforcement': true,
+ 'site': true
+ };
- for (i = wrap(idxA - 1); i !== idxA; i = wrap(i - 1)) {
- length += dist(graph, nodes[i], nodes[wrap(i + 1)]);
+ 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);
- if (length < lengths[i]) {
- lengths[i] = length;
+ 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));
}
- } // determine best opposite node to split
+ });
+ return graph;
+ };
+ action.connections = function (graph) {
+ var candidates = [];
+ var keeping = false;
+ var parentWays = graph.parentWays(graph.entity(nodeId));
+ var way, waynode;
- for (i = 0; i < nodes.length; i++) {
- var cost = lengths[i] / dist(graph, nodes[idxA], nodes[i]);
+ for (var i = 0; i < parentWays.length; i++) {
+ way = parentWays[i];
- if (cost > best) {
- idxB = i;
- best = cost;
+ if (wayIds && wayIds.indexOf(way.id) === -1) {
+ keeping = true;
+ continue;
}
- }
-
- return idxB;
- }
-
- 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 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);
+ if (way.isArea() && way.nodes[0] === nodeId) {
+ candidates.push({
+ wayID: way.id,
+ index: 0
+ });
} 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);
- }
-
- var lengthA = totalLengthBetweenNodes(graph, nodesA);
- var lengthB = totalLengthBetweenNodes(graph, nodesB);
-
- 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 j = 0; j < way.nodes.length; j++) {
+ waynode = way.nodes[j];
- if (wayA.tags.step_count) {
- // divide up the the step count proportionally between the two ways
- var stepCount = parseFloat(wayA.tags.step_count);
+ if (waynode === nodeId) {
+ if (way.isClosed() && parentWays.length > 1 && wayIds && wayIds.indexOf(way.id) !== -1 && j === way.nodes.length - 1) {
+ continue;
+ }
- 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
- });
+ candidates.push({
+ wayID: way.id,
+ index: j
+ });
+ }
+ }
}
}
- 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;
-
- 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);
+ return keeping ? candidates : candidates.slice(1);
+ };
- if (wayVia && utilArrayIntersection(wayB.nodes, wayVia.nodes).length) {
- keepB = true;
- break;
- }
- }
+ 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.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;
}
-
- 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)
-
- } else {
- if (relation === isOuter) {
- graph = graph.replace(relation.mergeTags(wayA.tags));
- graph = graph.replace(wayA.update({
- tags: {}
- }));
- graph = graph.replace(wayB.update({
- tags: {}
- }));
+ seenRelationIds[relation.id] = way.id;
}
-
- 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 (sharedRelation) return 'relation';
+ };
- 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: {}
- }));
- }
+ action.limitWays = function (val) {
+ if (!arguments.length) return wayIds;
+ wayIds = val;
+ return action;
+ };
- _createdWayIDs.push(wayB.id);
+ return action;
+ }
- return graph;
- }
+ function actionExtract(entityID, projection) {
+ var extractedNodeID;
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 entity = graph.entity(entityID);
- for (var j = 0; j < candidates.length; j++) {
- graph = split(graph, nodeId, candidates[j], newWayIds && newWayIds[newWayIndex]);
- newWayIndex += 1;
- }
+ if (entity.type === 'node') {
+ return extractFromNode(entity, graph);
}
- return graph;
+ return extractFromWayOrRelation(entity, graph);
};
- action.getCreatedWayIDs = function () {
- return _createdWayIDs;
- };
+ function extractFromNode(node, graph) {
+ extractedNodeID = node.id; // Create a new node to replace the one we will detach
- action.waysForNode = function (nodeId, graph) {
- var node = graph.entity(nodeId);
- var splittableParents = graph.parentWays(node).filter(isSplittable);
+ var replacement = osmNode({
+ loc: node.loc
+ });
+ graph = graph.replace(replacement); // Process each way in turn, updating the graph as we go
- 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';
- });
+ graph = graph.parentWays(node).reduce(function (accGraph, parentWay) {
+ return accGraph.replace(parentWay.replaceNode(entityID, replacement.id));
+ }, graph); // Process any relations too
- if (hasLine) {
- return splittableParents.filter(function (parent) {
- return parent.geometry(graph) === 'line';
- });
- }
+ return graph.parentRelations(node).reduce(function (accGraph, parentRel) {
+ return accGraph.replace(parentRel.replaceMember(node, replacement));
+ }, graph);
+ }
+
+ 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 (!extractedLoc || !isFinite(extractedLoc[0]) || !isFinite(extractedLoc[1])) {
+ extractedLoc = entity.extent(graph).center();
}
- return splittableParents;
+ var indoorAreaValues = {
+ area: true,
+ corridor: true,
+ elevator: true,
+ level: true,
+ room: true
+ };
+ 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
- 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...
+ var pointTags = {};
- if (parent.isClosed()) return true; // otherwise, we can't split nodes at their endpoints.
+ for (var key in entityTags) {
+ if (entity.type === 'relation' && key === 'type') {
+ continue;
+ }
- for (var i = 1; i < parent.nodes.length - 1; i++) {
- if (parent.nodes[i] === nodeId) return true;
+ if (keysToRetain.indexOf(key) !== -1) {
+ continue;
}
- return false;
- }
- };
+ 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
- action.ways = function (graph) {
- return utilArrayUniq([].concat.apply([], nodeIds.map(function (nodeId) {
- return action.waysForNode(nodeId, graph);
- })));
- };
- action.disabled = function (graph) {
- for (var i = 0; i < nodeIds.length; i++) {
- var nodeId = nodeIds[i];
- var candidates = action.waysForNode(nodeId, graph);
+ if (isIndoorArea && key === 'indoor') {
+ continue;
+ } // copy the tag from the entity to the point
- if (candidates.length === 0 || _wayIDs && _wayIDs.length !== candidates.length) {
- return 'not_eligible';
- }
+
+ pointTags[key] = entityTags[key]; // leave addresses and some other tags so they're on both features
+
+ 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
+
+
+ delete entityTags[key];
}
- };
- action.limitWays = function (val) {
- if (!arguments.length) return _wayIDs;
- _wayIDs = val;
- return action;
- };
+ if (!isBuilding && !isIndoorArea && fromGeometry === 'area') {
+ // ensure that areas keep area geometry
+ entityTags.area = 'yes';
+ }
- action.keepHistoryOn = function (val) {
- if (!arguments.length) return _keepHistoryOn;
- _keepHistoryOn = val;
- return action;
+ var replacement = osmNode({
+ loc: extractedLoc,
+ tags: pointTags
+ });
+ graph = graph.replace(replacement);
+ extractedNodeID = replacement.id;
+ return graph.replace(entity.update({
+ tags: entityTags
+ }));
+ }
+
+ action.getExtractedNodeID = function () {
+ return extractedNodeID;
};
return action;
}
- function coreGraph(other, mutable) {
- if (!(this instanceof coreGraph)) return new coreGraph(other, mutable);
+ //
+ // 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 (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 actionJoin(ids) {
+ function groupEntitiesByGeometry(graph) {
+ var entities = ids.map(function (id) {
+ return graph.entity(id);
+ });
+ return Object.assign({
+ line: []
+ }, utilArrayGroupBy(entities, function (entity) {
+ return entity.geometry(graph);
+ }));
}
- 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 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 (!entity) {
- entity = this.entities.__proto__[id]; // eslint-disable-line no-proto
- }
+ 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
- if (!entity) {
- throw new Error('entity ' + id + ' not found');
- }
+ 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.
- 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] = {});
+ 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 (transients[key] !== undefined) {
- return transients[key];
- }
+ 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
- transients[key] = fn.call(entity);
- return transients[key];
- },
- parentWays: function parentWays(entity) {
- var parents = this._parentWays[entity.id];
- var result = [];
+ if (multipolygons.length !== 1) return;
+ var multipolygon = multipolygons[0];
- if (parents) {
- parents.forEach(function (id) {
- result.push(this.entity(id));
- }, this);
- }
+ 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;
+ }
- 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 = [];
+ survivor = survivor.mergeTags(multipolygon.tags);
+ graph = graph.replace(survivor);
+ graph = actionDeleteRelation(multipolygon.id, true
+ /* allow untagged members */
+ )(graph);
+ var tags = Object.assign({}, survivor.tags);
- if (parents) {
- parents.forEach(function (id) {
- result.push(this.entity(id));
- }, this);
- }
+ if (survivor.geometry(graph) !== 'area') {
+ // ensure the feature persists as an area
+ tags.area = 'yes';
+ }
- 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 = [];
+ delete tags.type; // remove type=multipolygon
- for (var i = 0; i < entity.nodes.length; i++) {
- nodes[i] = this.entity(entity.nodes[i]);
+ survivor = survivor.update({
+ tags: tags
+ });
+ graph = graph.replace(survivor);
}
- 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;
- 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
+ checkForSimpleMultipolygon();
+ return graph;
+ }; // Returns the number of nodes the resultant way is expected to have
- base.entities[entity.id] = entity;
- this._updateCalculated(undefined, entity, base.parentWays, base.parentRels); // Restore provisionally-deleted nodes that are discovered to have an extant parent
+ action.resultingWayNodesLength = function (graph) {
+ return ids.reduce(function (count, id) {
+ return count + graph.entity(id).nodes.length;
+ }, 0) - ids.length - 1;
+ };
+ action.disabled = function (graph) {
+ var geometries = groupEntitiesByGeometry(graph);
- if (entity.type === 'way') {
- for (j = 0; j < entity.nodes.length; j++) {
- id = entity.nodes[j];
+ if (ids.length < 2 || ids.length !== geometries.line.length) {
+ return 'not_eligible';
+ }
- for (k = 1; k < stack.length; k++) {
- var ents = stack[k].entities;
+ var joined = osmJoinWays(ids.map(graph.entity, graph), graph);
- if (ents.hasOwnProperty(id) && ents[id] === undefined) {
- delete ents[id];
- }
- }
- }
- }
+ if (joined.length > 1) {
+ return 'not_adjacent';
}
- 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 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 (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;
- }
+ 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;
+ });
+ };
- 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);
- }
+ var relsA = sortedParentRelations(ids[0]);
- 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;
- }) : [];
+ for (i = 1; i < ids.length; i++) {
+ var relsB = sortedParentRelations(ids[i]);
- if (oldentity && entity) {
- removed = utilArrayDifference(oldentityMemberIDs, entityMemberIDs);
- added = utilArrayDifference(entityMemberIDs, oldentityMemberIDs);
- } else if (oldentity) {
- removed = oldentityMemberIDs;
- added = [];
- } else if (entity) {
- removed = [];
- added = entityMemberIDs;
+ if (!utilArrayIdentical(relsA, relsB)) {
+ return 'conflicting_relations';
}
+ } // Loop through all combinations of path-pairs
+ // to check potential intersections between all pairs
- 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);
- }
- 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);
+ 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
+
+ var common = utilArrayIntersection(joined[0].nodes.map(function (n) {
+ return n.loc.toString();
+ }), intersections.map(function (n) {
+ return n.toString();
+ }));
+
+ if (common.length !== intersections.length) {
+ return 'paths_intersect';
+ }
}
}
- },
- 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;
- });
- },
- remove: function remove(entity) {
- return this.update(function () {
- this._updateCalculated(entity, undefined);
-
- 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);
+ 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;
+ }
+ });
- delete this.entities[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;
+ }
+ }
});
- },
- 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 (relation) {
+ return relation.isRestriction() ? 'restriction' : 'connectivity';
}
- 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);
-
- for (var i in entities) {
- this.entities[i] = entities[i];
-
- this._updateCalculated(base.entities[i], this.entities[i]);
+ if (conflicting) {
+ return 'conflicting_tags';
}
+ };
- return this;
- }
- };
-
- function osmTurn(turn) {
- if (!(this instanceof osmTurn)) {
- return new osmTurn(turn);
- }
-
- Object.assign(this, turn);
+ return action;
}
- function osmIntersection(graph, startVertexId, maxDistance) {
- maxDistance = maxDistance || 30; // in meters
-
- var vgraph = coreGraph(); // virtual graph
- var i, j, k;
-
- function memberOfRestriction(entity) {
- return graph.parentRelations(entity).some(function (r) {
- return r.isRestriction();
+ 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);
+ }));
}
- 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];
- }
+ 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 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
+ if (!point.isNew()) {
+ // Try to preserve the original point if it already has
+ // an ID in the database.
+ var inserted = false;
- var actions = []; // STEP 1: walk the graph outwards from starting vertex to search
- // for more key vertices and ways to include in the intersection..
+ var canBeReplaced = function canBeReplaced(node) {
+ return !(graph.parentWays(node).length > 1 || graph.parentRelations(node).length);
+ };
- while (checkVertices.length) {
- vertex = checkVertices.pop(); // check this vertex for parent ways that are roads
+ 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;
+ };
- checkWays = graph.parentWays(vertex);
- var hasWays = false;
+ var i;
+ var node; // First, try to replace a new child node on the target way.
- 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
+ for (i = 0; i < nodes.length; i++) {
+ node = nodes[i];
- hasWays = true; // check the way's children for more key vertices
+ if (canBeReplaced(node) && node.isNew()) {
+ replaceNode(node);
+ break;
+ }
+ }
- nodes = utilArrayUniq(graph.childNodes(way));
+ 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];
- for (j = 0; j < nodes.length; j++) {
- node = nodes[j];
- if (node === vertex) continue; // same thing
+ if (canBeReplaced(node) && !node.hasInterestingTags()) {
+ replaceNode(node);
+ break;
+ }
+ }
- if (vertices.indexOf(node) !== -1) continue; // seen it already
+ 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];
- if (geoSphericalDistance(node.loc, startNode.loc) > maxDistance) continue; // too far from start
- // a key vertex will have parents that are also roads
+ 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.
- var hasParents = false;
- parents = graph.parentWays(node);
+ }
+ }
- for (k = 0; k < parents.length; k++) {
- parent = parents[k];
- if (parent === way) continue; // same thing
+ graph = graph.remove(removeNode);
+ });
- if (ways.indexOf(parent) !== -1) continue; // seen it already
+ if (target.tags.area === 'yes') {
+ var tags = Object.assign({}, target.tags); // shallow copy
- if (!isRoad(parent)) continue; // not a road
+ delete tags.area;
- hasParents = true;
- break;
- }
-
- if (hasParents) {
- checkVertices.push(node);
- }
+ if (osmTagSuggestingArea(tags)) {
+ // remove the `area` tag if area geometry is now implied - #3851
+ target = target.update({
+ tags: tags
+ });
+ graph = graph.replace(target);
}
}
- if (hasWays) {
- vertices.push(vertex);
+ return graph;
+ };
+
+ action.disabled = function (graph) {
+ var geometries = groupEntitiesByGeometry(graph);
+
+ if (geometries.point.length === 0 || geometries.area.length + geometries.line.length !== 1 || geometries.relation.length !== 0) {
+ return 'not_eligible';
}
- }
+ };
- 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
+ return action;
+ }
- 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
+ //
+ // 1. move all the nodes to a common location
+ // 2. `actionConnect` them
- ways.forEach(function (w) {
- var way = vgraph.entity(w.id);
+ 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 (way.tags.oneway === '-1') {
- var action = actionReverse(way.id, {
- reverseOneway: true
- });
- actions.push(action);
- vgraph = action(vgraph);
- }
- }); // STEP 4: Split ways on key vertices
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var node = graph.entity(nodeIDs[i]);
- 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 (node.hasInterestingTags()) {
+ interestingLoc = ++interestingCount === 1 ? node.loc : null;
+ }
- 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);
- });
+ sum = geoVecAdd(sum, node.loc);
}
- }); // 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
- osmEntity.id.next.way = origCount; // STEP 5: Update arrays to point to vgraph entities
+ return interestingLoc || geoVecScale(sum, 1 / nodeIDs.length);
+ }
- 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 action = function action(graph) {
+ if (nodeIDs.length < 2) return graph;
+ var toLoc = loc;
- function withMetadata(way, vertexIds) {
- var __oneWay = way.isOneWay(); // which affixes are key vertices?
+ if (!toLoc) {
+ toLoc = chooseLoc(graph);
+ }
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var node = graph.entity(nodeIDs[i]);
- var __first = vertexIds.indexOf(way.first()) !== -1;
+ if (node.loc !== toLoc) {
+ graph = graph.replace(node.move(toLoc));
+ }
+ }
- var __last = vertexIds.indexOf(way.last()) !== -1; // what roles is this way eligible for?
+ return actionConnect(nodeIDs)(graph);
+ };
+ action.disabled = function (graph) {
+ if (nodeIDs.length < 2) return 'not_eligible';
- var __via = __first && __last;
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var entity = graph.entity(nodeIDs[i]);
+ if (entity.type !== 'node') return 'not_eligible';
+ }
- var __from = __first && !__oneWay || __last;
+ return actionConnect(nodeIDs).disabled(graph);
+ };
- var __to = __first || __last && !__oneWay;
+ return action;
+ }
- return way.update({
- __first: __first,
- __last: __last,
- __from: __from,
- __via: __via,
- __to: __to,
- __oneWay: __oneWay
- });
+ 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;
- 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
+ function nest(x, order) {
+ var groups = {};
- var keepGoing;
- var removeWayIds = [];
- var removeVertexIds = [];
+ 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]);
+ }
- do {
- keepGoing = false;
- checkVertices = vertexIds.slice();
+ var ordered = {};
+ order.forEach(function (o) {
+ if (groups[o]) ordered[o] = groups[o];
+ });
+ return ordered;
+ } // sort relations in a changeset by dependencies
- for (i = 0; i < checkVertices.length; i++) {
- var vertexId = checkVertices[i];
- vertex = vgraph.hasEntity(vertexId);
- if (!vertex) {
- if (vertexIds.indexOf(vertexId) !== -1) {
- vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
- }
+ 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
- removeVertexIds.push(vertexId);
- continue;
+
+ function isNew(item) {
+ return !sorted[item['@id']] && !processing.find(function (proc) {
+ return proc['@id'] === item['@id'];
+ });
}
- parents = vgraph.parentWays(vertex);
+ var processing = [];
+ var sorted = {};
+ var relations = changes.relation;
+ if (!relations) return changes;
- if (parents.length < 3) {
- if (vertexIds.indexOf(vertexId) !== -1) {
- vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
+ for (var i = 0; i < relations.length; i++) {
+ var relation = relations[i]; // skip relation if already sorted
+
+ if (!sorted[relation['@id']]) {
+ processing.push(relation);
}
- }
- 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;
+ while (processing.length > 0) {
+ var next = processing[0],
+ deps = next.member.map(resolve).filter(Boolean).filter(isNew);
- if (aIsLeaf && !bIsLeaf) {
- leaf = a;
- survivor = b;
- } else if (!aIsLeaf && bIsLeaf) {
- leaf = b;
- survivor = a;
+ if (deps.length === 0) {
+ sorted[next['@id']] = next;
+ processing.shift();
+ } else {
+ processing = deps.concat(processing);
+ }
}
+ }
- if (leaf && survivor) {
- survivor = withMetadata(survivor, vertexIds); // update survivor way
+ changes.relation = Object.values(sorted);
+ return changes;
+ }
- vgraph = vgraph.replace(survivor).remove(leaf); // update graph
+ function rep(entity) {
+ return entity.asJXON(changeset_id);
+ }
- removeWayIds.push(leaf.id);
- keepGoing = true;
- }
+ 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 {};
+ }
+ });
- parents = vgraph.parentWays(vertex);
+ function osmNote() {
+ if (!(this instanceof osmNote)) {
+ return new osmNote().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
- 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
- }
+ osmNote.id = function () {
+ return osmNote.id.next--;
+ };
- removeVertexIds.push(vertexId);
- keepGoing = true;
- }
+ 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 (parents.length < 1) {
- // vertex is no longer attached to anything
- vgraph = vgraph.remove(vertex);
+ for (var prop in source) {
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
+ if (source[prop] === undefined) {
+ delete this[prop];
+ } else {
+ this[prop] = source[prop];
+ }
+ }
}
}
- } 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..
- 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.
- //
+ if (!this.id) {
+ this.id = osmNote.id().toString();
+ }
- 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;
+ 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
});
- 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)
-
- var maxPathLength = maxViaWay * 2 + 3;
- var turns = [];
- step(start);
- return turns; // traverse the intersection graph and find all the valid paths
-
- function step(entity, currPath, currRestrictions, matchedRestriction) {
- currPath = (currPath || []).slice(); // shallow copy
+ }
+ });
- if (currPath.length >= maxPathLength) return;
- currPath.push(entity.id);
- currRestrictions = (currRestrictions || []).slice(); // shallow copy
+ 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 i, j;
+ 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 (entity.type === 'node') {
- var parents = vgraph.parentWays(entity);
- var nextWays = []; // which ways can we step into?
+ 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 (i = 0; i < parents.length; i++) {
- var way = parents[i]; // if next way is a oneway incoming to this vertex, skip
+ for (var i = 0; i < this.members.length; i++) {
+ var member = resolver.hasEntity(this.members[i].id);
- if (way.__oneWay && way.nodes[0] !== entity.id) continue; // if we have seen it before (allowing for an initial u-turn), skip
+ if (member) {
+ extent._extend(member.extent(resolver, memo));
+ }
+ }
- if (currPath.indexOf(way.id) !== -1 && currPath.length >= 3) continue; // Check all "current" restrictions (where we've already walked the `FROM`)
+ 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);
- var restrict = null;
+ for (var i = 0; i < this.members.length; i++) {
+ result[i] = Object.assign({}, this.members[i], {
+ index: i
+ });
+ }
- 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?
+ 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 = [];
- var matchesFrom = f.id === fromWayId;
- var matchesViaTo = false;
- var isAlongOnlyPath = false;
+ 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 (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 (k = 2; k < currPath.length; k += 2) {
- // k = 2 skips FROM
- pathVias.push(currPath[k]); // (path goes way-node-way...)
- }
+ 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 = [];
- var restrictionVias = [];
+ for (var i = 0; i < this.members.length; i++) {
+ var member = this.members[i];
- for (k = 0; k < v.length; k++) {
- if (v[k].type === 'way') {
- restrictionVias.push(v[k].id);
- }
- }
+ 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
+ });
+ }
+ }
- 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;
- }
- }
+ 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 (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)
-
-
- if (restrict && restrict.direct) break;
- }
-
- nextWays.push({
- way: way,
- restrict: restrict
- });
- }
+ if (changeset_id) {
+ r.relation['@changeset'] = changeset_id;
+ }
- nextWays.forEach(function (nextWay) {
- step(nextWay.way, currPath, currRestrictions, nextWay.restrict);
- });
+ return r;
+ },
+ asGeoJSON: function asGeoJSON(resolver) {
+ return resolver["transient"](this, 'GeoJSON', function () {
+ if (this.isMultipolygon()) {
+ return {
+ type: 'MultiPolygon',
+ coordinates: this.multipolygon(resolver)
+ };
} 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)
-
- if (matchedRestriction && matchedRestriction.direct === false) {
- for (i = 0; i < turnPath.length; i++) {
- if (turnPath[i] === matchedRestriction.from) {
- turnPath = turnPath.slice(i);
- break;
- }
- }
- }
-
- var turn = pathToTurn(turnPath);
+ 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;
+ }
+ }
- if (turn) {
- if (matchedRestriction) {
- turn.restrictionID = matchedRestriction.id;
- turn.no = matchedRestriction.no;
- turn.only = matchedRestriction.only;
- turn.direct = matchedRestriction.direct;
- }
+ 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);
- turns.push(osmTurn(turn));
- }
+ 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]);
+ }
- if (currPath[0] === currPath[2]) return; // if we made a u-turn - stop here
- }
+ return sequence.nodes.map(function (node) {
+ return node.loc;
+ });
+ };
- if (matchedRestriction && matchedRestriction.end) return; // don't advance any further
- // which nodes can we step into?
+ 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];
+ });
- var n1 = vgraph.entity(entity.first());
- var n2 = vgraph.entity(entity.last());
- var dist = geoSphericalDistance(n1.loc, n2.loc);
- var nextNodes = [];
+ function findOuter(inner) {
+ var o, outer;
- if (currPath.length > 1) {
- if (dist > maxDistance) return; // the next node is too far
+ for (o = 0; o < outers.length; o++) {
+ outer = outers[o];
- if (!entity.__via) return; // this way is a leaf / can't be a via
+ if (geoPolygonContainsPolygon(outer, inner)) {
+ return o;
}
+ }
- 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
- }
+ for (o = 0; o < outers.length; o++) {
+ outer = outers[o];
- 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
+ if (geoPolygonIntersectsPolygon(outer, inner, false)) {
+ return o;
}
-
- 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 isOnlyVia = false;
- var v = r.membersByRole('via');
-
- 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 (viaWay.first() === nextNode.id || viaWay.last() === nextNode.id) {
- isOnlyVia = true;
- break;
- }
- }
- }
-
- return isOnlyVia;
- });
- step(nextNode, currPath, currRestrictions.concat(fromRestrictions), false);
- });
}
- } // assumes path is alternating way-node-way of odd length
-
-
- 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 (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);
+ for (var i = 0; i < inners.length; i++) {
+ var inner = inners[i];
- 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
- }
+ if (d3_geoArea({
+ type: 'Polygon',
+ coordinates: [inner]
+ }) < 2 * Math.PI) {
+ inner = inner.reverse();
}
- 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
- };
+ var o = findOuter(inners[i]);
- function adjacentNode(wayId, affixId) {
- var nodes = vgraph.entity(wayId).nodes;
- return affixId === nodes[0] ? nodes[1] : nodes[nodes.length - 2];
+ if (o !== undefined) {
+ result[o].push(inners[i]);
+ } else {
+ result.push([inners[i]]); // Invalid geometry
}
}
- };
-
- 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;
-
- while (angle < 0) {
- angle += 360;
- }
- if (fromNode === toNode) {
- return 'no_u_turn';
+ return result;
}
+ });
- if ((angle < 23 || angle > 336) && fromOneWay && toOneWay) {
- return 'no_u_turn'; // wider tolerance for u-turn if both ways are oneway
- }
+ var QAItem = /*#__PURE__*/function () {
+ function QAItem(loc, service, itemType, id, props) {
+ _classCallCheck$1(this, QAItem);
- 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)
- }
+ // 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
- if (angle < 158) {
- return 'no_right_turn';
- }
+ this.id = id ? id : "".concat(QAItem.id());
+ this.update(props); // Some QA services have marker icons to differentiate issues
- if (angle > 202) {
- return 'no_left_turn';
+ if (service && typeof service.getIcon === 'function') {
+ this.icon = service.getIcon(itemType);
+ }
}
- return 'no_straight_on';
- }
+ _createClass$1(QAItem, [{
+ key: "update",
+ value: function update(props) {
+ var _this = this;
- 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 Object.assign({
- closedWay: [],
- multipolygon: [],
- other: []
- }, geometryGroups);
- }
+ // 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
- 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.
+ }], [{
+ key: "id",
+ value: function id() {
+ return this.nextId--;
+ }
+ }]);
- 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.
+ return QAItem;
+ }();
+ QAItem.nextId = -1;
- 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
+ //
+ // 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 members = [];
- var outer = true;
+ function actionSplit(nodeIds, newWayIds) {
+ // accept single ID for backwards-compatiblity
+ if (typeof nodeIds === 'string') nodeIds = [nodeIds];
- while (polygons.length) {
- extractUncontained(polygons);
- polygons = polygons.filter(isContained);
- contained = contained.filter(isContained).map(filterContained);
- }
+ var _wayIDs; // the strategy for picking which way will have a new version and which way is newly created
- function isContained(d, i) {
- return contained[i].some(function (val) {
- return val;
- });
- }
- function filterContained(d) {
- return d.filter(isContained);
- }
+ var _keepHistoryOn = 'longest'; // 'longest', 'first'
+ // The IDs of the ways actually created by running this 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
+ var _createdWayIDs = [];
+
+ 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.
- 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 splitArea(nodes, idxA, graph) {
+ var lengths = new Array(nodes.length);
+ var length;
+ var i;
+ var best = 0;
+ var idxB;
- 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 wrap(index) {
+ return utilWrap(index, nodes.length);
+ } // calculate lengths
- action.disabled = function (graph) {
- var entities = groupEntities(graph);
- if (entities.other.length > 0 || entities.closedWay.length + entities.multipolygon.length < 2) {
- return 'not_eligible';
- }
+ length = 0;
- if (!entities.multipolygon.every(function (r) {
- return r.isComplete(graph);
- })) {
- return 'incomplete_relation';
+ for (i = wrap(idxA + 1); i !== idxA; i = wrap(i + 1)) {
+ length += dist(graph, nodes[i], nodes[wrap(i - 1)]);
+ lengths[i] = length;
}
- 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;
- });
+ length = 0;
- if (sharedMultipolygons.length) {
- // don't create a new multipolygon if it'd be redundant
- return 'not_eligible';
+ 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
+
+
+ for (i = 0; i < nodes.length; i++) {
+ var cost = lengths[i] / dist(graph, nodes[idxA], nodes[i]);
+
+ if (cost > best) {
+ idxB = i;
+ best = cost;
}
- } 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;
- }
+ return idxB;
+ }
- var DESCRIPTORS = descriptors;
- var objectDefinePropertyModule = objectDefineProperty;
- var regExpFlags = regexpFlags$1;
- var fails$4 = fails$N;
+ function totalLengthBetweenNodes(graph, nodes) {
+ var totalLength = 0;
- var FORCED$2 = DESCRIPTORS && fails$4(function () {
- // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
- return Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags').get.call({ dotAll: true, sticky: true }) !== 'sy';
- });
+ for (var i = 0; i < nodes.length - 1; i++) {
+ totalLength += dist(graph, nodes[i], nodes[i + 1]);
+ }
- // `RegExp.prototype.flags` getter
- // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
- if (FORCED$2) objectDefinePropertyModule.f(RegExp.prototype, 'flags', {
- configurable: true,
- get: regExpFlags
- });
+ return totalLength;
+ }
- var fastDeepEqual = function equal(a, b) {
- if (a === b) return true;
+ function split(graph, nodeId, wayA, newWayId) {
+ var wayB = osmWay({
+ id: newWayId,
+ tags: wayA.tags
+ }); // `wayB` is the NEW way
- if (a && b && _typeof(a) == 'object' && _typeof(b) == 'object') {
- if (a.constructor !== b.constructor) return false;
- var length, i, keys;
+ var origNodes = wayA.nodes.slice();
+ var nodesA;
+ var nodesB;
+ var isArea = wayA.isArea();
+ var isOuter = osmIsOldMultipolygonOuterMember(wayA, graph);
- if (Array.isArray(a)) {
- length = a.length;
- if (length != b.length) return false;
+ if (wayA.isClosed()) {
+ var nodes = wayA.nodes.slice(0, -1);
+ var idxA = nodes.indexOf(nodeId);
+ var idxB = splitArea(nodes, idxA, graph);
- for (i = length; i-- !== 0;) {
- if (!equal(a[i], b[i])) return false;
+ 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));
}
-
- return true;
+ } else {
+ var idx = wayA.nodes.indexOf(nodeId, 1);
+ nodesA = wayA.nodes.slice(0, idx + 1);
+ nodesB = wayA.nodes.slice(idx);
}
- 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 lengthA = totalLengthBetweenNodes(graph, nodesA);
+ var lengthB = totalLengthBetweenNodes(graph, nodesB);
- for (i = length; i-- !== 0;) {
- if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
+ 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 (i = length; i-- !== 0;) {
- var key = keys[i];
- if (!equal(a[key], b[key])) return false;
+ if (wayA.tags.step_count) {
+ // divide up the the step count proportionally between the two ways
+ var stepCount = parseFloat(wayA.tags.step_count);
+
+ 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
+ });
+ }
}
- return true;
- } // true if both NaN, false otherwise
+ 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
- return a !== a && b !== b;
- };
+ if (f.id === wayA.id || t.id === wayA.id) {
+ var keepB = false;
- // 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 (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 LCS(buffer1, buffer2) {
- var equivalenceClasses = {};
+ if (wayVia && utilArrayIntersection(wayB.nodes, wayVia.nodes).length) {
+ keepB = true;
+ break;
+ }
+ }
+ }
+ }
- for (var j = 0; j < buffer2.length; j++) {
- var item = buffer2[j];
+ if (keepB) {
+ relation = relation.replaceMember(wayA, wayB);
+ graph = graph.replace(relation);
+ } // 2. split a VIA
- if (equivalenceClasses[item]) {
- equivalenceClasses[item].push(j);
- } else {
- equivalenceClasses[item] = [j];
- }
- }
+ } 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)
- var NULLRESULT = {
- buffer1index: -1,
- buffer2index: -1,
- chain: null
- };
- var candidates = [NULLRESULT];
+ } 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 i = 0; i < buffer1.length; i++) {
- var _item = buffer1[i];
- var buffer2indices = equivalenceClasses[_item] || [];
- var r = 0;
- var c = candidates[0];
+ 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);
+ }
+ });
- for (var jx = 0; jx < buffer2indices.length; jx++) {
- var _j = buffer2indices[jx];
- var s = void 0;
+ 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: {}
+ }));
+ }
- for (s = r; s < candidates.length; s++) {
- if (candidates[s].buffer2index < _j && (s === candidates.length - 1 || candidates[s + 1].buffer2index > _j)) {
- break;
- }
- }
+ _createdWayIDs.push(wayB.id);
- if (s < candidates.length) {
- var newCandidate = {
- buffer1index: i,
- buffer2index: _j,
- chain: candidates[s]
- };
+ return graph;
+ }
- if (r === candidates.length) {
- candidates.push(c);
- } else {
- candidates[r] = c;
- }
+ var action = function action(graph) {
+ _createdWayIDs = [];
+ var newWayIndex = 0;
- r = s + 1;
- c = newCandidate;
+ for (var i = 0; i < nodeIds.length; i++) {
+ var nodeId = nodeIds[i];
+ var candidates = action.waysForNode(nodeId, graph);
- if (r === candidates.length) {
- break; // no point in examining further (j)s
- }
+ for (var j = 0; j < candidates.length; j++) {
+ graph = split(graph, nodeId, candidates[j], newWayIds && newWayIds[newWayIndex]);
+ newWayIndex += 1;
}
}
- 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].
+ return graph;
+ };
+ action.getCreatedWayIDs = function () {
+ return _createdWayIDs;
+ };
- 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.
+ action.waysForNode = function (nodeId, graph) {
+ var node = graph.entity(nodeId);
+ var splittableParents = graph.parentWays(node).filter(isSplittable);
+ 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';
+ });
- function diffIndices(buffer1, buffer2) {
- var lcs = LCS(buffer1, buffer2);
- var result = [];
- var tail1 = buffer1.length;
- var tail2 = buffer2.length;
+ if (hasLine) {
+ return splittableParents.filter(function (parent) {
+ return parent.geometry(graph) === 'line';
+ });
+ }
+ }
- 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 (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)
- });
- }
- }
-
- 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)
- //
-
-
- 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 splittableParents;
- 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])
+ 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 (parent.isClosed()) return true; // otherwise, we can't split nodes at their endpoints.
- 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;
+ for (var i = 1; i < parent.nodes.length - 1; i++) {
+ if (parent.nodes[i] === nodeId) return true;
+ }
- function advanceTo(endOffset) {
- if (endOffset > currOffset) {
- results.push({
- stable: true,
- buffer: 'o',
- bufferStart: currOffset,
- bufferLength: endOffset - currOffset,
- bufferContent: o.slice(currOffset, endOffset)
- });
- currOffset = endOffset;
+ return false;
}
- }
-
- 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
-
- while (hunks.length) {
- var nextHunk = hunks[0];
- var nextHunkStart = nextHunk.oStart;
- if (nextHunkStart > regionEnd) break; // no overlap
+ };
- regionEnd = Math.max(regionEnd, nextHunkStart + nextHunk.oLength);
- regionHunks.push(hunks.shift());
- }
+ action.ways = function (graph) {
+ return utilArrayUniq([].concat.apply([], nodeIds.map(function (nodeId) {
+ return action.waysForNode(nodeId, 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]
- };
+ action.disabled = function (graph) {
+ for (var i = 0; i < nodeIds.length; i++) {
+ var nodeId = nodeIds[i];
+ var candidates = 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]);
+ if (candidates.length === 0 || _wayIDs && _wayIDs.length !== candidates.length) {
+ return 'not_eligible';
}
-
- 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);
}
-
- 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;
+ 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 (var i = 0; i < children.length; i++) {
- var id = children[i];
- var node = graph.hasEntity(id); // remove unused childNodes..
+ 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 (targetWay.nodes.indexOf(id) === -1) {
- if (node && !isUsed(node, targetWay)) {
- updates.removeIds.push(id);
- }
+ 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;
+ }) : [];
- continue;
- } // restore used childNodes..
+ 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);
+ }
- var local = localGraph.hasEntity(id);
- var remote = remoteGraph.hasEntity(id);
- var 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);
- if (_option === 'force_remote' && remote && remote.visible) {
- updates.replacements.push(remote);
- } else if (_option === 'force_local' && local) {
- target = osmEntity(local);
-
- if (remote) {
- target = target.update({
- version: remote.version
- });
- }
+ this.entities[entity.id] = entity;
+ });
+ },
+ remove: function remove(entity) {
+ return this.update(function () {
+ this._updateCalculated(entity, undefined);
- updates.replacements.push(target);
- } else if (_option === 'safe' && local && remote && local.version !== remote.version) {
- target = osmEntity(local, {
- version: remote.version
- });
+ 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 (remote.visible) {
- target = mergeLocation(remote, target);
- } else {
- _conflicts.push(_t('merge_remote_changes.conflict.deleted', {
- user: user(remote.user)
- }));
- }
+ delete this.entities[id];
+ });
+ },
+ update: function update() {
+ var graph = this.frozen ? coreGraph(this, true) : this;
- if (_conflicts.length !== ccount) break;
- updates.replacements.push(target);
- }
+ for (var i = 0; i < arguments.length; i++) {
+ arguments[i].call(graph, graph);
}
- return targetWay;
- }
+ 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 updateChildren(updates, graph) {
- for (var i = 0; i < updates.replacements.length; i++) {
- graph = graph.replace(updates.replacements[i]);
- }
+ for (var i in entities) {
+ this.entities[i] = entities[i];
- if (updates.removeIds.length) {
- graph = actionDeleteMultiple(updates.removeIds)(graph);
+ this._updateCalculated(base.entities[i], this.entities[i]);
}
- return graph;
+ return this;
}
+ };
- function mergeMembers(remote, target) {
- if (_option === 'force_local' || fastDeepEqual(target.members, remote.members)) {
- return target;
- }
+ function osmTurn(turn) {
+ if (!(this instanceof osmTurn)) {
+ return new osmTurn(turn);
+ }
- if (_option === 'force_remote') {
- return target.update({
- members: remote.members
- });
- }
+ Object.assign(this, turn);
+ }
+ function osmIntersection(graph, startVertexId, maxDistance) {
+ maxDistance = maxDistance || 30; // in meters
- _conflicts.push(_t('merge_remote_changes.conflict.memberlist', {
- user: user(remote.user)
- }));
+ var vgraph = coreGraph(); // virtual graph
- return target;
+ var i, j, k;
+
+ function memberOfRestriction(entity) {
+ return graph.parentRelations(entity).some(function (r) {
+ return r.isRestriction();
+ });
}
- function mergeTags(base, remote, target) {
- if (_option === 'force_local' || fastDeepEqual(target.tags, remote.tags)) {
- return target;
- }
+ 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 (_option === 'force_remote') {
- return target.update({
- tags: remote.tags
- });
- }
+ 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
- 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 actions = []; // STEP 1: walk the graph outwards from starting vertex to search
+ // for more key vertices and ways to include in the intersection..
- var changed = false;
+ while (checkVertices.length) {
+ vertex = checkVertices.pop(); // check this vertex for parent ways that are roads
- for (var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ checkWays = graph.parentWays(vertex);
+ var hasWays = false;
- 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];
- }
+ 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
- changed = true;
- }
- }
- }
+ hasWays = true; // check the way's children for more key vertices
- 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`
- //
+ nodes = utilArrayUniq(graph.childNodes(way));
+ for (j = 0; j < nodes.length; j++) {
+ node = nodes[j];
+ if (node === vertex) continue; // same thing
- 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
+ if (vertices.indexOf(node) !== -1) continue; // seen it already
- 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);
+ if (geoSphericalDistance(node.loc, startNode.loc) > maxDistance) continue; // too far from start
+ // a key vertex will have parents that are also roads
+
+ var hasParents = false;
+ parents = graph.parentWays(node);
+
+ for (k = 0; k < parents.length; k++) {
+ parent = parents[k];
+ if (parent === way) continue; // same thing
+
+ if (ways.indexOf(parent) !== -1) continue; // seen it already
+
+ if (!isRoad(parent)) continue; // not a road
+
+ hasParents = true;
+ break;
}
- return graph.replace(target);
- } else {
- _conflicts.push(_t('merge_remote_changes.conflict.deleted', {
- user: user(remote.user)
- }));
+ if (hasParents) {
+ checkVertices.push(node);
+ }
+ }
+ }
- return graph; // do nothing
+ 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
+
+ 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));
+ }
}
- } // merge
+ });
+ }); // STEP 3: Force all oneways to be drawn in the forward direction
+ ways.forEach(function (w) {
+ var way = vgraph.entity(w.id);
- 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 (way.tags.oneway === '-1') {
+ var action = actionReverse(way.id, {
+ reverseOneway: true
+ });
+ actions.push(action);
+ vgraph = action(vgraph);
}
+ }); // STEP 4: Split ways on key vertices
- target = mergeTags(base, remote, target);
+ 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 (!_conflicts.length) {
- graph = updateChildren(updates, graph).replace(target);
+ 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
- return graph;
- };
+ osmEntity.id.next.way = origCount; // STEP 5: Update arrays to point to vgraph entities
- action.withOption = function (opt) {
- _option = opt;
- return action;
- };
+ 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.
- action.conflicts = function () {
- return _conflicts;
- };
+ function withMetadata(way, vertexIds) {
+ var __oneWay = way.isOneWay(); // which affixes are key vertices?
- return action;
- }
- // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
+ var __first = vertexIds.indexOf(way.first()) !== -1;
- function actionMove(moveIDs, tryDelta, projection, cache) {
- var _delta = tryDelta;
+ var __last = vertexIds.indexOf(way.last()) !== -1; // what roles is this way eligible for?
- 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..
- 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..
+ var __via = __first && __last;
- var parentsMoving = parents.every(function (way) {
- return cache.moving[way.id];
- });
- if (!parentsMoving) delete cache.moving[nodeID];
- return parentsMoving;
- }
+ var __from = __first && !__oneWay || __last;
- 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 __to = __first || __last && !__oneWay;
- 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;
- }));
+ return way.update({
+ __first: __first,
+ __last: __last,
+ __from: __from,
+ __via: __via,
+ __to: __to,
+ __oneWay: __oneWay
+ });
+ }
+
+ 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 keepGoing;
+ var removeWayIds = [];
+ var removeVertexIds = [];
+
+ do {
+ keepGoing = false;
+ checkVertices = vertexIds.slice();
+
+ for (i = 0; i < checkVertices.length; i++) {
+ var vertexId = checkVertices[i];
+ vertex = vgraph.hasEntity(vertexId);
+
+ if (!vertex) {
+ if (vertexIds.indexOf(vertexId) !== -1) {
+ vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
}
+
+ removeVertexIds.push(vertexId);
+ continue;
}
- }
- function cacheIntersections(ids) {
- function isEndpoint(way, id) {
- return !way.isClosed() && !!way.affix(id);
+ parents = vgraph.parentWays(vertex);
+
+ if (parents.length < 3) {
+ if (vertexIds.indexOf(vertexId) !== -1) {
+ vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
+ }
}
- for (var i = 0; i < ids.length; i++) {
- var id = ids[i]; // consider only intersections with 1 moved and 1 unmoved way.
+ 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;
- var childNodes = graph.childNodes(graph.entity(id));
+ if (aIsLeaf && !bIsLeaf) {
+ leaf = a;
+ survivor = b;
+ } else if (!aIsLeaf && bIsLeaf) {
+ leaf = b;
+ survivor = a;
+ }
- 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;
+ if (leaf && survivor) {
+ survivor = withMetadata(survivor, vertexIds); // update survivor way
- for (var k = 0; k < parents.length; k++) {
- var way = parents[k];
+ vgraph = vgraph.replace(survivor).remove(leaf); // update graph
- if (!cache.moving[way.id]) {
- unmoved = way;
- break;
- }
- }
+ removeWayIds.push(leaf.id);
+ keepGoing = true;
+ }
+ }
- if (!unmoved) continue; // exclude ways that are overly connected..
+ parents = vgraph.parentWays(vertex);
- 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 (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;
}
- }
- if (!cache) {
- cache = {};
+ if (parents.length < 1) {
+ // vertex is no longer attached to anything
+ vgraph = vgraph.remove(vertex);
+ }
}
+ } while (keepGoing);
- 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
+ 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.
+ //
+ // 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.
//
+ 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)
- 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;
+ var maxPathLength = maxViaWay * 2 + 3;
+ var turns = [];
+ step(start);
+ return turns; // traverse the intersection graph and find all the valid paths
- 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;
- }
+ function step(entity, currPath, currRestrictions, matchedRestriction) {
+ currPath = (currPath || []).slice(); // shallow copy
- var prev = graph.hasEntity(way.nodes[prevIndex]);
- var next = graph.hasEntity(way.nodes[nextIndex]); // Don't add orig vertex at endpoint..
+ if (currPath.length >= maxPathLength) return;
+ currPath.push(entity.id);
+ currRestrictions = (currRestrictions || []).slice(); // shallow copy
- if (!prev || !next) return graph;
- var key = wayId + '_' + nodeId;
- var orig = cache.replacedVertex[key];
+ var i, j;
- if (!orig) {
- orig = osmNode();
- cache.replacedVertex[key] = orig;
- cache.startLoc[orig.id] = cache.startLoc[nodeId];
- }
+ if (entity.type === 'node') {
+ var parents = vgraph.parentWays(entity);
+ var nextWays = []; // which ways can we step into?
- var start, end;
+ for (i = 0; i < parents.length; i++) {
+ var way = parents[i]; // if next way is a oneway incoming to this vertex, skip
- if (delta) {
- start = projection(cache.startLoc[nodeId]);
- end = projection.invert(geoVecAdd(start, delta));
- } else {
- end = cache.startLoc[nodeId];
- }
+ if (way.__oneWay && way.nodes[0] !== entity.id) continue; // if we have seen it before (allowing for an initial u-turn), skip
- 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..
+ if (currPath.indexOf(way.id) !== -1 && currPath.length >= 3) continue; // Check all "current" restrictions (where we've already walked the `FROM`)
- if (angle > 175 && angle < 185) return graph; // moving forward or backward along way?
+ var restrict = null;
- 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?
+ 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 (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.
+ var matchesFrom = f.id === fromWayId;
+ var matchesViaTo = false;
+ var isAlongOnlyPath = false;
+ 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 = [];
- function removeDuplicateVertices(wayId, graph) {
- var way = graph.entity(wayId);
- var epsilon = 1e-6;
- var prev, curr;
+ for (k = 2; k < currPath.length; k += 2) {
+ // k = 2 skips FROM
+ pathVias.push(currPath[k]); // (path goes way-node-way...)
+ }
- function isInteresting(node, graph) {
- return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
- }
+ var restrictionVias = [];
- for (var i = 0; i < way.nodes.length; i++) {
- curr = graph.entity(way.nodes[i]);
+ for (k = 0; k < v.length; k++) {
+ if (v[k].type === 'way') {
+ restrictionVias.push(v[k].id);
+ }
+ }
- 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);
- }
- }
+ 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;
+ }
+ }
+ }
- prev = curr;
- }
+ 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;
- } // Reorder nodes around intersections that have moved..
- //
- // Start: way1.nodes: b,e (moving)
- // a - b - c ----- d way2.nodes: a,b,c,d (static)
- // | vertex: b
- // e isEP1: true, isEP2, false
- //
- // way1 `b,e` moved here:
- // a ----- c = b - d
- // |
- // e
- //
- // reorder nodes way1.nodes: b,e
- // a ----- c - b - d way2.nodes: a,c,b,d
- // |
- // e
- //
+ if (restrict && restrict.direct) break;
+ }
- 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.
+ nextWays.push({
+ way: way,
+ restrict: restrict
+ });
+ }
- 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;
- });
- 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)..
+ 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)
- if (!isEP1 && !isEP2) {
- var epsilon = 1e-6,
- maxIter = 10;
+ if (matchedRestriction && matchedRestriction.direct === false) {
+ for (i = 0; i < turnPath.length; i++) {
+ if (turnPath[i] === matchedRestriction.from) {
+ turnPath = turnPath.slice(i);
+ break;
+ }
+ }
+ }
- 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 turn = pathToTurn(turnPath);
- graph = graph.replace(vertex.move(loc)); // if zorro happened, reorder nodes..
+ if (turn) {
+ if (matchedRestriction) {
+ turn.restrictionID = matchedRestriction.id;
+ turn.no = matchedRestriction.no;
+ turn.only = matchedRestriction.only;
+ turn.direct = matchedRestriction.direct;
+ }
- if (!isEP1 && edge1.index !== way1.nodes.indexOf(vertex.id)) {
- way1 = way1.removeNode(vertex.id).addNode(vertex.id, edge1.index);
- graph = graph.replace(way1);
- }
+ turns.push(osmTurn(turn));
+ }
- if (!isEP2 && edge2.index !== way2.nodes.indexOf(vertex.id)) {
- way2 = way2.removeNode(vertex.id).addNode(vertex.id, edge2.index);
- graph = graph.replace(way2);
- }
+ if (currPath[0] === currPath[2]) return; // if we made a u-turn - stop here
+ }
- return graph;
- }
+ if (matchedRestriction && matchedRestriction.end) return; // don't advance any further
+ // which nodes 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);
- }
+ var n1 = vgraph.entity(entity.first());
+ var n2 = vgraph.entity(entity.last());
+ var dist = geoSphericalDistance(n1.loc, n2.loc);
+ var nextNodes = [];
- return graph;
- } // check if moving way endpoint can cross an unmoved way, if so limit delta..
+ if (currPath.length > 1) {
+ if (dist > maxDistance) return; // the next node is too far
+ if (!entity.__via) return; // this way is a leaf / can't be a via
+ }
- function limitDelta(graph) {
- function moveNode(loc) {
- return geoVecAdd(projection(loc), _delta);
- }
+ 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
+ }
- 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..
+ 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
+ }
- if (obj.movedIsEP && obj.unmovedIsEP) continue; // Don't limit movement if this vertex is not an endpoint anyway..
+ 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
- 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);
+ var isOnlyVia = false;
+ var v = r.membersByRole('via');
- 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);
+ 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 (viaWay.first() === nextNode.id || viaWay.last() === nextNode.id) {
+ isOnlyVia = true;
+ break;
+ }
+ }
+ }
+
+ return isOnlyVia;
+ });
+ step(nextNode, currPath, currRestrictions.concat(fromRestrictions), false);
+ });
}
- }
- }
+ } // assumes path is alternating way-node-way of odd length
- var action = function action(graph) {
- if (_delta[0] === 0 && _delta[1] === 0) return graph;
- setupCache(graph);
- if (cache.intersections.length) {
- limitDelta(graph);
- }
+ 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];
- 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)));
- }
+ 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 (cache.intersections.length) {
- graph = cleanupIntersections(graph);
- }
+ 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 graph;
- };
+ 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
+ };
- action.delta = function () {
- return _delta;
+ function adjacentNode(wayId, affixId) {
+ var nodes = vgraph.entity(wayId).nodes;
+ return affixId === nodes[0] ? nodes[1] : nodes[nodes.length - 2];
+ }
+ }
};
- return action;
+ 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;
- function actionMoveMember(relationId, fromIndex, toIndex) {
- return function (graph) {
- return graph.replace(graph.entity(relationId).moveMember(fromIndex, toIndex));
- };
- }
+ while (angle < 0) {
+ angle += 360;
+ }
- 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 (fromNode === toNode) {
+ return 'no_u_turn';
+ }
- action.transitionable = true;
- return action;
- }
+ if ((angle < 23 || angle > 336) && fromOneWay && toOneWay) {
+ return 'no_u_turn'; // wider tolerance for u-turn if both ways are oneway
+ }
- function actionNoop() {
- return function (graph) {
- return graph;
- };
- }
+ 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)
+ }
- 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)
+ if (angle < 158) {
+ return 'no_right_turn';
+ }
- var lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180);
- var upperThreshold = Math.cos(threshold * Math.PI / 180);
+ if (angle > 202) {
+ return 'no_left_turn';
+ }
- 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
+ return 'no_straight_on';
+ }
- if (way.tags.nonsquare) {
- var tags = Object.assign({}, way.tags); // since we're squaring, remove indication that this is physically unsquare
+ 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 Object.assign({
+ closedWay: [],
+ multipolygon: [],
+ other: []
+ }, geometryGroups);
+ }
- delete tags.nonsquare;
- way = way.update({
- tags: tags
+ 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.
+
+ 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 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
+
+ var members = [];
+ var outer = true;
+
+ while (polygons.length) {
+ extractUncontained(polygons);
+ polygons = polygons.filter(isContained);
+ contained = contained.filter(isContained).map(filterContained);
}
- graph = graph.replace(way);
- var isClosed = way.isClosed();
- var nodes = graph.childNodes(way).slice(); // shallow copy
+ function isContained(d, i) {
+ return contained[i].some(function (val) {
+ return val;
+ });
+ }
- if (isClosed) nodes.pop();
+ function filterContained(d) {
+ return d.filter(isContained);
+ }
- 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
+ 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.
- var nodeCount = {};
- var points = [];
- var corner = {
- i: 0,
- dotp: 1
- };
- var node, point, loc, score, motions, i, j;
+ var relation;
- 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 (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 {
+ relation = osmRelation({
+ id: newRelationId,
+ tags: {
+ type: 'multipolygon'
+ }
});
}
- 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;
+ entities.multipolygon.forEach(function (m) {
+ if (m.id !== relation.id) {
+ 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';
+ }
- if (score < epsilon) {
- break;
- }
+ 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'])
+ }));
+ };
- 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
+ action.disabled = function (graph) {
+ var entities = groupEntities(graph);
- for (i = 0; i < points.length; i++) {
- point = points[i];
- var dotp = 0;
+ if (entities.other.length > 0 || entities.closedWay.length + entities.multipolygon.length < 2) {
+ return 'not_eligible';
+ }
- 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 (!entities.multipolygon.every(function (r) {
+ return r.isComplete(graph);
+ })) {
+ return 'incomplete_relation';
+ }
- if (dotp > upperThreshold) {
- straights.push(point);
+ if (!entities.multipolygon.length) {
+ var sharedMultipolygons = [];
+ entities.closedWay.forEach(function (way, i) {
+ if (i === 0) {
+ sharedMultipolygons = graph.parentMultipolygons(way);
} else {
- simplified.push(point);
+ sharedMultipolygons = utilArrayIntersection(sharedMultipolygons, graph.parentMultipolygons(way));
}
- } // Orthogonalize the simplified shape
+ });
+ sharedMultipolygons = sharedMultipolygons.filter(function (relation) {
+ return relation.members.length === entities.closedWay.length;
+ });
+ 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';
+ }
+ };
- var bestPoints = clonePoints(simplified);
- var originalPoints = clonePoints(simplified);
- score = Infinity;
+ return action;
+ }
- for (i = 0; i < 1000; i++) {
- motions = simplified.map(calcMotion);
+ var DESCRIPTORS$1 = descriptors;
+ var objectDefinePropertyModule = objectDefineProperty;
+ var regExpFlags = regexpFlags$1;
+ var fails$4 = fails$S;
- for (j = 0; j < motions.length; j++) {
- simplified[j].coord = geoVecAdd(simplified[j].coord, motions[j]);
- }
+ var RegExpPrototype = RegExp.prototype;
- var newScore = geoOrthoCalcScore(simplified, isClosed, epsilon, threshold);
+ 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';
+ });
- if (newScore < score) {
- bestPoints = clonePoints(simplified);
- score = newScore;
- }
+ // `RegExp.prototype.flags` getter
+ // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
+ if (FORCED$2) objectDefinePropertyModule.f(RegExpPrototype, 'flags', {
+ configurable: true,
+ get: regExpFlags
+ });
- if (score < epsilon) {
- break;
- }
+ 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;
+
+ if (Array.isArray(a)) {
+ length = a.length;
+ if (length != b.length) return false;
+
+ for (i = length; i-- !== 0;) {
+ if (!equal(a[i], b[i])) return false;
}
- var bestCoords = bestPoints.map(function (p) {
- return p.coord;
- });
- if (isClosed) bestCoords.push(bestCoords[0]); // move the nodes that should move
+ return true;
+ }
- for (i = 0; i < bestPoints.length; i++) {
- point = bestPoints[i];
+ 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;
- 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
+ for (i = length; i-- !== 0;) {
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
+ }
+
+ for (i = length; i-- !== 0;) {
+ var key = keys[i];
+ if (!equal(a[key], b[key])) return false;
+ }
+ return true;
+ } // true if both NaN, false otherwise
- for (i = 0; i < straights.length; i++) {
- point = straights[i];
- if (nodeCount[point.id] > 1) continue; // skip self-intersections
- node = graph.entity(point.id);
+ return a !== a && b !== b;
+ };
- 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);
+ // 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 (choice) {
- loc = projection.invert(choice.target);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
- }
- }
- }
- }
+ function LCS(buffer1, buffer2) {
+ var equivalenceClasses = {};
- return graph;
+ for (var j = 0; j < buffer2.length; j++) {
+ var item = buffer2[j];
- function clonePoints(array) {
- return array.map(function (p) {
- return {
- id: p.id,
- coord: [p.coord[0], p.coord[1]]
- };
- });
+ if (equivalenceClasses[item]) {
+ equivalenceClasses[item].push(j);
+ } else {
+ equivalenceClasses[item] = [j];
}
+ }
- 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)
+ var NULLRESULT = {
+ buffer1index: -1,
+ buffer2index: -1,
+ chain: null
+ };
+ var candidates = [NULLRESULT];
- 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);
+ for (var i = 0; i < buffer1.length; i++) {
+ var _item = buffer1[i];
+ var buffer2indices = equivalenceClasses[_item] || [];
+ var r = 0;
+ var c = candidates[0];
- if (val < lowerThreshold) {
- // nearly orthogonal
- corner.i = i;
- corner.dotp = val;
- var vec = geoVecNormalize(geoVecAdd(p, q));
- return geoVecScale(vec, 0.1 * dotp * scale);
+ for (var jx = 0; jx < buffer2indices.length; jx++) {
+ var _j = buffer2indices[jx];
+ var s = void 0;
+
+ for (s = r; s < candidates.length; s++) {
+ if (candidates[s].buffer2index < _j && (s === candidates.length - 1 || candidates[s + 1].buffer2index > _j)) {
+ break;
+ }
}
- return [0, 0]; // do nothing
- }
- }; // if we are only orthogonalizing one vertex,
- // get that vertex and the previous and next
+ if (s < candidates.length) {
+ var newCandidate = {
+ buffer1index: i,
+ buffer2index: _j,
+ chain: candidates[s]
+ };
+ if (r === candidates.length) {
+ candidates.push(c);
+ } else {
+ candidates[r] = c;
+ }
- function nodeSubset(nodes, vertexID, isClosed) {
- var first = isClosed ? 0 : 1;
- var last = isClosed ? nodes.length : nodes.length - 1;
+ r = s + 1;
+ c = newCandidate;
- 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]];
+ if (r === candidates.length) {
+ break; // no point in examining further (j)s
+ }
}
}
- return [];
- }
+ 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].
- action.disabled = function (graph) {
- var way = graph.entity(wayID);
- way = way.removeNode(''); // sanity check - remove any consecutive duplicates
- graph = graph.replace(way);
- var isClosed = way.isClosed();
- var nodes = graph.childNodes(way).slice(); // shallow copy
+ 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 (isClosed) nodes.pop();
- var allowStraightAngles = false;
- if (vertexID !== undefined) {
- allowStraightAngles = true;
- nodes = nodeSubset(nodes, vertexID, isClosed);
- if (nodes.length !== 3) return 'end_vertex';
- }
+ function diffIndices(buffer1, buffer2) {
+ var lcs = LCS(buffer1, buffer2);
+ var result = [];
+ var tail1 = buffer1.length;
+ var tail2 = buffer2.length;
- var coords = nodes.map(function (n) {
- return projection(n.loc);
- });
- var score = geoOrthoCanOrthogonalize(coords, isClosed, epsilon, threshold, allowStraightAngles);
+ 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 (score === null) {
- return 'not_squarish';
- } else if (score === 0) {
- return 'square_enough';
- } else {
- return false;
+ 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)
+ });
}
- };
-
- action.transitionable = true;
- return action;
- }
+ }
+ 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
//
- // `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)
+ // 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.
//
- // 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.
+ // (http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf)
//
- 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'
+
+ 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 = [];
+
+ 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])
+
});
+ }
- if (viaNode) {
- members.push({
- id: viaNode.id,
- type: 'node',
- role: 'via'
- });
- } else if (viaWays) {
- viaWays.forEach(function (viaWay) {
- members.push({
- id: viaWay.id,
- type: 'way',
- role: 'via'
- });
+ 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;
}
+ }
- members.push({
- id: toWay.id,
- type: 'way',
- role: 'to'
- });
- return graph.replace(osmRelation({
- id: restrictionID,
- tags: {
- type: 'restriction',
- restriction: restrictionType
- },
- members: members
- }));
- };
- }
+ 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
- function actionRevert(id) {
- var action = function action(graph) {
- var entity = graph.hasEntity(id),
- base = graph.base().entities[id];
+ while (hunks.length) {
+ var nextHunk = hunks[0];
+ var nextHunkStart = nextHunk.oStart;
+ if (nextHunkStart > regionEnd) break; // no overlap
- 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);
+ regionEnd = Math.max(regionEnd, nextHunkStart + nextHunk.oLength);
+ regionHunks.push(hunks.shift());
+ }
- if (parent.isDegenerate()) {
- graph = actionDeleteWay(parent.id)(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]
+ };
- graph.parentRelations(entity).forEach(function (parent) {
- parent = parent.removeMembersWithID(id);
- graph = graph.replace(parent);
+ 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]);
+ }
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(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);
}
- return graph.revert(id);
- };
+ currOffset = regionEnd;
+ }
- return action;
- }
+ 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 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)));
- });
- });
- };
- return action;
- }
+ 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 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)));
+ function flushOk() {
+ if (okBuffer.length) {
+ results.push({
+ ok: okBuffer
});
- });
- };
- }
+ }
- /* Align nodes along their common axis */
+ okBuffer = [];
+ }
- 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
+ function isFalseConflict(a, b) {
+ if (a.length !== b.length) return false;
+ for (var i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
- 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
+ return true;
+ }
- 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);
+ regions.forEach(function (region) {
+ if (region.stable) {
+ var _okBuffer;
- if (isLong) {
- return [p1, q1];
+ (_okBuffer = okBuffer).push.apply(_okBuffer, _toConsumableArray(region.bufferContent));
+ } else {
+ if (options.excludeFalseConflicts && isFalseConflict(region.aContent, region.bContent)) {
+ var _okBuffer2;
+
+ (_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 [p2, q2];
- }
+ var lodash = {exports: {}};
- 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
+ (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).
+ */
- 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)));
- }
+ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+ reHasRegExpChar = RegExp(reRegExpChar.source);
+ /** Used to match leading whitespace. */
- return graph;
- };
+ var reTrimStart = /^\s+/;
+ /** Used to match a single whitespace character. */
- 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;
+ var reWhitespace = /\s/;
+ /** Used to match wrap detail comments. */
- 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);
+ var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
+ reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
+ reSplitDetails = /,? & /;
+ /** Used to match words composed of alphanumeric characters. */
- if (!isNaN(dist) && dist > maxDistance) {
- maxDistance = dist;
- }
- }
+ 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 (maxDistance < 0.0001) {
- return 'straight_enough';
- }
- };
+ var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
+ /** Used to match backslashes in property paths. */
- action.transitionable = true;
- return action;
- }
+ var reEscapeChar = /\\(\\)?/g;
+ /**
+ * Used to match
+ * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
+ */
- /*
- * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as
- */
+ 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).
+ */
- 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
+ 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`. */
+ var freeParseFloat = parseFloat,
+ freeParseInt = parseInt;
+ /** Detect free variable `global` from Node.js. */
- 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';
- });
+ var freeGlobal = _typeof(commonjsGlobal) == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
+ /** Detect free variable `self`. */
- 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"]
+ var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self;
+ /** Used as a reference to the global object. */
+ var root = freeGlobal || freeSelf || Function('return this')();
+ /** Detect free variable `exports`. */
- 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
+ var freeExports = exports && !exports.nodeType && exports;
+ /** Detect free variable `module`. */
- 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 freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
+ /** Detect the popular CommonJS extension `module.exports`. */
- 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
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+ /** Detect free variable `process` from Node.js. */
+ var freeProcess = moduleExports && freeGlobal.process;
+ /** Used to access faster Node.js helpers. */
- while (remainingWays.length) {
- nextWay = getNextWay(currNode, remainingWays);
- remainingWays = utilArrayDifference(remainingWays, [nextWay]);
+ var nodeUtil = function () {
+ try {
+ // Use `util.types` for Node.js 10+.
+ var types = freeModule && freeModule.require && freeModule.require('util').types;
- if (nextWay[0] !== currNode) {
- nextWay.reverse();
- }
+ if (types) {
+ return types;
+ } // Legacy `process.binding('util')` for Node.js < 10.
- nodes = nodes.concat(nextWay);
- currNode = nodes[nodes.length - 1];
- } // If user selected 2 nodes to straighten between, then slice nodes array to those nodes
+ return freeProcess && freeProcess.binding && freeProcess.binding('util');
+ } catch (e) {}
+ }();
+ /* Node.js helper references. */
- 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;
- });
- nodes = nodes.slice(sortedStartEnd[0], sortedStartEnd[1] + 1);
- }
- return nodes.map(function (n) {
- return graph.entity(n);
- });
- }
+ 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 shouldKeepNode(node, graph) {
- return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
- }
+ /**
+ * 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`.
+ */
- 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;
+ function apply(func, thisArg, args) {
+ switch (args.length) {
+ case 0:
+ return func.call(thisArg);
- for (i = 1; i < points.length - 1; i++) {
- var node = nodes[i];
- var point = points[i];
+ case 1:
+ return func.call(thisArg, args[0]);
- 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);
- }
+ case 2:
+ return func.call(thisArg, args[0], args[1]);
+
+ case 3:
+ return func.call(thisArg, args[0], args[1], args[2]);
}
- }
- for (i = 0; i < toDelete.length; i++) {
- graph = actionDeleteNode(toDelete[i].id)(graph);
+ 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`.
+ */
- return graph;
- };
- 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;
+ function arrayAggregator(array, setter, iteratee, accumulator) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- if (threshold === 0) {
- return 'too_bendy';
+ while (++index < length) {
+ var value = array[index];
+ setter(accumulator, value, iteratee(value), array);
+ }
+
+ 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`.
+ */
- var maxDistance = 0;
- 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
+ function arrayEach(array, iteratee) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- if (isNaN(dist) || dist > threshold) {
- return 'too_bendy';
- } else if (dist > maxDistance) {
- maxDistance = dist;
+ while (++index < length) {
+ if (iteratee(array[index], index, array) === false) {
+ break;
+ }
}
+
+ 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`.
+ */
- var keepingAllNodes = nodes.every(function (node, i) {
- return i === 0 || i === nodes.length - 1 || shouldKeepNode(node, graph);
- });
- if (maxDistance < 0.0001 && // Allow straightening even if already straight in order to remove extraneous nodes
- keepingAllNodes) {
- return 'straight_enough';
- }
- };
+ function arrayEachRight(array, iteratee) {
+ var length = array == null ? 0 : array.length;
- action.transitionable = true;
- return action;
- }
+ while (length--) {
+ if (iteratee(array[length], length, array) === false) {
+ break;
+ }
+ }
- //
- // `turn` must be an `osmTurn` object with a `restrictionID` property.
- // see osm/intersection.js, pathToTurn()
- //
+ 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`.
+ */
- function actionUnrestrictTurn(turn) {
- return function (graph) {
- return actionDeleteRelation(turn.restrictionID)(graph);
- };
- }
- /* Reflect the given area around its axis of symmetry */
+ function arrayEvery(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- function actionReflect(reflectIds, projection) {
- var _useLongAxis = true;
+ while (++index < length) {
+ if (!predicate(array[index], index, array)) {
+ 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 = 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.
+ 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.
+ */
- 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);
- 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
+ function arrayFilter(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ resIndex = 0,
+ result = [];
+ while (++index < length) {
+ var value = array[index];
- 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);
+ if (predicate(value, index, array)) {
+ result[resIndex++] = value;
+ }
+ }
- 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);
+ 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`.
+ */
- return graph;
- };
- action.useLongAxis = function (val) {
- if (!arguments.length) return _useLongAxis;
- _useLongAxis = val;
- return action;
- };
+ 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`.
+ */
- action.transitionable = true;
- return action;
- }
- function actionUpgradeTags(entityId, oldTags, replaceTags) {
- return function (graph) {
- var entity = graph.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ function arrayIncludesWith(array, value, comparator) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- var transferValue;
- var semiIndex;
+ while (++index < length) {
+ if (comparator(value, array[index])) {
+ return true;
+ }
+ }
- for (var oldTagKey in oldTags) {
- if (!(oldTagKey in tags)) continue; // wildcard match
+ 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.
+ */
- 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]);
- 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;
- }
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ result = Array(length);
- vals.splice(oldIndex, 1);
- tags[oldTagKey] = vals.join(';');
- }
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
}
+
+ 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 (replaceTags) {
- for (var replaceKey in replaceTags) {
- var replaceValue = replaceTags[replaceKey];
- 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);
+ function arrayPush(array, values) {
+ var index = -1,
+ length = values.length,
+ offset = array.length;
- if (existingVals.indexOf(replaceValue) === -1) {
- existingVals.splice(semiIndex, 0, replaceValue);
- tags[replaceKey] = existingVals.join(';');
- }
- } else {
- tags[replaceKey] = replaceValue;
- }
- }
+ while (++index < length) {
+ array[offset + index] = values[index];
}
- }
-
- return graph.replace(entity.update({
- tags: tags
- }));
- };
- }
- function behaviorEdit(context) {
- function behavior() {
- context.map().minzoom(context.minEditableZoom());
- }
+ 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 () {
- context.map().minzoom(0);
- };
- return behavior;
- }
+ function arrayReduce(array, iteratee, accumulator, initAccum) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- /*
- The hover behavior adds the `.hover` class on pointerover to all elements to which
- the identical datum is bound, and removes it on pointerout.
+ if (initAccum && length) {
+ accumulator = array[++index];
+ }
- 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.
- */
+ while (++index < length) {
+ accumulator = iteratee(accumulator, array[index], index, array);
+ }
- function behaviorHover(context) {
- var dispatch = dispatch$8('hover');
+ 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 _selection = select(null);
- var _newNodeId = null;
- var _initialNodeID = null;
+ function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+ var length = array == null ? 0 : array.length;
- var _altDisables;
+ if (initAccum && length) {
+ accumulator = array[--length];
+ }
- var _ignoreVertex;
+ while (length--) {
+ accumulator = iteratee(accumulator, array[length], length, array);
+ }
- var _targets = []; // use pointer events on supported platforms; fallback to mouse events
+ 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 _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
- function keydown(d3_event) {
- if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- _selection.selectAll('.hover').classed('hover-suppressed', true).classed('hover', false);
+ function arraySome(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- _selection.classed('hover-disabled', true);
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return true;
+ }
+ }
- dispatch.call('hover', this, null);
+ return false;
}
- }
+ /**
+ * Gets the size of an ASCII `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
- function keyup(d3_event) {
- if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false).classed('hover', true);
- _selection.classed('hover-disabled', false);
+ 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.
+ */
- dispatch.call('hover', this, _targets);
+ 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`.
+ */
- function behavior(selection) {
- _selection = selection;
- _targets = [];
- if (_initialNodeID) {
- _newNodeId = _initialNodeID;
- _initialNodeID = null;
- } else {
- _newNodeId = null;
+ 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`.
+ */
- _selection.on(_pointerPrefix + 'over.hover', pointerover).on(_pointerPrefix + 'out.hover', pointerout) // treat pointerdown as pointerover for touch devices
- .on(_pointerPrefix + 'down.hover', pointerover);
- select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', pointerout, true).on('keydown.hover', keydown).on('keyup.hover', keyup);
+ 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 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 baseFindIndex(array, predicate, fromIndex, fromRight) {
+ var length = array.length,
+ index = fromIndex + (fromRight ? 1 : -1);
+
+ while (fromRight ? index-- : ++index < length) {
+ if (predicate(array[index], index, array)) {
+ return index;
+ }
}
- return datum;
+ 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`.
+ */
- 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);
- updateHover(d3_event, _targets);
- }
+ 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 pointerout(d3_event) {
- var target = eventTarget(d3_event);
-
- var index = _targets.indexOf(target);
- if (index !== -1) {
- _targets.splice(index);
+ function baseIndexOfWith(array, value, fromIndex, comparator) {
+ var index = fromIndex - 1,
+ length = array.length;
- updateHover(d3_event, _targets);
+ while (++index < length) {
+ if (comparator(array[index], value)) {
+ return index;
+ }
}
- }
- function allowsVertex(d) {
- return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
+ return -1;
}
+ /**
+ * 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 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 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.
+ */
- return true;
+
+ 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.
+ */
- function updateHover(d3_event, targets) {
- _selection.selectAll('.hover').classed('hover', false);
- _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
+ function baseProperty(key) {
+ return function (object) {
+ return object == null ? undefined$1 : object[key];
+ };
+ }
+ /**
+ * 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.
+ */
- 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 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.
+ */
- 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);
- }
- return true;
+ function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+ eachFunc(collection, function (value, index, collection) {
+ accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index, collection);
});
- var selector = '';
+ 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`.
+ */
- 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 baseSortBy(array, comparer) {
+ var length = array.length;
+ array.sort(comparer);
- if (datum.type === 'relation') {
- for (var j in datum.members) {
- selector += ', .' + datum.members[j].id;
- }
- }
- }
+ while (length--) {
+ array[length] = array[length].value;
}
- var suppressed = _altDisables && d3_event && d3_event.altKey;
+ 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.
+ */
- if (selector.trim().length) {
- // remove the first comma
- selector = selector.slice(1);
- _selection.selectAll(selector).classed(suppressed ? 'hover-suppressed' : 'hover', true);
+ function baseSum(array, iteratee) {
+ var result,
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var current = iteratee(array[index]);
+
+ if (current !== undefined$1) {
+ result = result === undefined$1 ? current : result + current;
+ }
}
- dispatch.call('hover', this, !suppressed && targets);
+ 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.
+ */
- 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 baseTimes(n, iteratee) {
+ var index = -1,
+ result = Array(n);
- behavior.ignoreVertex = function (val) {
- if (!arguments.length) return _ignoreVertex;
- _ignoreVertex = val;
- return behavior;
- };
+ while (++index < n) {
+ result[index] = iteratee(index);
+ }
- behavior.initialNodeID = function (nodeId) {
- _initialNodeID = nodeId;
- return behavior;
- };
+ 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 utilRebind(behavior, dispatch, 'on');
- }
- 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');
+ 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.
+ */
- var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true).on('hover', context.ui().sidebar.hover);
- var _edit = behaviorEdit(context);
+ 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.
+ */
- var _closeTolerance = 4;
- var _tolerance = 12;
- var _mouseLeave = false;
- var _lastMouse = null;
- var _lastPointerUpEvent;
+ 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.
+ */
- var _downPointer; // use pointer events on supported platforms; fallback to mouse events
+ 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`.
+ */
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // related code
- // - `mode/drag_node.js` `datum()`
+ 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.
+ */
- 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)
+ function charsStartIndex(strSymbols, chrSymbols) {
+ var index = -1,
+ length = strSymbols.length;
+ while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
- var d = element.__data__;
- return d && d.properties && d.properties.target ? d : {};
- }
+ 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 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 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);
+ function charsEndIndex(strSymbols, chrSymbols) {
+ var index = strSymbols.length;
- 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);
+ while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+
+ 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.
+ */
- 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 countHolders(array, placeholder) {
+ var length = array.length,
+ result = 0;
- if (dist >= _closeTolerance) {
- _downPointer.isCancelled = true;
- dispatch.call('downcancel', this);
+ while (length--) {
+ if (array[length] === placeholder) {
+ ++result;
+ }
}
+
+ 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.
+ */
- 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));
- }
+ 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 pointercancel(d3_event) {
- if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse')) {
- if (!_downPointer.isCancelled) {
- dispatch.call('downcancel', this);
- }
+ 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.
+ */
- _downPointer = null;
+ 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.
+ */
- function mouseenter() {
- _mouseLeave = false;
- }
- function mouseleave() {
- _mouseLeave = true;
- }
+ 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 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 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`.
+ */
- function click(d3_event, loc) {
- var d = datum(d3_event);
- var target = d && d.properties && d.properties.entity;
- var mode = context.mode();
- 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());
+ 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.
+ */
- if (choice) {
- var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]];
- dispatch.call('clickWay', this, choice.loc, edge, d);
- return;
+
+ function iteratorToArray(iterator) {
+ var data,
+ result = [];
+
+ while (!(data = iterator.next()).done) {
+ result.push(data.value);
}
- } else if (mode.id !== 'add-point' || mode.preset.matchGeometry('point')) {
- var locLatLng = context.projection.invert(loc);
- dispatch.call('click', this, locLatLng, d);
+
+ return result;
}
- } // treat a spacebar press like a click
+ /**
+ * Converts `map` to its key-value pairs.
+ *
+ * @private
+ * @param {Object} map The map to convert.
+ * @returns {Array} Returns the key-value pairs.
+ */
- function space(d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- var currSpace = context.map().mouse();
+ function mapToArray(map) {
+ var index = -1,
+ result = Array(map.size);
+ map.forEach(function (value, key) {
+ result[++index] = [key, value];
+ });
+ return result;
+ }
+ /**
+ * 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.
+ */
- if (_disableSpace && _lastSpace) {
- var dist = geoVecLength(_lastSpace, currSpace);
- if (dist > _tolerance) {
- _disableSpace = false;
- }
+ 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.
+ */
- 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
+ function replaceHolders(array, placeholder) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
- 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);
- }
+ while (++index < length) {
+ var value = array[index];
- function backspace(d3_event) {
- d3_event.preventDefault();
- dispatch.call('undo');
- }
-
- function del(d3_event) {
- d3_event.preventDefault();
- dispatch.call('cancel');
- }
+ if (value === placeholder || value === PLACEHOLDER) {
+ array[index] = PLACEHOLDER;
+ result[resIndex++] = index;
+ }
+ }
- function ret(d3_event) {
- d3_event.preventDefault();
- dispatch.call('finish');
- }
+ return result;
+ }
+ /**
+ * Converts `set` to an array of its values.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the values.
+ */
- 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;
- }
- 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
+ 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.
+ */
- select(document).call(keybinding.unbind);
- };
- behavior.hover = function () {
- return _hover;
- };
+ 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`.
+ */
- return utilRebind(behavior, dispatch, 'on');
- }
- function initRange(domain, range) {
- switch (arguments.length) {
- case 0:
- break;
+ function strictIndexOf(array, value, fromIndex) {
+ var index = fromIndex - 1,
+ length = array.length;
- case 1:
- this.range(domain);
- break;
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
- default:
- this.range(range).domain(domain);
- break;
- }
+ 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`.
+ */
- return this;
- }
- function constants(x) {
- return function () {
- return x;
- };
- }
+ function strictLastIndexOf(array, value, fromIndex) {
+ var index = fromIndex + 1;
- function number(x) {
- return +x;
- }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
- var unit = [0, 1];
- function identity$1(x) {
- return x;
- }
+ return index;
+ }
+ /**
+ * Gets the number of symbols in `string`.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {number} Returns the string size.
+ */
- 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 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.
+ */
- 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 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.
+ */
- 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 trimmedEndIndex(string) {
+ var index = string.length;
- while (++i < j) {
- d[i] = normalize(domain[i], domain[i + 1]);
- r[i] = interpolate(range[i], range[i + 1]);
- }
+ while (index-- && reWhitespace.test(string.charAt(index))) {}
- return function (x) {
- var i = bisectRight(domain, x, 1, j) - 1;
- return r[i](d[i](x));
- };
- }
+ 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.
+ */
- function copy(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;
- 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;
- }
+ var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
+ /**
+ * Gets the size of a Unicode `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
- function scale(x) {
- return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x)));
- }
+ function unicodeSize(string) {
+ var result = reUnicode.lastIndex = 0;
- scale.invert = function (y) {
- return clamp(untransform((input || (input = piecewise(range, domain.map(transform), d3_interpolateNumber)))(y)));
- };
+ while (reUnicode.test(string)) {
+ ++result;
+ }
- scale.domain = function (_) {
- return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice();
- };
+ return result;
+ }
+ /**
+ * Converts a Unicode `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
- scale.range = function (_) {
- return arguments.length ? (range = Array.from(_), rescale()) : range.slice();
- };
- scale.rangeRound = function (_) {
- return range = Array.from(_), interpolate = interpolateRound, rescale();
- };
+ 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`.
+ */
- scale.clamp = function (_) {
- return arguments.length ? (clamp = _ ? true : identity$1, rescale()) : clamp !== identity$1;
- };
- scale.interpolate = function (_) {
- return arguments.length ? (interpolate = _, rescale()) : interpolate;
- };
+ function unicodeWords(string) {
+ return string.match(reUnicodeWord) || [];
+ }
+ /*--------------------------------------------------------------------------*/
- scale.unknown = function (_) {
- return arguments.length ? (unknown = _, scale) : unknown;
- };
+ /**
+ * 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;
+ */
- 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 runInContext = function runInContext(context) {
+ context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
+ /** Built-in constructor references. */
- function formatDecimalParts(x, p) {
- if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
+ 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. */
- 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).
+ var arrayProto = Array.prototype,
+ funcProto = Function.prototype,
+ objectProto = Object.prototype;
+ /** Used to detect overreaching core-js shims. */
- return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)];
- }
+ var coreJsData = context['__core-js_shared__'];
+ /** Used to resolve the decompiled source of functions. */
- function exponent (x) {
- return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN;
- }
+ var funcToString = funcProto.toString;
+ /** Used to check objects for own properties. */
- function formatGroup (grouping, thousands) {
- return function (value, width) {
- var i = value.length,
- t = [],
- j = 0,
- g = grouping[0],
- length = 0;
+ var hasOwnProperty = objectProto.hasOwnProperty;
+ /** Used to generate unique IDs. */
- 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];
- }
+ var idCounter = 0;
+ /** Used to detect methods masquerading as native. */
- return t.reverse().join(thousands);
- };
- }
+ 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.
+ */
- function formatNumerals (numerals) {
- return function (value) {
- return value.replace(/[0-9]/g, function (i) {
- return numerals[+i];
- });
- };
- }
- // [[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
+ var nativeObjectToString = objectProto.toString;
+ /** Used to infer the `Object` constructor. */
- 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 + "";
- }
+ var objectCtorString = funcToString.call(Object);
+ /** Used to restore the original `_` reference in `_.noConflict`. */
- 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;
- };
+ var oldDash = root._;
+ /** Used to detect if a method is native. */
- // 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;
+ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
+ /** Built-in value references. */
- case "0":
- if (i0 === 0) i0 = i;
- i1 = i;
- 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;
- default:
- if (!+s[i]) break out;
- if (i0 > 0) i0 = 0;
- break;
- }
- }
+ 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 i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
- }
+ /**
+ * 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
+ */
- var $$7 = _export;
- var fails$3 = fails$N;
- var thisNumberValue = thisNumberValue$2;
+ function lodash(value) {
+ if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+ if (value instanceof LodashWrapper) {
+ return value;
+ }
- var nativeToPrecision = 1.0.toPrecision;
+ if (hasOwnProperty.call(value, '__wrapped__')) {
+ return wrapperClone(value);
+ }
+ }
- var FORCED$1 = fails$3(function () {
- // IE7-
- return nativeToPrecision.call(1, undefined) !== '1';
- }) || !fails$3(function () {
- // V8 ~ Android 4.3-
- nativeToPrecision.call({});
- });
+ 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.
+ */
- // `Number.prototype.toPrecision` method
- // https://tc39.es/ecma262/#sec-number.prototype.toprecision
- $$7({ 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!
- }
+ var baseCreate = function () {
+ function object() {}
- 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");
- }
+ return function (proto) {
+ if (!isObject(proto)) {
+ return {};
+ }
- 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 (objectCreate) {
+ return objectCreate(proto);
+ }
- function identity (x) {
- return x;
- }
+ object.prototype = proto;
+ var result = new object();
+ object.prototype = undefined$1;
+ return result;
+ };
+ }();
+ /**
+ * The function whose prototype chain sequence wrappers inherit from.
+ *
+ * @private
+ */
- 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 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.
+ */
- 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 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}
+ */
- 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].
+ lodash.templateSettings = {
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'escape': reEscape,
- precision = precision === undefined ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision));
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'evaluate': reEvaluate,
- function format(value) {
- var valuePrefix = prefix,
- valueSuffix = suffix,
- i,
- n,
- c;
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'interpolate': reInterpolate,
- if (type === "c") {
- valueSuffix = formatType(value) + valueSuffix;
- value = "";
- } else {
- value = +value; // Determine the sign. -0 is not less than 0, but 1 / -0 is!
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type {string}
+ */
+ 'variable': '',
- var valueNegative = value < 0 || 1 / value < 0; // Perform the initial formatting.
+ /**
+ * 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;
+ /*------------------------------------------------------------------------*/
- value = isNaN(value) ? nan : formatType(Math.abs(value), precision); // Trim insignificant zeros.
+ /**
+ * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+ *
+ * @private
+ * @constructor
+ * @param {*} value The value to wrap.
+ */
- if (trim) value = formatTrim(value); // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
+ 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.
+ */
- 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 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.
+ */
- 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;
- }
- }
+ function lazyReverse() {
+ if (this.__filtered__) {
+ var result = new LazyWrapper(this);
+ result.__dir__ = -1;
+ result.__filtered__ = true;
+ } else {
+ result = this.clone();
+ result.__dir__ *= -1;
}
- } // If the fill character is not "0", grouping is applied before padding.
-
-
- if (comma && !zero) value = group(value, Infinity); // Compute the padding.
- 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.
+ return result;
+ }
+ /**
+ * Extracts the unwrapped value from its lazy wrapper.
+ *
+ * @private
+ * @name value
+ * @memberOf LazyWrapper
+ * @returns {*} Returns the unwrapped value.
+ */
- if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; // Reconstruct the final output based on the desired alignment.
- switch (align) {
- case "<":
- value = valuePrefix + value + valueSuffix + padding;
- break;
+ 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__);
- case "=":
- value = valuePrefix + padding + value + valueSuffix;
- break;
+ if (!isArr || !isRight && arrLength == length && takeCount == length) {
+ return baseWrapperValue(array, this.__actions__);
+ }
- case "^":
- value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
- break;
+ var result = [];
- default:
- value = padding + valuePrefix + value + valueSuffix;
- break;
- }
+ 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;
+ }
+ }
+ }
- return numerals(value);
- }
+ result[resIndex++] = value;
+ }
- format.toString = function () {
- return specifier + "";
- };
+ return result;
+ } // Ensure `LazyWrapper` is an instance of `baseLodash`.
- return format;
- }
- 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;
- };
- }
+ LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+ LazyWrapper.prototype.constructor = LazyWrapper;
+ /*------------------------------------------------------------------------*/
- return {
- format: newFormat,
- formatPrefix: formatPrefix
- };
- }
+ /**
+ * Creates a hash object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- var locale;
- var format$1;
- var formatPrefix;
- defaultLocale({
- thousands: ",",
- grouping: [3],
- currency: ["$", ""]
- });
- function defaultLocale(definition) {
- locale = formatLocale(definition);
- format$1 = locale.format;
- formatPrefix = locale.formatPrefix;
- return locale;
- }
+ function Hash(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- function precisionFixed (step) {
- return Math.max(0, -exponent(Math.abs(step)));
- }
+ 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
+ */
- function precisionPrefix (step, value) {
- return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));
- }
- function precisionRound (step, max) {
- step = Math.abs(step), max = Math.abs(max) - step;
- return Math.max(0, exponent(max) - exponent(step)) + 1;
- }
+ 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`.
+ */
- function tickFormat(start, stop, count, specifier) {
- var step = tickStep(start, stop, count),
- precision;
- specifier = formatSpecifier(specifier == null ? ",f" : specifier);
- 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);
+ 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.
+ */
- 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;
+
+ function hashGet(key) {
+ var data = this.__data__;
+
+ if (nativeCreate) {
+ var result = data[key];
+ return result === HASH_UNDEFINED ? undefined$1 : result;
+ }
+
+ 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`.
+ */
- case "f":
- case "%":
- {
- if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2;
- break;
+
+ 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.
+ */
- return format$1(specifier);
- }
- function linearish(scale) {
- var domain = scale.domain;
+ 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`.
- 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);
- };
+ Hash.prototype.clear = hashClear;
+ Hash.prototype['delete'] = hashDelete;
+ Hash.prototype.get = hashGet;
+ Hash.prototype.has = hashHas;
+ Hash.prototype.set = hashSet;
+ /*------------------------------------------------------------------------*/
- 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;
+ /**
+ * Creates an list cache object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- if (stop < start) {
- step = start, start = stop, stop = step;
- step = i0, i0 = i1, i1 = step;
- }
+ function ListCache(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- while (maxIter-- > 0) {
- step = tickIncrement(start, stop, count);
+ 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
+ */
- 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;
+
+ 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`.
+ */
- prestep = step;
- }
- return scale;
- };
+ function listCacheDelete(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
- return scale;
- }
- function linear() {
- var scale = continuous();
+ if (index < 0) {
+ return false;
+ }
- scale.copy = function () {
- return copy(scale, linear());
- };
+ var lastIndex = data.length - 1;
- initRange.apply(scale, arguments);
- return linearish(scale);
- }
+ if (index == lastIndex) {
+ data.pop();
+ } else {
+ splice.call(data, index, 1);
+ }
- // eslint-disable-next-line es/no-math-expm1 -- safe
- var $expm1 = Math.expm1;
- var exp$1 = Math.exp;
+ --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.
+ */
- // `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 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`.
+ */
- function scale(x) {
- return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown;
- }
- function rescale() {
- var i = -1;
- domain = new Array(n);
+ 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.
+ */
- while (++i < n) {
- domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
- }
- return scale;
- }
+ function listCacheSet(key, value) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
- scale.domain = function (_) {
- var _ref, _ref2;
+ if (index < 0) {
+ ++this.size;
+ data.push([key, value]);
+ } else {
+ data[index][1] = value;
+ }
- return arguments.length ? ((_ref = _, _ref2 = _slicedToArray(_ref, 2), x0 = _ref2[0], x1 = _ref2[1], _ref), x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
- };
+ return this;
+ } // Add methods to `ListCache`.
- 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]];
- };
+ ListCache.prototype.clear = listCacheClear;
+ ListCache.prototype['delete'] = listCacheDelete;
+ ListCache.prototype.get = listCacheGet;
+ ListCache.prototype.has = listCacheHas;
+ ListCache.prototype.set = listCacheSet;
+ /*------------------------------------------------------------------------*/
- scale.unknown = function (_) {
- return arguments.length ? (unknown = _, scale) : scale;
- };
+ /**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- scale.thresholds = function () {
- return domain.slice();
- };
+ function MapCache(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- scale.copy = function () {
- return quantize().domain([x0, x1]).range(range).unknown(unknown);
- };
+ 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 initRange.apply(linearish(scale), arguments);
- }
- // https://github.com/tc39/proposal-string-pad-start-end
- var toLength$2 = toLength$q;
- var repeat$1 = stringRepeat;
- var requireObjectCoercible$2 = requireObjectCoercible$e;
+ 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`.
+ */
- var ceil = Math.ceil;
- // `String.prototype.{ padStart, padEnd }` methods implementation
- var createMethod = function (IS_END) {
- return function ($this, maxLength, fillString) {
- var S = String(requireObjectCoercible$2($this));
- var stringLength = S.length;
- var fillStr = fillString === undefined ? ' ' : String(fillString);
- var intMaxLength = toLength$2(maxLength);
- var fillLen, stringFiller;
- if (intMaxLength <= stringLength || fillStr == '') return S;
- fillLen = intMaxLength - stringLength;
- stringFiller = repeat$1.call(fillStr, ceil(fillLen / fillStr.length));
- if (stringFiller.length > fillLen) stringFiller = stringFiller.slice(0, fillLen);
- return IS_END ? S + stringFiller : stringFiller + S;
- };
- };
+ 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.
+ */
- 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 fails$2 = fails$N;
- var padStart = stringPad.start;
+ 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`.
+ */
- 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$2(function () {
- return nativeDateToISOString.call(new Date(-5e13 - 1)) != '0385-07-25T07:06:39.999Z';
- }) || !fails$2(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 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.
+ */
- var $$6 = _export;
- var toISOString = dateToIsoString;
- // `Date.prototype.toISOString` method
- // https://tc39.es/ecma262/#sec-date.prototype.toisostring
- // PhantomJS / old WebKit has a broken implementations
- $$6({ target: 'Date', proto: true, forced: Date.prototype.toISOString !== toISOString }, {
- toISOString: toISOString
- });
+ 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 behaviorBreathe() {
- var duration = 800;
- var steps = 4;
- var selector = '.selected.shadow, .selected .shadow';
- var _selected = select(null);
+ MapCache.prototype.clear = mapCacheClear;
+ MapCache.prototype['delete'] = mapCacheDelete;
+ MapCache.prototype.get = mapCacheGet;
+ MapCache.prototype.has = mapCacheHas;
+ MapCache.prototype.set = mapCacheSet;
+ /*------------------------------------------------------------------------*/
- var _classed = '';
- var _params = {};
- var _done = false;
+ /**
+ *
+ * Creates an array cache object to store unique values.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
- var _timer;
+ function SetCache(values) {
+ var index = -1,
+ length = values == null ? 0 : values.length;
+ this.__data__ = new MapCache();
- 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 || '');
- };
- }
+ 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.
+ */
- function reset(selection) {
- selection.style('stroke-opacity', null).style('stroke-width', null).style('fill-opacity', null).style('r', null);
- }
- 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');
- });
- }
+ function setCacheAdd(value) {
+ this.__data__.set(value, HASH_UNDEFINED);
- 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
+ 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`.
+ */
- 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..
+ function setCacheHas(value) {
+ return this.__data__.has(value);
+ } // Add methods to `SetCache`.
- 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 run(surface, fromTo) {
- var toFrom = fromTo === 'from' ? 'to' : 'from';
- var currSelected = surface.selectAll(selector);
- var currClassed = surface.attr('class');
+ SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
+ SetCache.prototype.has = setCacheHas;
+ /*------------------------------------------------------------------------*/
- if (_done || currSelected.empty()) {
- _selected.call(reset);
+ /**
+ * Creates a stack cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- _selected = select(null);
- return;
- }
+ 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
+ */
- if (!fastDeepEqual(currSelected.data(), _selected.data()) || currClassed !== _classed) {
- _selected.call(reset);
- _classed = currClassed;
- _selected = currSelected.call(calcAnimationParams);
- }
+ 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`.
+ */
- 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 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 (!select(this).classed('selected')) {
- reset(select(this));
+ 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`.
+ */
- function behavior(surface) {
- _done = false;
- _timer = timer(function () {
- // wait for elements to actually become selected
- if (surface.selectAll(selector).empty()) {
- return false;
+
+ 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.
+ */
- surface.call(run, 'from');
- _timer.stop();
+ function stackSet(key, value) {
+ var data = this.__data__;
- return true;
- }, 20);
- }
+ if (data instanceof ListCache) {
+ var pairs = data.__data__;
- behavior.restartIfNeeded = function (surface) {
- if (_selected.empty()) {
- surface.call(run, 'from');
+ if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
+ pairs.push([key, value]);
+ this.size = ++data.size;
+ return this;
+ }
- if (_timer) {
- _timer.stop();
- }
- }
- };
+ data = this.__data__ = new MapCache(pairs);
+ }
- behavior.off = function () {
- _done = true;
+ data.set(key, value);
+ this.size = data.size;
+ return this;
+ } // Add methods to `Stack`.
- if (_timer) {
- _timer.stop();
- }
- _selected.interrupt().call(reset);
- };
+ Stack.prototype.clear = stackClear;
+ Stack.prototype['delete'] = stackDelete;
+ Stack.prototype.get = stackGet;
+ Stack.prototype.has = stackHas;
+ Stack.prototype.set = stackSet;
+ /*------------------------------------------------------------------------*/
- return behavior;
- }
+ /**
+ * 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.
+ */
- /* Creates a keybinding behavior for an operation */
- function behaviorOperation(context) {
- var _operation;
+ 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);
+ }
+ }
- function keypress(d3_event) {
- // prevent operations during low zoom selection
- if (!context.map().withinEditableZoom()) return;
- if (_operation.availableForKeypress && !_operation.availableForKeypress()) return;
- d3_event.preventDefault();
+ return result;
+ }
+ /**
+ * A specialized version of `_.sample` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to sample.
+ * @returns {*} Returns the random element.
+ */
- var disabled = _operation.disabled();
- 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);
-
- _operation();
- }
- }
+ 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 behavior() {
- if (_operation && _operation.available()) {
- context.keybinding().on(_operation.keys, keypress);
- }
- return behavior;
- }
+ 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.
+ */
- behavior.off = function () {
- context.keybinding().off(_operation.keys);
- };
- behavior.which = function (_) {
- if (!arguments.length) return _operation;
- _operation = _;
- return behavior;
- };
+ 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.
+ */
- return behavior;
- }
- function operationCircularize(context, selectedIDs) {
- var _extent;
+ 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.
+ */
- var _actions = selectedIDs.map(getAction).filter(Boolean);
- var _amount = _actions.length === 1 ? 'single' : 'multiple';
+ function assignValue(object, key, value) {
+ var objValue = object[key];
- var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
- return n.loc;
- });
+ 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`.
+ */
- 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()));
- }
+ function assocIndexOf(array, key) {
+ var length = array.length;
- return actionCircularize(entityID, context.projection);
- }
+ while (length--) {
+ if (eq(array[length][0], key)) {
+ return length;
+ }
+ }
- var operation = function operation() {
- if (!_actions.length) return;
+ 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 combinedAction = function combinedAction(graph, t) {
- _actions.forEach(function (action) {
- if (!action.disabled(graph)) {
- graph = action(graph, t);
- }
- });
- return graph;
- };
+ 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`.
+ */
- 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
+ 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`.
+ */
- operation.disabled = function () {
- if (!_actions.length) return '';
+ 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.
+ */
- var actionDisableds = _actions.map(function (action) {
- return action.disabled(context.graph());
- }).filter(Boolean);
- if (actionDisableds.length === _actions.length) {
- // none of the features can be circularized
- if (new Set(actionDisableds).size > 1) {
- return 'multiple_blockers';
+ 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 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 baseAt(object, paths) {
+ var index = -1,
+ length = paths.length,
+ result = Array(length),
+ skip = object == null;
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ while (++index < length) {
+ result[index] = skip ? undefined$1 : get(object, paths[index]);
+ }
- if (osm) {
- var missing = _coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ 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.
+ */
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+
+ function baseClamp(number, lower, upper) {
+ if (number === number) {
+ if (upper !== undefined$1) {
+ number = number <= upper ? number : upper;
+ }
+
+ if (lower !== undefined$1) {
+ number = number >= lower ? number : lower;
+ }
}
+
+ 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 false;
- }
- };
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.circularize.' + disable + '.' + _amount) : _t('operations.circularize.description.' + _amount);
- };
+ 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;
- operation.annotation = function () {
- return _t('operations.circularize.annotation.feature', {
- n: _actions.length
- });
- };
+ if (customizer) {
+ result = object ? customizer(value, key, object, stack) : customizer(value);
+ }
- operation.id = 'circularize';
- operation.keys = [_t('operations.circularize.key')];
- operation.title = _t('operations.circularize.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
+ if (result !== undefined$1) {
+ return result;
+ }
- // For example, ⌘Z -> Ctrl+Z
+ if (!isObject(value)) {
+ return value;
+ }
- var uiCmd = function uiCmd(code) {
- var detected = utilDetect();
+ var isArr = isArray(value);
- if (detected.os === 'mac') {
- return code;
- }
+ if (isArr) {
+ result = initCloneArray(value);
- if (detected.os === 'win') {
- if (code === '⌘⇧Z') return 'Ctrl+Y';
- }
+ if (!isDeep) {
+ return copyArray(value, result);
+ }
+ } else {
+ var tag = getTag(value),
+ isFunc = tag == funcTag || tag == genTag;
- var result = '',
- replacements = {
- '⌘': 'Ctrl',
- '⇧': 'Shift',
- '⌥': 'Alt',
- '⌫': 'Backspace',
- '⌦': 'Delete'
- };
+ if (isBuffer(value)) {
+ return cloneBuffer(value, isDeep);
+ }
- 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];
- }
- }
+ if (tag == objectTag || tag == argsTag || isFunc && !object) {
+ result = isFlat || isFunc ? {} : initCloneObject(value);
- return result;
- }; // return a display-focused string for a given keyboard code
+ if (!isDeep) {
+ return isFlat ? copySymbolsIn(value, baseAssignIn(result, value)) : copySymbols(value, baseAssign(result, value));
+ }
+ } else {
+ if (!cloneableTags[tag]) {
+ return object ? value : {};
+ }
- 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;
- };
+ result = initCloneByTag(value, tag, isDeep);
+ }
+ } // Check for circular references and return its corresponding clone.
- 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;
+ stack || (stack = new Stack());
+ var stacked = stack.get(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 (stacked) {
+ return stacked;
+ }
- if (geometry === 'vertex') {
- var nodes = parent.nodes;
- var i = nodes.indexOf(id);
+ stack.set(value, result);
- 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;
+ 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));
+ });
}
- nextSelectedID = nodes[i];
- nextSelectedLoc = context.entity(nextSelectedID).loc;
+ 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).
+
+
+ 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.
+ */
- 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));
+ function baseConforms(source) {
+ var props = keys(source);
+ return function (object) {
+ return baseConformsTo(object, source, props);
+ };
}
- } else {
- context.enter(modeBrowse(context));
- }
- };
+ /**
+ * 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 true;
- };
- 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';
- }
+ 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 hasWikidataTag(id) {
- var entity = context.entity(id);
- return entity.tags.wikidata && entity.tags.wikidata.trim().length > 0;
- }
+ function baseDelay(func, wait, args) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
+ 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.
+ */
- function protectedMember(id) {
- var entity = context.entity(id);
- if (entity.type !== 'way') return false;
- var parents = context.graph().parentRelations(entity);
- for (var i = 0; i < parents.length; i++) {
- var parent = parents[i];
- var type = parent.tags.type;
- var role = parent.memberById(id).role || 'outer';
+ function baseDifference(array, values, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ isCommon = true,
+ length = array.length,
+ result = [],
+ valuesLength = values.length;
- if (type === 'route' || type === 'boundary' || type === 'multipolygon' && role === 'outer') {
- return true;
+ if (!length) {
+ return result;
}
- }
- return false;
- }
- };
+ if (iteratee) {
+ values = arrayMap(values, baseUnary(iteratee));
+ }
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.delete.' + disable + '.' + multi) : _t('operations.delete.description.' + multi);
- };
+ if (comparator) {
+ includes = arrayIncludesWith;
+ isCommon = false;
+ } else if (values.length >= LARGE_ARRAY_SIZE) {
+ includes = cacheHas;
+ isCommon = false;
+ values = new SetCache(values);
+ }
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.delete.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.delete.annotation.feature', {
- n: selectedIDs.length
- });
- };
+ outer: while (++index < length) {
+ var value = array[index],
+ computed = iteratee == null ? value : iteratee(value);
+ value = comparator || value !== 0 ? value : 0;
- operation.id = 'delete';
- operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦'), uiCmd('⌦')];
- operation.title = _t('operations.delete.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
+ if (isCommon && computed === computed) {
+ var valuesIndex = valuesLength;
- function operationOrthogonalize(context, selectedIDs) {
- var _extent;
+ while (valuesIndex--) {
+ if (values[valuesIndex] === computed) {
+ continue outer;
+ }
+ }
- var _type;
+ result.push(value);
+ } else if (!includes(values, computed, comparator)) {
+ result.push(value);
+ }
+ }
- var _actions = selectedIDs.map(chooseAction).filter(Boolean);
+ 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`.
+ */
- var _amount = _actions.length === 1 ? 'single' : 'multiple';
- var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
- return n.loc;
- });
+ 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`.
+ */
- function chooseAction(entityID) {
- var entity = context.entity(entityID);
- var geometry = entity.geometry(context.graph());
+ 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`
+ */
- if (!_extent) {
- _extent = entity.extent(context.graph());
- } else {
- _extent = _extent.extend(entity.extent(context.graph()));
- } // square a line/area
+ 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 (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 baseExtremum(array, iteratee, comparator) {
+ var index = -1,
+ length = array.length;
- if (parents.length === 1) {
- var way = parents[0];
+ while (++index < length) {
+ var value = array[index],
+ current = iteratee(value);
- if (way.nodes.indexOf(entityID) !== -1) {
- return actionOrthogonalize(way.id, context.projection, entityID);
+ if (current != null && (computed === undefined$1 ? current === current && !isSymbol(current) : comparator(current, computed))) {
+ var computed = current,
+ result = value;
+ }
}
+
+ 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 null;
- }
- var operation = function operation() {
- if (!_actions.length) return;
+ function baseFill(array, value, start, end) {
+ var length = array.length;
+ start = toInteger(start);
- var combinedAction = function combinedAction(graph, t) {
- _actions.forEach(function (action) {
- if (!action.disabled(graph)) {
- graph = action(graph, t);
+ if (start < 0) {
+ start = -start > length ? 0 : length + start;
}
- });
- return graph;
- };
+ end = end === undefined$1 || end > length ? length : toInteger(end);
- combinedAction.transitionable = true;
- context.perform(combinedAction, operation.annotation());
- window.setTimeout(function () {
- context.validator().validate();
- }, 300); // after any transition
- };
+ if (end < 0) {
+ end += length;
+ }
- operation.available = function () {
- return _actions.length && selectedIDs.length === _actions.length;
- }; // don't cache this because the visible extent could change
+ end = start > end ? 0 : toLength(end);
+ while (start < end) {
+ array[start++] = value;
+ }
- operation.disabled = function () {
- if (!_actions.length) return '';
+ 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.
+ */
- var actionDisableds = _actions.map(function (action) {
- return action.disabled(context.graph());
- }).filter(Boolean);
- if (actionDisableds.length === _actions.length) {
- // none of the features can be squared
- if (new Set(actionDisableds).size > 1) {
- return 'multiple_blockers';
+ 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.
+ */
- 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 baseFlatten(array, depth, predicate, isStrict, result) {
+ var index = -1,
+ length = array.length;
+ predicate || (predicate = isFlattenable);
+ result || (result = []);
- if (osm) {
- var missing = _coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ while (++index < length) {
+ var value = array[index];
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ 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 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`.
+ */
- return false;
- }
- };
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.orthogonalize.' + disable + '.' + _amount) : _t('operations.orthogonalize.description.' + _type + '.' + _amount);
- };
+ 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`.
+ */
- operation.annotation = function () {
- return _t('operations.orthogonalize.annotation.' + _type, {
- n: _actions.length
- });
- };
-
- operation.id = 'orthogonalize';
- operation.keys = [_t('operations.orthogonalize.key')];
- operation.title = _t('operations.orthogonalize.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
-
- 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());
+ 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`.
+ */
- 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
- };
+ 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`.
+ */
- operation.available = function () {
- return nodes.length >= 3;
- }; // don't cache this because the visible extent could change
+ 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.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;
+ 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.
+ */
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ function baseGet(object, path) {
+ path = castPath(path, object);
+ var index = 0,
+ length = path.length;
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ while (object != null && index < length) {
+ object = object[toKey(path[index++])];
}
+
+ 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.
+ */
- return false;
- }
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
+ 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`.
+ */
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.reflect.' + disable + '.' + multi) : _t('operations.reflect.description.' + axis + '.' + multi);
- };
- operation.annotation = function () {
- return _t('operations.reflect.annotation.' + axis + '.feature', {
- n: selectedIDs.length
- });
- };
+ function baseGetTag(value) {
+ if (value == null) {
+ return value === undefined$1 ? undefinedTag : nullTag;
+ }
- 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;
- }
+ 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 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 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`.
+ */
- 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 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`.
+ */
- return false;
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ 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 (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
- }
+ 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.
+ */
- return false;
- }
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
+ 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 = [];
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.move.' + disable + '.' + multi) : _t('operations.move.description.' + multi);
- };
+ while (othIndex--) {
+ var array = arrays[othIndex];
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.move.annotation.feature', {
- n: selectedIDs.length
- });
- };
+ if (othIndex && iteratee) {
+ array = arrayMap(array, baseUnary(iteratee));
+ }
- 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;
- }
+ maxLength = nativeMin(array.length, maxLength);
+ caches[othIndex] = !comparator && (iteratee || length >= 120 && array.length >= 120) ? new SetCache(othIndex && array) : undefined$1;
+ }
- function modeRotate(context, entityIDs) {
- var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeMove
+ array = arrays[0];
+ var index = -1,
+ seen = caches[0];
- 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
- });
+ outer: while (++index < length && result.length < maxLength) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+ value = comparator || value !== 0 ? value : 0;
- var _prevGraph;
+ if (!(seen ? cacheHas(seen, computed) : includes(result, computed, comparator))) {
+ othIndex = othLength;
- var _prevAngle;
+ while (--othIndex) {
+ var cache = caches[othIndex];
- var _prevTransform;
+ if (!(cache ? cacheHas(cache, computed) : includes(arrays[othIndex], computed, comparator))) {
+ continue outer;
+ }
+ }
- var _pivot; // use pointer events on supported platforms; fallback to mouse events
+ if (seen) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ 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`.
+ */
- function doRotate(d3_event) {
- var fn;
- if (context.graph() !== _prevGraph) {
- fn = context.perform;
- } else {
- fn = context.replace;
- } // projection changed, recalculate _pivot
+ 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 projection = context.projection;
- var currTransform = projection.transform();
+ 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);
+ }
+ /**
+ * The base implementation of `_.isArguments`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ */
- 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 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 getPivot(points) {
- var _pivot;
- 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);
+ 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`.
+ */
- if (polygonHull.length === 2) {
- _pivot = geoVecInterp(points[0], points[1], 0.5);
- } else {
- _pivot = d3_polygonCentroid(d3_polygonHull(points));
+
+ 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`.
+ */
- return _pivot;
- }
- function finish(d3_event) {
- d3_event.stopPropagation();
- context.replace(actionNoop(), annotation);
- context.enter(modeSelect(context, entityIDs));
- }
+ function baseIsEqual(value, other, bitmask, customizer, stack) {
+ if (value === other) {
+ return true;
+ }
- function cancel() {
- if (_prevGraph) context.pop(); // remove the rotate
+ if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) {
+ return value !== value && other !== other;
+ }
- context.enter(modeSelect(context, entityIDs));
- }
+ 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`.
+ */
- function undone() {
- context.enter(modeBrowse(context));
- }
- 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);
- };
+ 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;
- 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([]);
- };
+ if (isSameTag && isBuffer(object)) {
+ if (!isBuffer(other)) {
+ return false;
+ }
- mode.selectedIDs = function () {
- if (!arguments.length) return entityIDs; // no assign
+ objIsArr = true;
+ objIsObj = false;
+ }
- return mode;
- };
+ 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);
+ }
- return mode;
- }
+ if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
+ var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+ othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
- 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());
+ 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 operation = function operation() {
- context.enter(modeRotate(context, selectedIDs));
- };
+ if (!isSameTag) {
+ return false;
+ }
- operation.available = function () {
- return nodes.length >= 2;
- };
+ 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`.
+ */
- 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;
+ 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`.
+ */
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ function baseIsMatch(object, source, matchData, customizer) {
+ var index = matchData.length,
+ length = index,
+ noCustomizer = !customizer;
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ if (object == null) {
+ return !length;
}
- }
- return false;
- }
+ object = Object(object);
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
+ while (index--) {
+ var data = matchData[index];
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.rotate.' + disable + '.' + multi) : _t('operations.rotate.description.' + multi);
- };
+ if (noCustomizer && data[2] ? data[1] !== object[data[0]] : !(data[0] in object)) {
+ return false;
+ }
+ }
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.rotate.annotation.feature', {
- n: selectedIDs.length
- });
- };
+ while (++index < length) {
+ data = matchData[index];
+ var key = data[0],
+ objValue = object[key],
+ srcValue = data[1];
- 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;
- }
+ if (noCustomizer && data[2]) {
+ if (objValue === undefined$1 && !(key in object)) {
+ return false;
+ }
+ } else {
+ var stack = new Stack();
- function modeMove(context, entityIDs, baseGraph) {
- var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeRotate
+ if (customizer) {
+ var result = customizer(objValue, srcValue, key, object, source, stack);
+ }
- 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
- });
+ if (!(result === undefined$1 ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) : result)) {
+ return false;
+ }
+ }
+ }
- var _prevGraph;
+ 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`.
+ */
- var _cache;
- var _origin;
+ function baseIsNative(value) {
+ if (!isObject(value) || isMasked(value)) {
+ return false;
+ }
- var _nudgeInterval; // use pointer events on supported platforms; fallback to mouse events
+ 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`.
+ */
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ 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`.
+ */
- 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 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`.
+ */
- 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 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.
+ */
- function stopNudge() {
- if (_nudgeInterval) {
- window.clearInterval(_nudgeInterval);
- _nudgeInterval = null;
- }
- }
- function move() {
- doMove();
- var nudge = geoViewportEdge(context.map().mouse(), context.map().dimensions());
+ 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;
+ }
- if (nudge) {
- startNudge(nudge);
- } else {
- stopNudge();
- }
- }
+ if (value == null) {
+ return identity;
+ }
- function finish(d3_event) {
- d3_event.stopPropagation();
- context.replace(actionNoop(), annotation);
- context.enter(modeSelect(context, entityIDs));
- stopNudge();
- }
+ if (_typeof(value) == 'object') {
+ return isArray(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value);
+ }
- function cancel() {
- if (baseGraph) {
- while (context.graph() !== baseGraph) {
- context.pop();
- } // reset to baseGraph
+ 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.
+ */
- context.enter(modeBrowse(context));
- } else {
- if (_prevGraph) context.pop(); // remove the move
-
- context.enter(modeSelect(context, entityIDs));
- }
-
- stopNudge();
- }
+ function baseKeys(object) {
+ if (!isPrototype(object)) {
+ return nativeKeys(object);
+ }
- function undone() {
- context.enter(modeBrowse(context));
- }
+ var result = [];
- 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);
- };
+ for (var key in Object(object)) {
+ if (hasOwnProperty.call(object, key) && key != 'constructor') {
+ result.push(key);
+ }
+ }
- 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([]);
- };
+ 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.
+ */
- mode.selectedIDs = function () {
- if (!arguments.length) return entityIDs; // no assign
- return mode;
- };
+ function baseKeysIn(object) {
+ if (!isObject(object)) {
+ return nativeKeysIn(object);
+ }
- return mode;
- }
+ var isProto = isPrototype(object),
+ result = [];
- 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);
- });
+ for (var key in object) {
+ if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
+ }
- for (var id in copies) {
- var oldEntity = oldGraph.entity(id);
- var newEntity = copies[id];
+ 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`.
+ */
- extent._extend(oldEntity.extent(oldGraph)); // Exclude child nodes from newIDs if their parent way was also copied.
+ 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.
+ */
- var parents = context.graph().parentWays(newEntity);
- var parentCopied = parents.some(function (parent) {
- return originals.has(parent.id);
- });
- if (!parentCopied) {
- newIDs.push(newEntity.id);
+ 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;
}
- } // Put pasted objects where mouse pointer is..
+ /**
+ * 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.
+ */
- 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 baseMatches(source) {
+ var matchData = getMatchData(source);
- function behavior() {
- context.keybinding().on(uiCmd('⌘V'), doPaste);
- return behavior;
- }
+ if (matchData.length == 1 && matchData[0][2]) {
+ return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+ }
- behavior.off = function () {
- context.keybinding().off(uiCmd('⌘V'));
- };
+ 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.
+ */
- return behavior;
- }
- var $$5 = _export;
- var repeat = stringRepeat;
+ function baseMatchesProperty(path, srcValue) {
+ if (isKey(path) && isStrictComparable(srcValue)) {
+ return matchesStrictComparable(toKey(path), srcValue);
+ }
- // `String.prototype.repeat` method
- // https://tc39.es/ecma262/#sec-string.prototype.repeat
- $$5({ target: 'String', proto: true }, {
- repeat: repeat
- });
+ 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.
+ */
- /*
- `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 baseMerge(object, source, srcIndex, customizer, stack) {
+ if (object === source) {
+ return;
+ }
- function behaviorDrag() {
- var dispatch = dispatch$8('start', 'move', 'end'); // see also behaviorSelect
+ baseFor(source, function (srcValue, key) {
+ stack || (stack = new Stack());
- var _tolerancePx = 1; // keep this low to facilitate pixel-perfect micromapping
+ 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;
- var _penTolerancePx = 4; // styluses can be touchy so require greater movement - #1981
+ if (newValue === undefined$1) {
+ newValue = srcValue;
+ }
- var _origin = null;
- var _selector = '';
+ 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.
+ */
- var _targetNode;
- var _targetEntity;
+ function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+ var objValue = safeGet(object, key),
+ srcValue = safeGet(source, key),
+ stacked = stack.get(srcValue);
- var _surface;
+ if (stacked) {
+ assignMergeValue(object, key, stacked);
+ return;
+ }
- var _pointerId; // use pointer events on supported platforms; fallback to mouse events
+ 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;
+ }
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ 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);
+ }
- var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect');
+ 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`.
+ */
- 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 baseNth(array, n) {
+ var length = array.length;
- 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 (!length) {
+ return;
+ }
- if (_origin) {
- offset = _origin.call(_targetNode, _targetEntity);
- offset = [offset[0] - startOrigin[0], offset[1] - startOrigin[1]];
- } else {
- offset = [0, 0];
- }
+ 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.
+ */
- d3_event.stopPropagation();
- function pointermove(d3_event) {
- if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
- var p = pointerLocGetter(d3_event);
+ 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 (!started) {
- var dist = geoVecLength(startOrigin, p);
- var tolerance = d3_event.pointerType === 'pen' ? _penTolerancePx : _tolerancePx; // don't start until the drag has actually moved somewhat
+ return iteratee;
+ });
+ } else {
+ iteratees = [identity];
+ }
- 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]);
+ 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.
+ */
- function pointerup(d3_event) {
- if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
- _pointerId = null;
- if (started) {
- dispatch.call('end', this, d3_event, _targetEntity);
- d3_event.preventDefault();
+ 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.
+ */
- select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
- selectEnable();
- }
- }
-
- function behavior(selection) {
- var matchesSelector = utilPrefixDOMProperty('matchesSelector');
- var delegate = pointerdown;
- if (_selector) {
- delegate = function delegate(d3_event) {
- var root = this;
- var target = d3_event.target;
+ function basePickBy(object, paths, predicate) {
+ var index = -1,
+ length = paths.length,
+ result = {};
- for (; target && target !== root; target = target.parentNode) {
- var datum = target.__data__;
- _targetEntity = datum instanceof osmNote ? datum : datum && datum.properties && datum.properties.entity;
+ while (++index < length) {
+ var path = paths[index],
+ value = baseGet(object, path);
- if (_targetEntity && target[matchesSelector](_selector)) {
- return pointerdown.call(target, d3_event);
+ if (predicate(value, path)) {
+ baseSet(result, castPath(path, object), value);
}
}
- };
- }
- selection.on(_pointerPrefix + 'down.drag' + _selector, delegate);
- }
+ 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.
+ */
- behavior.off = function (selection) {
- selection.on(_pointerPrefix + 'down.drag' + _selector, null);
- };
- behavior.selector = function (_) {
- if (!arguments.length) return _selector;
- _selector = _;
- return behavior;
- };
+ 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`.
+ */
- 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 basePullAll(array, values, iteratee, comparator) {
+ var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+ index = -1,
+ length = values.length,
+ seen = array;
- behavior.targetNode = function (_) {
- if (!arguments.length) return _targetNode;
- _targetNode = _;
- return behavior;
- };
+ if (array === values) {
+ values = copyArray(values);
+ }
- behavior.targetEntity = function (_) {
- if (!arguments.length) return _targetEntity;
- _targetEntity = _;
- return behavior;
- };
+ if (iteratee) {
+ seen = arrayMap(array, baseUnary(iteratee));
+ }
- behavior.surface = function (_) {
- if (!arguments.length) return _surface;
- _surface = _;
- return behavior;
- };
+ while (++index < length) {
+ var fromIndex = 0,
+ value = values[index],
+ computed = iteratee ? iteratee(value) : value;
- return utilRebind(behavior, dispatch, 'on');
- }
+ while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+ if (seen !== array) {
+ splice.call(seen, fromIndex, 1);
+ }
- 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);
+ splice.call(array, fromIndex, 1);
+ }
+ }
- var _nudgeInterval;
+ 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`.
+ */
- var _restoreSelectedIDs = [];
- var _wasMidpoint = false;
- var _isCancelled = false;
- var _activeEntity;
+ function basePullAt(array, indexes) {
+ var length = array ? indexes.length : 0,
+ lastIndex = length - 1;
- var _startLoc;
+ while (length--) {
+ var index = indexes[length];
- var _lastLoc;
+ if (length == lastIndex || index !== previous) {
+ var previous = index;
- 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);
- }
+ if (isIndex(index)) {
+ splice.call(array, index, 1);
+ } else {
+ baseUnset(array, index);
+ }
+ }
+ }
- function stopNudge() {
- if (_nudgeInterval) {
- window.clearInterval(_nudgeInterval);
- _nudgeInterval = null;
- }
- }
+ 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.
+ */
- 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 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.
+ */
- 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
- 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');
+ function baseRange(start, end, step, fromRight) {
+ var index = -1,
+ length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+ result = Array(length);
+
+ while (length--) {
+ result[fromRight ? length : ++index] = start;
+ start += step;
}
- return _t('operations.connect.annotation.from_vertex.to_sibling_vertex');
+ 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.
+ */
- 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 baseRepeat(string, n) {
+ var result = '';
- function origin(entity) {
- return context.projection(entity.loc);
- }
+ 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 keydown(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope')) {
- context.surface().classed('nope-suppressed', true);
- }
- context.surface().classed('nope', false).classed('nope-disabled', true);
- }
- }
+ do {
+ if (n % 2) {
+ result += string;
+ }
- function keyup(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope-suppressed')) {
- context.surface().classed('nope', true);
+ n = nativeFloor(n / 2);
+
+ if (n) {
+ string += string;
+ }
+ } while (n);
+
+ 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.
+ */
- 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 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.
+ */
- if (_isCancelled) {
- if (hasHidden) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('modes.drag_node.connected_to_hidden'))();
+
+ 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.
+ */
- 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 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`.
+ */
- var vertex = context.surface().selectAll('.' + entity.id);
- drag.targetNode(vertex.node()).targetEntity(entity);
- } else {
- context.perform(actionNoop());
- }
- _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()`
+ function baseSet(object, path, value, customizer) {
+ if (!isObject(object)) {
+ return object;
+ }
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length,
+ lastIndex = length - 1,
+ nested = object;
- 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 : {};
- }
- }
+ while (nested != null && ++index < length) {
+ var key = toKey(path[index]),
+ newValue = value;
- 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 (key === '__proto__' || key === 'constructor' || key === 'prototype') {
+ return object;
+ }
- 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;
+ if (index != lastIndex) {
+ var objValue = nested[key];
+ newValue = customizer ? customizer(objValue, key, nested) : undefined$1;
- if (targetLoc) {
- // snap to node/vertex - a point target with `.loc`
- if (shouldSnapToNode(target)) {
- loc = targetLoc;
- }
- } else if (targetNodes) {
- // snap to way - a line target with `.nodes`
- edge = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, end.id);
+ if (newValue === undefined$1) {
+ newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
+ }
+ }
- if (edge) {
- loc = edge.loc;
+ assignValue(nested, key, newValue);
+ nested = nested[key];
}
- }
- }
-
- context.replace(actionMoveNode(entity.id, loc)); // Below here: validations
- var isInvalid = false; // Check if this connection to `target` could cause relations to break..
-
- if (target) {
- isInvalid = hasRelationConflict(entity, target, edge, context.graph());
- } // Check if this drag causes the geometry to break..
+ 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`.
+ */
- if (!isInvalid) {
- isInvalid = hasInvalidGeometry(entity, context.graph());
- }
+ 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 nope = context.surface().classed('nope');
+ 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.
+ */
- 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('')();
+ function baseShuffle(collection) {
+ return shuffleSelf(values(collection));
}
- }
-
- var nopeDisabled = context.surface().classed('nope-disabled');
+ /**
+ * 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`.
+ */
- if (nopeDisabled) {
- context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
- } else {
- context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
- }
- _lastLoc = loc;
- } // Uses `actionConnect.disabled()` to know whether this connection is ok..
+ function baseSlice(array, start, end) {
+ var index = -1,
+ length = array.length;
+ if (start < 0) {
+ start = -start > length ? 0 : length + start;
+ }
- function hasRelationConflict(entity, target, edge, graph) {
- var testGraph = graph.update(); // copy
- // if snapping to way - add midpoint there and consider that the target..
+ end = end > length ? length : end;
- 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 (end < 0) {
+ end += length;
+ }
+ length = start > end ? 0 : end - start >>> 0;
+ start >>>= 0;
+ var result = Array(length);
- var ids = [entity.id, target.id];
- return actionConnect(ids).disabled(testGraph);
- }
+ while (++index < length) {
+ result[index] = array[index + start];
+ }
- function hasInvalidGeometry(entity, graph) {
- var parents = graph.parentWays(entity);
- var i, j, k;
+ 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`.
+ */
- 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 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`.
+ */
- 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
- for (k = 0; k < rings.length; k++) {
- nodes = rings[k].nodes;
+ function baseSortedIndex(array, value, retHighest) {
+ var low = 0,
+ high = array == null ? low : array.length;
- if (nodes.find(function (n) {
- return n.id === entity.id;
- })) {
- activeIndex = k;
+ if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+ while (low < high) {
+ var mid = low + high >>> 1,
+ computed = array[mid];
- if (geoHasSelfIntersections(nodes, entity.id)) {
- return 'multipolygonMember';
+ if (computed !== null && !isSymbol(computed) && (retHighest ? computed <= value : computed < value)) {
+ low = mid + 1;
+ } else {
+ high = mid;
}
}
- rings[k].coords = nodes.map(function (n) {
- return n.loc;
- });
- } // test active ring for intersections with other rings in the multipolygon
+ return high;
+ }
+
+ 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`.
+ */
- for (k = 0; k < rings.length; k++) {
- if (k === activeIndex) continue; // make sure active ring doesn't cross passive rings
+ function baseSortedIndexBy(array, value, iteratee, retHighest) {
+ var low = 0,
+ high = array == null ? 0 : array.length;
- if (geoHasLineIntersections(rings[activeIndex].nodes, rings[k].nodes, entity.id)) {
- return 'multipolygonRing';
+ 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;
}
}
- } // 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 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.
+ */
- 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 baseSortedUniq(array, iteratee) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
+
+ 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;
+ }
}
+
+ 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;
- }
- 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 baseToNumber(value) {
+ if (typeof value == 'number') {
+ return value;
+ }
- if (nudge) {
- startNudge(d3_event, entity, nudge);
- } else {
- stopNudge();
- }
- }
+ if (isSymbol(value)) {
+ return NAN;
+ }
- 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 +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.
+ */
- 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 baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
- if (reselection.length) {
- context.enter(modeSelect(context, reselection));
- } else {
- context.enter(modeBrowse(context));
- }
- }
- }
+ if (isArray(value)) {
+ // Recursively convert values (susceptible to call stack limits).
+ return arrayMap(value, baseToString) + '';
+ }
- function _actionBounceBack(nodeID, toLoc) {
- var moveNode = actionMoveNode(nodeID, toLoc);
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
+ }
- 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);
- };
+ 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.
+ */
- action.transitionable = true;
- return action;
- }
- function cancel() {
- drag.cancel();
- context.enter(modeBrowse(context));
- }
+ function baseUniq(array, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ length = array.length,
+ isCommon = true,
+ result = [],
+ seen = result;
- 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);
+ if (comparator) {
+ isCommon = false;
+ includes = arrayIncludesWith;
+ } else if (length >= LARGE_ARRAY_SIZE) {
+ var set = iteratee ? null : createSet(array);
- 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);
- };
+ if (set) {
+ return setToArray(set);
+ }
- 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();
- };
+ isCommon = false;
+ includes = cacheHas;
+ seen = new SetCache();
+ } else {
+ seen = iteratee ? [] : result;
+ }
- mode.selectedIDs = function () {
- if (!arguments.length) return _activeEntity ? [_activeEntity.id] : []; // no assign
+ outer: while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+ value = comparator || value !== 0 ? value : 0;
- return mode;
- };
+ if (isCommon && computed === computed) {
+ var seenIndex = seen.length;
- mode.activeID = function () {
- if (!arguments.length) return _activeEntity && _activeEntity.id; // no assign
+ while (seenIndex--) {
+ if (seen[seenIndex] === computed) {
+ continue outer;
+ }
+ }
- return mode;
- };
+ if (iteratee) {
+ seen.push(computed);
+ }
- mode.restoreSelectedIDs = function (_) {
- if (!arguments.length) return _restoreSelectedIDs;
- _restoreSelectedIDs = _;
- return mode;
- };
+ result.push(value);
+ } else if (!includes(seen, computed, comparator)) {
+ if (seen !== result) {
+ seen.push(computed);
+ }
- mode.behavior = drag;
- return mode;
- }
+ result.push(value);
+ }
+ }
- var $$4 = _export;
- var NativePromise = nativePromiseConstructor;
- var fails$1 = fails$N;
- var getBuiltIn = getBuiltIn$9;
- var speciesConstructor = speciesConstructor$8;
- var promiseResolve = promiseResolve$2;
- var redefine = redefine$g.exports;
+ 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`.
+ */
- // Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829
- var NON_GENERIC = !!NativePromise && fails$1(function () {
- NativePromise.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ });
- });
- // `Promise.prototype.finally` method
- // https://tc39.es/ecma262/#sec-promise.prototype.finally
- $$4({ 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
- );
- }
- });
+ 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`.
+ */
- // makes sure that native promise-based APIs `Promise#finally` properly works with patched `Promise#then`
- if (typeof NativePromise == 'function') {
- var method = getBuiltIn('Promise').prototype['finally'];
- if (NativePromise.prototype['finally'] !== method) {
- redefine(NativePromise.prototype, 'finally', method, { unsafe: true });
- }
- }
- function quickselect(arr, k, left, right, compare) {
- quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare);
- }
+ 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`.
+ */
- 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);
- }
- var t = arr[k];
- var i = left;
- var j = right;
- swap(arr, left, k);
- if (compare(arr[right], t) > 0) swap(arr, left, right);
+ function baseWhile(array, predicate, isDrop, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
- while (i < j) {
- swap(arr, i, j);
- i++;
- j--;
+ while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
- while (compare(arr[i], t) < 0) {
- i++;
+ 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.
+ */
- while (compare(arr[j], t) > 0) {
- j--;
- }
- }
- 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 baseWrapperValue(value, actions) {
+ var result = value;
- function swap(arr, i, j) {
- var tmp = arr[i];
- arr[i] = arr[j];
- arr[j] = tmp;
- }
+ if (result instanceof LazyWrapper) {
+ result = result.value();
+ }
- function defaultCompare(a, b) {
- return a < b ? -1 : a > b ? 1 : 0;
- }
+ 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.
+ */
- var RBush = /*#__PURE__*/function () {
- function RBush() {
- var maxEntries = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 9;
- _classCallCheck$1(this, RBush);
+ function baseXor(arrays, iteratee, comparator) {
+ var length = arrays.length;
- // 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 (length < 2) {
+ return length ? baseUniq(arrays[0]) : [];
+ }
- _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 = [];
+ var index = -1,
+ result = Array(length);
- while (node) {
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var childBBox = node.leaf ? toBBox(child) : child;
+ while (++index < length) {
+ var array = arrays[index],
+ othIndex = -1;
- if (intersects(bbox, childBBox)) {
- if (node.leaf) result.push(child);else if (contains(bbox, childBBox)) this._all(child, result);else nodesToSearch.push(child);
+ while (++othIndex < length) {
+ if (othIndex != index) {
+ result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
+ }
}
}
- node = nodesToSearch.pop();
+ 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 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 baseZipObject(props, values, assignFunc) {
+ var index = -1,
+ length = props.length,
+ valsLength = values.length,
+ result = {};
- if (intersects(bbox, childBBox)) {
- if (node.leaf || contains(bbox, childBBox)) return true;
- nodesToSearch.push(child);
- }
+ while (++index < length) {
+ var value = index < valsLength ? values[index] : undefined$1;
+ assignFunc(result, props[index], value);
}
- node = nodesToSearch.pop();
+ 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.
+ */
- 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]);
- }
- return this;
- } // recursively build the tree with the given data from scratch using OMT algorithm
+ 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.
+ */
- var node = this._build(data.slice(), 0, data.length - 1, 0);
+ 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.
+ */
- 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
+ function castPath(value, object) {
+ if (isArray(value)) {
+ return value;
+ }
- this._insert(node, this.data.height - node.height - 1, true);
+ 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.
+ */
- 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;
- }
+ 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 (node.leaf) {
- // check current node
- var index = findItem(item, node.children, equalsFn);
+ 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.
+ */
- if (index !== -1) {
- // item found, remove the item and condense tree upwards
- node.children.splice(index, 1);
- path.push(node);
- this._condense(path);
+ 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.
+ */
- return this;
- }
+
+ function cloneBuffer(buffer, isDeep) {
+ if (isDeep) {
+ return buffer.slice();
}
- 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
+ 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.
+ */
+
+ 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.
+ */
- 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 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 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 cloneRegExp(regexp) {
+ var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+ result.lastIndex = regexp.lastIndex;
+ return result;
}
+ /**
+ * Creates a clone of the `symbol` object.
+ *
+ * @private
+ * @param {Object} symbol The symbol object to clone.
+ * @returns {Object} Returns the cloned symbol object.
+ */
- 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 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.
+ */
- node = createNode([]);
- node.leaf = false;
- node.height = height; // split the items into M mostly square tiles
- var N2 = Math.ceil(N / M);
- var N1 = N2 * Math.ceil(Math.sqrt(M));
- multiSelect(items, left, right, N1, this.compareMinX);
+ 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 = 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 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);
- node.children.push(this._build(items, j, right3, height - 1));
+ if (!othIsNull && !othIsSymbol && !valIsSymbol && value > other || valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol || valIsNull && othIsDefined && othIsReflexive || !valIsDefined && othIsReflexive || !valIsReflexive) {
+ return 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`.
+ */
- 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 compareMultiple(object, other, orders) {
+ var index = -1,
+ objCriteria = object.criteria,
+ othCriteria = other.criteria,
+ length = objCriteria.length,
+ ordersLength = orders.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;
+ while (++index < length) {
+ var result = compareAscending(objCriteria[index], othCriteria[index]);
+
+ if (result) {
+ if (index >= ordersLength) {
+ return result;
}
- }
- }
- node = targetNode || node.children[0];
- }
+ 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.
- 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
+ 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.
+ */
- node.children.push(item);
- extend$1(node, bbox); // split on node overflow; propagate upwards if necessary
+ 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;
- while (level >= 0) {
- if (insertPath[level].children.length > this._maxEntries) {
- this._split(insertPath, level);
+ while (++leftIndex < leftLength) {
+ result[leftIndex] = partials[leftIndex];
+ }
- level--;
- } else break;
- } // adjust bboxes along the insertion path
+ while (++argsIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[holders[argsIndex]] = args[argsIndex];
+ }
+ }
+ while (rangeLength--) {
+ result[leftIndex++] = args[argsIndex++];
+ }
- this._adjustParentBBoxes(bbox, insertPath, level);
- } // split overflowed node into two
+ 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.
+ */
- }, {
- 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 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;
- var splitIndex = this._chooseSplitIndex(node, m, M);
+ while (++argsIndex < rangeLength) {
+ result[argsIndex] = args[argsIndex];
+ }
- 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;
+ var offset = argsIndex;
- 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
+ while (++rightIndex < rightLength) {
+ result[offset + rightIndex] = partials[rightIndex];
+ }
- 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;
+ while (++holdersIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[offset + holders[holdersIndex]] = args[argsIndex++];
}
}
+
+ 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`.
+ */
- 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;
+ function copyArray(source, array) {
+ var index = -1,
+ length = source.length;
+ array || (array = Array(length));
- var xMargin = this._allDistMargin(node, m, M, compareMinX);
+ while (++index < length) {
+ array[index] = source[index];
+ }
- 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
+ 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`.
+ */
- if (xMargin < yMargin) node.children.sort(compareMinX);
- } // total margin of all possible split distributions where each node is at least m full
+ function copyObject(source, props, object, customizer) {
+ var isNew = !object;
+ object || (object = {});
+ var index = -1,
+ length = props.length;
- }, {
- 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);
+ while (++index < length) {
+ var key = props[index];
+ var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined$1;
- for (var i = m; i < M - m; i++) {
- var child = node.children[i];
- extend$1(leftBBox, node.leaf ? toBBox(child) : child);
- margin += bboxMargin(leftBBox);
- }
+ if (newValue === undefined$1) {
+ newValue = source[key];
+ }
- 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);
+ 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`.
+ */
- 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 copySymbols(source, object) {
+ return copyObject(source, getSymbols(source), object);
}
- }
- }, {
- 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);
+ /**
+ * 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 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 RBush;
- }();
- function findItem(item, items, equalsFn) {
- if (!equalsFn) return items.indexOf(item);
+ 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.
+ */
- 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 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 (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined$1 : customizer;
+ length = 1;
+ }
- function calcBBox(node, toBBox) {
- distBBox(node, 0, node.children.length, toBBox, node);
- } // min bounding rectangle of node children from k to p-1
+ object = Object(object);
+ while (++index < length) {
+ var source = sources[index];
- function distBBox(node, k, p, toBBox, destNode) {
- if (!destNode) destNode = createNode(null);
- destNode.minX = Infinity;
- destNode.minY = Infinity;
- destNode.maxX = -Infinity;
- destNode.maxY = -Infinity;
+ if (source) {
+ assigner(object, source, index, customizer);
+ }
+ }
- for (var i = k; i < p; i++) {
- var child = node.children[i];
- extend$1(destNode, node.leaf ? toBBox(child) : child);
- }
+ 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.
+ */
- return destNode;
- }
- 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;
- }
+ function createBaseEach(eachFunc, fromRight) {
+ return function (collection, iteratee) {
+ if (collection == null) {
+ return collection;
+ }
- function compareNodeMinX(a, b) {
- return a.minX - b.minX;
- }
+ if (!isArrayLike(collection)) {
+ return eachFunc(collection, iteratee);
+ }
- function compareNodeMinY(a, b) {
- return a.minY - b.minY;
- }
+ var length = collection.length,
+ index = fromRight ? length : -1,
+ iterable = Object(collection);
- function bboxArea(a) {
- return (a.maxX - a.minX) * (a.maxY - a.minY);
- }
+ while (fromRight ? index-- : ++index < length) {
+ if (iteratee(iterable[index], index, iterable) === false) {
+ break;
+ }
+ }
- function bboxMargin(a) {
- return a.maxX - a.minX + (a.maxY - a.minY);
- }
+ 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.
+ */
- 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 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);
- }
+ function createBaseFor(fromRight) {
+ return function (object, iteratee, keysFunc) {
+ var index = -1,
+ iterable = Object(object),
+ props = keysFunc(object),
+ length = props.length;
- function contains(a, b) {
- return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
- }
+ while (length--) {
+ var key = props[fromRight ? length : ++index];
- function intersects(a, b) {
- return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
- }
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
- 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 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.
+ */
- function multiSelect(arr, left, right, n, compare) {
- var stack = [left, right];
+ function createBind(func, bitmask, thisArg) {
+ var isBind = bitmask & WRAP_BIND_FLAG,
+ Ctor = createCtor(func);
- 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);
- }
- }
+ function wrapper() {
+ var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
+ return fn.apply(isBind ? thisArg : this, arguments);
+ }
- function responseText(response) {
- if (!response.ok) throw new Error(response.status + " " + response.statusText);
- return response.text();
- }
+ 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 d3_text (input, init) {
- return fetch(input, init).then(responseText);
- }
- function responseJson(response) {
- if (!response.ok) throw new Error(response.status + " " + response.statusText);
- if (response.status === 204 || response.status === 205) return;
- return response.json();
- }
+ 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 d3_json (input, init) {
- return fetch(input, init).then(responseJson);
- }
- function parser(type) {
- return function (input, init) {
- return d3_text(input, init).then(function (text) {
- return new DOMParser().parseFromString(text, type);
- });
- };
- }
+ 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.
+ */
- var d3_xml = parser("application/xml");
- var svg = parser("image/svg+xml");
- 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
+ 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;
- var _cache$2;
+ switch (args.length) {
+ case 0:
+ return new Ctor();
- 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];
+ case 1:
+ return new Ctor(args[0]);
- function abortRequest$6(controller) {
- if (controller) {
- controller.abort();
- }
- }
+ case 2:
+ return new Ctor(args[0], args[1]);
- function abortUnwantedRequests$3(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ case 3:
+ return new Ctor(args[0], args[1], args[2]);
- if (!wanted) {
- abortRequest$6(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
+ case 4:
+ return new Ctor(args[0], args[1], args[2], args[3]);
- 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
+ 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 updateRtree$3(item, replace) {
- _cache$2.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
+ case 7:
+ return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ }
- if (replace) {
- _cache$2.rtree.insert(item);
- }
- }
+ 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.
- function tokenReplacements(d) {
- if (!(d instanceof QAItem)) return;
- var htmlRegex = new RegExp(/<\/[a-z][\s\S]*>/);
- var replacements = {};
- var issueTemplate = _krData.errorTypes[d.whichType];
+ 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.
+ */
- if (!issueTemplate) {
- /* eslint-disable no-console */
- console.log('No Template: ', d.whichType);
- console.log(' ', d.description);
- /* eslint-enable no-console */
- return;
- } // some descriptions are just fixed text
+ function createCurry(func, bitmask, arity) {
+ var Ctor = createCtor(func);
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length,
+ placeholder = getHolder(wrapper);
- if (!issueTemplate.regex) return; // regex pattern should match description with variable details captured
+ while (index--) {
+ args[index] = arguments[index];
+ }
- var errorRegex = new RegExp(issueTemplate.regex, 'i');
- var errorMatch = errorRegex.exec(d.description);
+ var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
+ length -= holders.length;
- if (!errorMatch) {
- /* eslint-disable no-console */
- console.log('Unmatched: ', d.whichType);
- console.log(' ', d.description);
- console.log(' ', errorRegex);
- /* eslint-enable no-console */
+ if (length < arity) {
+ return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, undefined$1, args, holders, undefined$1, undefined$1, arity - length);
+ }
- return;
- }
+ var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
+ return apply(fn, this, args);
+ }
- 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] : '';
+ 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.
+ */
- 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 (_krData.localizeStrings[compare]) {
- // some replacement strings can be localized
- capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
+ function createFind(findIndexFunc) {
+ return function (collection, predicate, fromIndex) {
+ var iterable = Object(collection);
+
+ if (!isArrayLike(collection)) {
+ var iteratee = getIteratee(predicate, 3);
+ collection = keys(collection);
+
+ predicate = function predicate(key) {
+ return iteratee(iterable[key], key, iterable);
+ };
+ }
+
+ 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.
+ */
- replacements['var' + i] = capture;
- }
- return replacements;
- }
+ function createFlow(fromRight) {
+ return flatRest(function (funcs) {
+ var length = funcs.length,
+ index = length,
+ prereq = LodashWrapper.prototype.thru;
- function parseError(capture, idType) {
- var compare = capture.toLowerCase();
+ if (fromRight) {
+ funcs.reverse();
+ }
- if (_krData.localizeStrings[compare]) {
- // some replacement strings can be localized
- capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
- }
+ while (index--) {
+ var func = funcs[index];
- switch (idType) {
- // link a string like "this node"
- case 'this':
- capture = linkErrorObject(capture);
- break;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- case 'url':
- capture = linkURL(capture);
- break;
- // link an entity ID
+ if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+ var wrapper = new LodashWrapper([], true);
+ }
+ }
- case 'n':
- case 'w':
- case 'r':
- capture = linkEntity(idType + capture);
- break;
- // some errors have more complex ID lists/variance
+ index = wrapper ? index : length;
- case '20':
- capture = parse20(capture);
- break;
+ while (++index < length) {
+ func = funcs[index];
+ var funcName = getFuncName(func),
+ data = funcName == 'wrapper' ? getData(func) : undefined$1;
- case '211':
- capture = parse211(capture);
- break;
+ 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);
+ }
+ }
- case '231':
- capture = parse231(capture);
- break;
+ return function () {
+ var args = arguments,
+ value = args[0];
- case '294':
- capture = parse294(capture);
- break;
+ if (wrapper && args.length == 1 && isArray(value)) {
+ return wrapper.plant(value).value();
+ }
- case '370':
- capture = parse370(capture);
- break;
- }
+ var index = 0,
+ result = length ? funcs[index].apply(this, args) : value;
- return capture;
+ while (++index < length) {
+ result = funcs[index].call(this, result);
+ }
- function linkErrorObject(d) {
- return "<a class=\"error_object_link\">".concat(d, "</a>");
- }
+ return result;
+ };
+ });
+ }
+ /**
+ * 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.
+ */
- function linkEntity(d) {
- return "<a class=\"error_entity_link\">".concat(d, "</a>");
- }
- function linkURL(d) {
- return "<a class=\"kr_external_link\" target=\"_blank\" href=\"".concat(d, "\">").concat(d, "</a>");
- } // arbitrary node list of form: #ID, #ID, #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);
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length;
- 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)...
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ if (isCurried) {
+ var placeholder = getHolder(wrapper),
+ holdersCount = countHolders(args, placeholder);
+ }
- function parse231(capture) {
- var newList = []; // unfortunately 'layer' can itself contain commas, so we split on '),'
+ if (partials) {
+ args = composeArgs(args, partials, holders, isCurried);
+ }
- var items = capture.split('),');
- items.forEach(function (item) {
- var match = item.match(/\#(\d+)\((.+)\)?/);
+ if (partialsRight) {
+ args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+ }
- 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...
+ length -= holdersCount;
+ if (isCurried && length < arity) {
+ var newHolders = replaceHolders(args, placeholder);
+ return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length);
+ }
- 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
+ var thisBinding = isBind ? thisArg : this,
+ fn = isBindKey ? thisBinding[func] : func;
+ length = args.length;
- var role = "\"".concat(item[0], "\""); // first letter of node/relation provides the type
+ if (argPos) {
+ args = reorder(args, argPos);
+ } else if (isFlip && length > 1) {
+ args.reverse();
+ }
- var idType = item[1].slice(0, 1); // ID has # at the front
+ if (isAry && ary < length) {
+ args.length = ary;
+ }
- 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')"
+ if (this && this !== root && this instanceof wrapper) {
+ fn = Ctor || createCtor(fn);
+ }
+ return fn.apply(thisBinding, args);
+ }
- function parse370(capture) {
- if (!capture) return '';
- var match = capture.match(/\(including the name (\'.+\')\)/);
+ 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 (match && match.length) {
- return _t('QA.keepRight.errorTypes.370.including_the_name', {
- name: match[1]
- });
- }
- return '';
- } // arbitrary node list of form: #ID,#ID,#ID...
+ 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 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 createMathOperation(operator, defaultValue) {
+ return function (value, other) {
+ var result;
- var serviceKeepRight = {
- title: 'keepRight',
- init: function init() {
- _mainFileFetcher.get('keepRight').then(function (d) {
- return _krData = d;
- });
+ if (value === undefined$1 && other === undefined$1) {
+ return defaultValue;
+ }
- if (!_cache$2) {
- this.reset();
- }
+ if (value !== undefined$1) {
+ result = value;
+ }
- this.event = utilRebind(this, dispatch$7, 'on');
- },
- reset: function reset() {
- if (_cache$2) {
- Object.values(_cache$2.inflightTile).forEach(abortRequest$6);
- }
+ if (other !== undefined$1) {
+ if (result === undefined$1) {
+ return other;
+ }
- _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 (typeof value == 'string' || typeof other == 'string') {
+ value = baseToString(value);
+ other = baseToString(other);
+ } else {
+ value = baseToNumber(value);
+ other = baseToNumber(other);
+ }
- var options = {
- format: 'geojson',
- ch: _krRuleset
- }; // determine the needed tiles to cover the view
+ result = operator(value, other);
+ }
- var tiles = tiler$6.zoomExtent([_tileZoom$3, _tileZoom$3]).getTiles(projection); // abort inflight requests that are no longer needed
+ return result;
+ };
+ }
+ /**
+ * Creates a function like `_.over`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over iteratees.
+ * @returns {Function} Returns the new over 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 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`.
+ */
- 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];
- 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;
+ function createPadding(length, chars) {
+ chars = chars === undefined$1 ? ' ' : baseToString(chars);
+ var charsLength = chars.length;
- if (!data || !data.features || !data.features.length) {
- throw new Error('No Data');
+ if (charsLength < 2) {
+ return charsLength ? baseRepeat(chars, length) : chars;
}
- 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 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.
+ */
- var issueTemplate = _krData.errorTypes[itemType];
- var parentIssueType = (Math.floor(itemType / 10) * 10).toString(); // try to handle error type directly, fallback to parent error type.
- 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.
+ function createPartial(func, bitmask, thisArg, partials) {
+ var isBind = bitmask & WRAP_BIND_FLAG,
+ Ctor = createCtor(func);
- switch (whichType) {
- case '170':
- description = "This feature has a FIXME tag: ".concat(description);
- break;
+ 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;
- case '292':
- case '293':
- description = description.replace('A turn-', 'This turn-');
- break;
+ while (++leftIndex < leftLength) {
+ args[leftIndex] = partials[leftIndex];
+ }
- case '294':
- case '295':
- case '296':
- case '297':
- case '298':
- description = "This turn-restriction~".concat(description);
- break;
+ while (argsLength--) {
+ args[leftIndex++] = arguments[++argsIndex];
+ }
- case '300':
- description = 'This highway is missing a maxspeed tag';
- break;
+ return apply(fn, isBind ? thisArg : this, args);
+ }
- 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
+ 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.
+ */
- var coincident = false;
+ 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.
- 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 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;
+ start = toFinite(start);
- _cache$2.rtree.insert(encodeIssueRtree$2(d));
- });
- 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;
+ if (end === undefined$1) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
- if (_cache$2.inflightPost[d.id]) {
- return callback({
- message: 'Error update already inflight',
- status: -2
- }, d);
- }
+ 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 params = {
- schema: d.schema,
- id: d.id
- };
- if (d.newStatus) {
- params.st = d.newStatus;
- }
+ function createRelationalOperation(operator) {
+ return function (value, other) {
+ if (!(typeof value == 'string' && typeof other == 'string')) {
+ value = toNumber(value);
+ other = toNumber(other);
+ }
- 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.
+ 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.
+ */
- 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)
+ 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);
- d3_json(url, {
- signal: controller.signal
- })["finally"](function () {
- delete _cache$2.inflightPost[d.id];
+ if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
+ bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
+ }
- if (d.newStatus === 'ignore') {
- // ignore permanently (false positive)
- _this2.removeItem(d);
- } else if (d.newStatus === 'ignore_t') {
- // ignore temporarily (error fixed)
- _this2.removeItem(d);
+ var newData = [func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, newHoldersRight, argPos, ary, arity];
+ var result = wrapFunc.apply(undefined$1, newData);
- _cache$2.closed["".concat(d.schema, ":").concat(d.id)] = true;
- } else {
- d = _this2.replaceItem(d.update({
- comment: d.newComment,
- newComment: undefined,
- newState: undefined
- }));
+ if (isLaziable(func)) {
+ setData(result, newData);
+ }
+
+ 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 (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
- 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();
- }
- };
+ function createRound(methodName) {
+ var func = Math[methodName];
+ return function (number, precision) {
+ number = toNumber(number);
+ precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
- 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 (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));
+ }
- var _cache$1;
+ 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.
+ */
- function abortRequest$5(i) {
- Object.values(i).forEach(function (controller) {
- if (controller) {
- controller.abort();
- }
- });
- }
- function abortUnwantedRequests$2(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ 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.
+ */
- if (!wanted) {
- abortRequest$5(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
+ function createToPairs(keysFunc) {
+ return function (object) {
+ var tag = getTag(object);
- 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
+ if (tag == mapTag) {
+ return mapToArray(object);
+ }
+ if (tag == setTag) {
+ return setToPairs(object);
+ }
- function updateRtree$2(item, replace) {
- _cache$1.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
+ 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.
+ */
- if (replace) {
- _cache$1.rtree.insert(item);
- }
- }
- function linkErrorObject(d) {
- return "<a class=\"error_object_link\">".concat(d, "</a>");
- }
+ function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+ var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
- function linkEntity(d) {
- return "<a class=\"error_entity_link\">".concat(d, "</a>");
- }
+ if (!isBindKey && typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- 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];
- }
- }
+ var length = partials ? partials.length : 0;
- function relativeBearing(p1, p2) {
- var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
+ if (!length) {
+ bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
+ partials = holders = undefined$1;
+ }
- if (angle < 0) {
- angle += 2 * Math.PI;
- } // Return degrees
+ ary = ary === undefined$1 ? ary : nativeMax(toInteger(ary), 0);
+ arity = arity === undefined$1 ? arity : toInteger(arity);
+ length -= holders ? holders.length : 0;
+ if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
+ var partialsRight = partials,
+ holdersRight = holders;
+ partials = holders = undefined$1;
+ }
- return angle * 180 / Math.PI;
- } // Assuming range [0,360)
+ var data = isBindKey ? undefined$1 : getData(func);
+ var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
+ if (data) {
+ mergeData(newData, data);
+ }
- 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
+ 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);
+ if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
+ bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
+ }
- function preventCoincident$1(loc, bumpUp) {
- var coincident = false;
+ 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);
+ }
- 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);
+ 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.
+ */
- return loc;
- }
- var serviceImproveOSM = {
- title: 'improveOSM',
- init: function init() {
- _mainFileFetcher.get('qa_data').then(function (d) {
- return _impOsmData = d.improveOSM;
- });
+ function customDefaultsAssignIn(objValue, srcValue, key, object) {
+ if (objValue === undefined$1 || eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key)) {
+ return srcValue;
+ }
- if (!_cache$1) {
- this.reset();
- }
+ 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.
+ */
- this.event = utilRebind(this, dispatch$6, 'on');
- },
- reset: function reset() {
- if (_cache$1) {
- Object.values(_cache$1.inflightTile).forEach(abortRequest$5);
- }
- _cache$1 = {
- data: {},
- loadedTile: {},
- inflightTile: {},
- inflightPost: {},
- closed: {},
- rtree: new RBush()
- };
- },
- loadIssues: function loadIssues(projection) {
- var _this = this;
+ 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);
+ }
- var options = {
- client: 'iD',
- status: 'OPEN',
- zoom: '19' // Use a high zoom so that clusters aren't returned
+ 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`.
+ */
- }; // 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 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`.
+ */
- abortUnwantedRequests$2(_cache$1, tiles); // issue new requests..
- tiles.forEach(function (tile) {
- if (_cache$1.loadedTile[tile.id] || _cache$1.inflightTile[tile.id]) return;
+ function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
+ var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+ arrLength = array.length,
+ othLength = other.length;
- 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 (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+ return false;
+ } // Check that cyclic values are equal.
- var params = Object.assign({}, options, {
- east: east,
- south: south,
- west: west,
- north: north
- }); // 3 separate requests to store for each tile
- 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];
+ var arrStacked = stack.get(array);
+ var othStacked = stack.get(other);
- 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
+ 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.
- 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
+ while (++index < arrLength) {
+ var arrValue = array[index],
+ othValue = other[index];
- 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
+ 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;
+ }
- 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
+ result = false;
+ break;
+ } // Recursively compare arrays (susceptible to call stack limits).
- 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;
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
- });
- } // Tiles at high zoom == missing roads
+ 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;
+ }
+ }
+ 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`.
+ */
- 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
- 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
+ function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
+ switch (tag) {
+ case dataViewTag:
+ if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) {
+ return false;
+ }
- if (numberOfTrips === -1) {
- d.desc = _t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
- }
+ object = object.buffer;
+ other = other.buffer;
- _cache$1.data[d.id] = d;
+ case arrayBufferTag:
+ if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+ return false;
+ }
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
- });
- } // Entities at high zoom == turn restrictions
+ return true;
+ 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);
- 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
+ case errorTag:
+ return object.name == other.name && object.message == other.message;
- var loc = preventCoincident$1([point.lon, point.lat], true); // Elements are presented in a strange way
+ 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 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
+ case mapTag:
+ var convert = mapToArray;
- var _segments$0$points = _slicedToArray(segments[0].points, 2),
- p1 = _segments$0$points[0],
- p2 = _segments$0$points[1];
+ case setTag:
+ var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
+ convert || (convert = setToArray);
- var dir_of_travel = cardinalDirection(relativeBearing(p1, p2)); // Variables used in the description
+ if (object.size != other.size && !isPartial) {
+ return false;
+ } // Assume cyclic values are equal.
- 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;
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ var stacked = stack.get(object);
- dispatch$6.call('loaded');
- });
- }
- })["catch"](function () {
- delete _cache$1.inflightTile[tile.id][k];
+ if (stacked) {
+ return stacked == other;
+ }
- 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;
+ bitmask |= COMPARE_UNORDERED_FLAG; // Recursively compare objects (susceptible to call stack limits).
- // If comments already retrieved no need to do so again
- if (item.comments) {
- return Promise.resolve(item);
- }
+ stack.set(object, other);
+ var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
+ stack['delete'](object);
+ return result;
- var key = item.issueKey;
- var qParams = {};
+ case symbolTag:
+ if (symbolValueOf) {
+ return symbolValueOf.call(object) == symbolValueOf.call(other);
+ }
- 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;
- }
+ }
- var url = "".concat(_impOsmUrls[key], "/retrieveComments?") + utilQsString(qParams);
+ 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`.
+ */
- var cacheComments = function cacheComments(data) {
- // Assign directly for immediate use afterwards
- // comments are served newest to oldest
- item.comments = data.comments ? data.comments.reverse() : [];
- _this2.replaceItem(item);
- };
+ 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;
- 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);
- }
+ if (objLength != othLength && !isPartial) {
+ return false;
+ }
- 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
+ var index = objLength;
+ while (index--) {
+ var key = objProps[index];
- serviceOsm.userDetails(sendPayload.bind(this));
+ if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+ return false;
+ }
+ } // Check that cyclic values are equal.
- function sendPayload(err, user) {
- var _this3 = this;
- if (err) {
- return callback(err, d);
- }
+ var objStacked = stack.get(object);
+ var othStacked = stack.get(other);
- var key = d.issueKey;
- var url = "".concat(_impOsmUrls[key], "/comment");
- var payload = {
- username: user.display_name,
- targetIds: [d.identifier]
- };
+ if (objStacked && othStacked) {
+ return objStacked == other && othStacked == object;
+ }
- if (d.newStatus) {
- payload.status = d.newStatus;
- payload.text = 'status changed';
- } // Comment take place of default text
+ var result = true;
+ stack.set(object, other);
+ stack.set(other, object);
+ var skipCtor = isPartial;
+ while (++index < objLength) {
+ key = objProps[index];
+ var objValue = object[key],
+ othValue = other[key];
- if (d.newComment) {
- payload.text = d.newComment;
- }
+ 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).
- 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 (!d.newStatus) {
- var now = new Date();
- var comments = d.comments ? d.comments : [];
- comments.push({
- username: payload.username,
- text: payload.text,
- timestamp: now.getTime() / 1000
- });
+ if (!(compared === undefined$1 ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) {
+ result = false;
+ break;
+ }
- _this3.replaceItem(d.update({
- comments: comments,
- newComment: undefined
- }));
- } else {
- _this3.removeItem(d);
+ skipCtor || (skipCtor = key == 'constructor');
+ }
- 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 (result && !skipCtor) {
+ var objCtor = object.constructor,
+ othCtor = other.constructor; // Non `Object` object instances with different constructors are not equal.
- _cache$1.closed[d.issueKey] += 1;
+ if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+ result = false;
}
}
- 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'](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 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 = {exports: {}};
+ 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 getDefaults$1() {
- 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 changeDefaults$1(newDefaults) {
- defaults$5.exports.defaults = newDefaults;
- }
+ 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.
+ */
- defaults$5.exports = {
- defaults: getDefaults$1(),
- getDefaults: getDefaults$1,
- changeDefaults: changeDefaults$1
- };
- var escapeTest = /[&<>"']/;
- var escapeReplace = /[&<>"']/g;
- var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
- var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
- var escapeReplacements = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
+ 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`.
+ */
- var getEscapeReplacement = function getEscapeReplacement(ch) {
- return escapeReplacements[ch];
- };
- 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);
- }
- }
+ 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 html;
- }
+ function getFuncName(func) {
+ var result = func.name + '',
+ array = realNames[result],
+ length = hasOwnProperty.call(realNames, result) ? array.length : 0;
- var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
+ while (length--) {
+ var data = array[length],
+ otherFunc = data.func;
- 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 ':';
+ if (otherFunc == null || otherFunc == func) {
+ return data.name;
+ }
+ }
- if (n.charAt(0) === '#') {
- return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
- }
+ return result;
+ }
+ /**
+ * Gets the argument placeholder value for `func`.
+ *
+ * @private
+ * @param {Function} func The function to inspect.
+ * @returns {*} Returns the placeholder value.
+ */
- return '';
- });
- }
- var caret = /(^|[^\[])\^/g;
+ 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.
+ */
- 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;
- }
- var nonWordAndColonTest = /[^\w:]/g;
- var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+ 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 cleanUrl$1(sanitize, base, href) {
- if (sanitize) {
- var prot;
- try {
- prot = decodeURIComponent(unescape$2(href)).replace(nonWordAndColonTest, '').toLowerCase();
- } catch (e) {
- return null;
- }
+ 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`.
+ */
- if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
- return null;
- }
- }
- if (base && !originIndependentUrl.test(href)) {
- href = resolveUrl$2(base, href);
- }
+ function getMatchData(object) {
+ var result = keys(object),
+ length = result.length;
- try {
- href = encodeURI(href).replace(/%25/g, '%');
- } catch (e) {
- return null;
- }
+ while (length--) {
+ var key = result[length],
+ value = object[key];
+ result[length] = [key, value, isStrictComparable(value)];
+ }
- return href;
- }
+ 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 baseUrls = {};
- var justDomain = /^[^:]+:\/*[^/]*$/;
- var protocol = /^([^:]+:)[\s\S]*$/;
- var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
- function resolveUrl$2(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);
- }
- }
+ 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`.
+ */
- base = baseUrls[' ' + base];
- var relativeBase = base.indexOf(':') === -1;
- if (href.substring(0, 2) === '//') {
- if (relativeBase) {
- return href;
- }
+ function getRawTag(value) {
+ var isOwn = hasOwnProperty.call(value, symToStringTag),
+ tag = value[symToStringTag];
- return base.replace(protocol, '$1') + href;
- } else if (href.charAt(0) === '/') {
- if (relativeBase) {
- return href;
- }
+ try {
+ value[symToStringTag] = undefined$1;
+ var unmasked = true;
+ } catch (e) {}
- return base.replace(domain, '$1') + href;
- } else {
- return base + href;
- }
- }
+ var result = nativeObjectToString.call(value);
- var noopTest$1 = {
- exec: function noopTest() {}
- };
+ if (unmasked) {
+ if (isOwn) {
+ value[symToStringTag] = tag;
+ } else {
+ delete value[symToStringTag];
+ }
+ }
- function merge$2(obj) {
- var i = 1,
- target,
- key;
+ 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.
+ */
- for (; i < arguments.length; i++) {
- target = arguments[i];
- for (key in target) {
- if (Object.prototype.hasOwnProperty.call(target, key)) {
- obj[key] = target[key];
- }
- }
- }
+ var getSymbols = !nativeGetSymbols ? stubArray : function (object) {
+ if (object == null) {
+ return [];
+ }
- return obj;
- }
+ 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.
+ */
- 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;
+ var getSymbolsIn = !nativeGetSymbols ? stubArray : function (object) {
+ var result = [];
- while (--curr >= 0 && str[curr] === '\\') {
- escaped = !escaped;
- }
+ while (object) {
+ arrayPush(result, getSymbols(object));
+ object = getPrototype(object);
+ }
- 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;
+ return result;
+ };
+ /**
+ * Gets the `toStringTag` of `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
- if (cells.length > count) {
- cells.splice(count);
- } else {
- while (cells.length < count) {
- cells.push('');
- }
- }
+ var getTag = baseGetTag; // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
- for (; i < cells.length; i++) {
- // leading or trailing whitespace is ignored per the gfm spec
- cells[i] = cells[i].trim().replace(/\\\|/g, '|');
- }
+ 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 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.
+ if (ctorString) {
+ switch (ctorString) {
+ case dataViewCtorString:
+ return dataViewTag;
+ case mapCtorString:
+ return mapTag;
- function rtrim$1(str, c, invert) {
- var l = str.length;
+ case promiseCtorString:
+ return promiseTag;
- if (l === 0) {
- return '';
- } // Length of suffix matching the invert condition.
+ case setCtorString:
+ return setTag;
+ case weakMapCtorString:
+ return weakMapTag;
+ }
+ }
- var suffLen = 0; // Step left until we fail to match the invert condition.
+ return result;
+ };
+ }
+ /**
+ * 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.
+ */
- while (suffLen < l) {
- var currChar = str.charAt(l - suffLen - 1);
- if (currChar === c && !invert) {
- suffLen++;
- } else if (currChar !== c && invert) {
- suffLen++;
- } else {
- break;
- }
- }
+ function getView(start, end, transforms) {
+ var index = -1,
+ length = transforms.length;
- return str.substr(0, l - suffLen);
- }
+ while (++index < length) {
+ var data = transforms[index],
+ size = data.size;
- function findClosingBracket$1(str, b) {
- if (str.indexOf(b[1]) === -1) {
- return -1;
- }
+ switch (data.type) {
+ case 'drop':
+ start += size;
+ break;
- var l = str.length;
- var level = 0,
- i = 0;
+ case 'dropRight':
+ end -= size;
+ break;
- for (; i < l; i++) {
- if (str[i] === '\\') {
- i++;
- } else if (str[i] === b[0]) {
- level++;
- } else if (str[i] === b[1]) {
- level--;
+ case 'take':
+ end = nativeMin(end, start + size);
+ break;
- if (level < 0) {
- return i;
+ case 'takeRight':
+ start = nativeMax(start, end - size);
+ break;
+ }
+ }
+
+ return {
+ 'start': start,
+ 'end': end
+ };
}
- }
- }
+ /**
+ * Extracts wrapper details from the `source` body comment.
+ *
+ * @private
+ * @param {string} source The source to inspect.
+ * @returns {Array} Returns the wrapper details.
+ */
- 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 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`.
+ */
- function repeatString$1(pattern, count) {
- if (count < 1) {
- return '';
- }
+ function hasPath(object, path, hasFunc) {
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length,
+ result = false;
- var result = '';
+ while (++index < length) {
+ var key = toKey(path[index]);
- while (count > 1) {
- if (count & 1) {
- result += pattern;
- }
+ if (!(result = object != null && hasFunc(object, key))) {
+ break;
+ }
- count >>= 1;
- pattern += pattern;
- }
+ object = object[key];
+ }
- return result + pattern;
- }
+ if (result || ++index != length) {
+ return result;
+ }
- var helpers = {
- escape: escape$3,
- unescape: unescape$2,
- edit: edit$1,
- cleanUrl: cleanUrl$1,
- resolveUrl: resolveUrl$2,
- noopTest: noopTest$1,
- merge: merge$2,
- splitCells: splitCells$1,
- rtrim: rtrim$1,
- findClosingBracket: findClosingBracket$1,
- checkSanitizeDeprecation: checkSanitizeDeprecation$1,
- repeatString: repeatString$1
- };
+ length = object == null ? 0 : object.length;
+ return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object));
+ }
+ /**
+ * Initializes an array clone.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the initialized clone.
+ */
- var defaults$4 = defaults$5.exports.defaults;
- var rtrim = helpers.rtrim,
- splitCells = helpers.splitCells,
- _escape = helpers.escape,
- findClosingBracket = helpers.findClosingBracket;
- function outputLink(cap, link, raw) {
- var href = link.href;
- var title = link.title ? _escape(link.title) : null;
- var text = cap[1].replace(/\\([\[\]])/g, '$1');
+ function initCloneArray(array) {
+ var length = array.length,
+ result = new array.constructor(length); // Add properties assigned by `RegExp#exec`.
- 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)
- };
- }
- }
+ if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+ result.index = array.index;
+ result.input = array.input;
+ }
- function indentCodeCompensation(raw, text) {
- var matchIndentToCode = raw.match(/^(\s+)(?:```)/);
+ return result;
+ }
+ /**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
- if (matchIndentToCode === null) {
- return text;
- }
- var indentToCode = matchIndentToCode[1];
- return text.split('\n').map(function (node) {
- var matchIndentInNode = node.match(/^\s+/);
+ function initCloneObject(object) {
+ return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {};
+ }
+ /**
+ * 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 (matchIndentInNode === null) {
- return node;
- }
- var _matchIndentInNode = _slicedToArray(matchIndentInNode, 1),
- indentInNode = _matchIndentInNode[0];
+ function initCloneByTag(object, tag, isDeep) {
+ var Ctor = object.constructor;
- if (indentInNode.length >= indentToCode.length) {
- return node.slice(indentToCode.length);
- }
+ switch (tag) {
+ case arrayBufferTag:
+ return cloneArrayBuffer(object);
- return node;
- }).join('\n');
- }
- /**
- * Tokenizer
- */
+ case boolTag:
+ case dateTag:
+ return new Ctor(+object);
+ case dataViewTag:
+ return cloneDataView(object, isDeep);
- var Tokenizer_1 = /*#__PURE__*/function () {
- function Tokenizer(options) {
- _classCallCheck$1(this, Tokenizer);
+ case float32Tag:
+ case float64Tag:
+ case int8Tag:
+ case int16Tag:
+ case int32Tag:
+ case uint8Tag:
+ case uint8ClampedTag:
+ case uint16Tag:
+ case uint32Tag:
+ return cloneTypedArray(object, isDeep);
- this.options = options || defaults$4;
- }
+ case mapTag:
+ return new Ctor();
- _createClass$1(Tokenizer, [{
- key: "space",
- value: function space(src) {
- var cap = this.rules.block.newline.exec(src);
+ case numberTag:
+ case stringTag:
+ return new Ctor(object);
- if (cap) {
- if (cap[0].length > 1) {
- return {
- type: 'space',
- raw: cap[0]
- };
- }
+ case regexpTag:
+ return cloneRegExp(object);
- return {
- raw: '\n'
- };
+ case setTag:
+ return new Ctor();
+
+ case symbolTag:
+ return cloneSymbol(object);
+ }
}
- }
- }, {
- key: "code",
- value: function code(src) {
- var cap = this.rules.block.code.exec(src);
+ /**
+ * 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 (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
- };
+
+ function insertWrapDetails(source, details) {
+ var length = details.length;
+
+ if (!length) {
+ return source;
+ }
+
+ var lastIndex = length - 1;
+ details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
+ details = details.join(length > 2 ? ', ' : ' ');
+ return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
}
- }
- }, {
- key: "fences",
- value: function fences(src) {
- var cap = this.rules.block.fences.exec(src);
+ /**
+ * 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`.
+ */
- 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
- };
+
+ function isFlattenable(value) {
+ return isArray(value) || isArguments(value) || !!(spreadableSymbol && value && value[spreadableSymbol]);
}
- }
- }, {
- key: "heading",
- value: function heading(src) {
- var cap = this.rules.block.heading.exec(src);
+ /**
+ * 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 (cap) {
- var text = cap[2].trim(); // remove trailing #s
- if (/#$/.test(text)) {
- var trimmed = rtrim(text, '#');
+ function isIndex(value, length) {
+ var type = _typeof(value);
- if (this.options.pedantic) {
- text = trimmed.trim();
- } else if (!trimmed || / $/.test(trimmed)) {
- // CommonMark requires space before trailing #s
- text = trimmed.trim();
- }
+ 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`.
+ */
+
+
+ function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
}
- return {
- type: 'heading',
- raw: cap[0],
- depth: cap[1].length,
- text: text
- };
- }
- }
- }, {
- key: "nptable",
- value: function nptable(src) {
- var cap = this.rules.block.nptable.exec(src);
+ var type = _typeof(index);
- 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 (type == 'number' ? isArrayLike(object) && isIndex(index, object.length) : type == 'string' && index in object) {
+ return eq(object[index], value);
+ }
- if (item.header.length === item.align.length) {
- var l = item.align.length;
- var i;
+ return false;
+ }
+ /**
+ * 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`.
+ */
- 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 isKey(value, object) {
+ if (isArray(value)) {
+ return false;
+ }
- for (i = 0; i < l; i++) {
- item.cells[i] = splitCells(item.cells[i], item.header.length);
- }
+ var type = _typeof(value);
- return item;
+ 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: "hr",
- value: function hr(src) {
- var cap = this.rules.block.hr.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) {
- return {
- type: 'hr',
- raw: cap[0]
- };
+
+ function isKeyable(value) {
+ var type = _typeof(value);
+
+ return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
}
- }
- }, {
- key: "blockquote",
- value: function blockquote(src) {
- var cap = this.rules.block.blockquote.exec(src);
+ /**
+ * 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`.
+ */
- if (cap) {
- var text = cap[0].replace(/^ *> ?/gm, '');
- return {
- type: 'blockquote',
- raw: cap[0],
- text: text
- };
+
+ function isLaziable(func) {
+ var funcName = getFuncName(func),
+ other = lodash[funcName];
+
+ 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: "list",
- value: function list(src) {
- var cap = this.rules.block.list.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) {
- 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 isMasked(func) {
+ return !!maskSrcKey && maskSrcKey in func;
+ }
+ /**
+ * 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`.
+ */
- for (var i = 0; i < l; i++) {
- item = itemMatch[i];
- raw = item;
- 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'));
+ 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`.
+ */
- 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.
+ function isPrototype(value) {
+ var Ctor = value && value.constructor,
+ proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
+ return value === proto;
+ }
+ /**
+ * 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 (i !== l - 1) {
- bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
+ function isStrictComparable(value) {
+ return value === value && !isObject(value);
+ }
+ /**
+ * 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 (!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;
- }
- bcurr = bnext;
- } // Remove the list item's bullet
- // so it is seen as the next token.
+ function matchesStrictComparable(key, srcValue) {
+ return function (object) {
+ if (object == null) {
+ return false;
+ }
+ return object[key] === srcValue && (srcValue !== undefined$1 || key in Object(object));
+ };
+ }
+ /**
+ * 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.
+ */
- space = item.length;
- item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the
- // list item contains. Hacky.
- 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 memoizeCapped(func) {
+ var result = memoize(func, function (key) {
+ if (cache.size === MAX_MEMOIZE_SIZE) {
+ cache.clear();
+ }
+ return key;
+ });
+ var cache = result.cache;
+ return result;
+ }
+ /**
+ * 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`.
+ */
- item = rtrim(item, '\n');
- if (i !== l - 1) {
- raw = raw + '\n';
- } // Determine whether item is loose or not.
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
- // for discount behavior.
+ 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.
- loose = next || /\n\n(?!\s*$)/.test(raw);
- if (i !== l - 1) {
- next = raw.slice(-2) === '\n\n';
- if (!loose) loose = next;
- }
+ if (srcBitmask & WRAP_BIND_FLAG) {
+ data[2] = source[2]; // Set when currying a bound function.
- if (loose) {
- list.loose = true;
- } // Check for task list items
+ newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
+ } // Compose partial arguments.
- if (this.options.gfm) {
- istask = /^\[[ xX]\] /.test(item);
- ischecked = undefined;
+ var value = source[3];
- if (istask) {
- ischecked = item[1] !== ' ';
- item = item.replace(/^\[[ xX]\] +/, '');
- }
- }
+ 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.
- list.items.push({
- type: 'list_item',
- raw: raw,
- task: istask,
- checked: ischecked,
- loose: loose,
- text: item
- });
- }
- return list;
+ 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;
}
- }
- }, {
- key: "html",
- value: function html(src) {
- var cap = this.rules.block.html.exec(src);
+ /**
+ * 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.
+ */
- 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]
- };
+
+ function nativeKeysIn(object) {
+ var result = [];
+
+ if (object != null) {
+ for (var key in Object(object)) {
+ result.push(key);
+ }
+ }
+
+ return result;
}
- }
- }, {
- key: "def",
- value: function def(src) {
- var cap = this.rules.block.def.exec(src);
+ /**
+ * Converts `value` to a string using `Object.prototype.toString`.
+ *
+ * @private
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ */
- 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 objectToString(value) {
+ return nativeObjectToString.call(value);
}
- }
- }, {
- key: "table",
- value: function table(src) {
- var cap = this.rules.block.table.exec(src);
+ /**
+ * 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.
+ */
- 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 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);
- 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;
- }
+ while (++index < length) {
+ array[index] = args[start + index];
}
- l = item.cells.length;
+ index = -1;
+ var otherArgs = Array(start + 1);
- for (i = 0; i < l; i++) {
- item.cells[i] = splitCells(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length);
+ while (++index < start) {
+ otherArgs[index] = args[index];
}
- return item;
- }
- }
- }
- }, {
- key: "lheading",
- value: function lheading(src) {
- var cap = this.rules.block.lheading.exec(src);
-
- if (cap) {
- return {
- type: 'heading',
- raw: cap[0],
- depth: cap[2].charAt(0) === '=' ? 1 : 2,
- text: cap[1]
+ otherArgs[start] = transform(array);
+ return apply(func, this, otherArgs);
};
}
- }
- }, {
- key: "paragraph",
- value: function paragraph(src) {
- var cap = this.rules.block.paragraph.exec(src);
+ /**
+ * 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.
+ */
- if (cap) {
- return {
- type: 'paragraph',
- raw: cap[0],
- text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
- };
- }
- }
- }, {
- key: "text",
- value: function text(src) {
- var cap = this.rules.block.text.exec(src);
- if (cap) {
- return {
- type: 'text',
- raw: cap[0],
- text: cap[0]
- };
+ function parent(object, path) {
+ return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
}
- }
- }, {
- key: "escape",
- value: function escape(src) {
- var cap = this.rules.inline.escape.exec(src);
+ /**
+ * 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`.
+ */
- if (cap) {
- return {
- type: 'escape',
- raw: cap[0],
- text: _escape(cap[1])
- };
+
+ 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 array;
}
- }
- }, {
- key: "tag",
- value: function tag(src, inLink, inRawBlock) {
- var cap = this.rules.inline.tag.exec(src);
+ /**
+ * 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) {
- if (!inLink && /^<a /i.test(cap[0])) {
- inLink = true;
- } else if (inLink && /^<\/a>/i.test(cap[0])) {
- inLink = false;
+
+ function safeGet(object, key) {
+ if (key === 'constructor' && typeof object[key] === 'function') {
+ return;
}
- 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;
+ if (key == '__proto__') {
+ return;
}
- 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 object[key];
}
- }
- }, {
- key: "link",
- value: function link(src) {
- var cap = this.rules.inline.link.exec(src);
+ /**
+ * 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 (cap) {
- var trimmedUrl = cap[2].trim();
- if (!this.options.pedantic && /^</.test(trimmedUrl)) {
- // commonmark requires matching angle brackets
- if (!/>$/.test(trimmedUrl)) {
- return;
- } // ending angle bracket cannot be escaped
+ 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.
+ */
+ 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`.
+ */
- var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
- if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
- return;
- }
- } else {
- // find closing parenthesis
- var lastParenIndex = findClosingBracket(cap[2], '()');
+ 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 (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] = '';
+ 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;
}
+
+ 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`.
+ */
+
+
+ function shuffleSelf(array, size) {
+ var index = -1,
+ length = array.length,
+ lastIndex = length - 1;
+ size = size === undefined$1 ? length : size;
+
+ while (++index < size) {
+ var rand = baseRandom(index, lastIndex),
+ value = array[rand];
+ array[rand] = array[index];
+ array[index] = value;
}
- var href = cap[2];
- var title = '';
+ array.length = size;
+ return array;
+ }
+ /**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
- if (this.options.pedantic) {
- // split pedantic href and title
- var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
- if (link) {
- href = link[1];
- title = link[3];
- }
- } else {
- title = cap[3] ? cap[3].slice(1, -1) : '';
+ var stringToPath = memoizeCapped(function (string) {
+ var result = [];
+
+ if (string.charCodeAt(0) === 46
+ /* . */
+ ) {
+ result.push('');
}
- href = href.trim();
+ 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.
+ */
- if (/^</.test(href)) {
- if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
- // pedantic allows starting angle bracket without ending angle bracket
- href = href.slice(1);
- } else {
- href = href.slice(1, -1);
- }
+ function toKey(value) {
+ if (typeof value == 'string' || isSymbol(value)) {
+ return value;
}
- 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]);
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
}
- }
- }, {
- key: "reflink",
- value: function reflink(src, links) {
- var cap;
+ /**
+ * Converts `func` to its source code.
+ *
+ * @private
+ * @param {Function} func The function to convert.
+ * @returns {string} Returns the source code.
+ */
- 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 toSource(func) {
+ if (func != null) {
+ try {
+ return funcToString.call(func);
+ } catch (e) {}
+
+ try {
+ return func + '';
+ } catch (e) {}
}
- return outputLink(cap, link, cap[0]);
+ return '';
}
- }
- }, {
- 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
+ /**
+ * 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 (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?)
+ function updateWrapDetails(details, bitmask) {
+ arrayEach(wrapFlags, function (pair) {
+ var value = '_.' + pair[0];
- maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
+ if (bitmask & pair[1] && !arrayIncludes(details, value)) {
+ details.push(value);
+ }
+ });
+ return details.sort();
+ }
+ /**
+ * Creates a clone of `wrapper`.
+ *
+ * @private
+ * @param {Object} wrapper The wrapper to clone.
+ * @returns {Object} Returns the cloned wrapper.
+ */
- 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;
+ function wrapperClone(wrapper) {
+ if (wrapper instanceof LazyWrapper) {
+ return wrapper.clone();
+ }
- 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
- }
- }
+ var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+ result.__actions__ = copyArray(wrapper.__actions__);
+ result.__index__ = wrapper.__index__;
+ result.__values__ = wrapper.__values__;
+ return result;
+ }
+ /*------------------------------------------------------------------------*/
- delimTotal -= rLength;
- if (delimTotal > 0) continue; // Haven't found enough closing delimiters
- // Remove extra characters. *a*** -> *a*
+ /**
+ * 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']]
+ */
- rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a***
- 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 chunk(array, size, guard) {
+ if (guard ? isIterateeCall(array, size, guard) : size === undefined$1) {
+ size = 1;
+ } else {
+ size = nativeMax(toInteger(size), 0);
+ }
+ var length = array == null ? 0 : array.length;
- return {
- type: 'strong',
- raw: src.slice(0, lLength + match.index + rLength + 1),
- text: src.slice(2, lLength + match.index + rLength - 1)
- };
+ if (!length || size < 1) {
+ return [];
}
- }
- }
- }, {
- key: "codespan",
- value: function codespan(src) {
- var cap = this.rules.inline.code.exec(src);
- if (cap) {
- var text = cap[2].replace(/\n/g, ' ');
- var hasNonSpaceChars = /[^ ]/.test(text);
- var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
+ var index = 0,
+ resIndex = 0,
+ result = Array(nativeCeil(length / size));
- if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
- text = text.substring(1, text.length - 1);
+ while (index < length) {
+ result[resIndex++] = baseSlice(array, index, index += size);
}
- text = _escape(text, true);
- return {
- type: 'codespan',
- raw: cap[0],
- text: text
- };
+ return result;
}
- }
- }, {
- key: "br",
- value: function br(src) {
- var cap = this.rules.inline.br.exec(src);
-
- if (cap) {
- return {
- type: 'br',
- raw: cap[0]
- };
- }
- }
- }, {
- key: "del",
- value: function del(src) {
- var cap = this.rules.inline.del.exec(src);
+ /**
+ * 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]
+ */
- if (cap) {
- return {
- type: 'del',
- raw: cap[0],
- text: cap[2]
- };
- }
- }
- }, {
- key: "autolink",
- value: function autolink(src, mangle) {
- var cap = this.rules.inline.autolink.exec(src);
- if (cap) {
- var text, href;
+ function compact(array) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ resIndex = 0,
+ result = [];
- if (cap[2] === '@') {
- text = _escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
- href = 'mailto:' + text;
- } else {
- text = _escape(cap[1]);
- href = text;
+ 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: "url",
- value: function url(src, mangle) {
- var cap;
+ /**
+ * 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 = this.rules.inline.url.exec(src)) {
- var text, href;
- if (cap[2] === '@') {
- text = _escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
- href = 'mailto:' + text;
- } else {
- // do extended autolink path validation
- var prevCapZero;
+ function concat() {
+ var length = arguments.length;
- do {
- prevCapZero = cap[0];
- cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
- } while (prevCapZero !== cap[0]);
+ if (!length) {
+ return [];
+ }
- text = _escape(cap[0]);
+ var args = Array(length - 1),
+ array = arguments[0],
+ index = length;
- if (cap[1] === 'www.') {
- href = 'http://' + text;
- } else {
- href = text;
- }
+ while (index--) {
+ args[index - 1] = arguments[index];
}
- return {
- type: 'link',
- raw: cap[0],
- text: text,
- href: href,
- tokens: [{
- type: 'text',
- raw: text,
- text: text
- }]
- };
+ return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
}
- }
- }, {
- key: "inlineText",
- value: function inlineText(src, inRawBlock, smartypants) {
- var cap = this.rules.inline.text.exec(src);
-
- if (cap) {
- var text;
+ /**
+ * 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]
+ */
- 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]);
- }
- return {
- type: 'text',
- raw: cap[0],
- text: text
- };
- }
- }
- }]);
+ 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 }]
+ */
- return Tokenizer;
- }();
+ var differenceBy = baseRest(function (array, values) {
+ var iteratee = last(values);
- var noopTest = helpers.noopTest,
- edit = helpers.edit,
- merge$1 = helpers.merge;
- /**
- * Block-Level Grammar
- */
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined$1;
+ }
- 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)
- + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
- + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\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 = /<!--(?!-?>)[\s\S]*?(?:-->|$)/;
- 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
- */
+ 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.normal = merge$1({}, block$1);
- /**
- * GFM Block Grammar
- */
+ var differenceWith = baseRest(function (array, values) {
+ var comparator = last(values);
- 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
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined$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)
- */
+ 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]
+ */
- block$1.pedantic = merge$1({}, block$1.normal, {
- html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
- + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\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
- */
+ function drop(array, n, guard) {
+ var length = array == null ? 0 : array.length;
- 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. <?php ?>
- + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
- + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>',
- // 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 _
+ if (!length) {
+ return [];
+ }
- },
- code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
- br: /^( {2,}|\\)\n(?!\s*$)/,
- del: noopTest,
- text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,
- punctuation: /^([\spunctuation])/
- }; // list of punctuation marks from CommonMark spec
- // without * and _ to handle the different emphasis markers * and _
+ 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._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
- inline$1.punctuation = edit(inline$1.punctuation).replace(/punctuation/g, inline$1._punctuation).getRegex(); // sequences em should skip over [title](link), `code`, <html>
- 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
- */
+ function dropRight(array, n, guard) {
+ var length = array == null ? 0 : array.length;
- inline$1.normal = merge$1({}, inline$1);
- /**
- * Pedantic Inline Grammar
- */
+ if (!length) {
+ return [];
+ }
- 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
- */
+ n = guard || n === undefined$1 ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+ /**
+ * Creates a slice of `array` excluding elements dropped from the end.
+ * 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': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.dropRightWhile(users, function(o) { return !o.active; });
+ * // => 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']
+ */
- 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]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
- });
- inline$1.gfm.url = edit(inline$1.gfm.url, 'i').replace('email', inline$1.gfm._extended_email).getRegex();
- /**
- * GFM + Line Breaks Inline Grammar
- */
- inline$1.breaks = merge$1({}, inline$1.gfm, {
- br: edit(inline$1.br).replace('{2,}', '*').getRegex(),
- text: edit(inline$1.gfm.text).replace('\\b_', '\\b_| {2,}\\n').replace(/\{2,\}/g, '*').getRegex()
- });
- var rules = {
- block: block$1,
- inline: inline$1
- };
+ 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']
+ */
- var Tokenizer$1 = Tokenizer_1;
- var defaults$3 = defaults$5.exports.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 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]
+ */
- function mangle(text) {
- var out = '',
- i,
- ch;
- var l = text.length;
+ function fill(array, value, start, end) {
+ var length = array == null ? 0 : array.length;
- for (i = 0; i < l; i++) {
- ch = text.charCodeAt(i);
+ if (!length) {
+ return [];
+ }
- if (Math.random() > 0.5) {
- ch = 'x' + ch.toString(16);
- }
+ if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+ start = 0;
+ end = length;
+ }
- out += '&#' + ch + ';';
- }
+ 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
+ */
- return out;
- }
- /**
- * Block Lexer
- */
+ function findIndex(array, predicate, fromIndex) {
+ var length = array == null ? 0 : array.length;
- var Lexer_1 = /*#__PURE__*/function () {
- function Lexer(options) {
- _classCallCheck$1(this, Lexer);
+ if (!length) {
+ return -1;
+ }
- 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
- };
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
- if (this.options.pedantic) {
- rules.block = block.pedantic;
- rules.inline = inline.pedantic;
- } else if (this.options.gfm) {
- rules.block = block.gfm;
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
- if (this.options.breaks) {
- rules.inline = inline.breaks;
- } else {
- rules.inline = inline.gfm;
+ 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);
+ }
+
+ 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]
+ */
- var token, i, l, lastToken;
- while (src) {
- // newline
- if (token = this.tokenizer.space(src)) {
- src = src.substring(token.raw.length);
+ 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]
+ */
- if (token.type) {
- tokens.push(token);
- }
- 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);
- }
-
- continue;
- } // fences
+ function flattenDepth(array, depth) {
+ var length = array == null ? 0 : array.length;
+ if (!length) {
+ return [];
+ }
- if (token = this.tokenizer.fences(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // heading
+ 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.heading(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // table no leading pipe (gfm)
+ function fromPairs(pairs) {
+ var index = -1,
+ length = pairs == null ? 0 : pairs.length,
+ result = {};
+ while (++index < length) {
+ var pair = pairs[index];
+ result[pair[0]] = pair[1];
+ }
- if (token = this.tokenizer.nptable(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // hr
+ 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.hr(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // blockquote
+ 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.blockquote(src)) {
- src = src.substring(token.raw.length);
- token.tokens = this.blockTokens(token.text, [], top);
- tokens.push(token);
- continue;
- } // list
+ function indexOf(array, value, fromIndex) {
+ var length = array == null ? 0 : array.length;
+ if (!length) {
+ return -1;
+ }
- if (token = this.tokenizer.list(src)) {
- src = src.substring(token.raw.length);
- l = token.items.length;
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
- for (i = 0; i < l; i++) {
- token.items[i].tokens = this.blockTokens(token.items[i].text, [], false);
- }
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
- tokens.push(token);
- continue;
- } // html
+ 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]
+ */
- if (token = this.tokenizer.html(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // def
+ 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 (top && (token = this.tokenizer.def(src))) {
- src = src.substring(token.raw.length);
+ 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 (!this.tokens.links[token.tag]) {
- this.tokens.links[token.tag] = {
- href: token.href,
- title: token.title
- };
- }
+ var intersectionBy = baseRest(function (arrays) {
+ var iteratee = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
- continue;
- } // table (gfm)
+ if (iteratee === last(mapped)) {
+ iteratee = undefined$1;
+ } else {
+ mapped.pop();
+ }
+ 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 }]
+ */
- if (token = this.tokenizer.table(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // lheading
+ var intersectionWith = baseRest(function (arrays) {
+ var comparator = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
+ comparator = typeof comparator == 'function' ? comparator : undefined$1;
+ if (comparator) {
+ mapped.pop();
+ }
- if (token = this.tokenizer.lheading(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // top-level paragraph
+ 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'
+ */
+ 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);
- if (token.type === 'link') {
- token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
- }
+ 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
+ */
- 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];
- 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);
- }
+ function sortedLastIndexOf(array, value) {
+ var length = array == null ? 0 : array.length;
- continue;
- } // em & strong
+ if (length) {
+ var index = baseSortedIndex(array, value, true) - 1;
+ if (eq(array[index], value)) {
+ return index;
+ }
+ }
- 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
+ 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.codespan(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // br
+ 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.br(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // del (gfm)
+ 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.del(src)) {
- src = src.substring(token.raw.length);
- token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
- tokens.push(token);
- continue;
- } // autolink
+ 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.autolink(src, mangle)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // url (gfm)
+ 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 (!inLink && (token = this.tokenizer.url(src, mangle))) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // text
+ function takeRight(array, n, guard) {
+ var length = array == null ? 0 : array.length;
- if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
- src = src.substring(token.raw.length);
+ if (!length) {
+ return [];
+ }
- if (token.raw.slice(-1) !== '_') {
- // Track prevChar before string of ____ started
- prevChar = token.raw.slice(-1);
- }
+ 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');
+ * // => []
+ */
- 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 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');
+ * // => []
+ */
- continue;
- }
- if (src) {
- var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+ 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]
+ */
- if (this.options.silent) {
- console.error(errMsg);
- break;
- } else {
- throw new Error(errMsg);
- }
+
+ 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 }]
+ */
+
+ var unionBy = baseRest(function (arrays) {
+ var iteratee = last(arrays);
+
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined$1;
}
+
+ 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 }]
+ */
+
+ 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]
+ */
+
+ 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 }]
+ */
- return tokens;
- }
- }], [{
- key: "rules",
- get: function get() {
- return {
- block: block,
- inline: inline
- };
- }
- /**
- * Static Lex Method
- */
- }, {
- key: "lex",
- value: function lex(src, options) {
- var lexer = new Lexer(options);
- return lexer.lex(src);
- }
- /**
- * Static Lex Inline Method
- */
+ 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 }]
+ */
- }, {
- key: "lexInline",
- value: function lexInline(src, options) {
- var lexer = new Lexer(options);
- return lexer.inlineTokens(src);
- }
- }]);
- return Lexer;
- }();
+ 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]]
+ */
- var defaults$2 = defaults$5.exports.defaults;
- var cleanUrl = helpers.cleanUrl,
- escape$2 = helpers.escape;
- /**
- * Renderer
- */
- var Renderer_1 = /*#__PURE__*/function () {
- function Renderer(options) {
- _classCallCheck$1(this, Renderer);
+ function unzip(array) {
+ if (!(array && array.length)) {
+ return [];
+ }
- this.options = options || defaults$2;
- }
+ 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]
+ */
- _createClass$1(Renderer, [{
- key: "code",
- value: function code(_code, infostring, escaped) {
- var lang = (infostring || '').match(/\S*/)[0];
- if (this.options.highlight) {
- var out = this.options.highlight(_code, lang);
+ function unzipWith(array, iteratee) {
+ if (!(array && array.length)) {
+ return [];
+ }
- if (out != null && out !== _code) {
- escaped = true;
- _code = out;
+ var result = unzip(array);
+
+ if (iteratee == null) {
+ return result;
}
+
+ 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]
+ */
- _code = _code.replace(/\n$/, '') + '\n';
- if (!lang) {
- return '<pre><code>' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
- }
+ var without = baseRest(function (array, values) {
+ return isArrayLikeObject(array) ? baseDifference(array, values) : [];
+ });
+ /**
+ * Creates an array of unique values that is the
+ * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+ * of the given arrays. The order of result values is determined by the order
+ * they occur in the arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.difference, _.without
+ * @example
+ *
+ * _.xor([2, 1], [2, 3]);
+ * // => [1, 3]
+ */
- return '<pre><code class="' + this.options.langPrefix + escape$2(lang, true) + '">' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
- }
- }, {
- key: "blockquote",
- value: function blockquote(quote) {
- return '<blockquote>\n' + quote + '</blockquote>\n';
- }
- }, {
- key: "html",
- value: function html(_html) {
- return _html;
- }
- }, {
- key: "heading",
- value: function heading(text, level, raw, slugger) {
- if (this.options.headerIds) {
- return '<h' + level + ' id="' + this.options.headerPrefix + slugger.slug(raw) + '">' + text + '</h' + level + '>\n';
- } // ignore IDs
+ 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 }]
+ */
+ var xorBy = baseRest(function (arrays) {
+ var iteratee = last(arrays);
- return '<h' + level + '>' + text + '</h' + level + '>\n';
- }
- }, {
- key: "hr",
- value: function hr() {
- return this.options.xhtml ? '<hr/>\n' : '<hr>\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 '<li>' + text + '</li>\n';
- }
- }, {
- key: "checkbox",
- value: function checkbox(checked) {
- return '<input ' + (checked ? 'checked="" ' : '') + 'disabled="" type="checkbox"' + (this.options.xhtml ? ' /' : '') + '> ';
- }
- }, {
- key: "paragraph",
- value: function paragraph(text) {
- return '<p>' + text + '</p>\n';
- }
- }, {
- key: "table",
- value: function table(header, body) {
- if (body) body = '<tbody>' + body + '</tbody>';
- return '<table>\n' + '<thead>\n' + header + '</thead>\n' + body + '</table>\n';
- }
- }, {
- key: "tablerow",
- value: function tablerow(content) {
- return '<tr>\n' + content + '</tr>\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
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined$1;
+ }
- }, {
- key: "strong",
- value: function strong(text) {
- return '<strong>' + text + '</strong>';
- }
- }, {
- key: "em",
- value: function em(text) {
- return '<em>' + text + '</em>';
- }
- }, {
- key: "codespan",
- value: function codespan(text) {
- return '<code>' + text + '</code>';
- }
- }, {
- key: "br",
- value: function br() {
- return this.options.xhtml ? '<br/>' : '<br>';
- }
- }, {
- key: "del",
- value: function del(text) {
- return '<del>' + text + '</del>';
- }
- }, {
- key: "link",
- value: function link(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
+ });
+ /**
+ * This method is like `_.xor` except that it accepts `comparator` which is
+ * invoked to compare elements of `arrays`. The order of result values is
+ * determined by the order they occur in the arrays. 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 filtered values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.xorWith(objects, others, _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
- if (href === null) {
- 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]]
+ */
- var out = '<a href="' + escape$2(href) + '"';
+ 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 }
+ */
- if (title) {
- out += ' title="' + title + '"';
+ 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 }] } }
+ */
- out += '>' + text + '</a>';
- return out;
- }
- }, {
- key: "image",
- value: function image(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
- if (href === null) {
- return text;
+ 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]
+ */
- var out = '<img src="' + href + '" alt="' + text + '"';
- if (title) {
- out += ' title="' + title + '"';
+ 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);
+ });
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * 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'
+ */
+
+ 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]
+ */
- out += this.options.xhtml ? '/>' : '>';
- return out;
- }
- }, {
- key: "text",
- value: function text(_text) {
- return _text;
- }
- }]);
- return Renderer;
- }();
+ 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']
+ */
- var TextRenderer_1 = /*#__PURE__*/function () {
- function TextRenderer() {
- _classCallCheck$1(this, TextRenderer);
- }
- _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 '';
- }
- }]);
+ 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 TextRenderer;
- }();
- var Slugger_1 = /*#__PURE__*/function () {
- function Slugger() {
- _classCallCheck$1(this, Slugger);
+ 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);
+ };
- this.seen = {};
- }
+ if (length > 1 || this.__actions__.length || !(value instanceof LazyWrapper) || !isIndex(start)) {
+ return this.thru(interceptor);
+ }
- _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
- */
+ value = value.slice(start, +start + (length ? 1 : 0));
- }, {
- key: "getNextSafeSlug",
- value: function getNextSafeSlug(originalSlug, isDryRun) {
- var slug = originalSlug;
- var occurenceAccumulator = 0;
+ value.__actions__.push({
+ 'func': thru,
+ 'args': [interceptor],
+ 'thisArg': undefined$1
+ });
- if (this.seen.hasOwnProperty(slug)) {
- occurenceAccumulator = this.seen[originalSlug];
+ return new LodashWrapper(value, this.__chain__).thru(function (array) {
+ if (length && !array.length) {
+ array.push(undefined$1);
+ }
- do {
- occurenceAccumulator++;
- slug = originalSlug + '-' + occurenceAccumulator;
- } while (this.seen.hasOwnProperty(slug));
+ 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' }
+ */
+
+ 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]
+ */
- if (!isDryRun) {
- this.seen[originalSlug] = occurenceAccumulator;
- this.seen[slug] = 0;
+
+ 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 }
+ */
- 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);
- }
- }]);
+ function wrapperNext() {
+ if (this.__values__ === undefined$1) {
+ this.__values__ = toArray(this.value());
+ }
- return Slugger;
- }();
+ 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]
+ */
- var Renderer$1 = Renderer_1;
- var TextRenderer$1 = TextRenderer_1;
- var Slugger$1 = Slugger_1;
- var defaults$1 = defaults$5.exports.defaults;
- var unescape$1 = helpers.unescape;
- /**
- * Parsing & Compiling
- */
- var Parser_1 = /*#__PURE__*/function () {
- function Parser(options) {
- _classCallCheck$1(this, Parser);
+ 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]
+ */
- 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
- */
+ function wrapperPlant(value) {
+ var result,
+ parent = this;
- _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;
+ while (parent instanceof baseLodash) {
+ var clone = wrapperClone(parent);
+ clone.__index__ = 0;
+ clone.__values__ = undefined$1;
- for (i = 0; i < l; i++) {
- token = tokens[i];
+ if (result) {
+ previous.__wrapped__ = clone;
+ } else {
+ result = clone;
+ }
- switch (token.type) {
- case 'space':
- {
- continue;
- }
+ var previous = clone;
+ parent = parent.__wrapped__;
+ }
- case 'hr':
- {
- out += this.renderer.hr();
- 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 'heading':
- {
- out += this.renderer.heading(this.parseInline(token.tokens), token.depth, unescape$1(this.parseInline(token.tokens, this.textRenderer)), this.slugger);
- continue;
- }
- case 'code':
- {
- out += this.renderer.code(token.text, token.lang, token.escaped);
- continue;
- }
+ function wrapperReverse() {
+ var value = this.__wrapped__;
- case 'table':
- {
- header = ''; // header
+ if (value instanceof LazyWrapper) {
+ var wrapped = value;
- cell = '';
- l2 = token.header.length;
+ if (this.__actions__.length) {
+ wrapped = new LazyWrapper(this);
+ }
- for (j = 0; j < l2; j++) {
- cell += this.renderer.tablecell(this.parseInline(token.tokens.header[j]), {
- header: true,
- align: token.align[j]
- });
- }
+ wrapped = wrapped.reverse();
- header += this.renderer.tablerow(cell);
- body = '';
- l2 = token.cells.length;
+ wrapped.__actions__.push({
+ 'func': thru,
+ 'args': [reverse],
+ 'thisArg': undefined$1
+ });
- for (j = 0; j < l2; j++) {
- row = token.tokens.cells[j];
- cell = '';
- l3 = row.length;
+ return new LodashWrapper(wrapped, this.__chain__);
+ }
- for (k = 0; k < l3; k++) {
- cell += this.renderer.tablecell(this.parseInline(row[k]), {
- header: false,
- align: token.align[k]
- });
- }
+ 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]
+ */
- body += this.renderer.tablerow(cell);
- }
- out += this.renderer.table(header, body);
- continue;
- }
+ function wrapperValue() {
+ return baseWrapperValue(this.__wrapped__, this.__actions__);
+ }
+ /*------------------------------------------------------------------------*/
- case 'blockquote':
- {
- body = this.parse(token.tokens);
- out += this.renderer.blockquote(body);
- continue;
- }
+ /**
+ * 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 }
+ */
- case 'list':
- {
- ordered = token.ordered;
- start = token.start;
- loose = token.loose;
- l2 = token.items.length;
- body = '';
- for (j = 0; j < l2; j++) {
- item = token.items[j];
- checked = item.checked;
- task = item.task;
- itemBody = '';
+ 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
+ */
- if (item.task) {
- checkbox = this.renderer.checkbox(checked);
+ function every(collection, predicate, guard) {
+ var func = isArray(collection) ? arrayEvery : baseEvery;
- if (loose) {
- if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
- item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined$1;
+ }
- 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;
- }
- }
+ 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']
+ */
- itemBody += this.parse(item.tokens, loose);
- body += this.renderer.listitem(itemBody, task, checked);
- }
- out += this.renderer.list(body, ordered, start);
- continue;
- }
+ 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'
+ */
- 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;
- }
+ 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 'text':
- {
- body = token.tokens ? this.parseInline(token.tokens) : token.text;
+ 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]
+ */
- while (i + 1 < l && tokens[i + 1].type === 'text') {
- token = tokens[++i];
- body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
- }
+ 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]
+ */
- out += top ? this.renderer.paragraph(body) : body;
- continue;
- }
- default:
- {
- var errMsg = 'Token with "' + token.type + '" type was not found.';
+ 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]]
+ */
- if (this.options.silent) {
- console.error(errMsg);
- return;
- } else {
- throw new Error(errMsg);
- }
- }
- }
+
+ 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).
+ */
- return out;
- }
- /**
- * Parse Inline Tokens
- */
- }, {
- key: "parseInline",
- value: function parseInline(tokens, renderer) {
- renderer = renderer || this.renderer;
- var out = '',
- i,
- token;
- var l = tokens.length;
+ 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`.
+ */
- for (i = 0; i < l; i++) {
- token = tokens[i];
- switch (token.type) {
- case 'escape':
- {
- out += renderer.text(token.text);
- break;
- }
+ 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'] }
+ */
- case 'html':
- {
- out += renderer.html(token.text);
- break;
- }
- case 'link':
- {
- out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
- break;
- }
+ 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
+ */
- case 'image':
- {
- out += renderer.image(token.href, token.title, token.text);
- break;
- }
+ function includes(collection, value, fromIndex, guard) {
+ collection = isArrayLike(collection) ? collection : values(collection);
+ fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
+ var length = collection.length;
- case 'strong':
- {
- out += renderer.strong(this.parseInline(token.tokens, renderer));
- break;
- }
+ if (fromIndex < 0) {
+ fromIndex = nativeMax(length + fromIndex, 0);
+ }
- case 'em':
- {
- out += renderer.em(this.parseInline(token.tokens, renderer));
- break;
- }
+ 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']]
+ */
- case 'codespan':
- {
- out += renderer.codespan(token.text);
- break;
- }
- case 'br':
- {
- out += renderer.br();
- break;
- }
+ 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 } }
+ */
- case 'del':
- {
- out += renderer.del(this.parseInline(token.tokens, renderer));
- break;
- }
+ 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']
+ */
- case 'text':
- {
- out += renderer.text(token.text);
- break;
- }
+ 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]]
+ */
- 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 orderBy(collection, iteratees, orders, guard) {
+ if (collection == null) {
+ return [];
}
- }
- return out;
- }
- }], [{
- key: "parse",
- value: function parse(tokens, options) {
- var parser = new Parser(options);
- return parser.parse(tokens);
- }
- /**
- * Static Parse Inline Method
- */
+ if (!isArray(iteratees)) {
+ iteratees = iteratees == null ? [] : [iteratees];
+ }
- }, {
- key: "parseInline",
- value: function parseInline(tokens, options) {
- var parser = new Parser(options);
- return parser.parseInline(tokens);
- }
- }]);
+ orders = guard ? undefined$1 : orders;
- return Parser;
- }();
+ if (!isArray(orders)) {
+ orders = orders == null ? [] : [orders];
+ }
- var Lexer = Lexer_1;
- var Parser = Parser_1;
- var Tokenizer = Tokenizer_1;
- var Renderer = Renderer_1;
- var TextRenderer = TextRenderer_1;
- var Slugger = Slugger_1;
- var merge = helpers.merge,
- checkSanitizeDeprecation = helpers.checkSanitizeDeprecation,
- escape$1 = helpers.escape;
- var getDefaults = defaults$5.exports.getDefaults,
- changeDefaults = defaults$5.exports.changeDefaults,
- defaults = defaults$5.exports.defaults;
- /**
- * Marked
- */
+ 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']]
+ */
- 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');
- }
- if (typeof src !== 'string') {
- throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
- }
+ 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 (typeof opt === 'function') {
- callback = opt;
- opt = null;
- }
+ 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]
+ */
- opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
- if (callback) {
- var highlight = opt.highlight;
- var tokens;
+ 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']
+ */
- try {
- tokens = Lexer.lex(src, opt);
- } catch (e) {
- return callback(e);
- }
- var done = function done(err) {
- var out;
+ 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
+ */
- if (!err) {
- try {
- if (opt.walkTokens) {
- marked.walkTokens(tokens, opt.walkTokens);
- }
- out = Parser.parse(tokens, opt);
- } catch (e) {
- err = e;
- }
+ 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]
+ */
- opt.highlight = highlight;
- return err ? callback(err) : callback(null, out);
- };
- if (!highlight || highlight.length < 3) {
- return done();
- }
+ function sampleSize(collection, n, guard) {
+ if (guard ? isIterateeCall(collection, n, guard) : n === undefined$1) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
- 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);
- }
-
- if (code != null && code !== token.text) {
- token.text = code;
- token.escaped = true;
- }
+ 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]
+ */
- pending--;
- if (pending === 0) {
- done();
- }
- });
- }, 0);
+ 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
+ */
- if (pending === 0) {
- done();
- }
- return;
- }
+ function size(collection) {
+ if (collection == null) {
+ return 0;
+ }
- try {
- var _tokens = Lexer.lex(src, opt);
+ if (isArrayLike(collection)) {
+ return isString(collection) ? stringSize(collection) : collection.length;
+ }
- if (opt.walkTokens) {
- marked.walkTokens(_tokens, opt.walkTokens);
- }
+ var tag = getTag(collection);
- return Parser.parse(_tokens, opt);
- } catch (e) {
- e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if (tag == mapTag || tag == setTag) {
+ return collection.size;
+ }
- if (opt.silent) {
- return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
- }
+ 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
+ */
- throw e;
- }
- }
- /**
- * Options
- */
+ function some(collection, predicate, guard) {
+ var func = isArray(collection) ? arraySome : baseSome;
- marked.options = marked.setOptions = function (opt) {
- merge(marked.defaults, opt);
- changeDefaults(marked.defaults);
- return marked;
- };
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined$1;
+ }
- marked.getDefaults = getDefaults;
- marked.defaults = defaults;
- /**
- * Use Extension
- */
+ 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]]
+ */
- marked.use = function (extension) {
- var opts = merge({}, extension);
- if (extension.renderer) {
- (function () {
- var renderer = marked.defaults.renderer || new Renderer();
+ var sortBy = baseRest(function (collection, iteratees) {
+ if (collection == null) {
+ return [];
+ }
- var _loop = function _loop(prop) {
- var prevRenderer = renderer[prop];
+ var length = iteratees.length;
- renderer[prop] = function () {
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
- args[_key] = arguments[_key];
- }
+ 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.renderer[prop].apply(renderer, args);
+ return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+ });
+ /*------------------------------------------------------------------------*/
- if (ret === false) {
- ret = prevRenderer.apply(renderer, 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.renderer) {
- _loop(prop);
+ /**
+ * 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.
+ */
+
+
+ function after(n, func) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+
+ 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]
+ */
- opts.renderer = renderer;
- })();
- }
- if (extension.tokenizer) {
- (function () {
- var tokenizer = marked.defaults.tokenizer || new Tokenizer();
+ 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.
+ */
- var _loop2 = function _loop2(prop) {
- var prevTokenizer = tokenizer[prop];
- tokenizer[prop] = function () {
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
- args[_key2] = arguments[_key2];
- }
+ function before(n, func) {
+ var result;
- var ret = extension.tokenizer[prop].apply(tokenizer, args);
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- if (ret === false) {
- ret = prevTokenizer.apply(tokenizer, args);
+ n = toInteger(n);
+ return function () {
+ if (--n > 0) {
+ result = func.apply(this, arguments);
}
- return ret;
- };
- };
+ if (n <= 1) {
+ func = undefined$1;
+ }
- for (var prop in extension.tokenizer) {
- _loop2(prop);
+ 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!'
+ */
- opts.tokenizer = tokenizer;
- })();
- }
- if (extension.walkTokens) {
- var walkTokens = marked.defaults.walkTokens;
+ var bind = baseRest(function (func, thisArg, partials) {
+ var bitmask = WRAP_BIND_FLAG;
- opts.walkTokens = function (token) {
- extension.walkTokens(token);
+ if (partials.length) {
+ var holders = replaceHolders(partials, getHolder(bind));
+ bitmask |= WRAP_PARTIAL_FLAG;
+ }
- if (walkTokens) {
- walkTokens(token);
+ 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!'
+ */
+
+ 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;
}
- };
- }
+ /**
+ * 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.setOptions(opts);
- };
- /**
- * Run callback for every token
- */
+ 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);
+ */
- 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 debounce(func, wait, options) {
+ var lastArgs,
+ lastThis,
+ maxWait,
+ result,
+ timerId,
+ lastCallTime,
+ lastInvokeTime = 0,
+ leading = false,
+ maxing = false,
+ trailing = true;
- 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();
- }
+ wait = toNumber(wait) || 0;
- var _iterator3 = _createForOfIteratorHelper(token.tokens.cells),
- _step3;
+ 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;
+ }
- try {
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
- var row = _step3.value;
+ function invokeFunc(time) {
+ var args = lastArgs,
+ thisArg = lastThis;
+ lastArgs = lastThis = undefined$1;
+ lastInvokeTime = time;
+ result = func.apply(thisArg, args);
+ return result;
+ }
- var _iterator4 = _createForOfIteratorHelper(row),
- _step4;
+ function leadingEdge(time) {
+ // Reset any `maxWait` timer.
+ lastInvokeTime = time; // Start the timer for the trailing edge.
- 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();
- }
+ timerId = setTimeout(timerExpired, wait); // Invoke the leading edge.
- break;
- }
+ return leading ? invokeFunc(time) : result;
+ }
- case 'list':
- {
- marked.walkTokens(token.items, callback);
- break;
- }
+ function remainingWait(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime,
+ timeWaiting = wait - timeSinceLastCall;
+ return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
+ }
- default:
- {
- if (token.tokens) {
- marked.walkTokens(token.tokens, callback);
- }
- }
- }
- }
- } catch (err) {
- _iterator.e(err);
- } finally {
- _iterator.f();
- }
- };
- /**
- * Parse Inline
- */
+ 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.
+ return lastCallTime === undefined$1 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
+ }
- 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 timerExpired() {
+ var time = now();
- if (typeof src !== 'string') {
- throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
- }
+ if (shouldInvoke(time)) {
+ return trailingEdge(time);
+ } // Restart the timer.
- opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
- try {
- var tokens = Lexer.lexInline(src, opt);
+ timerId = setTimeout(timerExpired, remainingWait(time));
+ }
- if (opt.walkTokens) {
- marked.walkTokens(tokens, opt.walkTokens);
- }
+ function trailingEdge(time) {
+ timerId = undefined$1; // Only invoke if we have `lastArgs` which means `func` has been
+ // debounced at least once.
- return Parser.parseInline(tokens, opt);
- } catch (e) {
- e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if (trailing && lastArgs) {
+ return invokeFunc(time);
+ }
- if (opt.silent) {
- return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
- }
+ lastArgs = lastThis = undefined$1;
+ return result;
+ }
- throw e;
- }
- };
- /**
- * Expose
- */
+ function cancel() {
+ if (timerId !== undefined$1) {
+ clearTimeout(timerId);
+ }
+ lastInvokeTime = 0;
+ lastArgs = lastCallTime = lastThis = timerId = undefined$1;
+ }
- marked.Parser = Parser;
- marked.parser = Parser.parse;
- marked.Renderer = Renderer;
- marked.TextRenderer = TextRenderer;
- marked.Lexer = Lexer;
- marked.lexer = Lexer.lex;
- marked.Tokenizer = Tokenizer;
- marked.Slugger = Slugger;
- marked.parse = marked;
- var marked_1 = marked;
+ function flush() {
+ return timerId === undefined$1 ? result : trailingEdge(now());
+ }
- 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 debounced() {
+ var time = now(),
+ isInvoking = shouldInvoke(time);
+ lastArgs = arguments;
+ lastThis = this;
+ lastCallTime = time;
- var _cache;
+ if (isInvoking) {
+ if (timerId === undefined$1) {
+ return leadingEdge(lastCallTime);
+ }
- function abortRequest$4(controller) {
- if (controller) {
- controller.abort();
- }
- }
+ if (maxing) {
+ // Handle invocations in a tight loop.
+ clearTimeout(timerId);
+ timerId = setTimeout(timerExpired, wait);
+ return invokeFunc(lastCallTime);
+ }
+ }
- function abortUnwantedRequests$1(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ if (timerId === undefined$1) {
+ timerId = setTimeout(timerExpired, wait);
+ }
- if (!wanted) {
- abortRequest$4(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
+ return result;
+ }
- 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
+ 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.
+ */
- function updateRtree$1(item, replace) {
- _cache.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
+ 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.
+ */
- if (replace) {
- _cache.rtree.insert(item);
- }
- } // Issues shouldn't obscure each other
+ 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']
+ */
+ 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;
+ */
- function preventCoincident(loc) {
- var coincident = false;
- 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);
+ function memoize(func, resolver) {
+ if (typeof func != 'function' || resolver != null && typeof resolver != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- return loc;
- }
+ var memoized = function memoized() {
+ var args = arguments,
+ key = resolver ? resolver.apply(this, args) : args[0],
+ cache = memoized.cache;
- 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]);
- }, []);
- });
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
- if (!_cache) {
- this.reset();
- }
+ var result = func.apply(this, args);
+ memoized.cache = cache.set(key, result) || cache;
+ return result;
+ };
- this.event = utilRebind(this, dispatch$5, 'on');
- },
- reset: function reset() {
- var _strings = {};
- var _colors = {};
+ memoized.cache = new (memoize.Cache || MapCache)();
+ return memoized;
+ } // Expose `MapCache`.
- if (_cache) {
- Object.values(_cache.inflightTile).forEach(abortRequest$4); // Strings and colors are static and should not be re-populated
- _strings = _cache.strings;
- _colors = _cache.colors;
- }
+ 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 = {
- data: {},
- loadedTile: {},
- inflightTile: {},
- inflightPost: {},
- closed: {},
- rtree: new RBush(),
- strings: _strings,
- colors: _colors
- };
- },
- loadIssues: function loadIssues(projection) {
- var _this = this;
+ function negate(predicate) {
+ if (typeof predicate != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- 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
+ return function () {
+ var args = arguments;
- var tiles = tiler$4.zoomExtent([_tileZoom$1, _tileZoom$1]).getTiles(projection); // abort inflight requests that are no longer needed
+ switch (args.length) {
+ case 0:
+ return !predicate.call(this);
- abortUnwantedRequests$1(_cache, tiles); // issue new requests..
+ case 1:
+ return !predicate.call(this, args[0]);
- tiles.forEach(function (tile) {
- if (_cache.loadedTile[tile.id] || _cache.inflightTile[tile.id]) return;
+ case 2:
+ return !predicate.call(this, args[0], args[1]);
- var _tile$xyz = _slicedToArray(tile.xyz, 3),
- x = _tile$xyz[0],
- y = _tile$xyz[1],
- z = _tile$xyz[2];
+ case 3:
+ return !predicate.call(this, args[0], args[1], args[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;
+ 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
+ */
- 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 itemType = "".concat(item, "-").concat(cl); // Filter out unsupported issue types (some are too specific or advanced)
+ 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 (itemType in _osmoseData.icons) {
- var loc = issue.geometry.coordinates; // lon, lat
- loc = preventCoincident(loc);
- var d = new QAItem(loc, _this, itemType, id, {
- item: item
- }); // Setting elems here prevents UI detail requests
+ 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);
- if (item === 8300 || item === 8360) {
- d.elems = [];
- }
+ while (++index < length) {
+ args[index] = transforms[index].call(this, args[index]);
+ }
- _cache.data[d.id] = d;
+ 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'
+ */
- _cache.rtree.insert(encodeIssueRtree(d));
- }
- });
- }
+ 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'
+ */
- dispatch$5.call('loaded');
- })["catch"](function () {
- delete _cache.inflightTile[tile.id];
- _cache.loadedTile[tile.id] = true;
+ var partialRight = baseRest(function (func, partials) {
+ var holders = replaceHolders(partials, getHolder(partialRight));
+ return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined$1, partials, holders);
});
- });
- },
- loadIssueDetail: function loadIssueDetail(issue) {
- var _this2 = this;
+ /**
+ * 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']
+ */
- // Issue details only need to be fetched once
- if (issue.elems !== undefined) {
- return Promise.resolve(issue);
- }
+ 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'
+ */
- var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "?langs=").concat(_mainLocalizer.localeCode());
+ function rest(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- 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
+ 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
+ */
- issue.detail = data.subtitle ? marked_1(data.subtitle.auto) : '';
- _this2.replaceItem(issue);
- };
+ function spread(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- 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);
+ start = start == null ? 0 : nativeMax(toInteger(start), 0);
+ return baseRest(function (args) {
+ var array = args[start],
+ otherArgs = castSlice(args, 0, start);
- 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
+ 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);
+ */
- 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
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
- var allRequests = items.map(function (itemType) {
- // No need to request data we already have
- if (itemType in _cache.strings[locale]) return null;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- 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$;
+ if (isObject(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
- var _cat$items = _slicedToArray(cat.items, 1),
- _cat$items$ = _cat$items[0],
- item = _cat$items$ === void 0 ? {
- "class": []
- } : _cat$items$;
+ 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]
+ */
- 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)
+ 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 '<p>' + func(text) + '</p>';
+ * });
+ *
+ * p('fred, barney, & pebbles');
+ * // => '<p>fred, barney, & pebbles</p>'
+ */
- if (!cl) {
- /* eslint-disable no-console */
- console.log("Osmose strings request (".concat(itemType, ") had unexpected data"));
- /* eslint-enable no-console */
- return;
- } // Cache served item colors to automatically style issue markers later
+ function wrap(value, wrapper) {
+ return partial(castFunction(wrapper), value);
+ }
+ /*------------------------------------------------------------------------*/
+ /**
+ * 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
+ */
- var itemInt = item.item,
- color = item.color;
- 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
+ function castArray() {
+ if (!arguments.length) {
+ return [];
+ }
+ 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
+ */
- 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 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
+ */
- 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
+ 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
+ */
- 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;
- 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 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
+ */
- var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "/").concat(issue.newStatus);
- var controller = new AbortController();
+ 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 after = function after() {
- delete _cache.inflightPost[issue.id];
- _this3.removeItem(issue);
+ 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 (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;
- }
- _cache.closed[issue.item] += 1;
+ 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 (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
+ 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
+ */
- 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);
- }
- };
+ 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
+ */
- var ieee754$1 = {};
+ 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
+ */
- /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
+ 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
+ */
- ieee754$1.read = function (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;
+ 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
+ */
- for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+ 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
+ */
- m = e & (1 << -nBits) - 1;
- e >>= -nBits;
- nBits += mLen;
- for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+ 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
+ */
- 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 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
+ */
- ieee754$1.write = function (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);
+ 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
+ */
- if (value * (c = Math.pow(2, -e)) < 1) {
- e--;
- c *= 2;
- }
+ 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('<body>');
+ * // => false
+ */
- if (e + eBias >= 1) {
- value += rt / c;
- } else {
- value += rt * Math.pow(2, 1 - eBias);
- }
+ 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
+ */
- 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;
- }
- }
+ function isEmpty(value) {
+ if (value == null) {
+ return true;
+ }
- for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+ if (isArrayLike(value) && (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || isBuffer(value) || isTypedArray(value) || isArguments(value))) {
+ return !value.length;
+ }
- e = e << mLen | m;
- eLen += mLen;
+ var tag = getTag(value);
- for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+ if (tag == mapTag || tag == setTag) {
+ return !value.size;
+ }
- buffer[offset + i - d] |= s * 128;
- };
+ if (isPrototype(value)) {
+ return !baseKeys(value).length;
+ }
- var pbf = Pbf;
- var ieee754 = ieee754$1;
+ for (var key in value) {
+ if (hasOwnProperty.call(value, key)) {
+ return false;
+ }
+ }
- 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;
- }
+ 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
+ */
- Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
- Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+ 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
+ */
- Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
- Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+ 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
+ */
- 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 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 isError(value) {
+ if (!isObjectLike(value)) {
+ return 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);
- }
+ 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
+ */
- 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;
- 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
+ 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
+ */
- 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 || [];
+ 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.readVarint(isSigned));
- }
- return arr;
- },
- readPackedSVarint: function readPackedSVarint(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
- 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.readSVarint());
- }
- return arr;
- },
- readPackedBoolean: function readPackedBoolean(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
- var end = readPackedEnd(this);
- arr = arr || [];
+ 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 (this.pos < end) {
- arr.push(this.readBoolean());
- }
- return arr;
- },
- readPackedFloat: function readPackedFloat(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
- var end = readPackedEnd(this);
- arr = arr || [];
+ 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
+ */
- while (this.pos < end) {
- arr.push(this.readFloat());
- }
- return arr;
- },
- readPackedDouble: function readPackedDouble(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
- var end = readPackedEnd(this);
- arr = arr || [];
+ function isObject(value) {
+ var type = _typeof(value);
- while (this.pos < end) {
- arr.push(this.readDouble());
- }
+ 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
+ */
- return arr;
- },
- readPackedFixed32: function readPackedFixed32(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
- var end = readPackedEnd(this);
- arr = arr || [];
- while (this.pos < end) {
- arr.push(this.readFixed32());
- }
+ 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
+ */
- return arr;
- },
- readPackedSFixed32: function readPackedSFixed32(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
- var end = readPackedEnd(this);
- arr = arr || [];
- while (this.pos < end) {
- arr.push(this.readSFixed32());
- }
+ 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
+ */
- return arr;
- },
- readPackedFixed64: function readPackedFixed64(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
- var end = readPackedEnd(this);
- arr = arr || [];
+ 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
+ */
- 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 || [];
+ 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
+ */
- 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 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
+ */
- 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 isNative(value) {
+ if (isMaskable(value)) {
+ throw new Error(CORE_ERROR_TEXT);
+ }
- if (val > 0xfffffff || val < 0) {
- writeBigVarint(val, this);
- return;
- }
+ 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
+ */
- 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
- var startPos = this.pos; // write the string directly to the buffer and see how much was written
+ 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
+ */
- 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 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
+ */
- 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
+ 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
+ */
- 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 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 toNum(low, high, isSigned) {
- if (isSigned) {
- return high * 0x100000000 + (low >>> 0);
- }
-
- return (high >>> 0) * 0x100000000 + (low >>> 0);
- }
-
- 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 isPlainObject(value) {
+ if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
+ return false;
+ }
- if (low ^ 0xffffffff) {
- low = low + 1 | 0;
- } else {
- low = 0;
- high = high + 1 | 0;
- }
- }
+ var proto = getPrototype(value);
- if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
- throw new Error('Given varint doesn\'t fit into 10 bytes');
- }
+ if (proto === null) {
+ return true;
+ }
- pbf.realloc(10);
- writeBigVarintLow(low, high, pbf);
- writeBigVarintHigh(high, pbf);
- }
+ 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 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;
- }
+ 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 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
+ 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
+ */
- pbf.realloc(extraLen);
- for (var i = pbf.pos - 1; i >= startPos; i--) {
- pbf.buf[i + extraLen] = pbf.buf[i];
- }
- }
+ 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 _writePackedVarint(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeVarint(arr[i]);
- }
- }
+ 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 _writePackedSVarint(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeSVarint(arr[i]);
- }
- }
- function _writePackedFloat(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFloat(arr[i]);
- }
- }
+ 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 _writePackedDouble(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeDouble(arr[i]);
- }
- }
- function _writePackedBoolean(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeBoolean(arr[i]);
- }
- }
+ 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
+ */
- function _writePackedFixed(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFixed32(arr[i]);
- }
- }
+ 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
+ */
- function _writePackedSFixed(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeSFixed32(arr[i]);
- }
- }
- function _writePackedFixed2(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFixed64(arr[i]);
- }
- }
+ function isWeakMap(value) {
+ return isObjectLike(value) && getTag(value) == weakMapTag;
+ }
+ /**
+ * 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
+ */
- 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
+ function isWeakSet(value) {
+ return isObjectLike(value) && baseGetTag(value) == weakSetTag;
+ }
+ /**
+ * 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
+ */
- 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;
- }
+ 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
+ */
- function readInt32(buf, pos) {
- return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + (buf[pos + 3] << 24);
- }
+ 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 readUtf8(buf, pos, end) {
- var str = '';
- var i = pos;
+ function toArray(value) {
+ if (!value) {
+ return [];
+ }
- while (i < end) {
- var b0 = buf[i];
- var c = null; // codepoint
+ if (isArrayLike(value)) {
+ return isString(value) ? stringToArray(value) : copyArray(value);
+ }
- var bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1;
- if (i + bytesPerSequence > end) break;
- var b1, b2, b3;
+ if (symIterator && value[symIterator]) {
+ return iteratorToArray(value[symIterator]());
+ }
- if (bytesPerSequence === 1) {
- if (b0 < 0x80) {
- c = b0;
+ var tag = getTag(value),
+ func = tag == mapTag ? mapToArray : tag == setTag ? setToArray : values;
+ return func(value);
}
- } else if (bytesPerSequence === 2) {
- b1 = buf[i + 1];
+ /**
+ * 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) {
- c = (b0 & 0x1F) << 0x6 | b1 & 0x3F;
- if (c <= 0x7F) {
- c = null;
+ function toFinite(value) {
+ if (!value) {
+ return value === 0 ? value : 0;
}
- }
- } else if (bytesPerSequence === 3) {
- b1 = buf[i + 1];
- b2 = buf[i + 2];
- if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
- c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | b2 & 0x3F;
+ value = toNumber(value);
- if (c <= 0x7FF || c >= 0xD800 && c <= 0xDFFF) {
- c = null;
+ if (value === INFINITY || value === -INFINITY) {
+ var sign = value < 0 ? -1 : 1;
+ return sign * MAX_INTEGER;
}
+
+ return value === value ? value : 0;
}
- } else if (bytesPerSequence === 4) {
- b1 = buf[i + 1];
- b2 = buf[i + 2];
- b3 = buf[i + 3];
+ /**
+ * 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 ((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 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
+ */
- 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 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
+ */
- return str;
- }
- function readUtf8TextDecoder(buf, pos, end) {
- return utf8TextDecoder.decode(buf.subarray(pos, end));
- }
+ function toNumber(value) {
+ if (typeof value == 'number') {
+ return value;
+ }
- function writeUtf8(buf, str, pos) {
- for (var i = 0, c, lead; i < str.length; i++) {
- c = str.charCodeAt(i); // code point
+ if (isSymbol(value)) {
+ return NAN;
+ }
- 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;
+ if (isObject(value)) {
+ var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+ value = isObject(other) ? other + '' : other;
}
- } else {
- if (c > 0xDBFF || i + 1 === str.length) {
- buf[pos++] = 0xEF;
- buf[pos++] = 0xBF;
- buf[pos++] = 0xBD;
- } else {
- lead = c;
+
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
}
- continue;
+ 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;
}
- } else if (lead) {
- buf[pos++] = 0xEF;
- buf[pos++] = 0xBF;
- buf[pos++] = 0xBD;
- lead = null;
- }
+ /**
+ * 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 }
+ */
- 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;
- }
- buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+ 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
+ */
- buf[pos++] = c & 0x3F | 0x80;
- }
- }
- return pos;
- }
+ 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'
+ */
- var vectorTile = {};
- var pointGeometry = Point$1;
- /**
- * 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 toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+ /*------------------------------------------------------------------------*/
- function Point$1(x, y) {
- this.x = x;
- this.y = y;
- }
+ /**
+ * 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 }
+ */
- Point$1.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$1(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);
- },
+ var assign = createAssigner(function (object, source) {
+ if (isPrototype(source) || isArrayLike(source)) {
+ copyObject(source, keys(source), object);
+ return;
+ }
- /**
- * 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);
- },
-
- /**
- * 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);
- },
+ 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 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 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 }
+ */
- /**
- * 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);
- },
+ 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 }
+ */
- /**
- * 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 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]
+ */
- /**
- * 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 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
+ */
- /**
- * 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);
- },
+ 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 }
+ */
- /**
- * Multiply this point by a 4x1 transformation matrix
- * @param {Array<Number>} m transformation matrix
- * @return {Point} output point
- */
- matMult: function matMult(m) {
- return this.clone()._matMult(m);
- },
- /**
- * 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();
- },
+ var defaults = baseRest(function (object, sources) {
+ object = Object(object);
+ var index = -1;
+ var length = sources.length;
+ var guard = length > 2 ? sources[2] : undefined$1;
- /**
- * 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();
- },
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ length = 1;
+ }
- /**
- * 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();
- },
+ while (++index < length) {
+ var source = sources[index];
+ var props = keysIn(source);
+ var propsIndex = -1;
+ var propsLength = props.length;
- /**
- * 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);
- },
+ while (++propsIndex < propsLength) {
+ var key = props[propsIndex];
+ var value = object[key];
- /**
- * 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;
- },
+ if (value === undefined$1 || eq(value, objectProto[key]) && !hasOwnProperty.call(object, key)) {
+ object[key] = source[key];
+ }
+ }
+ }
- /**
- * 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));
- },
+ 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 } }
+ */
- /**
- * 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;
- },
+ 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 from the 0, 0 coordinate to this point, in radians
- * coordinates.
- * @return {Number} angle
- */
- angle: function angle() {
- return Math.atan2(this.y, this.x);
- },
+ 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'
+ */
- /**
- * 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);
- },
- /**
- * 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 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).
+ */
- /*
- * 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<Number>|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 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'.
+ */
- Point$1.convert = function (a) {
- if (a instanceof Point$1) {
- return a;
- }
- if (Array.isArray(a)) {
- return new Point$1(a[0], a[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).
+ */
- return a;
- };
- var Point = pointGeometry;
- var vectortilefeature = VectorTileFeature$1;
+ 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 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 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']
+ */
- 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 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']
+ */
- while (pbf.pos < end) {
- var key = feature._keys[pbf.readVarint()],
- value = feature._values[pbf.readVarint()];
- feature.properties[key] = value;
- }
- }
+ 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'
+ */
- 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 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
+ */
- while (pbf.pos < end) {
- if (length <= 0) {
- var cmdLen = pbf.readVarint();
- cmd = cmdLen & 0x7;
- length = cmdLen >> 3;
- }
- length--;
+ 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
+ */
- if (cmd === 1 || cmd === 2) {
- x += pbf.readSVarint();
- y += pbf.readSVarint();
- if (cmd === 1) {
- // moveTo
- if (line) lines.push(line);
- line = [];
+ 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' }
+ */
- line.push(new Point(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;
- };
+ var invert = createInverter(function (result, value, key) {
+ if (value != null && typeof value.toString != 'function') {
+ value = nativeObjectToString.call(value);
+ }
- 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;
+ 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'] }
+ */
- while (pbf.pos < end) {
- if (length <= 0) {
- var cmdLen = pbf.readVarint();
- cmd = cmdLen & 0x7;
- length = cmdLen >> 3;
- }
+ var invertBy = createInverter(function (result, value, key) {
+ if (value != null && typeof value.toString != 'function') {
+ value = nativeObjectToString.call(value);
+ }
- length--;
+ 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]
+ */
- 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 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']
+ */
- return [x1, y1, x2, y2];
- };
+ 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)
+ */
- 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;
- 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 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 }
+ */
- switch (this.type) {
- case 1:
- var points = [];
- for (i = 0; i < coords.length; i++) {
- points[i] = coords[i][0];
+ 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)
+ */
- coords = points;
- project(coords);
- break;
- case 2:
- for (i = 0; i < coords.length; i++) {
- project(coords[i]);
+ 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;
-
- case 3:
- coords = classifyRings(coords);
- for (i = 0; i < coords.length; i++) {
- for (j = 0; j < coords[i].length; j++) {
- project(coords[i][j]);
- }
- }
+ 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] }
+ */
- break;
- }
+ 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 (coords.length === 1) {
- coords = coords[0];
- } else {
- type = 'Multi' + type;
- }
+ var omit = flatRest(function (object, paths) {
+ var result = {};
- var result = {
- type: "Feature",
- geometry: {
- type: type,
- coordinates: coords
- },
- properties: this.properties
- };
+ if (object == null) {
+ return result;
+ }
- if ('id' in this) {
- result.id = this.id;
- }
+ var isDeep = false;
+ paths = arrayMap(paths, function (path) {
+ path = castPath(path, object);
+ isDeep || (isDeep = path.length > 1);
+ return path;
+ });
+ copyObject(object, getAllKeysIn(object), result);
- return result;
- }; // classifies an array of rings into polygons with outer rings and holes
+ if (isDeep) {
+ result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone);
+ }
+ var length = paths.length;
- function classifyRings(rings) {
- var len = rings.length;
- if (len <= 1) return [rings];
- var polygons = [],
- polygon,
- ccw;
+ while (length--) {
+ baseUnset(result, paths[length]);
+ }
- for (var i = 0; i < len; i++) {
- var area = signedArea(rings[i]);
- if (area === 0) continue;
- if (ccw === undefined) ccw = area < 0;
+ 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' }
+ */
- if (ccw === area < 0) {
- if (polygon) polygons.push(polygon);
- polygon = [rings[i]];
- } else {
- polygon.push(rings[i]);
- }
- }
+ 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 }
+ */
- if (polygon) polygons.push(polygon);
- return polygons;
- }
- function signedArea(ring) {
- var sum = 0;
+ 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 }
+ */
- 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);
- }
+ function pickBy(object, predicate) {
+ if (object == null) {
+ return {};
+ }
- return sum;
- }
+ 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'
+ */
- var VectorTileFeature = vectortilefeature;
- var vectortilelayer = VectorTileLayer$1;
- function VectorTileLayer$1(pbf, end) {
- // Public
- this.version = 1;
- this.name = null;
- this.extent = 4096;
- this.length = 0; // Private
+ function result(object, path, defaultValue) {
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length; // Ensure the loop is entered when path is empty.
- this._pbf = pbf;
- this._keys = [];
- this._values = [];
- this._features = [];
- pbf.readFields(readLayer, this, end);
- this.length = this._features.length;
- }
+ if (!length) {
+ length = 1;
+ object = undefined$1;
+ }
- 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));
- }
+ while (++index < length) {
+ var value = object == null ? undefined$1 : object[toKey(path[index])];
- function readValueMessage(pbf) {
- var value = null,
- end = pbf.readVarint() + pbf.pos;
+ if (value === undefined$1) {
+ index = length;
+ value = defaultValue;
+ }
- 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;
- }
+ object = isFunction(value) ? value.call(object) : value;
+ }
- return value;
- } // return feature `i` from this layer as a `VectorTileFeature`
+ 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
+ */
- 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];
+ 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 end = this._pbf.readVarint() + this._pbf.pos;
- return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
- };
+ 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)
+ */
- var VectorTileLayer = vectortilelayer;
- var vectortile = VectorTile$1;
- function VectorTile$1(pbf, end) {
- this.layers = pbf.readFields(readTile, {}, end);
- }
+ 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)
+ */
- 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 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 VectorTile = vectorTile.VectorTile = vectortile;
- vectorTile.VectorTileFeature = vectortilefeature;
- vectorTile.VectorTileLayer = vectortilelayer;
+ function transform(object, iteratee, accumulator) {
+ var isArr = isArray(object),
+ isArrLike = isArr || isBuffer(object) || isTypedArray(object);
+ iteratee = getIteratee(iteratee, 4);
- 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');
+ if (accumulator == null) {
+ var Ctor = object && object.constructor;
- var _loadViewerPromise$2;
+ if (isArrLike) {
+ accumulator = isArr ? new Ctor() : [];
+ } else if (isObject(object)) {
+ accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+ } else {
+ accumulator = {};
+ }
+ }
- var _mlyActiveImage;
+ (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 _mlyCache;
- var _mlyFallback = 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 _mlyHighlightedDetection;
- var _mlyShowFeatureDetections = false;
- var _mlyShowSignDetections = false;
+ 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' } }
+ */
- var _mlyViewer;
- var _mlyViewerFilter = ['all']; // Load all data for the specified type from Mapillary vector tiles
+ 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 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 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)
+ */
- 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 valuesIn(object) {
+ return object == null ? [] : baseValues(object, keysIn(object));
+ }
+ /*------------------------------------------------------------------------*/
- loadTileDataToCache(data, tile, which);
+ /**
+ * 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
+ */
- 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 clamp(number, lower, upper) {
+ if (upper === undefined$1) {
+ upper = lower;
+ lower = undefined$1;
+ }
- function loadTileDataToCache(data, tile, which) {
- var vectorTile = new VectorTile(new pbf(data));
- var features, cache, layer, i, feature, loc, d;
+ if (upper !== undefined$1) {
+ upper = toNumber(upper);
+ upper = upper === upper ? upper : 0;
+ }
- if (vectorTile.layers.hasOwnProperty('image')) {
- features = [];
- cache = _mlyCache.images;
- layer = vectorTile.layers.image;
+ if (lower !== undefined$1) {
+ lower = toNumber(lower);
+ lower = lower === lower ? lower : 0;
+ }
- 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
- });
- }
+ 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
+ */
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
- if (vectorTile.layers.hasOwnProperty('sequence')) {
- features = [];
- cache = _mlyCache.sequences;
- layer = vectorTile.layers.sequence;
+ function inRange(number, start, end) {
+ start = toFinite(start);
- for (i = 0; i < layer.length; i++) {
- feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
+ if (end === undefined$1) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
- if (cache.lineString[feature.properties.id]) {
- cache.lineString[feature.properties.id].push(feature);
- } else {
- cache.lineString[feature.properties.id] = [feature];
+ 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 (vectorTile.layers.hasOwnProperty('point')) {
- features = [];
- cache = _mlyCache[which];
- layer = vectorTile.layers.point;
- 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
- });
- }
+ function random(lower, upper, floating) {
+ if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+ upper = floating = undefined$1;
+ }
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
+ if (floating === undefined$1) {
+ if (typeof upper == 'boolean') {
+ floating = upper;
+ upper = undefined$1;
+ } else if (typeof lower == 'boolean') {
+ floating = lower;
+ lower = undefined$1;
+ }
+ }
- if (vectorTile.layers.hasOwnProperty('traffic_sign')) {
- features = [];
- cache = _mlyCache[which];
- layer = vectorTile.layers.traffic_sign;
+ if (lower === undefined$1 && upper === undefined$1) {
+ lower = 0;
+ upper = 1;
+ } else {
+ lower = toFinite(lower);
- 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 (upper === undefined$1) {
+ upper = lower;
+ lower = 0;
+ } else {
+ upper = toFinite(upper);
+ }
+ }
+
+ if (lower > upper) {
+ var temp = lower;
+ lower = upper;
+ upper = temp;
+ }
+
+ if (floating || lower % 1 || upper % 1) {
+ var rand = nativeRandom();
+ return nativeMin(lower + rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1))), upper);
+ }
+
+ 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'
+ */
+
+
+ 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'
+ */
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
- } // Get data from the API
+ 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 loadData(url) {
- return fetch(url).then(function (response) {
- if (!response.ok) {
- throw new Error(response.status + ' ' + response.statusText);
- }
+ 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
+ */
- return response.json();
- }).then(function (result) {
- if (!result) {
- return [];
- }
- return result.data || [];
- });
- } // Partition viewport into higher zoom tiles
+ 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;
+ }
+ /**
+ * 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'
+ */
- 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
+ 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/\)'
+ */
- var tiler = utilTiler().zoomExtent([z2, z2]);
- return tiler.getTiles(projection).map(function (tile) {
- return tile.extent;
- });
- } // Return no more than `limit` results per partition.
+ function escapeRegExp(string) {
+ string = toString(string);
+ return string && reHasRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string;
+ }
+ /**
+ * 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'
+ */
- 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();
- }
+ 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'
+ */
- 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();
+ 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'
+ */
- _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: {}
+ 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'
+ */
+
+ function pad(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+ var strLength = length ? stringSize(string) : 0;
+
+ if (!length || strLength >= length) {
+ return string;
+ }
+
+ var mid = (length - strLength) / 2;
+ return createPadding(nativeFloor(mid), chars) + string + createPadding(nativeCeil(mid), chars);
}
- };
- _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 = [];
+ /**
+ * 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'
+ */
- _mlyCache.images.rtree.search(bbox).forEach(function (d) {
- if (d.data.sequence_id) {
- sequenceIds[d.data.sequence_id] = true;
+
+ 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'
+ */
- Object.keys(sequenceIds).forEach(function (sequenceId) {
- if (_mlyCache.sequences.lineString[sequenceId]) {
- lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceId]);
+
+ 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;
}
- });
- 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 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]
+ */
- 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
+ function parseInt(string, radix, guard) {
+ if (guard || radix == null) {
+ radix = 0;
+ } else if (radix) {
+ radix = +radix;
+ }
- if (loadedCount === 2) resolve();
+ 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);
+ * // => ''
+ */
- var head = select('head'); // load mapillary-viewercss
- 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 repeat(string, n, guard) {
+ if (guard ? isIterateeCall(string, n, guard) : n === undefined$1) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
- 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;
+ 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 (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
- this.resetTags();
- }
- },
- // Show traffic sign detections in image viewer
- showSignDetections: function showSignDetections(value) {
- _mlyShowSignDetections = value;
- 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]);
+ 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'
+ */
- if (fromDate) {
- filter.push(['>=', 'capturedAt', new Date(fromDate).getTime()]);
- }
- if (toDate) {
- filter.push(['>=', 'capturedAt', new Date(toDate).getTime()]);
- }
+ 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 (_mlyViewer) {
- _mlyViewer.setFilter(filter);
- }
+ function split(string, separator, limit) {
+ if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+ separator = limit = undefined$1;
+ }
- _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();
+ limit = limit === undefined$1 ? MAX_ARRAY_LENGTH : limit >>> 0;
- if (isHidden && _mlyViewer) {
- wrap.selectAll('.photo-wrapper:not(.mly-wrapper)').classed('hide', true);
- wrap.selectAll('.photo-wrapper.mly-wrapper').classed('hide', false);
+ if (!limit) {
+ return [];
+ }
- _mlyViewer.resize();
- }
+ string = toString(string);
- return this;
- },
- // Hide the image viewer and resets map markers
- hideViewer: function hideViewer(context) {
- _mlyActiveImage = null;
+ if (string && (typeof separator == 'string' || separator != null && !isRegExp(separator))) {
+ separator = baseToString(separator);
- if (!_mlyFallback && _mlyViewer) {
- _mlyViewer.getComponent('sequence').stop();
- }
+ if (!separator && hasUnicode(string)) {
+ return castSlice(stringToArray(string), 0, limit);
+ }
+ }
- 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 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'
+ */
- if (imageId) {
- hash.photo = 'mapillary/' + imageId;
- } else {
- delete hash.photo;
+
+ 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
+ */
+
+ 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('<b><%- value %></b>');
+ * compiled({ 'value': '<script>' });
+ * // => '<b><script></b>'
+ *
+ * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+ * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the internal `print` function in "evaluate" delimiters.
+ * var compiled = _.template('<% print("hello " + user); %>!');
+ * compiled({ 'user': 'barney' });
+ * // => 'hello barney!'
+ *
+ * // Use the ES template literal delimiter as an "interpolate" delimiter.
+ * // Disable support by replacing the "interpolate" delimiter.
+ * var compiled = _.template('hello ${ user }!');
+ * compiled({ 'user': 'pebbles' });
+ * // => 'hello pebbles!'
+ *
+ * // Use backslashes to treat delimiters as plain text.
+ * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+ * compiled({ 'value': 'ignored' });
+ * // => '<%- value %>'
+ *
+ * // Use the `imports` option to import `jQuery` as `jq`.
+ * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+ * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+ * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+ * compiled(data);
+ * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+ *
+ * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+ * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * // var __t, __p = '';
+ * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+ * // return __p;
+ * // }
+ *
+ * // Use custom template delimiters.
+ * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+ * var compiled = _.template('hello {{ user }}!');
+ * compiled({ 'user': 'mustache' });
+ * // => 'hello mustache!'
+ *
+ * // Use the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and stack traces.
+ * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
- 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
+ function template(string, options, guard) {
+ // Based on John Resig's `tmpl` implementation
+ // (http://ejohn.org/blog/javascript-micro-templating/)
+ // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+ var settings = lodash.templateSettings;
- 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
+ if (guard && isIterateeCall(string, options, guard)) {
+ options = undefined$1;
+ }
- };
- }
+ string = toString(string);
+ options = assignInWith({}, options, settings, customDefaultsAssignIn);
+ var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn),
+ importsKeys = keys(imports),
+ importsValues = baseValues(imports, importsKeys);
+ var isEscaping,
+ isEvaluating,
+ index = 0,
+ interpolate = options.interpolate || reNoMatch,
+ source = "__p += '"; // Compile the regexp to match each delimiter.
- _mlyViewer = new mapillary.Viewer(opts);
+ var reDelimiters = RegExp((options.escape || reNoMatch).source + '|' + interpolate.source + '|' + (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' + (options.evaluate || reNoMatch).source + '|$', 'g'); // Use a sourceURL for easier debugging.
+ // The sourceURL gets injected into the source that's eval-ed, so be careful
+ // to normalize all kinds of whitespace, so e.g. newlines (and unicode versions of it) can't sneak in
+ // and escape the comment, thus injecting code that gets evaled.
- _mlyViewer.on('image', imageChanged);
+ var sourceURL = '//# sourceURL=' + (hasOwnProperty.call(options, 'sourceURL') ? (options.sourceURL + '').replace(/\s/g, ' ') : 'lodash.templateSources[' + ++templateCounter + ']') + '\n';
+ string.replace(reDelimiters, function (match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+ interpolateValue || (interpolateValue = esTemplateValue); // Escape characters that can't be included in string literals.
- _mlyViewer.on('bearing', bearingChanged);
+ source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar); // Replace delimiters with snippets.
- if (_mlyViewerFilter) {
- _mlyViewer.setFilter(_mlyViewerFilter);
- } // Register viewer resize handler
+ if (escapeValue) {
+ isEscaping = true;
+ source += "' +\n__e(" + escapeValue + ") +\n'";
+ }
+ if (evaluateValue) {
+ isEvaluating = true;
+ source += "';\n" + evaluateValue + ";\n__p += '";
+ }
- context.ui().photoviewer.on('resize.mapillary', function () {
- if (_mlyViewer) _mlyViewer.resize();
- }); // imageChanged: called after the viewer has changed images and is ready.
+ if (interpolateValue) {
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+ }
- 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);
+ index = offset + match.length; // The JS engine embedded in Adobe products needs `match` returned in
+ // order to produce the correct `offset` value.
- if (_mlyShowFeatureDetections || _mlyShowSignDetections) {
- that.updateDetections(image.id, "".concat(apiUrl, "/").concat(image.id, "/detections?access_token=").concat(accessToken, "&fields=id,image,geometry,value"));
- }
+ return match;
+ });
+ source += "';\n"; // If `variable` is not specified wrap a with-statement around the generated
+ // code to add the data object to the top of the scope chain.
- dispatch$4.call('imageChanged');
- } // bearingChanged: called when the bearing changes in the image viewer.
+ var variable = hasOwnProperty.call(options, 'variable') && options.variable;
+ if (!variable) {
+ source = 'with (obj) {\n' + source + '\n}\n';
+ } // Throw an error if a forbidden character was found in `variable`, to prevent
+ // potential command injection attacks.
+ else if (reForbiddenIdentifierChars.test(variable)) {
+ throw new Error(INVALID_TEMPL_VAR_ERROR_TEXT);
+ } // Cleanup code by stripping empty strings.
- function bearingChanged(e) {
- dispatch$4.call('bearingChanged', undefined, e);
- }
- },
- // Move to an image
- selectImage: function selectImage(context, imageId) {
- if (_mlyViewer && imageId) {
- _mlyViewer.moveTo(imageId)["catch"](function (e) {
- console.error('mly3', e); // eslint-disable-line no-console
- });
- }
-
- return this;
- },
- // Return the currently displayed image
- getActiveImage: function getActiveImage() {
- return _mlyActiveImage;
- },
- // Return a list of detection objects for the given id
- getDetections: function getDetections(id) {
- return loadData("".concat(apiUrl, "/").concat(id, "/detections?access_token=").concat(accessToken, "&fields=id,value,image"));
- },
- // Set the currently visible image
- setActiveImage: function setActiveImage(image) {
- if (image) {
- _mlyActiveImage = {
- ca: image.originalCompassAngle,
- id: image.id,
- loc: [image.originalLngLat.lng, image.originalLngLat.lat],
- is_pano: image.cameraType === 'spherical',
- sequence_id: image.sequenceId
- };
- } else {
- _mlyActiveImage = null;
- }
- },
- // Update the currently highlighted sequence and selected bubble.
- setStyles: function setStyles(context, hovered) {
- var hoveredImageId = hovered && hovered.id;
- var hoveredSequenceId = hovered && hovered.sequence_id;
- var selectedSequenceId = _mlyActiveImage && _mlyActiveImage.sequence_id;
- context.container().selectAll('.layer-mapillary .viewfield-group').classed('highlighted', function (d) {
- return d.sequence_id === selectedSequenceId || d.id === hoveredImageId;
- }).classed('hovered', function (d) {
- return d.id === hoveredImageId;
- });
- context.container().selectAll('.layer-mapillary .sequence').classed('highlighted', function (d) {
- return d.properties.id === hoveredSequenceId;
- }).classed('currentView', function (d) {
- return d.properties.id === selectedSequenceId;
- });
- return this;
- },
- // Get detections for the current image and shows them in the image viewer
- updateDetections: function updateDetections(imageId, url) {
- if (!_mlyViewer || _mlyFallback) return;
- if (!imageId) return;
- var cache = _mlyCache.image_detections;
-
- if (cache.forImageId[imageId]) {
- showDetections(_mlyCache.image_detections.forImageId[imageId]);
- } else {
- loadData(url).then(function (detections) {
- detections.forEach(function (detection) {
- if (!cache.forImageId[imageId]) {
- cache.forImageId[imageId] = [];
- }
-
- cache.forImageId[imageId].push({
- geometry: detection.geometry,
- id: detection.id,
- image_id: imageId,
- value: detection.value
- });
- });
- showDetections(_mlyCache.image_detections.forImageId[imageId] || []);
- });
- } // Create a tag for each detection and shows it in the image viewer
-
-
- function showDetections(detections) {
- var tagComponent = _mlyViewer.getComponent('tag');
-
- detections.forEach(function (data) {
- var tag = makeTag(data);
-
- if (tag) {
- tagComponent.add([tag]);
- }
- });
- } // Create a Mapillary JS tag object
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source).replace(reEmptyStringMiddle, '$1').replace(reEmptyStringTrailing, '$1;'); // Frame code as the function body.
- function makeTag(data) {
- var valueParts = data.value.split('--');
- if (!valueParts.length) return;
- var tag;
- var text;
- var color = 0xffffff;
+ source = 'function(' + (variable || 'obj') + ') {\n' + (variable ? '' : 'obj || (obj = {});\n') + "var __t, __p = ''" + (isEscaping ? ', __e = _.escape' : '') + (isEvaluating ? ', __j = Array.prototype.join;\n' + "function print() { __p += __j.call(arguments, '') }\n" : ';\n') + source + 'return __p\n}';
+ var result = attempt(function () {
+ return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined$1, importsValues);
+ }); // Provide the compiled function's source by its `toString` method or
+ // the `source` property as a convenience for inlining compiled templates.
- if (_mlyHighlightedDetection === data.id) {
- color = 0xffff00;
- text = valueParts[1];
+ result.source = source;
- if (text === 'flat' || text === 'discrete' || text === 'sign') {
- text = valueParts[2];
+ if (isError(result)) {
+ throw result;
}
- text = text.replace(/-/g, ' ');
- text = text.charAt(0).toUpperCase() + text.slice(1);
- _mlyHighlightedDetection = null;
+ return result;
}
+ /**
+ * Converts `string`, as a whole, to lower case just like
+ * [String#toLowerCase](https://mdn.io/toLowerCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.toLower('--Foo-Bar--');
+ * // => '--foo-bar--'
+ *
+ * _.toLower('fooBar');
+ * // => 'foobar'
+ *
+ * _.toLower('__FOO_BAR__');
+ * // => '__foo_bar__'
+ */
- var decodedGeometry = window.atob(data.geometry);
- var uintArray = new Uint8Array(decodedGeometry.length);
- for (var i = 0; i < decodedGeometry.length; i++) {
- uintArray[i] = decodedGeometry.charCodeAt(i);
+ function toLower(value) {
+ return toString(value).toLowerCase();
}
+ /**
+ * Converts `string`, as a whole, to upper case just like
+ * [String#toUpperCase](https://mdn.io/toUpperCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.toUpper('--foo-bar--');
+ * // => '--FOO-BAR--'
+ *
+ * _.toUpper('fooBar');
+ * // => 'FOOBAR'
+ *
+ * _.toUpper('__foo_bar__');
+ * // => '__FOO_BAR__'
+ */
- var tile = new VectorTile(new pbf(uintArray.buffer));
- var layer = tile.layers['mpy-or'];
- var geometries = layer.feature(0).loadGeometry();
- var polygon = geometries.map(function (ring) {
- return ring.map(function (point) {
- return [point.x / layer.extent, point.y / layer.extent];
- });
- });
- tag = new mapillary.OutlineTag(data.id, new mapillary.PolygonGeometry(polygon[0]), {
- text: text,
- textColor: color,
- lineColor: color,
- lineWidth: 2,
- fillColor: color,
- fillOpacity: 0.3
- });
- return tag;
- }
- },
- // Return the current cache
- cache: function cache() {
- return _mlyCache;
- }
- };
- function validationIssue(attrs) {
- this.type = attrs.type; // required - name of rule that created the issue (e.g. 'missing_tag')
+ function toUpper(value) {
+ return toString(value).toUpperCase();
+ }
+ /**
+ * Removes leading and trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trim(' abc ');
+ * // => 'abc'
+ *
+ * _.trim('-_-abc-_-', '_-');
+ * // => 'abc'
+ *
+ * _.map([' foo ', ' bar '], _.trim);
+ * // => ['foo', 'bar']
+ */
- this.subtype = attrs.subtype; // optional - category of the issue within the type (e.g. 'relation_type' under 'missing_tag')
- this.severity = attrs.severity; // required - 'warning' or 'error'
+ function trim(string, chars, guard) {
+ string = toString(string);
- this.message = attrs.message; // required - function returning localized string
+ if (string && (guard || chars === undefined$1)) {
+ return baseTrim(string);
+ }
- this.reference = attrs.reference; // optional - function(selection) to render reference information
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
- this.entityIds = attrs.entityIds; // optional - array of IDs of entities involved in the issue
+ var strSymbols = stringToArray(string),
+ chrSymbols = stringToArray(chars),
+ start = charsStartIndex(strSymbols, chrSymbols),
+ end = charsEndIndex(strSymbols, chrSymbols) + 1;
+ return castSlice(strSymbols, start, end).join('');
+ }
+ /**
+ * Removes trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimEnd(' abc ');
+ * // => ' abc'
+ *
+ * _.trimEnd('-_-abc-_-', '_-');
+ * // => '-_-abc'
+ */
- this.loc = attrs.loc; // optional - [lon, lat] to zoom in on to see the issue
- this.data = attrs.data; // optional - object containing extra data for the fixes
+ function trimEnd(string, chars, guard) {
+ string = toString(string);
- this.dynamicFixes = attrs.dynamicFixes; // optional - function(context) returning fixes
+ if (string && (guard || chars === undefined$1)) {
+ return string.slice(0, trimmedEndIndex(string) + 1);
+ }
- this.hash = attrs.hash; // optional - string to further differentiate the issue
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
- this.id = generateID.apply(this); // generated - see below
+ var strSymbols = stringToArray(string),
+ end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
+ return castSlice(strSymbols, 0, end).join('');
+ }
+ /**
+ * Removes leading whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimStart(' abc ');
+ * // => 'abc '
+ *
+ * _.trimStart('-_-abc-_-', '_-');
+ * // => 'abc-_-'
+ */
- this.key = generateKey.apply(this); // generated - see below (call after generating this.id)
- this.autoFix = null; // generated - if autofix exists, will be set below
- // A unique, deterministic string hash.
- // Issues with identical id values are considered identical.
+ function trimStart(string, chars, guard) {
+ string = toString(string);
- function generateID() {
- var parts = [this.type];
+ if (string && (guard || chars === undefined$1)) {
+ return string.replace(reTrimStart, '');
+ }
- if (this.hash) {
- // subclasses can pass in their own differentiator
- parts.push(this.hash);
- }
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
- if (this.subtype) {
- parts.push(this.subtype);
- } // include the entities this issue is for
- // (sort them so the id is deterministic)
+ var strSymbols = stringToArray(string),
+ start = charsStartIndex(strSymbols, stringToArray(chars));
+ return castSlice(strSymbols, start).join('');
+ }
+ /**
+ * Truncates `string` if it's longer than the given maximum string length.
+ * The last characters of the truncated string are replaced with the omission
+ * string which defaults to "...".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to truncate.
+ * @param {Object} [options={}] The options object.
+ * @param {number} [options.length=30] The maximum string length.
+ * @param {string} [options.omission='...'] The string to indicate text is omitted.
+ * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+ * @returns {string} Returns the truncated string.
+ * @example
+ *
+ * _.truncate('hi-diddly-ho there, neighborino');
+ * // => 'hi-diddly-ho there, neighbo...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': ' '
+ * });
+ * // => 'hi-diddly-ho there,...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': /,? +/
+ * });
+ * // => 'hi-diddly-ho there...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'omission': ' [...]'
+ * });
+ * // => 'hi-diddly-ho there, neig [...]'
+ */
- if (this.entityIds) {
- var entityKeys = this.entityIds.slice().sort();
- parts.push.apply(parts, entityKeys);
- }
+ function truncate(string, options) {
+ var length = DEFAULT_TRUNC_LENGTH,
+ omission = DEFAULT_TRUNC_OMISSION;
- return parts.join(':');
- } // An identifier suitable for use as the second argument to d3.selection#data().
- // (i.e. this should change whenever the data needs to be refreshed)
+ if (isObject(options)) {
+ var separator = 'separator' in options ? options.separator : separator;
+ length = 'length' in options ? toInteger(options.length) : length;
+ omission = 'omission' in options ? baseToString(options.omission) : omission;
+ }
+ string = toString(string);
+ var strLength = string.length;
- function generateKey() {
- return this.id + ':' + Date.now().toString(); // include time of creation
- }
+ if (hasUnicode(string)) {
+ var strSymbols = stringToArray(string);
+ strLength = strSymbols.length;
+ }
- this.extent = function (resolver) {
- if (this.loc) {
- return geoExtent(this.loc);
- }
+ if (length >= strLength) {
+ return string;
+ }
- if (this.entityIds && this.entityIds.length) {
- return this.entityIds.reduce(function (extent, entityId) {
- return extent.extend(resolver.entity(entityId).extent(resolver));
- }, geoExtent());
- }
+ var end = length - stringSize(omission);
- return null;
- };
+ if (end < 1) {
+ return omission;
+ }
- this.fixes = function (context) {
- var fixes = this.dynamicFixes ? this.dynamicFixes(context) : [];
- var issue = this;
+ var result = strSymbols ? castSlice(strSymbols, 0, end).join('') : string.slice(0, end);
- if (issue.severity === 'warning') {
- // allow ignoring any issue that's not an error
- fixes.push(new validationIssueFix({
- title: _t.html('issues.fix.ignore_issue.title'),
- icon: 'iD-icon-close',
- onClick: function onClick() {
- context.validator().ignoreIssue(this.issue.id);
+ if (separator === undefined$1) {
+ return result + omission;
}
- }));
- }
- fixes.forEach(function (fix) {
- // the id doesn't matter as long as it's unique to this issue/fix
- fix.id = fix.title; // add a reference to the issue for use in actions
+ if (strSymbols) {
+ end += result.length - end;
+ }
- fix.issue = issue;
+ if (isRegExp(separator)) {
+ if (string.slice(end).search(separator)) {
+ var match,
+ substring = result;
- if (fix.autoArgs) {
- issue.autoFix = fix;
- }
- });
- return fixes;
- };
- }
- function validationIssueFix(attrs) {
- this.title = attrs.title; // Required
+ if (!separator.global) {
+ separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+ }
- this.onClick = attrs.onClick; // Optional - the function to run to apply the fix
+ separator.lastIndex = 0;
- this.disabledReason = attrs.disabledReason; // Optional - a string explaining why the fix is unavailable, if any
+ while (match = separator.exec(substring)) {
+ var newEnd = match.index;
+ }
- this.icon = attrs.icon; // Optional - shows 'iD-icon-wrench' if not set
+ result = result.slice(0, newEnd === undefined$1 ? end : newEnd);
+ }
+ } else if (string.indexOf(baseToString(separator), end) != end) {
+ var index = result.lastIndexOf(separator);
- this.entityIds = attrs.entityIds || []; // Optional - used for hover-higlighting.
+ if (index > -1) {
+ result = result.slice(0, index);
+ }
+ }
- this.autoArgs = attrs.autoArgs; // Optional - pass [actions, annotation] arglist if this fix can automatically run
+ return result + omission;
+ }
+ /**
+ * The inverse of `_.escape`; this method converts the HTML entities
+ * `&`, `<`, `>`, `"`, and `'` in `string` to
+ * their corresponding characters.
+ *
+ * **Note:** No other HTML entities are unescaped. To unescape additional
+ * HTML entities use a third-party library like [_he_](https://mths.be/he).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.6.0
+ * @category String
+ * @param {string} [string=''] The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ * @example
+ *
+ * _.unescape('fred, barney, & pebbles');
+ * // => 'fred, barney, & pebbles'
+ */
- this.issue = null; // Generated link - added by validationIssue
- }
- var buildRuleChecks = function buildRuleChecks() {
- return {
- equals: function equals(_equals) {
- return function (tags) {
- return Object.keys(_equals).every(function (k) {
- return _equals[k] === tags[k];
- });
- };
- },
- notEquals: function notEquals(_notEquals) {
- return function (tags) {
- return Object.keys(_notEquals).some(function (k) {
- return _notEquals[k] !== tags[k];
- });
- };
- },
- absence: function absence(_absence) {
- return function (tags) {
- return Object.keys(tags).indexOf(_absence) === -1;
- };
- },
- presence: function presence(_presence) {
- return function (tags) {
- return Object.keys(tags).indexOf(_presence) > -1;
- };
- },
- greaterThan: function greaterThan(_greaterThan) {
- var key = Object.keys(_greaterThan)[0];
- var value = _greaterThan[key];
- return function (tags) {
- return tags[key] > value;
- };
- },
- greaterThanEqual: function greaterThanEqual(_greaterThanEqual) {
- var key = Object.keys(_greaterThanEqual)[0];
- var value = _greaterThanEqual[key];
- return function (tags) {
- return tags[key] >= value;
- };
- },
- lessThan: function lessThan(_lessThan) {
- var key = Object.keys(_lessThan)[0];
- var value = _lessThan[key];
- return function (tags) {
- return tags[key] < value;
- };
- },
- lessThanEqual: function lessThanEqual(_lessThanEqual) {
- var key = Object.keys(_lessThanEqual)[0];
- var value = _lessThanEqual[key];
- return function (tags) {
- return tags[key] <= value;
- };
- },
- positiveRegex: function positiveRegex(_positiveRegex) {
- var tagKey = Object.keys(_positiveRegex)[0];
+ function unescape(string) {
+ string = toString(string);
+ return string && reHasEscapedHtml.test(string) ? string.replace(reEscapedHtml, unescapeHtmlChar) : string;
+ }
+ /**
+ * Converts `string`, as space separated words, to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.upperCase('--foo-bar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('fooBar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('__foo_bar__');
+ * // => 'FOO BAR'
+ */
- var expression = _positiveRegex[tagKey].join('|');
- var regex = new RegExp(expression);
- return function (tags) {
- return regex.test(tags[tagKey]);
- };
- },
- negativeRegex: function negativeRegex(_negativeRegex) {
- var tagKey = Object.keys(_negativeRegex)[0];
+ var upperCase = createCompounder(function (result, word, index) {
+ return result + (index ? ' ' : '') + word.toUpperCase();
+ });
+ /**
+ * Converts the first character of `string` to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.upperFirst('fred');
+ * // => 'Fred'
+ *
+ * _.upperFirst('FRED');
+ * // => 'FRED'
+ */
- var expression = _negativeRegex[tagKey].join('|');
+ var upperFirst = createCaseFirst('toUpperCase');
+ /**
+ * Splits `string` into an array of its words.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {RegExp|string} [pattern] The pattern to match words.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the words of `string`.
+ * @example
+ *
+ * _.words('fred, barney, & pebbles');
+ * // => ['fred', 'barney', 'pebbles']
+ *
+ * _.words('fred, barney, & pebbles', /[^, ]+/g);
+ * // => ['fred', 'barney', '&', 'pebbles']
+ */
- var regex = new RegExp(expression);
- return function (tags) {
- return !regex.test(tags[tagKey]);
- };
- }
- };
- };
+ function words(string, pattern, guard) {
+ string = toString(string);
+ pattern = guard ? undefined$1 : pattern;
- var buildLineKeys = function buildLineKeys() {
- return {
- highway: {
- rest_area: true,
- services: true
- },
- railway: {
- roundhouse: true,
- station: true,
- traverser: true,
- turntable: true,
- wash: true
- }
- };
- };
+ if (pattern === undefined$1) {
+ return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
+ }
- var serviceMapRules = {
- init: function init() {
- this._ruleChecks = buildRuleChecks();
- this._validationRules = [];
- this._areaKeys = osmAreaKeys;
- this._lineKeys = buildLineKeys();
- },
- // list of rules only relevant to tag checks...
- filterRuleChecks: function filterRuleChecks(selector) {
- var _ruleChecks = this._ruleChecks;
- return Object.keys(selector).reduce(function (rules, key) {
- if (['geometry', 'error', 'warning'].indexOf(key) === -1) {
- rules.push(_ruleChecks[key](selector[key]));
+ return string.match(pattern) || [];
}
+ /*------------------------------------------------------------------------*/
- return rules;
- }, []);
- },
- // builds tagMap from mapcss-parse selector object...
- buildTagMap: function buildTagMap(selector) {
- var getRegexValues = function getRegexValues(regexes) {
- return regexes.map(function (regex) {
- return regex.replace(/\$|\^/g, '');
- });
- };
+ /**
+ * Attempts to invoke `func`, returning either the result or the caught error
+ * object. Any additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Function} func The function to attempt.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {*} Returns the `func` result or error object.
+ * @example
+ *
+ * // Avoid throwing errors for invalid selectors.
+ * var elements = _.attempt(function(selector) {
+ * return document.querySelectorAll(selector);
+ * }, '>_>');
+ *
+ * if (_.isError(elements)) {
+ * elements = [];
+ * }
+ */
- var tagMap = Object.keys(selector).reduce(function (expectedTags, key) {
- var values;
- var isRegex = /regex/gi.test(key);
- var isEqual = /equals/gi.test(key);
- if (isRegex || isEqual) {
- Object.keys(selector[key]).forEach(function (selectorKey) {
- values = isEqual ? [selector[key][selectorKey]] : getRegexValues(selector[key][selectorKey]);
+ var attempt = baseRest(function (func, args) {
+ try {
+ return apply(func, undefined$1, args);
+ } catch (e) {
+ return isError(e) ? e : new Error(e);
+ }
+ });
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method.
+ *
+ * **Note:** This method doesn't set the "length" property of bound functions.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...(string|string[])} methodNames The object method names to bind.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'click': function() {
+ * console.log('clicked ' + this.label);
+ * }
+ * };
+ *
+ * _.bindAll(view, ['click']);
+ * jQuery(element).on('click', view.click);
+ * // => Logs 'clicked docs' when clicked.
+ */
- if (expectedTags.hasOwnProperty(selectorKey)) {
- values = values.concat(expectedTags[selectorKey]);
+ var bindAll = flatRest(function (object, methodNames) {
+ arrayEach(methodNames, function (key) {
+ key = toKey(key);
+ baseAssignValue(object, key, bind(object[key], object));
+ });
+ return object;
+ });
+ /**
+ * Creates a function that iterates over `pairs` and invokes the corresponding
+ * function of the first predicate to return truthy. The predicate-function
+ * pairs are invoked with the `this` binding and arguments of the created
+ * function.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {Array} pairs The predicate-function pairs.
+ * @returns {Function} Returns the new composite function.
+ * @example
+ *
+ * var func = _.cond([
+ * [_.matches({ 'a': 1 }), _.constant('matches A')],
+ * [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+ * [_.stubTrue, _.constant('no match')]
+ * ]);
+ *
+ * func({ 'a': 1, 'b': 2 });
+ * // => 'matches A'
+ *
+ * func({ 'a': 0, 'b': 1 });
+ * // => 'matches B'
+ *
+ * func({ 'a': '1', 'b': '2' });
+ * // => 'no match'
+ */
+
+ function cond(pairs) {
+ var length = pairs == null ? 0 : pairs.length,
+ toIteratee = getIteratee();
+ pairs = !length ? [] : arrayMap(pairs, function (pair) {
+ if (typeof pair[1] != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
}
- expectedTags[selectorKey] = values;
+ return [toIteratee(pair[0]), pair[1]];
});
- } else if (/(greater|less)Than(Equal)?|presence/g.test(key)) {
- var tagKey = /presence/.test(key) ? selector[key] : Object.keys(selector[key])[0];
- values = [selector[key][tagKey]];
+ return baseRest(function (args) {
+ var index = -1;
- if (expectedTags.hasOwnProperty(tagKey)) {
- values = values.concat(expectedTags[tagKey]);
- }
+ while (++index < length) {
+ var pair = pairs[index];
- expectedTags[tagKey] = values;
+ if (apply(pair[0], this, args)) {
+ return apply(pair[1], this, args);
+ }
+ }
+ });
}
+ /**
+ * Creates a function that invokes the predicate properties of `source` with
+ * the corresponding property values of a given object, returning `true` if
+ * all predicates return truthy, else `false`.
+ *
+ * **Note:** The created function is equivalent to `_.conformsTo` with
+ * `source` partially applied.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 2, 'b': 1 },
+ * { 'a': 1, 'b': 2 }
+ * ];
+ *
+ * _.filter(objects, _.conforms({ 'b': function(n) { return n > 1; } }));
+ * // => [{ 'a': 1, 'b': 2 }]
+ */
- return expectedTags;
- }, {});
- return tagMap;
- },
- // inspired by osmWay#isArea()
- inferGeometry: function inferGeometry(tagMap) {
- var _lineKeys = this._lineKeys;
- var _areaKeys = this._areaKeys;
- var keyValueDoesNotImplyArea = function keyValueDoesNotImplyArea(key) {
- return utilArrayIntersection(tagMap[key], Object.keys(_areaKeys[key])).length > 0;
- };
+ function conforms(source) {
+ return baseConforms(baseClone(source, CLONE_DEEP_FLAG));
+ }
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Util
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new constant function.
+ * @example
+ *
+ * var objects = _.times(2, _.constant({ 'a': 1 }));
+ *
+ * console.log(objects);
+ * // => [{ 'a': 1 }, { 'a': 1 }]
+ *
+ * console.log(objects[0] === objects[1]);
+ * // => true
+ */
- var keyValueImpliesLine = function keyValueImpliesLine(key) {
- return utilArrayIntersection(tagMap[key], Object.keys(_lineKeys[key])).length > 0;
- };
- if (tagMap.hasOwnProperty('area')) {
- if (tagMap.area.indexOf('yes') > -1) {
- return 'area';
+ function constant(value) {
+ return function () {
+ return value;
+ };
}
+ /**
+ * Checks `value` to determine whether a default value should be returned in
+ * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
+ * or `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.14.0
+ * @category Util
+ * @param {*} value The value to check.
+ * @param {*} defaultValue The default value.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * _.defaultTo(1, 10);
+ * // => 1
+ *
+ * _.defaultTo(undefined, 10);
+ * // => 10
+ */
- if (tagMap.area.indexOf('no') > -1) {
- return 'line';
- }
- }
- for (var key in tagMap) {
- if (key in _areaKeys && !keyValueDoesNotImplyArea(key)) {
- return 'area';
+ function defaultTo(value, defaultValue) {
+ return value == null || value !== value ? defaultValue : value;
}
+ /**
+ * Creates a function that returns the result of invoking the given functions
+ * with the `this` binding of the created function, where each successive
+ * invocation is supplied the return value of the previous.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] The functions to invoke.
+ * @returns {Function} Returns the new composite function.
+ * @see _.flowRight
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flow([_.add, square]);
+ * addSquare(1, 2);
+ * // => 9
+ */
- if (key in _lineKeys && keyValueImpliesLine(key)) {
- return 'area';
- }
- }
- return 'line';
- },
- // adds from mapcss-parse selector check...
- addRule: function addRule(selector) {
- var rule = {
- // checks relevant to mapcss-selector
- checks: this.filterRuleChecks(selector),
- // true if all conditions for a tag error are true..
- matches: function matches(entity) {
- return this.checks.every(function (check) {
- return check(entity.tags);
- });
- },
- // borrowed from Way#isArea()
- inferredGeometry: this.inferGeometry(this.buildTagMap(selector), this._areaKeys),
- geometryMatches: function geometryMatches(entity, graph) {
- if (entity.type === 'node' || entity.type === 'relation') {
- return selector.geometry === entity.type;
- } else if (entity.type === 'way') {
- return this.inferredGeometry === entity.geometry(graph);
- }
- },
- // when geometries match and tag matches are present, return a warning...
- findIssues: function findIssues(entity, graph, issues) {
- if (this.geometryMatches(entity, graph) && this.matches(entity)) {
- var severity = Object.keys(selector).indexOf('error') > -1 ? 'error' : 'warning';
- var _message = selector[severity];
- issues.push(new validationIssue({
- type: 'maprules',
- severity: severity,
- message: function message() {
- return _message;
- },
- entityIds: [entity.id]
- }));
- }
- }
- };
+ var flow = createFlow();
+ /**
+ * This method is like `_.flow` except that it creates a function that
+ * invokes the given functions from right to left.
+ *
+ * @static
+ * @since 3.0.0
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] The functions to invoke.
+ * @returns {Function} Returns the new composite function.
+ * @see _.flow
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flowRight([square, _.add]);
+ * addSquare(1, 2);
+ * // => 9
+ */
- this._validationRules.push(rule);
- },
- clearRules: function clearRules() {
- this._validationRules = [];
- },
- // returns validationRules...
- validationRules: function validationRules() {
- return this._validationRules;
- },
- // returns ruleChecks
- ruleChecks: function ruleChecks() {
- return this._ruleChecks;
- }
- };
+ var flowRight = createFlow(true);
+ /**
+ * This method returns the first argument it receives.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ *
+ * console.log(_.identity(object) === object);
+ * // => true
+ */
- var apibase$2 = 'https://nominatim.openstreetmap.org/';
- var _inflight$2 = {};
+ function identity(value) {
+ return value;
+ }
+ /**
+ * Creates a function that invokes `func` with the arguments of the created
+ * function. If `func` is a property name, the created function returns the
+ * property value for a given element. If `func` is an array or object, the
+ * created function returns `true` for elements that contain the equivalent
+ * source properties, otherwise it returns `false`.
+ *
+ * @static
+ * @since 4.0.0
+ * @memberOf _
+ * @category Util
+ * @param {*} [func=_.identity] The value to convert to a callback.
+ * @returns {Function} Returns the callback.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+ * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.filter(users, _.iteratee(['user', 'fred']));
+ * // => [{ 'user': 'fred', 'age': 40 }]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.map(users, _.iteratee('user'));
+ * // => ['barney', 'fred']
+ *
+ * // Create custom iteratee shorthands.
+ * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+ * return !_.isRegExp(func) ? iteratee(func) : function(string) {
+ * return func.test(string);
+ * };
+ * });
+ *
+ * _.filter(['abc', 'def'], /ef/);
+ * // => ['def']
+ */
- var _nominatimCache;
- var serviceNominatim = {
- init: function init() {
- _inflight$2 = {};
- _nominatimCache = new RBush();
- },
- reset: function reset() {
- Object.values(_inflight$2).forEach(function (controller) {
- controller.abort();
- });
- _inflight$2 = {};
- _nominatimCache = new RBush();
- },
- countryCode: function countryCode(location, callback) {
- this.reverse(location, function (err, result) {
- if (err) {
- return callback(err);
- } else if (result.address) {
- return callback(null, result.address.country_code);
- } else {
- return callback('Unable to geocode', null);
+ function iteratee(func) {
+ return baseIteratee(typeof func == 'function' ? func : baseClone(func, CLONE_DEEP_FLAG));
}
- });
- },
- reverse: function reverse(loc, callback) {
- var cached = _nominatimCache.search({
- minX: loc[0],
- minY: loc[1],
- maxX: loc[0],
- maxY: loc[1]
- });
+ /**
+ * Creates a function that performs a partial deep comparison between a given
+ * object and `source`, returning `true` if the given object has equivalent
+ * property values, else `false`.
+ *
+ * **Note:** The created function is equivalent to `_.isMatch` with `source`
+ * 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.
+ *
+ * **Note:** Multiple values can be checked by combining several matchers
+ * using `_.overSome`
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 1, 'b': 2, 'c': 3 },
+ * { 'a': 4, 'b': 5, 'c': 6 }
+ * ];
+ *
+ * _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
+ * // => [{ 'a': 4, 'b': 5, 'c': 6 }]
+ *
+ * // Checking for several possible values
+ * _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })]));
+ * // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
+ */
- if (cached.length > 0) {
- if (callback) callback(null, cached[0].data);
- return;
- }
- var params = {
- zoom: 13,
- format: 'json',
- addressdetails: 1,
- lat: loc[1],
- lon: loc[0]
- };
- var url = apibase$2 + 'reverse?' + utilQsString(params);
- if (_inflight$2[url]) return;
- var controller = new AbortController();
- _inflight$2[url] = controller;
- d3_json(url, {
- signal: controller.signal
- }).then(function (result) {
- delete _inflight$2[url];
+ function matches(source) {
+ return baseMatches(baseClone(source, CLONE_DEEP_FLAG));
+ }
+ /**
+ * Creates a function that performs a partial deep comparison between the
+ * value at `path` of a given object to `srcValue`, returning `true` if the
+ * object value is equivalent, else `false`.
+ *
+ * **Note:** Partial comparisons will match empty array and empty object
+ * `srcValue` values against any array or object value, respectively. See
+ * `_.isEqual` for a list of supported value comparisons.
+ *
+ * **Note:** Multiple values can be checked by combining several matchers
+ * using `_.overSome`
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 1, 'b': 2, 'c': 3 },
+ * { 'a': 4, 'b': 5, 'c': 6 }
+ * ];
+ *
+ * _.find(objects, _.matchesProperty('a', 4));
+ * // => { 'a': 4, 'b': 5, 'c': 6 }
+ *
+ * // Checking for several possible values
+ * _.filter(objects, _.overSome([_.matchesProperty('a', 1), _.matchesProperty('a', 4)]));
+ * // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }]
+ */
- if (result && result.error) {
- throw new Error(result.error);
+
+ function matchesProperty(path, srcValue) {
+ return baseMatchesProperty(path, baseClone(srcValue, CLONE_DEEP_FLAG));
}
+ /**
+ * Creates a function that invokes the method at `path` of a given object.
+ * Any additional arguments are provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Util
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new invoker function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': _.constant(2) } },
+ * { 'a': { 'b': _.constant(1) } }
+ * ];
+ *
+ * _.map(objects, _.method('a.b'));
+ * // => [2, 1]
+ *
+ * _.map(objects, _.method(['a', 'b']));
+ * // => [2, 1]
+ */
- var extent = geoExtent(loc).padByMeters(200);
- _nominatimCache.insert(Object.assign(extent.bbox(), {
- data: result
- }));
+ var method = baseRest(function (path, args) {
+ return function (object) {
+ return baseInvoke(object, path, args);
+ };
+ });
+ /**
+ * The opposite of `_.method`; this method creates a function that invokes
+ * the method at a given path of `object`. Any additional arguments are
+ * provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Util
+ * @param {Object} object The object to query.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new invoker function.
+ * @example
+ *
+ * var array = _.times(3, _.constant),
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+ * // => [2, 0]
+ */
- if (callback) callback(null, result);
- })["catch"](function (err) {
- delete _inflight$2[url];
- if (err.name === 'AbortError') return;
- if (callback) callback(err.message);
- });
- },
- search: function search(val, callback) {
- var searchVal = encodeURIComponent(val);
- var url = apibase$2 + 'search/' + searchVal + '?limit=10&format=json';
- if (_inflight$2[url]) return;
- var controller = new AbortController();
- _inflight$2[url] = controller;
- d3_json(url, {
- signal: controller.signal
- }).then(function (result) {
- delete _inflight$2[url];
+ var methodOf = baseRest(function (object, args) {
+ return function (path) {
+ return baseInvoke(object, path, args);
+ };
+ });
+ /**
+ * Adds all own enumerable string keyed function properties of a source
+ * object to the destination object. If `object` is a function, then methods
+ * are added to its prototype as well.
+ *
+ * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+ * avoid conflicts caused by modifying the original.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {Function|Object} [object=lodash] The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+ * @returns {Function|Object} Returns `object`.
+ * @example
+ *
+ * function vowels(string) {
+ * return _.filter(string, function(v) {
+ * return /[aeiou]/i.test(v);
+ * });
+ * }
+ *
+ * _.mixin({ 'vowels': vowels });
+ * _.vowels('fred');
+ * // => ['e']
+ *
+ * _('fred').vowels().value();
+ * // => ['e']
+ *
+ * _.mixin({ 'vowels': vowels }, { 'chain': false });
+ * _('fred').vowels();
+ * // => ['e']
+ */
- if (result && result.error) {
- throw new Error(result.error);
+ function mixin(object, source, options) {
+ var props = keys(source),
+ methodNames = baseFunctions(source, props);
+
+ if (options == null && !(isObject(source) && (methodNames.length || !props.length))) {
+ options = source;
+ source = object;
+ object = this;
+ methodNames = baseFunctions(source, keys(source));
+ }
+
+ var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+ isFunc = isFunction(object);
+ arrayEach(methodNames, function (methodName) {
+ var func = source[methodName];
+ object[methodName] = func;
+
+ if (isFunc) {
+ object.prototype[methodName] = function () {
+ var chainAll = this.__chain__;
+
+ if (chain || chainAll) {
+ var result = object(this.__wrapped__),
+ actions = result.__actions__ = copyArray(this.__actions__);
+ actions.push({
+ 'func': func,
+ 'args': arguments,
+ 'thisArg': object
+ });
+ result.__chain__ = chainAll;
+ return result;
+ }
+
+ return func.apply(object, arrayPush([this.value()], arguments));
+ };
+ }
+ });
+ return object;
}
+ /**
+ * Reverts the `_` variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
- if (callback) callback(null, result);
- })["catch"](function (err) {
- delete _inflight$2[url];
- if (err.name === 'AbortError') return;
- if (callback) callback(err.message);
- });
- }
- };
- // for punction see https://stackoverflow.com/a/21224179
+ function noConflict() {
+ if (root._ === this) {
+ root._ = oldDash;
+ }
- function simplify$1(str) {
- if (typeof str !== 'string') return '';
- return diacritics.remove(str.replace(/&/g, 'and').replace(/İ/ig, 'i') // for BİM, İşbank - #5017
- .replace(/[\s\-=_!"#%'*{},.\/:;?\(\)\[\]@\\$\^*+<>«»~`’\u00a1\u00a7\u00b6\u00b7\u00bf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d\u166e\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u2000-\u206f\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00-\u2e7f\u3001-\u3003\u303d\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a\ufe6b\ufeff\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65]+/g, '').toLowerCase());
- }
+ return this;
+ }
+ /**
+ * This method returns `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.3.0
+ * @category Util
+ * @example
+ *
+ * _.times(2, _.noop);
+ * // => [undefined, undefined]
+ */
- var matchGroups$1 = {adult_gaming_centre:["amenity/casino","amenity/gambling","leisure/adult_gaming_centre"],beauty:["shop/beauty","shop/hairdresser_supply"],bed:["shop/bed","shop/furniture"],beverages:["shop/alcohol","shop/beer","shop/beverages","shop/wine"],camping:["leisure/park","tourism/camp_site","tourism/caravan_site"],car_parts:["shop/car_parts","shop/car_repair","shop/tires","shop/tyres"],clinic:["amenity/clinic","amenity/doctors","healthcare/clinic","healthcare/dialysis"],confectionery:["shop/candy","shop/chocolate","shop/confectionery"],convenience:["shop/beauty","shop/chemist","shop/convenience","shop/cosmetics","shop/grocery","shop/newsagent"],coworking:["amenity/coworking_space","office/coworking","office/coworking_space"],dentist:["amenity/dentist","amenity/doctors","healthcare/dentist"],electronics:["office/telecommunication","shop/computer","shop/electronics","shop/hifi","shop/mobile","shop/mobile_phone","shop/telecommunication"],fabric:["shop/fabric","shop/haberdashery","shop/sewing"],fashion:["shop/accessories","shop/bag","shop/botique","shop/clothes","shop/department_store","shop/fashion","shop/fashion_accessories","shop/sports","shop/shoes"],financial:["amenity/bank","office/accountant","office/financial","office/financial_advisor","office/tax_advisor","shop/tax"],fitness:["leisure/fitness_centre","leisure/fitness_center","leisure/sports_centre","leisure/sports_center"],food:["amenity/pub","amenity/bar","amenity/cafe","amenity/fast_food","amenity/ice_cream","amenity/restaurant","shop/bakery","shop/ice_cream","shop/pastry","shop/tea","shop/coffee"],fuel:["amenity/fuel","shop/gas","shop/convenience;gas","shop/gas;convenience"],gift:["shop/gift","shop/card","shop/cards","shop/stationery"],hardware:["shop/bathroom_furnishing","shop/carpet","shop/diy","shop/doityourself","shop/doors","shop/electrical","shop/flooring","shop/hardware","shop/hardware_store","shop/power_tools","shop/tool_hire","shop/tools","shop/trade"],health_food:["shop/health","shop/health_food","shop/herbalist","shop/nutrition_supplements"],hobby:["shop/electronics","shop/hobby","shop/books","shop/games","shop/collector","shop/toys","shop/model","shop/video_games","shop/anime"],hospital:["amenity/doctors","amenity/hospital","healthcare/hospital"],houseware:["shop/houseware","shop/interior_decoration"],lifeboat_station:["amenity/lifeboat_station","emergency/lifeboat_station","emergency/marine_rescue"],lodging:["tourism/hotel","tourism/motel"],money_transfer:["amenity/money_transfer","shop/money_transfer"],office_supplies:["shop/office_supplies","shop/stationary","shop/stationery"],outdoor:["shop/outdoor","shop/sports"],pharmacy:["amenity/doctors","amenity/pharmacy","healthcare/pharmacy"],playground:["amenity/theme_park","leisure/amusement_arcade","leisure/playground"],rental:["amenity/bicycle_rental","amenity/boat_rental","amenity/car_rental","amenity/truck_rental","amenity/vehicle_rental","shop/rental"],school:["amenity/childcare","amenity/college","amenity/kindergarten","amenity/language_school","amenity/prep_school","amenity/school","amenity/university"],supermarket:["shop/food","shop/frozen_food","shop/greengrocer","shop/grocery","shop/supermarket","shop/wholesale"],variety_store:["shop/variety_store","shop/discount","shop/convenience"],vending:["amenity/vending_machine","shop/vending_machine"],storage:["shop/storage_units","shop/storage_rental"],weight_loss:["amenity/doctors","amenity/weight_clinic","healthcare/counselling","leisure/fitness_centre","office/therapist","shop/beauty","shop/diet","shop/food","shop/health_food","shop/herbalist","shop/nutrition","shop/nutrition_supplements","shop/weight_loss"],wholesale:["shop/wholesale","shop/supermarket","shop/department_store"]};
- var matchGroupsJSON = {
- matchGroups: matchGroups$1
- };
- var genericWords = ["^(barn|bazaa?r|bench|bou?tique|building|casa|church)$","^(baseball|basketball|football|soccer|softball|tennis(halle)?)\\s?(field|court)?$","^(club|green|out|ware)\\s?house$","^(driveway|el árbol|fountain|golf|government|graveyard)$","^(hofladen|librairie|magazine?|maison)$","^(mobile home|skate)?\\s?park$","^(n\\s?\\/?\\s?a|name|no\\s?name|none|null|temporary|test|unknown)$","^(obuwie|pond|pool|sale|shops?|sklep|stores?)$","^\\?+$","^tattoo( studio)?$","^windmill$","^церковная( лавка)?$"];
- var genericWordsJSON = {
- genericWords: genericWords
- };
+ function noop() {// No operation performed.
+ }
+ /**
+ * Creates a function that gets the argument at index `n`. If `n` is negative,
+ * the nth argument from the end is returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {number} [n=0] The index of the argument to return.
+ * @returns {Function} Returns the new pass-thru function.
+ * @example
+ *
+ * var func = _.nthArg(1);
+ * func('a', 'b', 'c', 'd');
+ * // => 'b'
+ *
+ * var func = _.nthArg(-2);
+ * func('a', 'b', 'c', 'd');
+ * // => 'c'
+ */
- var trees$1 = {brands:{emoji:"🍔",mainTag:"brand:wikidata",sourceTags:["brand","name"],nameTags:{primary:"^(name|name:\\w+)$",alternate:"^(brand|brand:\\w+|operator|operator:\\w+|\\w+_name|\\w+_name:\\w+)$"}},flags:{emoji:"🚩",mainTag:"flag:wikidata",nameTags:{primary:"^(flag:name|flag:name:\\w+)$",alternate:"^(country|country:\\w+|flag|flag:\\w+|subject|subject:\\w+)$"}},operators:{emoji:"💼",mainTag:"operator:wikidata",sourceTags:["operator"],nameTags:{primary:"^(name|name:\\w+|operator|operator:\\w+)$",alternate:"^(brand|brand:\\w+|\\w+_name|\\w+_name:\\w+)$"}},transit:{emoji:"🚇",mainTag:"network:wikidata",sourceTags:["network"],nameTags:{primary:"^network$",alternate:"^(operator|operator:\\w+|network:\\w+|\\w+_name|\\w+_name:\\w+)$"}}};
- var treesJSON = {
- trees: trees$1
- };
- var matchGroups = matchGroupsJSON.matchGroups;
- var trees = treesJSON.trees;
- var Matcher = /*#__PURE__*/function () {
- //
- // `constructor`
- // initialize the genericWords regexes
- function Matcher() {
- var _this = this;
+ function nthArg(n) {
+ n = toInteger(n);
+ return baseRest(function (args) {
+ return baseNth(args, n);
+ });
+ }
+ /**
+ * Creates a function that invokes `iteratees` with the arguments it receives
+ * and returns their results.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [iteratees=[_.identity]]
+ * The iteratees to invoke.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.over([Math.max, Math.min]);
+ *
+ * func(1, 2, 3, 4);
+ * // => [4, 1]
+ */
- _classCallCheck$1(this, Matcher);
- // The `matchIndex` is a specialized structure that allows us to quickly answer
- // _"Given a [key/value tagpair, name, location], what canonical items (brands etc) can match it?"_
- //
- // The index contains all valid combinations of k/v tagpairs and names
- // matchIndex:
- // {
- // 'k/v': {
- // 'primary': Map (String 'nsimple' -> Set (itemIDs…), // matches for tags like `name`, `name:xx`, etc.
- // 'alternate': Map (String 'nsimple' -> Set (itemIDs…), // matches for tags like `alt_name`, `brand`, etc.
- // 'excludeNamed': Map (String 'pattern' -> RegExp),
- // 'excludeGeneric': Map (String 'pattern' -> RegExp)
- // },
- // }
- //
- // {
- // 'amenity/bank': {
- // 'primary': {
- // 'firstbank': Set ("firstbank-978cca", "firstbank-9794e6", "firstbank-f17495", …),
- // …
- // },
- // 'alternate': {
- // '1stbank': Set ("firstbank-f17495"),
- // …
- // }
- // },
- // 'shop/supermarket': {
- // 'primary': {
- // 'coop': Set ("coop-76454b", "coop-ebf2d9", "coop-36e991", …),
- // 'coopfood': Set ("coopfood-a8278b", …),
- // …
- // },
- // 'alternate': {
- // 'coop': Set ("coopfood-a8278b", …),
- // 'federatedcooperatives': Set ("coop-76454b", …),
- // 'thecooperative': Set ("coopfood-a8278b", …),
- // …
- // }
- // }
- // }
- //
- this.matchIndex = undefined; // The `genericWords` structure matches the contents of genericWords.json to instantiated RegExp objects
- // Map (String 'pattern' -> RegExp),
+ var over = createOver(arrayMap);
+ /**
+ * Creates a function that checks if **all** of the `predicates` return
+ * truthy when invoked with the arguments it receives.
+ *
+ * Following shorthands are possible for providing predicates.
+ * Pass an `Object` and it will be used as an parameter for `_.matches` to create the predicate.
+ * Pass an `Array` of parameters for `_.matchesProperty` and the predicate will be created using them.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [predicates=[_.identity]]
+ * The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overEvery([Boolean, isFinite]);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => false
+ *
+ * func(NaN);
+ * // => false
+ */
- this.genericWords = new Map();
- (genericWordsJSON.genericWords || []).forEach(function (s) {
- return _this.genericWords.set(s, new RegExp(s, 'i'));
- }); // The `itemLocation` structure maps itemIDs to locationSetIDs:
- // {
- // 'firstbank-f17495': '+[first_bank_western_us.geojson]',
- // 'firstbank-978cca': '+[first_bank_carolinas.geojson]',
- // 'coop-76454b': '+[Q16]',
- // 'coopfood-a8278b': '+[Q23666]',
- // …
- // }
+ var overEvery = createOver(arrayEvery);
+ /**
+ * Creates a function that checks if **any** of the `predicates` return
+ * truthy when invoked with the arguments it receives.
+ *
+ * Following shorthands are possible for providing predicates.
+ * Pass an `Object` and it will be used as an parameter for `_.matches` to create the predicate.
+ * Pass an `Array` of parameters for `_.matchesProperty` and the predicate will be created using them.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [predicates=[_.identity]]
+ * The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overSome([Boolean, isFinite]);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => true
+ *
+ * func(NaN);
+ * // => false
+ *
+ * var matchesFunc = _.overSome([{ 'a': 1 }, { 'a': 2 }])
+ * var matchesPropertyFunc = _.overSome([['a', 1], ['a', 2]])
+ */
- this.itemLocation = undefined; // The `locationSets` structure maps locationSetIDs to *resolved* locationSets:
- // {
- // '+[first_bank_western_us.geojson]': GeoJSON {…},
- // '+[first_bank_carolinas.geojson]': GeoJSON {…},
- // '+[Q16]': GeoJSON {…},
- // '+[Q23666]': GeoJSON {…},
- // …
- // }
+ var overSome = createOver(arraySome);
+ /**
+ * Creates a function that returns the value at `path` of a given object.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': 2 } },
+ * { 'a': { 'b': 1 } }
+ * ];
+ *
+ * _.map(objects, _.property('a.b'));
+ * // => [2, 1]
+ *
+ * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+ * // => [1, 2]
+ */
- this.locationSets = undefined; // The `locationIndex` is an instance of which-polygon spatial index for the locationSets.
+ function property(path) {
+ return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
+ }
+ /**
+ * The opposite of `_.property`; this method creates a function that returns
+ * the value at a given path of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Object} object The object to query.
+ * @returns {Function} Returns the new accessor function.
+ * @example
+ *
+ * var array = [0, 1, 2],
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+ * // => [2, 0]
+ */
- this.locationIndex = undefined; // Array of match conflict pairs (currently unused)
- this.warnings = [];
- } //
- // `buildMatchIndex()`
- // Call this to prepare the matcher for use
- //
- // `data` needs to be an Object indexed on a 'tree/key/value' path.
- // (e.g. cache filled by `fileTree.read` or data found in `dist/nsi.json`)
- // {
- // 'brands/amenity/bank': { properties: {}, items: [ {}, {}, … ] },
- // 'brands/amenity/bar': { properties: {}, items: [ {}, {}, … ] },
- // …
- // }
- //
+ function propertyOf(object) {
+ return function (path) {
+ return object == null ? undefined$1 : baseGet(object, path);
+ };
+ }
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+ * `start` is specified without an `end` or `step`. If `end` is not specified,
+ * it's set to `start` with `start` then set to `0`.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the range of numbers.
+ * @see _.inRange, _.rangeRight
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(-4);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
- _createClass$1(Matcher, [{
- key: "buildMatchIndex",
- value: function buildMatchIndex(data) {
- var that = this;
- if (that.matchIndex) return; // it was built already
+ var range = createRange();
+ /**
+ * This method is like `_.range` except that it populates values in
+ * descending order.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the range of numbers.
+ * @see _.inRange, _.range
+ * @example
+ *
+ * _.rangeRight(4);
+ * // => [3, 2, 1, 0]
+ *
+ * _.rangeRight(-4);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 5);
+ * // => [4, 3, 2, 1]
+ *
+ * _.rangeRight(0, 20, 5);
+ * // => [15, 10, 5, 0]
+ *
+ * _.rangeRight(0, -4, -1);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.rangeRight(0);
+ * // => []
+ */
- that.matchIndex = new Map();
- Object.keys(data).forEach(function (tkv) {
- var category = data[tkv];
- var parts = tkv.split('/', 3); // tkv = "tree/key/value"
+ var rangeRight = createRange(true);
+ /**
+ * This method returns a new empty array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {Array} Returns the new empty array.
+ * @example
+ *
+ * var arrays = _.times(2, _.stubArray);
+ *
+ * console.log(arrays);
+ * // => [[], []]
+ *
+ * console.log(arrays[0] === arrays[1]);
+ * // => false
+ */
- var t = parts[0];
- var k = parts[1];
- var v = parts[2];
- var thiskv = "".concat(k, "/").concat(v);
- var tree = trees[t];
- var branch = that.matchIndex.get(thiskv);
+ function stubArray() {
+ return [];
+ }
+ /**
+ * This method returns `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {boolean} Returns `false`.
+ * @example
+ *
+ * _.times(2, _.stubFalse);
+ * // => [false, false]
+ */
- if (!branch) {
- branch = {
- primary: new Map(),
- alternate: new Map(),
- excludeGeneric: new Map(),
- excludeNamed: new Map()
- };
- that.matchIndex.set(thiskv, branch);
- } // ADD EXCLUSIONS
+ function stubFalse() {
+ return false;
+ }
+ /**
+ * This method returns a new empty object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {Object} Returns the new empty object.
+ * @example
+ *
+ * var objects = _.times(2, _.stubObject);
+ *
+ * console.log(objects);
+ * // => [{}, {}]
+ *
+ * console.log(objects[0] === objects[1]);
+ * // => false
+ */
- var properties = category.properties || {};
- var exclude = properties.exclude || {};
- (exclude.generic || []).forEach(function (s) {
- return branch.excludeGeneric.set(s, new RegExp(s, 'i'));
- });
- (exclude.named || []).forEach(function (s) {
- return branch.excludeNamed.set(s, new RegExp(s, 'i'));
- });
- var excludeRegexes = [].concat(_toConsumableArray(branch.excludeGeneric.values()), _toConsumableArray(branch.excludeNamed.values())); // ADD ITEMS
- var items = category.items;
- if (!Array.isArray(items) || !items.length) return; // Primary name patterns, match tags to take first
- // e.g. `name`, `name:ru`
+ function stubObject() {
+ return {};
+ }
+ /**
+ * This method returns an empty string.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {string} Returns the empty string.
+ * @example
+ *
+ * _.times(2, _.stubString);
+ * // => ['', '']
+ */
- var primaryName = new RegExp(tree.nameTags.primary, 'i'); // Alternate name patterns, match tags to consider after primary
- // e.g. `alt_name`, `short_name`, `brand`, `brand:ru`, etc..
- var alternateName = new RegExp(tree.nameTags.alternate, 'i'); // There are a few exceptions to the name matching regexes.
- // Usually a tag suffix contains a language code like `name:en`, `name:ru`
- // but we want to exclude things like `operator:type`, `name:etymology`, etc..
+ function stubString() {
+ return '';
+ }
+ /**
+ * This method returns `true`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {boolean} Returns `true`.
+ * @example
+ *
+ * _.times(2, _.stubTrue);
+ * // => [true, true]
+ */
- var notName = /:(colou?r|type|forward|backward|left|right|etymology|pronunciation|wikipedia)$/i; // For certain categories we do not want to match generic KV pairs like `building/yes` or `amenity/yes`
- var skipGenericKV = skipGenericKVMatches(t, k, v); // We will collect the generic KV pairs anyway (for the purpose of filtering them out of matchTags)
+ function stubTrue() {
+ return true;
+ }
+ /**
+ * Invokes the iteratee `n` times, returning an array of the results of
+ * each invocation. The iteratee is invoked with one argument; (index).
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.times(3, String);
+ * // => ['0', '1', '2']
+ *
+ * _.times(4, _.constant(0));
+ * // => [0, 0, 0, 0]
+ */
- var genericKV = new Set(["".concat(k, "/yes"), "building/yes"]); // Collect alternate tagpairs for this kv category from matchGroups.
- // We might also pick up a few more generic KVs (like `shop/yes`)
- var matchGroupKV = new Set();
- Object.values(matchGroups).forEach(function (matchGroup) {
- var inGroup = matchGroup.some(function (otherkv) {
- return otherkv === thiskv;
- });
- if (!inGroup) return;
- matchGroup.forEach(function (otherkv) {
- if (otherkv === thiskv) return; // skip self
+ function times(n, iteratee) {
+ n = toInteger(n);
- matchGroupKV.add(otherkv);
- var otherk = otherkv.split('/', 2)[0]; // we might pick up a `shop/yes`
+ if (n < 1 || n > MAX_SAFE_INTEGER) {
+ return [];
+ }
- genericKV.add("".concat(otherk, "/yes"));
- });
- }); // For each item, insert all [key, value, name] combinations into the match index
+ var index = MAX_ARRAY_LENGTH,
+ length = nativeMin(n, MAX_ARRAY_LENGTH);
+ iteratee = getIteratee(iteratee);
+ n -= MAX_ARRAY_LENGTH;
+ var result = baseTimes(length, iteratee);
- items.forEach(function (item) {
- if (!item.id) return; // Automatically remove redundant `matchTags` - #3417
- // (i.e. This kv is already covered by matchGroups, so it doesn't need to be in `item.matchTags`)
+ while (++index < n) {
+ iteratee(index);
+ }
- if (Array.isArray(item.matchTags) && item.matchTags.length) {
- item.matchTags = item.matchTags.filter(function (matchTag) {
- return !matchGroupKV.has(matchTag) && !genericKV.has(matchTag);
- });
- if (!item.matchTags.length) delete item.matchTags;
- } // key/value tagpairs to insert into the match index..
+ return result;
+ }
+ /**
+ * Converts `value` to a property path array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the new property path array.
+ * @example
+ *
+ * _.toPath('a.b.c');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toPath('a[0].b.c');
+ * // => ['a', '0', 'b', 'c']
+ */
- var kvTags = ["".concat(thiskv)].concat(item.matchTags || []);
+ function toPath(value) {
+ if (isArray(value)) {
+ return arrayMap(value, toKey);
+ }
- if (!skipGenericKV) {
- kvTags = kvTags.concat(Array.from(genericKV)); // #3454 - match some generic tags
- } // Index all the namelike tag values
+ return isSymbol(value) ? [value] : copyArray(stringToPath(toString(value)));
+ }
+ /**
+ * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {string} [prefix=''] The value to prefix the ID with.
+ * @returns {string} Returns the unique ID.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ *
+ * _.uniqueId();
+ * // => '105'
+ */
- Object.keys(item.tags).forEach(function (osmkey) {
- if (notName.test(osmkey)) return; // osmkey is not a namelike tag, skip
+ function uniqueId(prefix) {
+ var id = ++idCounter;
+ return toString(prefix) + id;
+ }
+ /*------------------------------------------------------------------------*/
- var osmvalue = item.tags[osmkey];
- if (!osmvalue || excludeRegexes.some(function (regex) {
- return regex.test(osmvalue);
- })) return; // osmvalue missing or excluded
+ /**
+ * Adds two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.4.0
+ * @category Math
+ * @param {number} augend The first number in an addition.
+ * @param {number} addend The second number in an addition.
+ * @returns {number} Returns the total.
+ * @example
+ *
+ * _.add(6, 4);
+ * // => 10
+ */
- if (primaryName.test(osmkey)) {
- kvTags.forEach(function (kv) {
- return insertName('primary', kv, simplify$1(osmvalue), item.id);
- });
- } else if (alternateName.test(osmkey)) {
- kvTags.forEach(function (kv) {
- return insertName('alternate', kv, simplify$1(osmvalue), item.id);
- });
- }
- }); // Index `matchNames` after indexing all other names..
- var keepMatchNames = new Set();
- (item.matchNames || []).forEach(function (matchName) {
- // If this matchname isn't already indexed, add it to the alternate index
- var nsimple = simplify$1(matchName);
- kvTags.forEach(function (kv) {
- var branch = that.matchIndex.get(kv);
- var primaryLeaf = branch && branch.primary.get(nsimple);
- var alternateLeaf = branch && branch.alternate.get(nsimple);
- var inPrimary = primaryLeaf && primaryLeaf.has(item.id);
- var inAlternate = alternateLeaf && alternateLeaf.has(item.id);
+ var add = createMathOperation(function (augend, addend) {
+ return augend + addend;
+ }, 0);
+ /**
+ * Computes `number` rounded up to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round up.
+ * @param {number} [precision=0] The precision to round up to.
+ * @returns {number} Returns the rounded up number.
+ * @example
+ *
+ * _.ceil(4.006);
+ * // => 5
+ *
+ * _.ceil(6.004, 2);
+ * // => 6.01
+ *
+ * _.ceil(6040, -2);
+ * // => 6100
+ */
- if (!inPrimary && !inAlternate) {
- insertName('alternate', kv, nsimple, item.id);
- keepMatchNames.add(matchName);
- }
- });
- }); // Automatically remove redundant `matchNames` - #3417
- // (i.e. This name got indexed some other way, so it doesn't need to be in `item.matchNames`)
+ var ceil = createRound('ceil');
+ /**
+ * Divide two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {number} dividend The first number in a division.
+ * @param {number} divisor The second number in a division.
+ * @returns {number} Returns the quotient.
+ * @example
+ *
+ * _.divide(6, 4);
+ * // => 1.5
+ */
- if (keepMatchNames.size) {
- item.matchNames = Array.from(keepMatchNames);
- } else {
- delete item.matchNames;
- }
- }); // each item
- }); // each tkv
- // Insert this item into the matchIndex
+ var divide = createMathOperation(function (dividend, divisor) {
+ return dividend / divisor;
+ }, 1);
+ /**
+ * Computes `number` rounded down to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round down.
+ * @param {number} [precision=0] The precision to round down to.
+ * @returns {number} Returns the rounded down number.
+ * @example
+ *
+ * _.floor(4.006);
+ * // => 4
+ *
+ * _.floor(0.046, 2);
+ * // => 0.04
+ *
+ * _.floor(4060, -2);
+ * // => 4000
+ */
- function insertName(which, kv, nsimple, itemID) {
- if (!nsimple) return;
- var branch = that.matchIndex.get(kv);
+ var floor = createRound('floor');
+ /**
+ * Computes the maximum value of `array`. If `array` is empty or falsey,
+ * `undefined` is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * _.max([]);
+ * // => undefined
+ */
- if (!branch) {
- branch = {
- primary: new Map(),
- alternate: new Map(),
- excludeGeneric: new Map(),
- excludeNamed: new Map()
- };
- that.matchIndex.set(kv, branch);
- }
+ function max(array) {
+ return array && array.length ? baseExtremum(array, identity, baseGt) : undefined$1;
+ }
+ /**
+ * This method is like `_.max` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.maxBy(objects, function(o) { return o.n; });
+ * // => { 'n': 2 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.maxBy(objects, 'n');
+ * // => { 'n': 2 }
+ */
- var leaf = branch[which].get(nsimple);
- if (!leaf) {
- leaf = new Set();
- branch[which].set(nsimple, leaf);
- }
+ function maxBy(array, iteratee) {
+ return array && array.length ? baseExtremum(array, getIteratee(iteratee, 2), baseGt) : undefined$1;
+ }
+ /**
+ * Computes the mean of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the mean.
+ * @example
+ *
+ * _.mean([4, 2, 8, 6]);
+ * // => 5
+ */
- leaf.add(itemID); // insert
- } // For certain categories we do not want to match generic KV pairs like `building/yes` or `amenity/yes`
+
+ function mean(array) {
+ return baseMean(array, identity);
+ }
+ /**
+ * This method is like `_.mean` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the value to be averaged.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the mean.
+ * @example
+ *
+ * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+ *
+ * _.meanBy(objects, function(o) { return o.n; });
+ * // => 5
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.meanBy(objects, 'n');
+ * // => 5
+ */
- function skipGenericKVMatches(t, k, v) {
- return t === 'flags' || t === 'transit' || k === 'landuse' || v === 'atm' || v === 'bicycle_parking' || v === 'car_sharing' || v === 'caravan_site' || v === 'charging_station' || v === 'dog_park' || v === 'parking' || v === 'phone' || v === 'playground' || v === 'post_box' || v === 'public_bookcase' || v === 'recycling' || v === 'vending_machine';
+ function meanBy(array, iteratee) {
+ return baseMean(array, getIteratee(iteratee, 2));
}
- } //
- // `buildLocationIndex()`
- // Call this to prepare a which-polygon location index.
- // This *resolves* all the locationSets into GeoJSON, which takes some time.
- // You can skip this step if you don't care about matching within a location.
- //
- // `data` needs to be an Object indexed on a 'tree/key/value' path.
- // (e.g. cache filled by `fileTree.read` or data found in `dist/nsi.json`)
- // {
- // 'brands/amenity/bank': { properties: {}, items: [ {}, {}, … ] },
- // 'brands/amenity/bar': { properties: {}, items: [ {}, {}, … ] },
- // …
- // }
- //
+ /**
+ * Computes the minimum value of `array`. If `array` is empty or falsey,
+ * `undefined` is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * _.min([]);
+ * // => undefined
+ */
- }, {
- key: "buildLocationIndex",
- value: function buildLocationIndex(data, loco) {
- var that = this;
- if (that.locationIndex) return; // it was built already
- that.itemLocation = new Map();
- that.locationSets = new Map();
- Object.keys(data).forEach(function (tkv) {
- var items = data[tkv].items;
- if (!Array.isArray(items) || !items.length) return;
- items.forEach(function (item) {
- if (that.itemLocation.has(item.id)) return; // we've seen item id already - shouldn't be possible?
+ function min(array) {
+ return array && array.length ? baseExtremum(array, identity, baseLt) : undefined$1;
+ }
+ /**
+ * This method is like `_.min` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.minBy(objects, function(o) { return o.n; });
+ * // => { 'n': 1 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.minBy(objects, 'n');
+ * // => { 'n': 1 }
+ */
- var resolved;
- try {
- resolved = loco.resolveLocationSet(item.locationSet); // resolve a feature for this locationSet
- } catch (err) {
- console.warn("buildLocationIndex: ".concat(err.message)); // couldn't resolve
- }
+ function minBy(array, iteratee) {
+ return array && array.length ? baseExtremum(array, getIteratee(iteratee, 2), baseLt) : undefined$1;
+ }
+ /**
+ * Multiply two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {number} multiplier The first number in a multiplication.
+ * @param {number} multiplicand The second number in a multiplication.
+ * @returns {number} Returns the product.
+ * @example
+ *
+ * _.multiply(6, 4);
+ * // => 24
+ */
- if (!resolved || !resolved.id) return;
- that.itemLocation.set(item.id, resolved.id); // link it to the item
- if (that.locationSets.has(resolved.id)) return; // we've seen this locationSet feature before..
- // First time seeing this locationSet feature, make a copy and add to locationSet cache..
+ var multiply = createMathOperation(function (multiplier, multiplicand) {
+ return multiplier * multiplicand;
+ }, 1);
+ /**
+ * Computes `number` rounded to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round.
+ * @param {number} [precision=0] The precision to round to.
+ * @returns {number} Returns the rounded number.
+ * @example
+ *
+ * _.round(4.006);
+ * // => 4
+ *
+ * _.round(4.006, 2);
+ * // => 4.01
+ *
+ * _.round(4060, -2);
+ * // => 4100
+ */
- var feature = _cloneDeep(resolved.feature);
+ var round = createRound('round');
+ /**
+ * Subtract two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {number} minuend The first number in a subtraction.
+ * @param {number} subtrahend The second number in a subtraction.
+ * @returns {number} Returns the difference.
+ * @example
+ *
+ * _.subtract(6, 4);
+ * // => 2
+ */
- feature.id = resolved.id; // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`)
+ var subtract = createMathOperation(function (minuend, subtrahend) {
+ return minuend - subtrahend;
+ }, 0);
+ /**
+ * Computes the sum of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.4.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * _.sum([4, 2, 8, 6]);
+ * // => 20
+ */
- feature.properties.id = resolved.id;
+ function sum(array) {
+ return array && array.length ? baseSum(array, identity) : 0;
+ }
+ /**
+ * This method is like `_.sum` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the value to be summed.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+ *
+ * _.sumBy(objects, function(o) { return o.n; });
+ * // => 20
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sumBy(objects, 'n');
+ * // => 20
+ */
- if (!feature.geometry.coordinates.length || !feature.properties.area) {
- console.warn("buildLocationIndex: locationSet ".concat(resolved.id, " for ").concat(item.id, " resolves to an empty feature:"));
- console.warn(JSON.stringify(feature));
- return;
- }
- that.locationSets.set(resolved.id, feature);
+ function sumBy(array, iteratee) {
+ return array && array.length ? baseSum(array, getIteratee(iteratee, 2)) : 0;
+ }
+ /*------------------------------------------------------------------------*/
+ // Add methods that return wrapped values in chain sequences.
+
+
+ lodash.after = after;
+ lodash.ary = ary;
+ lodash.assign = assign;
+ lodash.assignIn = assignIn;
+ lodash.assignInWith = assignInWith;
+ lodash.assignWith = assignWith;
+ lodash.at = at;
+ lodash.before = before;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.bindKey = bindKey;
+ lodash.castArray = castArray;
+ lodash.chain = chain;
+ lodash.chunk = chunk;
+ lodash.compact = compact;
+ lodash.concat = concat;
+ lodash.cond = cond;
+ lodash.conforms = conforms;
+ lodash.constant = constant;
+ lodash.countBy = countBy;
+ lodash.create = create;
+ lodash.curry = curry;
+ lodash.curryRight = curryRight;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defaultsDeep = defaultsDeep;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.differenceBy = differenceBy;
+ lodash.differenceWith = differenceWith;
+ lodash.drop = drop;
+ lodash.dropRight = dropRight;
+ lodash.dropRightWhile = dropRightWhile;
+ lodash.dropWhile = dropWhile;
+ lodash.fill = fill;
+ lodash.filter = filter;
+ lodash.flatMap = flatMap;
+ lodash.flatMapDeep = flatMapDeep;
+ lodash.flatMapDepth = flatMapDepth;
+ lodash.flatten = flatten;
+ lodash.flattenDeep = flattenDeep;
+ lodash.flattenDepth = flattenDepth;
+ lodash.flip = flip;
+ lodash.flow = flow;
+ lodash.flowRight = flowRight;
+ lodash.fromPairs = fromPairs;
+ lodash.functions = functions;
+ lodash.functionsIn = functionsIn;
+ lodash.groupBy = groupBy;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.intersectionBy = intersectionBy;
+ lodash.intersectionWith = intersectionWith;
+ lodash.invert = invert;
+ lodash.invertBy = invertBy;
+ lodash.invokeMap = invokeMap;
+ lodash.iteratee = iteratee;
+ lodash.keyBy = keyBy;
+ lodash.keys = keys;
+ lodash.keysIn = keysIn;
+ lodash.map = map;
+ lodash.mapKeys = mapKeys;
+ lodash.mapValues = mapValues;
+ lodash.matches = matches;
+ lodash.matchesProperty = matchesProperty;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.mergeWith = mergeWith;
+ lodash.method = method;
+ lodash.methodOf = methodOf;
+ lodash.mixin = mixin;
+ lodash.negate = negate;
+ lodash.nthArg = nthArg;
+ lodash.omit = omit;
+ lodash.omitBy = omitBy;
+ lodash.once = once;
+ lodash.orderBy = orderBy;
+ lodash.over = over;
+ lodash.overArgs = overArgs;
+ lodash.overEvery = overEvery;
+ lodash.overSome = overSome;
+ lodash.partial = partial;
+ lodash.partialRight = partialRight;
+ lodash.partition = partition;
+ lodash.pick = pick;
+ lodash.pickBy = pickBy;
+ lodash.property = property;
+ lodash.propertyOf = propertyOf;
+ lodash.pull = pull;
+ lodash.pullAll = pullAll;
+ lodash.pullAllBy = pullAllBy;
+ lodash.pullAllWith = pullAllWith;
+ lodash.pullAt = pullAt;
+ lodash.range = range;
+ lodash.rangeRight = rangeRight;
+ lodash.rearg = rearg;
+ lodash.reject = reject;
+ lodash.remove = remove;
+ lodash.rest = rest;
+ lodash.reverse = reverse;
+ lodash.sampleSize = sampleSize;
+ lodash.set = set;
+ lodash.setWith = setWith;
+ lodash.shuffle = shuffle;
+ lodash.slice = slice;
+ lodash.sortBy = sortBy;
+ lodash.sortedUniq = sortedUniq;
+ lodash.sortedUniqBy = sortedUniqBy;
+ lodash.split = split;
+ lodash.spread = spread;
+ lodash.tail = tail;
+ lodash.take = take;
+ lodash.takeRight = takeRight;
+ lodash.takeRightWhile = takeRightWhile;
+ lodash.takeWhile = takeWhile;
+ lodash.tap = tap;
+ lodash.throttle = throttle;
+ lodash.thru = thru;
+ lodash.toArray = toArray;
+ lodash.toPairs = toPairs;
+ lodash.toPairsIn = toPairsIn;
+ lodash.toPath = toPath;
+ lodash.toPlainObject = toPlainObject;
+ lodash.transform = transform;
+ lodash.unary = unary;
+ lodash.union = union;
+ lodash.unionBy = unionBy;
+ lodash.unionWith = unionWith;
+ lodash.uniq = uniq;
+ lodash.uniqBy = uniqBy;
+ lodash.uniqWith = uniqWith;
+ lodash.unset = unset;
+ lodash.unzip = unzip;
+ lodash.unzipWith = unzipWith;
+ lodash.update = update;
+ lodash.updateWith = updateWith;
+ lodash.values = values;
+ lodash.valuesIn = valuesIn;
+ lodash.without = without;
+ lodash.words = words;
+ lodash.wrap = wrap;
+ lodash.xor = xor;
+ lodash.xorBy = xorBy;
+ lodash.xorWith = xorWith;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+ lodash.zipObjectDeep = zipObjectDeep;
+ lodash.zipWith = zipWith; // Add aliases.
+
+ lodash.entries = toPairs;
+ lodash.entriesIn = toPairsIn;
+ lodash.extend = assignIn;
+ lodash.extendWith = assignInWith; // Add methods to `lodash.prototype`.
+
+ mixin(lodash, lodash);
+ /*------------------------------------------------------------------------*/
+ // Add methods that return unwrapped values in chain sequences.
+
+ lodash.add = add;
+ lodash.attempt = attempt;
+ lodash.camelCase = camelCase;
+ lodash.capitalize = capitalize;
+ lodash.ceil = ceil;
+ lodash.clamp = clamp;
+ lodash.clone = clone;
+ lodash.cloneDeep = cloneDeep;
+ lodash.cloneDeepWith = cloneDeepWith;
+ lodash.cloneWith = cloneWith;
+ lodash.conformsTo = conformsTo;
+ lodash.deburr = deburr;
+ lodash.defaultTo = defaultTo;
+ lodash.divide = divide;
+ lodash.endsWith = endsWith;
+ lodash.eq = eq;
+ lodash.escape = escape;
+ lodash.escapeRegExp = escapeRegExp;
+ lodash.every = every;
+ lodash.find = find;
+ lodash.findIndex = findIndex;
+ lodash.findKey = findKey;
+ lodash.findLast = findLast;
+ lodash.findLastIndex = findLastIndex;
+ lodash.findLastKey = findLastKey;
+ lodash.floor = floor;
+ lodash.forEach = forEach;
+ lodash.forEachRight = forEachRight;
+ lodash.forIn = forIn;
+ lodash.forInRight = forInRight;
+ lodash.forOwn = forOwn;
+ lodash.forOwnRight = forOwnRight;
+ lodash.get = get;
+ lodash.gt = gt;
+ lodash.gte = gte;
+ lodash.has = has;
+ lodash.hasIn = hasIn;
+ lodash.head = head;
+ lodash.identity = identity;
+ lodash.includes = includes;
+ lodash.indexOf = indexOf;
+ lodash.inRange = inRange;
+ lodash.invoke = invoke;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isArrayBuffer = isArrayBuffer;
+ lodash.isArrayLike = isArrayLike;
+ lodash.isArrayLikeObject = isArrayLikeObject;
+ lodash.isBoolean = isBoolean;
+ lodash.isBuffer = isBuffer;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isEqualWith = isEqualWith;
+ lodash.isError = isError;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isInteger = isInteger;
+ lodash.isLength = isLength;
+ lodash.isMap = isMap;
+ lodash.isMatch = isMatch;
+ lodash.isMatchWith = isMatchWith;
+ lodash.isNaN = isNaN;
+ lodash.isNative = isNative;
+ lodash.isNil = isNil;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isObjectLike = isObjectLike;
+ lodash.isPlainObject = isPlainObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isSafeInteger = isSafeInteger;
+ lodash.isSet = isSet;
+ lodash.isString = isString;
+ lodash.isSymbol = isSymbol;
+ lodash.isTypedArray = isTypedArray;
+ lodash.isUndefined = isUndefined;
+ lodash.isWeakMap = isWeakMap;
+ lodash.isWeakSet = isWeakSet;
+ lodash.join = join;
+ lodash.kebabCase = kebabCase;
+ lodash.last = last;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.lowerCase = lowerCase;
+ lodash.lowerFirst = lowerFirst;
+ lodash.lt = lt;
+ lodash.lte = lte;
+ lodash.max = max;
+ lodash.maxBy = maxBy;
+ lodash.mean = mean;
+ lodash.meanBy = meanBy;
+ lodash.min = min;
+ lodash.minBy = minBy;
+ lodash.stubArray = stubArray;
+ lodash.stubFalse = stubFalse;
+ lodash.stubObject = stubObject;
+ lodash.stubString = stubString;
+ lodash.stubTrue = stubTrue;
+ lodash.multiply = multiply;
+ lodash.nth = nth;
+ lodash.noConflict = noConflict;
+ lodash.noop = noop;
+ lodash.now = now;
+ lodash.pad = pad;
+ lodash.padEnd = padEnd;
+ lodash.padStart = padStart;
+ lodash.parseInt = parseInt;
+ lodash.random = random;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.repeat = repeat;
+ lodash.replace = replace;
+ lodash.result = result;
+ lodash.round = round;
+ lodash.runInContext = runInContext;
+ lodash.sample = sample;
+ lodash.size = size;
+ lodash.snakeCase = snakeCase;
+ lodash.some = some;
+ lodash.sortedIndex = sortedIndex;
+ lodash.sortedIndexBy = sortedIndexBy;
+ lodash.sortedIndexOf = sortedIndexOf;
+ lodash.sortedLastIndex = sortedLastIndex;
+ lodash.sortedLastIndexBy = sortedLastIndexBy;
+ lodash.sortedLastIndexOf = sortedLastIndexOf;
+ lodash.startCase = startCase;
+ lodash.startsWith = startsWith;
+ lodash.subtract = subtract;
+ lodash.sum = sum;
+ lodash.sumBy = sumBy;
+ lodash.template = template;
+ lodash.times = times;
+ lodash.toFinite = toFinite;
+ lodash.toInteger = toInteger;
+ lodash.toLength = toLength;
+ lodash.toLower = toLower;
+ lodash.toNumber = toNumber;
+ lodash.toSafeInteger = toSafeInteger;
+ lodash.toString = toString;
+ lodash.toUpper = toUpper;
+ lodash.trim = trim;
+ lodash.trimEnd = trimEnd;
+ lodash.trimStart = trimStart;
+ lodash.truncate = truncate;
+ lodash.unescape = unescape;
+ lodash.uniqueId = uniqueId;
+ lodash.upperCase = upperCase;
+ lodash.upperFirst = upperFirst; // Add aliases.
+
+ lodash.each = forEach;
+ lodash.eachRight = forEachRight;
+ lodash.first = head;
+ mixin(lodash, function () {
+ var source = {};
+ baseForOwn(lodash, function (func, methodName) {
+ if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+ source[methodName] = func;
+ }
});
+ return source;
+ }(), {
+ 'chain': false
});
- that.locationIndex = whichPolygon_1({
- type: 'FeatureCollection',
- features: _toConsumableArray(that.locationSets.values())
- });
+ /*------------------------------------------------------------------------*/
- function _cloneDeep(obj) {
- return JSON.parse(JSON.stringify(obj));
- }
- } //
- // `match()`
- // Pass parts and return an Array of matches.
- // `k` - key
- // `v` - value
- // `n` - namelike
- // `loc` - optional - [lon,lat] location to search
- //
- // 1. If the [k,v,n] tuple matches a canonical item…
- // Return an Array of match results.
- // Each result will include the area in km² that the item is valid.
- //
- // Order of results:
- // Primary ordering will be on the "match" column:
- // "primary" - where the query matches the `name` tag, followed by
- // "alternate" - where the query matches an alternate name tag (e.g. short_name, brand, operator, etc)
- // Secondary ordering will be on the "area" column:
- // "area descending" if no location was provided, (worldwide before local)
- // "area ascending" if location was provided (local before worldwide)
- //
- // [
- // { match: 'primary', itemID: String, area: Number, kv: String, nsimple: String },
- // { match: 'primary', itemID: String, area: Number, kv: String, nsimple: String },
- // { match: 'alternate', itemID: String, area: Number, kv: String, nsimple: String },
- // { match: 'alternate', itemID: String, area: Number, kv: String, nsimple: String },
- // …
- // ]
- //
- // -or-
- //
- // 2. If the [k,v,n] tuple matches an exclude pattern…
- // Return an Array with a single exclude result, either
- //
- // [ { match: 'excludeGeneric', pattern: String, kv: String } ] // "generic" e.g. "Food Court"
- // or
- // [ { match: 'excludeNamed', pattern: String, kv: String } ] // "named", e.g. "Kebabai"
- //
- // About results
- // "generic" - a generic word that is probably not really a name.
- // For these, iD should warn the user "Hey don't put 'food court' in the name tag".
- // "named" - a real name like "Kebabai" that is just common, but not a brand.
- // For these, iD should just let it be. We don't include these in NSI, but we don't want to nag users about it either.
- //
- // -or-
- //
- // 3. If the [k,v,n] tuple matches nothing of any kind, return `null`
- //
- //
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type {string}
+ */
- }, {
- key: "match",
- value: function match(k, v, n, loc) {
- var that = this;
+ lodash.VERSION = VERSION; // Assign default placeholders.
- if (!that.matchIndex) {
- throw new Error('match: matchIndex not built.');
- } // If we were supplied a location, and a that.locationIndex has been set up,
- // get the locationSets that are valid there so we can filter results.
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function (methodName) {
+ lodash[methodName].placeholder = lodash;
+ }); // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+ arrayEach(['drop', 'take'], function (methodName, index) {
+ LazyWrapper.prototype[methodName] = function (n) {
+ n = n === undefined$1 ? 1 : nativeMax(toInteger(n), 0);
+ var result = this.__filtered__ && !index ? new LazyWrapper(this) : this.clone();
- var matchLocations;
+ if (result.__filtered__) {
+ result.__takeCount__ = nativeMin(n, result.__takeCount__);
+ } else {
+ result.__views__.push({
+ 'size': nativeMin(n, MAX_ARRAY_LENGTH),
+ 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+ });
+ }
- if (Array.isArray(loc) && that.locationIndex) {
- // which-polygon query returns an array of GeoJSON properties, pass true to return all results
- matchLocations = that.locationIndex([loc[0], loc[1], loc[0], loc[1]], true);
- }
+ return result;
+ };
- var nsimple = simplify$1(n);
- var seen = new Set();
- var results = [];
- gatherResults('primary');
- gatherResults('alternate');
- if (results.length) return results;
- gatherResults('exclude');
- return results.length ? results : null;
+ LazyWrapper.prototype[methodName + 'Right'] = function (n) {
+ return this.reverse()[methodName](n).reverse();
+ };
+ }); // Add `LazyWrapper` methods that accept an `iteratee` value.
- function gatherResults(which) {
- // First try an exact match on k/v
- var kv = "".concat(k, "/").concat(v);
- var didMatch = tryMatch(which, kv);
- if (didMatch) return; // If that didn't work, look in match groups for other pairs considered equivalent to k/v..
+ arrayEach(['filter', 'map', 'takeWhile'], function (methodName, index) {
+ var type = index + 1,
+ isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
- for (var mg in matchGroups) {
- var matchGroup = matchGroups[mg];
- var inGroup = matchGroup.some(function (otherkv) {
- return otherkv === kv;
+ LazyWrapper.prototype[methodName] = function (iteratee) {
+ var result = this.clone();
+
+ result.__iteratees__.push({
+ 'iteratee': getIteratee(iteratee, 3),
+ 'type': type
});
- if (!inGroup) continue;
- for (var i = 0; i < matchGroup.length; i++) {
- var otherkv = matchGroup[i];
- if (otherkv === kv) continue; // skip self
+ result.__filtered__ = result.__filtered__ || isFilter;
+ return result;
+ };
+ }); // Add `LazyWrapper` methods for `_.head` and `_.last`.
- didMatch = tryMatch(which, otherkv);
- if (didMatch) return;
- }
- } // If finished 'exclude' pass and still haven't matched anything, try the global `genericWords.json` patterns
+ arrayEach(['head', 'last'], function (methodName, index) {
+ var takeName = 'take' + (index ? 'Right' : '');
+ LazyWrapper.prototype[methodName] = function () {
+ return this[takeName](1).value()[0];
+ };
+ }); // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
- if (which === 'exclude') {
- var regex = _toConsumableArray(that.genericWords.values()).find(function (regex) {
- return regex.test(n);
- });
+ arrayEach(['initial', 'tail'], function (methodName, index) {
+ var dropName = 'drop' + (index ? '' : 'Right');
- if (regex) {
- results.push({
- match: 'excludeGeneric',
- pattern: String(regex)
- }); // note no `branch`, no `kv`
+ LazyWrapper.prototype[methodName] = function () {
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+ };
+ });
- return;
- }
+ LazyWrapper.prototype.compact = function () {
+ return this.filter(identity);
+ };
+
+ LazyWrapper.prototype.find = function (predicate) {
+ return this.filter(predicate).head();
+ };
+
+ LazyWrapper.prototype.findLast = function (predicate) {
+ return this.reverse().find(predicate);
+ };
+
+ LazyWrapper.prototype.invokeMap = baseRest(function (path, args) {
+ if (typeof path == 'function') {
+ return new LazyWrapper(this);
}
- }
- function tryMatch(which, kv) {
- var branch = that.matchIndex.get(kv);
- if (!branch) return;
+ return this.map(function (value) {
+ return baseInvoke(value, path, args);
+ });
+ });
- if (which === 'exclude') {
- // Test name `n` against named and generic exclude patterns
- var regex = _toConsumableArray(branch.excludeNamed.values()).find(function (regex) {
- return regex.test(n);
- });
+ LazyWrapper.prototype.reject = function (predicate) {
+ return this.filter(negate(getIteratee(predicate)));
+ };
- if (regex) {
- results.push({
- match: 'excludeNamed',
- pattern: String(regex),
- kv: kv
- });
- return;
- }
+ LazyWrapper.prototype.slice = function (start, end) {
+ start = toInteger(start);
+ var result = this;
- regex = _toConsumableArray(branch.excludeGeneric.values()).find(function (regex) {
- return regex.test(n);
- });
+ if (result.__filtered__ && (start > 0 || end < 0)) {
+ return new LazyWrapper(result);
+ }
- if (regex) {
- results.push({
- match: 'excludeGeneric',
- pattern: String(regex),
- kv: kv
- });
- return;
- }
+ if (start < 0) {
+ result = result.takeRight(-start);
+ } else if (start) {
+ result = result.drop(start);
+ }
+
+ if (end !== undefined$1) {
+ end = toInteger(end);
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+ }
+ return result;
+ };
+
+ LazyWrapper.prototype.takeRightWhile = function (predicate) {
+ return this.reverse().takeWhile(predicate).reverse();
+ };
+
+ LazyWrapper.prototype.toArray = function () {
+ return this.take(MAX_ARRAY_LENGTH);
+ }; // Add `LazyWrapper` methods to `lodash.prototype`.
+
+
+ baseForOwn(LazyWrapper.prototype, function (func, methodName) {
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+ isTaker = /^(?:head|last)$/.test(methodName),
+ lodashFunc = lodash[isTaker ? 'take' + (methodName == 'last' ? 'Right' : '') : methodName],
+ retUnwrapped = isTaker || /^find/.test(methodName);
+
+ if (!lodashFunc) {
return;
}
- var leaf = branch[which].get(nsimple);
- if (!leaf || !leaf.size) return; // If we get here, we matched something..
- // Prepare the results, calculate areas (if location index was set up)
+ lodash.prototype[methodName] = function () {
+ var value = this.__wrapped__,
+ args = isTaker ? [1] : arguments,
+ isLazy = value instanceof LazyWrapper,
+ iteratee = args[0],
+ useLazy = isLazy || isArray(value);
- var hits = Array.from(leaf).map(function (itemID) {
- var area = Infinity;
+ var interceptor = function interceptor(value) {
+ var result = lodashFunc.apply(lodash, arrayPush([value], args));
+ return isTaker && chainAll ? result[0] : result;
+ };
- if (that.itemLocation && that.locationSets) {
- var location = that.locationSets.get(that.itemLocation.get(itemID));
- area = location && location.properties.area || Infinity;
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
+ isLazy = useLazy = false;
}
- return {
- match: which,
- itemID: itemID,
- area: area,
- kv: kv,
- nsimple: nsimple
- };
- });
- var sortFn = byAreaDescending; // Filter the match to include only results valid in the requested `loc`..
+ var chainAll = this.__chain__,
+ isHybrid = !!this.__actions__.length,
+ isUnwrapped = retUnwrapped && !chainAll,
+ onlyLazy = isLazy && !isHybrid;
- if (matchLocations) {
- hits = hits.filter(isValidLocation);
- sortFn = byAreaAscending;
- }
+ if (!retUnwrapped && useLazy) {
+ value = onlyLazy ? value : new LazyWrapper(this);
+ var result = func.apply(value, args);
- if (!hits.length) return; // push results
+ result.__actions__.push({
+ 'func': thru,
+ 'args': [interceptor],
+ 'thisArg': undefined$1
+ });
- hits.sort(sortFn).forEach(function (hit) {
- if (seen.has(hit.itemID)) return;
- seen.add(hit.itemID);
- results.push(hit);
- });
- return true;
+ return new LodashWrapper(result, chainAll);
+ }
- function isValidLocation(hit) {
- if (!that.itemLocation) return true;
- return matchLocations.find(function (props) {
- return props.id === that.itemLocation.get(hit.itemID);
+ if (isUnwrapped && onlyLazy) {
+ return func.apply(this, args);
+ }
+
+ result = this.thru(interceptor);
+ return isUnwrapped ? isTaker ? result.value()[0] : result.value() : result;
+ };
+ }); // Add `Array` methods to `lodash.prototype`.
+
+ arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function (methodName) {
+ var func = arrayProto[methodName],
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+ retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+ lodash.prototype[methodName] = function () {
+ var args = arguments;
+
+ if (retUnwrapped && !this.__chain__) {
+ var value = this.value();
+ return func.apply(isArray(value) ? value : [], args);
+ }
+
+ return this[chainName](function (value) {
+ return func.apply(isArray(value) ? value : [], args);
});
- } // Sort smaller (more local) locations first.
+ };
+ }); // Map minified method names to their real names.
+ baseForOwn(LazyWrapper.prototype, function (func, methodName) {
+ var lodashFunc = lodash[methodName];
- function byAreaAscending(hitA, hitB) {
- return hitA.area - hitB.area;
- } // Sort larger (more worldwide) locations first.
+ if (lodashFunc) {
+ var key = lodashFunc.name + '';
+ if (!hasOwnProperty.call(realNames, key)) {
+ realNames[key] = [];
+ }
- function byAreaDescending(hitA, hitB) {
- return hitB.area - hitA.area;
+ realNames[key].push({
+ 'name': methodName,
+ 'func': lodashFunc
+ });
}
- }
- } //
- // `getWarnings()`
- // Return any warnings discovered when buiding the index.
- // (currently this does nothing)
- //
+ });
+ realNames[createHybrid(undefined$1, WRAP_BIND_KEY_FLAG).name] = [{
+ 'name': 'wrapper',
+ 'func': undefined$1
+ }]; // Add methods to `LazyWrapper`.
- }, {
- key: "getWarnings",
- value: function getWarnings() {
- return this.warnings;
- }
- }]);
+ LazyWrapper.prototype.clone = lazyClone;
+ LazyWrapper.prototype.reverse = lazyReverse;
+ LazyWrapper.prototype.value = lazyValue; // Add chain sequence methods to the `lodash` wrapper.
- return Matcher;
- }();
+ lodash.prototype.at = wrapperAt;
+ lodash.prototype.chain = wrapperChain;
+ lodash.prototype.commit = wrapperCommit;
+ lodash.prototype.next = wrapperNext;
+ lodash.prototype.plant = wrapperPlant;
+ lodash.prototype.reverse = wrapperReverse;
+ lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue; // Add lazy aliases.
- /**
- * 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
- */
- function isObject$2(value) {
- var type = _typeof(value);
+ lodash.prototype.first = lodash.prototype.head;
- return value != null && (type == 'object' || type == 'function');
- }
+ if (symIterator) {
+ lodash.prototype[symIterator] = wrapperToIterator;
+ }
- /** Detect free variable `global` from Node.js. */
- var freeGlobal = (typeof global === "undefined" ? "undefined" : _typeof(global)) == 'object' && global && global.Object === Object && global;
+ return lodash;
+ };
+ /*--------------------------------------------------------------------------*/
+ // Export lodash.
- /** Detect free variable `self`. */
- var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self;
- /** Used as a reference to the global object. */
+ var _ = runInContext(); // Some AMD build optimizers, like r.js, check for condition patterns like:
- var root = freeGlobal || freeSelf || Function('return this')();
- /**
- * 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.
- */
+ if (freeModule) {
+ // Export for Node.js.
+ (freeModule.exports = _)._ = _; // Export for CommonJS support.
- var now = function now() {
- return root.Date.now();
- };
-
- /** Used to match a single whitespace character. */
- var reWhitespace = /\s/;
- /**
- * 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.
- */
-
- function trimmedEndIndex(string) {
- var index = string.length;
-
- while (index-- && reWhitespace.test(string.charAt(index))) {}
+ freeExports._ = _;
+ } else {
+ // Export to the global object.
+ root._ = _;
+ }
+ }).call(commonjsGlobal);
+ })(lodash, lodash.exports);
- return index;
- }
+ function actionMergeRemoteChanges(id, localGraph, remoteGraph, discardTags, formatUser) {
+ discardTags = discardTags || {};
+ var _option = 'safe'; // 'safe', 'force_local', 'force_remote'
- /** Used to match leading whitespace. */
+ var _conflicts = [];
- var reTrimStart = /^\s+/;
- /**
- * The base implementation of `_.trim`.
- *
- * @private
- * @param {string} string The string to trim.
- * @returns {string} Returns the trimmed string.
- */
+ function user(d) {
+ return typeof formatUser === 'function' ? formatUser(d) : lodash.exports.escape(d);
+ }
- function baseTrim(string) {
- return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string;
- }
+ 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;
+ }
- /** Built-in value references. */
+ if (_option === 'force_local' || pointEqual(target.loc, remote.loc)) {
+ return target;
+ }
- var _Symbol = root.Symbol;
+ if (_option === 'force_remote') {
+ return target.update({
+ loc: remote.loc
+ });
+ }
- /** Used for built-in method references. */
+ _conflicts.push(_t.html('merge_remote_changes.conflict.location', {
+ user: {
+ html: user(remote.user)
+ }
+ }));
- var objectProto$1 = Object.prototype;
- /** Used to check objects for own properties. */
+ return target;
+ }
- var hasOwnProperty$2 = objectProto$1.hasOwnProperty;
- /**
- * Used to resolve the
- * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
- * of values.
- */
+ function mergeNodes(base, remote, target) {
+ if (_option === 'force_local' || fastDeepEqual(target.nodes, remote.nodes)) {
+ return target;
+ }
- var nativeObjectToString$1 = objectProto$1.toString;
- /** Built-in value references. */
+ if (_option === 'force_remote') {
+ return target.update({
+ nodes: remote.nodes
+ });
+ }
- var symToStringTag$1 = _Symbol ? _Symbol.toStringTag : undefined;
- /**
- * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
- *
- * @private
- * @param {*} value The value to query.
- * @returns {string} Returns the raw `toStringTag`.
- */
+ 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
+ });
- function getRawTag(value) {
- var isOwn = hasOwnProperty$2.call(value, symToStringTag$1),
- tag = value[symToStringTag$1];
+ for (var i = 0; i < hunks.length; i++) {
+ var hunk = hunks[i];
- try {
- value[symToStringTag$1] = undefined;
- var unmasked = true;
- } catch (e) {}
+ 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;
- var result = nativeObjectToString$1.call(value);
+ 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.html('merge_remote_changes.conflict.nodelist', {
+ user: {
+ html: user(remote.user)
+ }
+ }));
- if (unmasked) {
- if (isOwn) {
- value[symToStringTag$1] = tag;
- } else {
- delete value[symToStringTag$1];
+ break;
+ }
+ }
}
- }
- return result;
- }
+ return _conflicts.length === ccount ? target.update({
+ nodes: nodes
+ }) : target;
+ }
- /** Used for built-in method references. */
- var objectProto = Object.prototype;
- /**
- * Used to resolve the
- * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
- * of values.
- */
+ 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;
+ }
- var nativeObjectToString = objectProto.toString;
- /**
- * Converts `value` to a string using `Object.prototype.toString`.
- *
- * @private
- * @param {*} value The value to convert.
- * @returns {string} Returns the converted string.
- */
+ var ccount = _conflicts.length;
- function objectToString(value) {
- return nativeObjectToString.call(value);
- }
+ for (var i = 0; i < children.length; i++) {
+ var id = children[i];
+ var node = graph.hasEntity(id); // remove unused childNodes..
- /** `Object#toString` result references. */
+ if (targetWay.nodes.indexOf(id) === -1) {
+ if (node && !isUsed(node, targetWay)) {
+ updates.removeIds.push(id);
+ }
- var nullTag = '[object Null]',
- undefinedTag = '[object Undefined]';
- /** Built-in value references. */
+ continue;
+ } // restore used childNodes..
- var symToStringTag = _Symbol ? _Symbol.toStringTag : undefined;
- /**
- * The base implementation of `getTag` without fallbacks for buggy environments.
- *
- * @private
- * @param {*} value The value to query.
- * @returns {string} Returns the `toStringTag`.
- */
- function baseGetTag(value) {
- if (value == null) {
- return value === undefined ? undefinedTag : nullTag;
- }
+ var local = localGraph.hasEntity(id);
+ var remote = remoteGraph.hasEntity(id);
+ var target;
- return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
- }
+ if (_option === 'force_remote' && remote && remote.visible) {
+ updates.replacements.push(remote);
+ } else if (_option === 'force_local' && local) {
+ target = osmEntity(local);
- /**
- * 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
- */
- function isObjectLike(value) {
- return value != null && _typeof(value) == 'object';
- }
+ if (remote) {
+ target = target.update({
+ version: remote.version
+ });
+ }
- /** `Object#toString` result references. */
+ updates.replacements.push(target);
+ } else if (_option === 'safe' && local && remote && local.version !== remote.version) {
+ target = osmEntity(local, {
+ version: remote.version
+ });
- var symbolTag = '[object Symbol]';
- /**
- * 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
- */
+ if (remote.visible) {
+ target = mergeLocation(remote, target);
+ } else {
+ _conflicts.push(_t.html('merge_remote_changes.conflict.deleted', {
+ user: {
+ html: user(remote.user)
+ }
+ }));
+ }
- function isSymbol(value) {
- return _typeof(value) == 'symbol' || isObjectLike(value) && baseGetTag(value) == symbolTag;
- }
+ if (_conflicts.length !== ccount) break;
+ updates.replacements.push(target);
+ }
+ }
- /** Used as references for various `Number` constants. */
+ return targetWay;
+ }
- var NAN = 0 / 0;
- /** Used to detect bad signed hexadecimal string values. */
+ function updateChildren(updates, graph) {
+ for (var i = 0; i < updates.replacements.length; i++) {
+ graph = graph.replace(updates.replacements[i]);
+ }
- var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
- /** Used to detect binary string values. */
+ if (updates.removeIds.length) {
+ graph = actionDeleteMultiple(updates.removeIds)(graph);
+ }
- var reIsBinary = /^0b[01]+$/i;
- /** Used to detect octal string values. */
+ return graph;
+ }
- var reIsOctal = /^0o[0-7]+$/i;
- /** Built-in method references without a dependency on `root`. */
+ function mergeMembers(remote, target) {
+ if (_option === 'force_local' || fastDeepEqual(target.members, remote.members)) {
+ return target;
+ }
- var freeParseInt = parseInt;
- /**
- * 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
- */
+ if (_option === 'force_remote') {
+ return target.update({
+ members: remote.members
+ });
+ }
- function toNumber(value) {
- if (typeof value == 'number') {
- return value;
- }
+ _conflicts.push(_t.html('merge_remote_changes.conflict.memberlist', {
+ user: {
+ html: user(remote.user)
+ }
+ }));
- if (isSymbol(value)) {
- return NAN;
+ return target;
}
- if (isObject$2(value)) {
- var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
- value = isObject$2(other) ? other + '' : other;
- }
+ function mergeTags(base, remote, target) {
+ if (_option === 'force_local' || fastDeepEqual(target.tags, remote.tags)) {
+ return target;
+ }
- if (typeof value != 'string') {
- return value === 0 ? value : +value;
- }
+ if (_option === 'force_remote') {
+ return target.update({
+ tags: remote.tags
+ });
+ }
- 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;
- }
+ 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
- /** Error message constants. */
+ var changed = false;
- var FUNC_ERROR_TEXT$1 = 'Expected a function';
- /* Built-in method references for those with the same name as other `lodash` methods. */
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
- var nativeMax = Math.max,
- nativeMin = Math.min;
- /**
- * 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 (o[k] !== b[k] && a[k] !== b[k]) {
+ // changed remotely..
+ if (o[k] !== a[k]) {
+ // changed locally..
+ _conflicts.push(_t.html('merge_remote_changes.conflict.tags', {
+ tag: k,
+ local: a[k],
+ remote: b[k],
+ user: {
+ html: user(remote.user)
+ }
+ }));
+ } else {
+ // unchanged locally, accept remote change..
+ if (b.hasOwnProperty(k)) {
+ tags[k] = b[k];
+ } else {
+ delete tags[k];
+ }
- function debounce(func, wait, options) {
- var lastArgs,
- lastThis,
- maxWait,
- result,
- timerId,
- lastCallTime,
- lastInvokeTime = 0,
- leading = false,
- maxing = false,
- trailing = true;
+ changed = true;
+ }
+ }
+ }
- if (typeof func != 'function') {
- throw new TypeError(FUNC_ERROR_TEXT$1);
- }
+ 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`
+ //
- wait = toNumber(wait) || 0;
- if (isObject$2(options)) {
- leading = !!options.leading;
- maxing = 'maxWait' in options;
- maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
- trailing = 'trailing' in options ? !!options.trailing : trailing;
- }
+ 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
- function invokeFunc(time) {
- var args = lastArgs,
- thisArg = lastThis;
- lastArgs = lastThis = undefined;
- lastInvokeTime = time;
- result = func.apply(thisArg, args);
- return result;
- }
+ 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);
+ }
- function leadingEdge(time) {
- // Reset any `maxWait` timer.
- lastInvokeTime = time; // Start the timer for the trailing edge.
+ return graph.replace(target);
+ } else {
+ _conflicts.push(_t.html('merge_remote_changes.conflict.deleted', {
+ user: {
+ html: user(remote.user)
+ }
+ }));
- timerId = setTimeout(timerExpired, wait); // Invoke the leading edge.
+ return graph; // do nothing
+ }
+ } // merge
- return leading ? invokeFunc(time) : result;
- }
- function remainingWait(time) {
- var timeSinceLastCall = time - lastCallTime,
- timeSinceLastInvoke = time - lastInvokeTime,
- timeWaiting = wait - timeSinceLastCall;
- return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
- }
-
- 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.
+ 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);
+ }
- return lastCallTime === undefined || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
- }
+ target = mergeTags(base, remote, target);
- function timerExpired() {
- var time = now();
+ if (!_conflicts.length) {
+ graph = updateChildren(updates, graph).replace(target);
+ }
- if (shouldInvoke(time)) {
- return trailingEdge(time);
- } // Restart the timer.
+ return graph;
+ };
+ action.withOption = function (opt) {
+ _option = opt;
+ return action;
+ };
- timerId = setTimeout(timerExpired, remainingWait(time));
- }
+ action.conflicts = function () {
+ return _conflicts;
+ };
- function trailingEdge(time) {
- timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been
- // debounced at least once.
+ return action;
+ }
- if (trailing && lastArgs) {
- return invokeFunc(time);
- }
+ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
- lastArgs = lastThis = undefined;
- return result;
- }
+ function actionMove(moveIDs, tryDelta, projection, cache) {
+ var _delta = tryDelta;
- function cancel() {
- if (timerId !== undefined) {
- clearTimeout(timerId);
- }
+ 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..
- lastInvokeTime = 0;
- lastArgs = lastCallTime = lastThis = timerId = undefined;
- }
+ 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..
- function flush() {
- return timerId === undefined ? result : trailingEdge(now());
- }
+ var parentsMoving = parents.every(function (way) {
+ return cache.moving[way.id];
+ });
+ if (!parentsMoving) delete cache.moving[nodeID];
+ return parentsMoving;
+ }
- function debounced() {
- var time = now(),
- isInvoking = shouldInvoke(time);
- lastArgs = arguments;
- lastThis = this;
- lastCallTime = time;
+ 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;
- if (isInvoking) {
- if (timerId === undefined) {
- return leadingEdge(lastCallTime);
+ 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 (maxing) {
- // Handle invocations in a tight loop.
- clearTimeout(timerId);
- timerId = setTimeout(timerExpired, wait);
- return invokeFunc(lastCallTime);
+ function cacheIntersections(ids) {
+ function isEndpoint(way, id) {
+ return !way.isClosed() && !!way.affix(id);
}
- }
- if (timerId === undefined) {
- timerId = setTimeout(timerExpired, wait);
- }
+ for (var i = 0; i < ids.length; i++) {
+ var id = ids[i]; // consider only intersections with 1 moved and 1 unmoved way.
- return result;
- }
+ var childNodes = graph.childNodes(graph.entity(id));
- debounced.cancel = cancel;
- debounced.flush = flush;
- return debounced;
- }
+ 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;
- /*
- iD.coreDifference represents the difference between two graphs.
- It knows how to calculate the set of entities that were
- created, modified, or deleted, and also contains the logic
- for recursively extending a difference to the complete set
- of entities that will require a redraw, taking into account
- child and parent relationships.
- */
+ for (var k = 0; k < parents.length; k++) {
+ var way = parents[k];
- function coreDifference(base, head) {
- var _changes = {};
- var _didChange = {}; // 'addition', 'deletion', 'geometry', 'properties'
+ if (!cache.moving[way.id]) {
+ unmoved = way;
+ break;
+ }
+ }
- var _diff = {};
+ if (!unmoved) continue; // exclude ways that are overly connected..
- function checkEntityID(id) {
- var h = head.entities[id];
- var b = base.entities[id];
- if (h === b) return;
- if (_changes[id]) return;
+ 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 (!h && b) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.deletion = true;
- return;
+ if (!cache) {
+ cache = {};
}
- if (h && !b) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.addition = true;
- return;
+ 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
+ //
+ //
- if (h && b) {
- if (h.members && b.members && !fastDeepEqual(h.members, b.members)) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.geometry = true;
- _didChange.properties = true;
- return;
- }
- if (h.loc && b.loc && !geoVecEqual(h.loc, b.loc)) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.geometry = true;
- }
+ 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;
- if (h.nodes && b.nodes && !fastDeepEqual(h.nodes, b.nodes)) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.geometry = true;
- }
+ 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;
+ }
- if (h.tags && b.tags && !fastDeepEqual(h.tags, b.tags)) {
- _changes[id] = {
- base: b,
- head: h
- };
- _didChange.properties = true;
- }
+ var prev = graph.hasEntity(way.nodes[prevIndex]);
+ var next = graph.hasEntity(way.nodes[nextIndex]); // Don't add orig vertex at endpoint..
+
+ if (!prev || !next) return graph;
+ var key = wayId + '_' + nodeId;
+ var orig = cache.replacedVertex[key];
+
+ if (!orig) {
+ orig = osmNode();
+ cache.replacedVertex[key] = orig;
+ cache.startLoc[orig.id] = cache.startLoc[nodeId];
}
- }
- function load() {
- // HOT CODE: there can be many thousands of downloaded entities, so looping
- // through them all can become a performance bottleneck. Optimize by
- // resolving duplicates and using a basic `for` loop
- var ids = utilArrayUniq(Object.keys(head.entities).concat(Object.keys(base.entities)));
+ var start, end;
- for (var i = 0; i < ids.length; i++) {
- checkEntityID(ids[i]);
+ if (delta) {
+ start = projection(cache.startLoc[nodeId]);
+ end = projection.invert(geoVecAdd(start, delta));
+ } else {
+ end = cache.startLoc[nodeId];
}
- }
- load();
+ 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..
- _diff.length = function length() {
- return Object.keys(_changes).length;
- };
+ if (angle > 175 && angle < 185) return graph; // moving forward or backward along way?
- _diff.changes = function changes() {
- return _changes;
- };
+ 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?
- _diff.didChange = _didChange; // pass true to include affected relation members
+ 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.
- _diff.extantIDs = function extantIDs(includeRelMembers) {
- var result = new Set();
- Object.keys(_changes).forEach(function (id) {
- if (_changes[id].head) {
- result.add(id);
- }
- var h = _changes[id].head;
- var b = _changes[id].base;
- var entity = h || b;
+ function removeDuplicateVertices(wayId, graph) {
+ var way = graph.entity(wayId);
+ var epsilon = 1e-6;
+ var prev, curr;
- if (includeRelMembers && entity.type === 'relation') {
- var mh = h ? h.members.map(function (m) {
- return m.id;
- }) : [];
- var mb = b ? b.members.map(function (m) {
- return m.id;
- }) : [];
- utilArrayUnion(mh, mb).forEach(function (memberID) {
- if (head.hasEntity(memberID)) {
- result.add(memberID);
- }
- });
- }
- });
- return Array.from(result);
- };
+ function isInteresting(node, graph) {
+ return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
+ }
- _diff.modified = function modified() {
- var result = [];
- Object.values(_changes).forEach(function (change) {
- if (change.base && change.head) {
- result.push(change.head);
- }
- });
- return result;
- };
+ for (var i = 0; i < way.nodes.length; i++) {
+ curr = graph.entity(way.nodes[i]);
- _diff.created = function created() {
- var result = [];
- Object.values(_changes).forEach(function (change) {
- if (!change.base && change.head) {
- result.push(change.head);
+ 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);
+ }
}
- });
- return result;
- };
- _diff.deleted = function deleted() {
- var result = [];
- Object.values(_changes).forEach(function (change) {
- if (change.base && !change.head) {
- result.push(change.base);
- }
- });
- return result;
- };
+ prev = curr;
+ }
- _diff.summary = function summary() {
- var relevant = {};
- var keys = Object.keys(_changes);
+ return graph;
+ } // Reorder nodes around intersections that have moved..
+ //
+ // Start: way1.nodes: b,e (moving)
+ // a - b - c ----- d way2.nodes: a,b,c,d (static)
+ // | vertex: b
+ // e isEP1: true, isEP2, false
+ //
+ // way1 `b,e` moved here:
+ // a ----- c = b - d
+ // |
+ // e
+ //
+ // reorder nodes way1.nodes: b,e
+ // a ----- c - b - d way2.nodes: a,c,b,d
+ // |
+ // e
+ //
- for (var i = 0; i < keys.length; i++) {
- var change = _changes[keys[i]];
- if (change.head && change.head.geometry(head) !== 'vertex') {
- addEntity(change.head, head, change.base ? 'modified' : 'created');
- } else if (change.base && change.base.geometry(base) !== 'vertex') {
- addEntity(change.base, base, 'deleted');
- } else if (change.base && change.head) {
- // modified vertex
- var moved = !fastDeepEqual(change.base.loc, change.head.loc);
- var retagged = !fastDeepEqual(change.base.tags, change.head.tags);
+ 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 (moved) {
- addParents(change.head);
- }
+ 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;
+ });
+ 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 (retagged || moved && change.head.hasInterestingTags()) {
- addEntity(change.head, head, 'modified');
- }
- } else if (change.head && change.head.hasInterestingTags()) {
- // created vertex
- addEntity(change.head, head, 'created');
- } else if (change.base && change.base.hasInterestingTags()) {
- // deleted vertex
- addEntity(change.base, base, 'deleted');
+ if (!isEP1 && !isEP2) {
+ var epsilon = 1e-6,
+ maxIter = 10;
+
+ 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;
}
- return Object.values(relevant);
+ graph = graph.replace(vertex.move(loc)); // if zorro happened, reorder nodes..
- function addEntity(entity, graph, changeType) {
- relevant[entity.id] = {
- entity: entity,
- graph: graph,
- changeType: changeType
- };
+ if (!isEP1 && edge1.index !== way1.nodes.indexOf(vertex.id)) {
+ way1 = way1.removeNode(vertex.id).addNode(vertex.id, edge1.index);
+ graph = graph.replace(way1);
}
- function addParents(entity) {
- var parents = head.parentWays(entity);
+ if (!isEP2 && edge2.index !== way2.nodes.indexOf(vertex.id)) {
+ way2 = way2.removeNode(vertex.id).addNode(vertex.id, edge2.index);
+ graph = graph.replace(way2);
+ }
- for (var j = parents.length - 1; j >= 0; j--) {
- var parent = parents[j];
+ return graph;
+ }
- if (!(parent.id in relevant)) {
- addEntity(parent, head, 'modified');
- }
- }
+ 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);
}
- }; // returns complete set of entities that require a redraw
- // (optionally within given `extent`)
+ return graph;
+ } // check if moving way endpoint can cross an unmoved way, if so limit delta..
- _diff.complete = function complete(extent) {
- var result = {};
- var id, change;
- for (id in _changes) {
- change = _changes[id];
- var h = change.head;
- var b = change.base;
- var entity = h || b;
- var i;
+ function limitDelta(graph) {
+ function moveNode(loc) {
+ return geoVecAdd(projection(loc), _delta);
+ }
- if (extent && (!h || !h.intersects(extent, head)) && (!b || !b.intersects(extent, base))) {
- continue;
- }
+ 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..
- result[id] = h;
+ if (obj.movedIsEP && obj.unmovedIsEP) continue; // Don't limit movement if this vertex is not an endpoint anyway..
- if (entity.type === 'way') {
- var nh = h ? h.nodes : [];
- var nb = b ? b.nodes : [];
- var diff;
- diff = utilArrayDifference(nh, nb);
-
- for (i = 0; i < diff.length; i++) {
- result[diff[i]] = head.hasEntity(diff[i]);
- }
-
- diff = utilArrayDifference(nb, nh);
+ 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);
- for (i = 0; i < diff.length; i++) {
- result[diff[i]] = head.hasEntity(diff[i]);
- }
+ 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);
}
+ }
+ }
- if (entity.type === 'relation' && entity.isMultipolygon()) {
- var mh = h ? h.members.map(function (m) {
- return m.id;
- }) : [];
- var mb = b ? b.members.map(function (m) {
- return m.id;
- }) : [];
- var ids = utilArrayUnion(mh, mb);
-
- for (i = 0; i < ids.length; i++) {
- var member = head.hasEntity(ids[i]);
- if (!member) continue; // not downloaded
+ var action = function action(graph) {
+ if (_delta[0] === 0 && _delta[1] === 0) return graph;
+ setupCache(graph);
- if (extent && !member.intersects(extent, head)) continue; // not visible
+ if (cache.intersections.length) {
+ limitDelta(graph);
+ }
- result[ids[i]] = member;
- }
- }
+ 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)));
+ }
- addParents(head.parentWays(entity), result);
- addParents(head.parentRelations(entity), result);
+ if (cache.intersections.length) {
+ graph = cleanupIntersections(graph);
}
- return result;
+ return graph;
+ };
- function addParents(parents, result) {
- for (var i = 0; i < parents.length; i++) {
- var parent = parents[i];
- if (parent.id in result) continue;
- result[parent.id] = parent;
- addParents(head.parentRelations(parent), result);
- }
- }
+ action.delta = function () {
+ return _delta;
};
- return _diff;
+ return action;
}
- function coreTree(head) {
- // tree for entities
- var _rtree = new RBush();
-
- var _bboxes = {}; // maintain a separate tree for granular way segments
-
- var _segmentsRTree = new RBush();
+ function actionMoveMember(relationId, fromIndex, toIndex) {
+ return function (graph) {
+ return graph.replace(graph.entity(relationId).moveMember(fromIndex, toIndex));
+ };
+ }
- var _segmentsBBoxes = {};
- var _segmentsByWayId = {};
- var tree = {};
+ 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)));
+ };
- function entityBBox(entity) {
- var bbox = entity.extent(head).bbox();
- bbox.id = entity.id;
- _bboxes[entity.id] = bbox;
- return bbox;
- }
+ action.transitionable = true;
+ return action;
+ }
- function segmentBBox(segment) {
- var extent = segment.extent(head); // extent can be null if the node entities aren't in the graph for some reason
+ function actionNoop() {
+ return function (graph) {
+ return graph;
+ };
+ }
- if (!extent) return null;
- var bbox = extent.bbox();
- bbox.segment = segment;
- _segmentsBBoxes[segment.id] = bbox;
- return bbox;
- }
+ 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)
- function removeEntity(entity) {
- _rtree.remove(_bboxes[entity.id]);
+ var lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180);
+ var upperThreshold = Math.cos(threshold * Math.PI / 180);
- delete _bboxes[entity.id];
+ 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 (_segmentsByWayId[entity.id]) {
- _segmentsByWayId[entity.id].forEach(function (segment) {
- _segmentsRTree.remove(_segmentsBBoxes[segment.id]);
+ if (way.tags.nonsquare) {
+ var tags = Object.assign({}, way.tags); // since we're squaring, remove indication that this is physically unsquare
- delete _segmentsBBoxes[segment.id];
+ delete tags.nonsquare;
+ way = way.update({
+ tags: tags
});
-
- delete _segmentsByWayId[entity.id];
}
- }
-
- function loadEntities(entities) {
- _rtree.load(entities.map(entityBBox));
-
- var segments = [];
- entities.forEach(function (entity) {
- if (entity.segments) {
- var entitySegments = entity.segments(head); // cache these to make them easy to remove later
- _segmentsByWayId[entity.id] = entitySegments;
- segments = segments.concat(entitySegments);
- }
- });
- if (segments.length) _segmentsRTree.load(segments.map(segmentBBox).filter(Boolean));
- }
+ graph = graph.replace(way);
+ var isClosed = way.isClosed();
+ var nodes = graph.childNodes(way).slice(); // shallow copy
- function updateParents(entity, insertions, memo) {
- head.parentWays(entity).forEach(function (way) {
- if (_bboxes[way.id]) {
- removeEntity(way);
- insertions[way.id] = way;
- }
+ if (isClosed) nodes.pop();
- updateParents(way, insertions, memo);
- });
- head.parentRelations(entity).forEach(function (relation) {
- if (memo[entity.id]) return;
- memo[entity.id] = true;
+ 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 (_bboxes[relation.id]) {
- removeEntity(relation);
- insertions[relation.id] = relation;
- }
- updateParents(relation, insertions, memo);
- });
- }
+ var nodeCount = {};
+ var points = [];
+ var corner = {
+ i: 0,
+ dotp: 1
+ };
+ var node, point, loc, score, motions, i, j;
- tree.rebase = function (entities, force) {
- var insertions = {};
+ 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)
+ });
+ }
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- if (!entity.visible) continue;
+ 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 (head.entities.hasOwnProperty(entity.id) || _bboxes[entity.id]) {
- if (!force) {
- continue;
- } else if (_bboxes[entity.id]) {
- removeEntity(entity);
+ if (score < epsilon) {
+ break;
}
}
- insertions[entity.id] = entity;
- updateParents(entity, insertions, {});
- }
+ 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
- loadEntities(Object.values(insertions));
- return tree;
- };
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+ var dotp = 0;
- function updateToGraph(graph) {
- if (graph === head) return;
- var diff = coreDifference(head, graph);
- head = graph;
- var changed = diff.didChange;
- if (!changed.addition && !changed.deletion && !changed.geometry) return;
- var insertions = {};
+ 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 (changed.deletion) {
- diff.deleted().forEach(function (entity) {
- removeEntity(entity);
- });
- }
+ if (dotp > upperThreshold) {
+ straights.push(point);
+ } else {
+ simplified.push(point);
+ }
+ } // Orthogonalize the simplified shape
- if (changed.geometry) {
- diff.modified().forEach(function (entity) {
- removeEntity(entity);
- insertions[entity.id] = entity;
- updateParents(entity, insertions, {});
- });
- }
- if (changed.addition) {
- diff.created().forEach(function (entity) {
- insertions[entity.id] = entity;
- });
- }
+ var bestPoints = clonePoints(simplified);
+ var originalPoints = clonePoints(simplified);
+ score = Infinity;
- loadEntities(Object.values(insertions));
- } // returns an array of entities with bounding boxes overlapping `extent` for the given `graph`
+ 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]);
+ }
- tree.intersects = function (extent, graph) {
- updateToGraph(graph);
- return _rtree.search(extent.bbox()).map(function (bbox) {
- return graph.entity(bbox.id);
- });
- }; // returns an array of segment objects with bounding boxes overlapping `extent` for the given `graph`
+ var newScore = geoOrthoCalcScore(simplified, isClosed, epsilon, threshold);
+ if (newScore < score) {
+ bestPoints = clonePoints(simplified);
+ score = newScore;
+ }
- tree.waySegments = function (extent, graph) {
- updateToGraph(graph);
- return _segmentsRTree.search(extent.bbox()).map(function (bbox) {
- return bbox.segment;
- });
- };
+ if (score < epsilon) {
+ break;
+ }
+ }
- return tree;
- }
+ var bestCoords = bestPoints.map(function (p) {
+ return p.coord;
+ });
+ if (isClosed) bestCoords.push(bestCoords[0]); // move the nodes that should move
- function svgIcon(name, svgklass, useklass) {
- return function drawIcon(selection) {
- selection.selectAll('svg.icon' + (svgklass ? '.' + svgklass.split(' ')[0] : '')).data([0]).enter().append('svg').attr('class', 'icon ' + (svgklass || '')).append('use').attr('xlink:href', name).attr('class', useklass);
- };
- }
+ for (i = 0; i < bestPoints.length; i++) {
+ point = bestPoints[i];
- function uiModal(selection, blocking) {
- var _this = this;
+ 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
- var keybinding = utilKeybinding('modal');
- var previous = selection.select('div.modal');
- var animate = previous.empty();
- previous.transition().duration(200).style('opacity', 0).remove();
- var shaded = selection.append('div').attr('class', 'shaded').style('opacity', 0);
- shaded.close = function () {
- shaded.transition().duration(200).style('opacity', 0).remove();
- modal.transition().duration(200).style('top', '0px');
- select(document).call(keybinding.unbind);
- };
+ for (i = 0; i < straights.length; i++) {
+ point = straights[i];
+ if (nodeCount[point.id] > 1) continue; // skip self-intersections
- var modal = shaded.append('div').attr('class', 'modal fillL');
- modal.append('input').attr('class', 'keytrap keytrap-first').on('focus.keytrap', moveFocusToLast);
+ node = graph.entity(point.id);
- if (!blocking) {
- shaded.on('click.remove-modal', function (d3_event) {
- if (d3_event.target === _this) {
- shaded.close();
- }
- });
- modal.append('button').attr('class', 'close').on('click', shaded.close).call(svgIcon('#iD-icon-close'));
- keybinding.on('⌫', shaded.close).on('⎋', shaded.close);
- select(document).call(keybinding);
- }
+ 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);
- modal.append('div').attr('class', 'content');
- modal.append('input').attr('class', 'keytrap keytrap-last').on('focus.keytrap', moveFocusToFirst);
+ if (choice) {
+ loc = projection.invert(choice.target);
+ graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
+ }
+ }
+ }
+ }
- if (animate) {
- shaded.transition().style('opacity', 1);
- } else {
- shaded.style('opacity', 1);
- }
+ return graph;
- return shaded;
+ function clonePoints(array) {
+ return array.map(function (p) {
+ return {
+ id: p.id,
+ coord: [p.coord[0], p.coord[1]]
+ };
+ });
+ }
- function moveFocusToFirst() {
- var node = modal // there are additional rules about what's focusable, but this suits our purposes
- .select('a, button, input:not(.keytrap), select, textarea').node();
+ 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 (node) {
- node.focus();
- } else {
- select(this).node().blur();
- }
- }
+ 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);
- function moveFocusToLast() {
- var nodes = modal.selectAll('a, button, input:not(.keytrap), select, textarea').nodes();
+ if (val < lowerThreshold) {
+ // nearly orthogonal
+ corner.i = i;
+ corner.dotp = val;
+ var vec = geoVecNormalize(geoVecAdd(p, q));
+ return geoVecScale(vec, 0.1 * dotp * scale);
+ }
- if (nodes.length) {
- nodes[nodes.length - 1].focus();
- } else {
- select(this).node().blur();
+ return [0, 0]; // do nothing
}
- }
- }
-
- function uiLoading(context) {
- var _modalSelection = select(null);
+ }; // if we are only orthogonalizing one vertex,
+ // get that vertex and the previous and next
- var _message = '';
- var _blocking = false;
- var loading = function loading(selection) {
- _modalSelection = uiModal(selection, _blocking);
+ function nodeSubset(nodes, vertexID, isClosed) {
+ var first = isClosed ? 0 : 1;
+ var last = isClosed ? nodes.length : nodes.length - 1;
- var loadertext = _modalSelection.select('.content').classed('loading-modal', true).append('div').attr('class', 'modal-section fillL');
+ 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]];
+ }
+ }
- loadertext.append('img').attr('class', 'loader').attr('src', context.imagePath('loader-white.gif'));
- loadertext.append('h3').html(_message);
+ return [];
+ }
- _modalSelection.select('button.close').attr('class', 'hide');
+ action.disabled = function (graph) {
+ var way = graph.entity(wayID);
+ way = way.removeNode(''); // sanity check - remove any consecutive duplicates
- return loading;
- };
+ graph = graph.replace(way);
+ var isClosed = way.isClosed();
+ var nodes = graph.childNodes(way).slice(); // shallow copy
- loading.message = function (val) {
- if (!arguments.length) return _message;
- _message = val;
- return loading;
- };
+ if (isClosed) nodes.pop();
+ var allowStraightAngles = false;
- loading.blocking = function (val) {
- if (!arguments.length) return _blocking;
- _blocking = val;
- return loading;
- };
+ if (vertexID !== undefined) {
+ allowStraightAngles = true;
+ nodes = nodeSubset(nodes, vertexID, isClosed);
+ if (nodes.length !== 3) return 'end_vertex';
+ }
- loading.close = function () {
- _modalSelection.remove();
- };
+ var coords = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var score = geoOrthoCanOrthogonalize(coords, isClosed, epsilon, threshold, allowStraightAngles);
- loading.isShown = function () {
- return _modalSelection && !_modalSelection.empty() && _modalSelection.node().parentNode;
+ if (score === null) {
+ return 'not_squarish';
+ } else if (score === 0) {
+ return 'square_enough';
+ } else {
+ return false;
+ }
};
- return loading;
+ action.transitionable = true;
+ return action;
}
- function coreHistory(context) {
- var dispatch = dispatch$8('reset', 'change', 'merge', 'restore', 'undone', 'redone', 'storage_error');
-
- var _lock = utilSessionMutex('lock'); // restorable if iD not open in another window/tab and a saved history exists in localStorage
-
-
- var _hasUnresolvedRestorableChanges = _lock.lock() && !!corePreferences(getKey('saved_history'));
-
- var duration = 150;
- var _imageryUsed = [];
- var _photoOverlaysUsed = [];
- var _checkpoints = {};
-
- var _pausedGraph;
+ //
+ // `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.
+ //
- var _stack;
+ 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 _index;
+ if (viaNode) {
+ members.push({
+ id: viaNode.id,
+ type: 'node',
+ role: 'via'
+ });
+ } else if (viaWays) {
+ viaWays.forEach(function (viaWay) {
+ members.push({
+ id: viaWay.id,
+ type: 'way',
+ role: 'via'
+ });
+ });
+ }
- var _tree; // internal _act, accepts list of actions and eased time
+ members.push({
+ id: toWay.id,
+ type: 'way',
+ role: 'to'
+ });
+ return graph.replace(osmRelation({
+ id: restrictionID,
+ tags: {
+ type: 'restriction',
+ restriction: restrictionType
+ },
+ members: members
+ }));
+ };
+ }
+ function actionRevert(id) {
+ var action = function action(graph) {
+ var entity = graph.hasEntity(id),
+ base = graph.base().entities[id];
- function _act(actions, t) {
- actions = Array.prototype.slice.call(actions);
- var annotation;
+ 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);
- if (typeof actions[actions.length - 1] !== 'function') {
- annotation = actions.pop();
- }
+ if (parent.isDegenerate()) {
+ graph = actionDeleteWay(parent.id)(graph);
+ }
+ });
+ }
- var graph = _stack[_index].graph;
+ graph.parentRelations(entity).forEach(function (parent) {
+ parent = parent.removeMembersWithID(id);
+ graph = graph.replace(parent);
- for (var i = 0; i < actions.length; i++) {
- graph = actions[i](graph, t);
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
+ }
+ });
}
- return {
- graph: graph,
- annotation: annotation,
- imageryUsed: _imageryUsed,
- photoOverlaysUsed: _photoOverlaysUsed,
- transform: context.projection.transform(),
- selectedIDs: context.selectedIDs()
- };
- } // internal _perform with eased time
+ return graph.revert(id);
+ };
+ return action;
+ }
- function _perform(args, t) {
- var previous = _stack[_index].graph;
- _stack = _stack.slice(0, _index + 1);
+ 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 actionResult = _act(args, t);
+ return action;
+ }
- _stack.push(actionResult);
+ 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)));
+ });
+ });
+ };
+ }
- _index++;
- return change(previous);
- } // internal _replace with eased time
+ /* Align nodes along their common axis */
+ 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
- function _replace(args, t) {
- var previous = _stack[_index].graph; // assert(_index == _stack.length - 1)
- var actionResult = _act(args, t);
+ 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
- _stack[_index] = actionResult;
- return change(previous);
- } // internal _overwrite with eased time
+ 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);
+ if (isLong) {
+ return [p1, q1];
+ }
- function _overwrite(args, t) {
- var previous = _stack[_index].graph;
+ return [p2, q2];
+ }
- if (_index > 0) {
- _index--;
+ 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
- _stack.pop();
+ 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)));
}
- _stack = _stack.slice(0, _index + 1);
+ return graph;
+ };
- var actionResult = _act(args, t);
+ 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;
- _stack.push(actionResult);
+ 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);
- _index++;
- return change(previous);
- } // determine difference and dispatch a change event
+ if (!isNaN(dist) && dist > maxDistance) {
+ maxDistance = dist;
+ }
+ }
+ if (maxDistance < 0.0001) {
+ return 'straight_enough';
+ }
+ };
- function change(previous) {
- var difference = coreDifference(previous, history.graph());
+ action.transitionable = true;
+ return action;
+ }
- if (!_pausedGraph) {
- dispatch.call('change', this, difference);
- }
+ /*
+ * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as
+ */
- return difference;
- } // iD uses namespaced keys so multiple installations do not conflict
+ 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
- function getKey(n) {
- return 'iD_' + window.location.origin + '_' + n;
- }
+ 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';
+ });
- var history = {
- graph: function graph() {
- return _stack[_index].graph;
- },
- tree: function tree() {
- return _tree;
- },
- base: function base() {
- return _stack[0].graph;
- },
- merge: function merge(entities
- /*, extent*/
- ) {
- var stack = _stack.map(function (state) {
- return state.graph;
- });
+ 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"]
- _stack[0].graph.rebase(entities, stack, false);
- _tree.rebase(entities, false);
+ 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
- dispatch.call('merge', this, entities);
- },
- perform: function perform() {
- // complete any transition already in progress
- select(document).interrupt('history.perform');
- var transitionable = false;
- var action0 = arguments[0];
+ 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
- if (arguments.length === 1 || arguments.length === 2 && typeof arguments[1] !== 'function') {
- transitionable = !!action0.transitionable;
- }
+ 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
- if (transitionable) {
- var origArguments = arguments;
- select(document).transition('history.perform').duration(duration).ease(linear$1).tween('history.tween', function () {
- return function (t) {
- if (t < 1) _overwrite([action0], t);
- };
- }).on('start', function () {
- _perform([action0], 0);
- }).on('end interrupt', function () {
- _overwrite(origArguments, 1);
- });
- } else {
- return _perform(arguments);
- }
- },
- replace: function replace() {
- select(document).interrupt('history.perform');
- return _replace(arguments, 1);
- },
- // Same as calling pop and then perform
- overwrite: function overwrite() {
- select(document).interrupt('history.perform');
- return _overwrite(arguments, 1);
- },
- pop: function pop(n) {
- select(document).interrupt('history.perform');
- var previous = _stack[_index].graph;
- if (isNaN(+n) || +n < 0) {
- n = 1;
+ while (remainingWays.length) {
+ nextWay = getNextWay(currNode, remainingWays);
+ remainingWays = utilArrayDifference(remainingWays, [nextWay]);
+
+ if (nextWay[0] !== currNode) {
+ nextWay.reverse();
}
- while (n-- > 0 && _index > 0) {
- _index--;
+ nodes = nodes.concat(nextWay);
+ currNode = nodes[nodes.length - 1];
+ } // If user selected 2 nodes to straighten between, then slice nodes array to those nodes
- _stack.pop();
- }
- return change(previous);
- },
- // Back to the previous annotated state or _index = 0.
- undo: function undo() {
- select(document).interrupt('history.perform');
- var previousStack = _stack[_index];
- var previous = previousStack.graph;
+ 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;
+ });
+ nodes = nodes.slice(sortedStartEnd[0], sortedStartEnd[1] + 1);
+ }
- while (_index > 0) {
- _index--;
- if (_stack[_index].annotation) break;
- }
+ return nodes.map(function (n) {
+ return graph.entity(n);
+ });
+ }
- dispatch.call('undone', this, _stack[_index], previousStack);
- return change(previous);
- },
- // Forward to the next annotated state.
- redo: function redo() {
- select(document).interrupt('history.perform');
- var previousStack = _stack[_index];
- var previous = previousStack.graph;
- var tryIndex = _index;
+ function shouldKeepNode(node, graph) {
+ return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
+ }
- while (tryIndex < _stack.length - 1) {
- tryIndex++;
+ 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;
- if (_stack[tryIndex].annotation) {
- _index = tryIndex;
- dispatch.call('redone', this, _stack[_index], previousStack);
- break;
+ for (i = 1; i < points.length - 1; i++) {
+ var node = nodes[i];
+ var point = points[i];
+
+ 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);
}
}
+ }
- return change(previous);
- },
- pauseChangeDispatch: function pauseChangeDispatch() {
- if (!_pausedGraph) {
- _pausedGraph = _stack[_index].graph;
- }
- },
- resumeChangeDispatch: function resumeChangeDispatch() {
- if (_pausedGraph) {
- var previous = _pausedGraph;
- _pausedGraph = null;
- return change(previous);
- }
- },
- undoAnnotation: function undoAnnotation() {
- var i = _index;
+ for (i = 0; i < toDelete.length; i++) {
+ graph = actionDeleteNode(toDelete[i].id)(graph);
+ }
- while (i >= 0) {
- if (_stack[i].annotation) return _stack[i].annotation;
- i--;
- }
- },
- redoAnnotation: function redoAnnotation() {
- var i = _index + 1;
+ return graph;
+ };
- while (i <= _stack.length - 1) {
- if (_stack[i].annotation) return _stack[i].annotation;
- i++;
- }
- },
- // Returns the entities from the active graph with bounding boxes
- // overlapping the given `extent`.
- intersects: function intersects(extent) {
- return _tree.intersects(extent, _stack[_index].graph);
- },
- difference: function difference() {
- var base = _stack[0].graph;
- var head = _stack[_index].graph;
- return coreDifference(base, head);
- },
- changes: function changes(action) {
- var base = _stack[0].graph;
- var head = _stack[_index].graph;
+ 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 (action) {
- head = action(head);
- }
+ if (threshold === 0) {
+ return 'too_bendy';
+ }
- var difference = coreDifference(base, head);
- return {
- modified: difference.modified(),
- created: difference.created(),
- deleted: difference.deleted()
- };
- },
- hasChanges: function hasChanges() {
- return this.difference().length() > 0;
- },
- imageryUsed: function imageryUsed(sources) {
- if (sources) {
- _imageryUsed = sources;
- return history;
- } else {
- var s = new Set();
+ var maxDistance = 0;
- _stack.slice(1, _index + 1).forEach(function (state) {
- state.imageryUsed.forEach(function (source) {
- if (source !== 'Custom') {
- s.add(source);
- }
- });
- });
+ 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
- return Array.from(s);
+ if (isNaN(dist) || dist > threshold) {
+ return 'too_bendy';
+ } else if (dist > maxDistance) {
+ maxDistance = dist;
}
- },
- photoOverlaysUsed: function photoOverlaysUsed(sources) {
- if (sources) {
- _photoOverlaysUsed = sources;
- return history;
- } else {
- var s = new Set();
+ }
- _stack.slice(1, _index + 1).forEach(function (state) {
- if (state.photoOverlaysUsed && Array.isArray(state.photoOverlaysUsed)) {
- state.photoOverlaysUsed.forEach(function (photoOverlay) {
- s.add(photoOverlay);
- });
- }
- });
+ var keepingAllNodes = nodes.every(function (node, i) {
+ return i === 0 || i === nodes.length - 1 || shouldKeepNode(node, graph);
+ });
- return Array.from(s);
- }
- },
- // save the current history state
- checkpoint: function checkpoint(key) {
- _checkpoints[key] = {
- stack: _stack,
- index: _index
- };
- return history;
- },
- // restore history state to a given checkpoint or reset completely
- reset: function reset(key) {
- if (key !== undefined && _checkpoints.hasOwnProperty(key)) {
- _stack = _checkpoints[key].stack;
- _index = _checkpoints[key].index;
- } else {
- _stack = [{
- graph: coreGraph()
- }];
- _index = 0;
- _tree = coreTree(_stack[0].graph);
- _checkpoints = {};
- }
+ if (maxDistance < 0.0001 && // Allow straightening even if already straight in order to remove extraneous nodes
+ keepingAllNodes) {
+ return 'straight_enough';
+ }
+ };
- dispatch.call('reset');
- dispatch.call('change');
- return history;
- },
- // `toIntroGraph()` is used to export the intro graph used by the walkthrough.
- //
- // To use it:
- // 1. Start the walkthrough.
- // 2. Get to a "free editing" tutorial step
- // 3. Make your edits to the walkthrough map
- // 4. In your browser dev console run:
- // `id.history().toIntroGraph()`
- // 5. This outputs stringified JSON to the browser console
- // 6. Copy it to `data/intro_graph.json` and prettify it in your code editor
- toIntroGraph: function toIntroGraph() {
- var nextID = {
- n: 0,
- r: 0,
- w: 0
- };
- var permIDs = {};
- var graph = this.graph();
- var baseEntities = {}; // clone base entities..
+ action.transitionable = true;
+ return action;
+ }
- Object.values(graph.base().entities).forEach(function (entity) {
- var copy = copyIntroEntity(entity);
- baseEntities[copy.id] = copy;
- }); // replace base entities with head entities..
+ //
+ // `turn` must be an `osmTurn` object with a `restrictionID` property.
+ // see osm/intersection.js, pathToTurn()
+ //
- Object.keys(graph.entities).forEach(function (id) {
- var entity = graph.entities[id];
+ function actionUnrestrictTurn(turn) {
+ return function (graph) {
+ return actionDeleteRelation(turn.restrictionID)(graph);
+ };
+ }
- if (entity) {
- var copy = copyIntroEntity(entity);
- baseEntities[copy.id] = copy;
- } else {
- delete baseEntities[id];
- }
- }); // swap temporary for permanent ids..
+ /* Reflect the given area around its axis of symmetry */
- Object.values(baseEntities).forEach(function (entity) {
- if (Array.isArray(entity.nodes)) {
- entity.nodes = entity.nodes.map(function (node) {
- return permIDs[node] || node;
- });
- }
+ function actionReflect(reflectIds, projection) {
+ var _useLongAxis = true;
- if (Array.isArray(entity.members)) {
- entity.members = entity.members.map(function (member) {
- member.id = permIDs[member.id] || member.id;
- return member;
- });
- }
- });
- return JSON.stringify({
- dataIntroGraph: baseEntities
- });
+ 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 copyIntroEntity(source) {
- var copy = utilObjectOmit(source, ['type', 'user', 'v', 'version', 'visible']); // Note: the copy is no longer an osmEntity, so it might not have `tags`
+ 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);
- if (copy.tags && !Object.keys(copy.tags)) {
- delete copy.tags;
- }
+ 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
- if (Array.isArray(copy.loc)) {
- copy.loc[0] = +copy.loc[0].toFixed(6);
- copy.loc[1] = +copy.loc[1].toFixed(6);
- }
- var match = source.id.match(/([nrw])-\d*/); // temporary id
+ 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);
- if (match !== null) {
- var nrw = match[1];
- var permID;
+ 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);
+ }
- do {
- permID = nrw + ++nextID[nrw];
- } while (baseEntities.hasOwnProperty(permID));
+ return graph;
+ };
- copy.id = permIDs[source.id] = permID;
- }
+ action.useLongAxis = function (val) {
+ if (!arguments.length) return _useLongAxis;
+ _useLongAxis = val;
+ return action;
+ };
- return copy;
- }
- },
- toJSON: function toJSON() {
- if (!this.hasChanges()) return;
- var allEntities = {};
- var baseEntities = {};
- var base = _stack[0];
+ action.transitionable = true;
+ return action;
+ }
- var s = _stack.map(function (i) {
- var modified = [];
- var deleted = [];
- Object.keys(i.graph.entities).forEach(function (id) {
- var entity = i.graph.entities[id];
+ function actionUpgradeTags(entityId, oldTags, replaceTags) {
+ return function (graph) {
+ var entity = graph.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- if (entity) {
- var key = osmEntity.key(entity);
- allEntities[key] = entity;
- modified.push(key);
- } else {
- deleted.push(id);
- } // make sure that the originals of changed or deleted entities get merged
- // into the base of the _stack after restoring the data from JSON.
+ var transferValue;
+ var semiIndex;
+ for (var oldTagKey in oldTags) {
+ if (!(oldTagKey in tags)) continue; // wildcard match
- if (id in base.graph.entities) {
- baseEntities[id] = base.graph.entities[id];
- }
+ 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]);
- if (entity && entity.nodes) {
- // get originals of pre-existing child nodes
- entity.nodes.forEach(function (nodeID) {
- if (nodeID in base.graph.entities) {
- baseEntities[nodeID] = base.graph.entities[nodeID];
- }
- });
- } // get originals of parent entities too
+ 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;
+ }
+ vals.splice(oldIndex, 1);
+ tags[oldTagKey] = vals.join(';');
+ }
+ }
+ }
- var baseParents = base.graph._parentWays[id];
+ if (replaceTags) {
+ for (var replaceKey in replaceTags) {
+ var replaceValue = replaceTags[replaceKey];
- if (baseParents) {
- baseParents.forEach(function (parentID) {
- if (parentID in base.graph.entities) {
- baseEntities[parentID] = base.graph.entities[parentID];
- }
- });
+ 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';
}
- });
- var x = {};
- if (modified.length) x.modified = modified;
- if (deleted.length) x.deleted = deleted;
- if (i.imageryUsed) x.imageryUsed = i.imageryUsed;
- if (i.photoOverlaysUsed) x.photoOverlaysUsed = i.photoOverlaysUsed;
- if (i.annotation) x.annotation = i.annotation;
- if (i.transform) x.transform = i.transform;
- if (i.selectedIDs) x.selectedIDs = i.selectedIDs;
- return x;
- });
+ } 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);
- return JSON.stringify({
- version: 3,
- entities: Object.values(allEntities),
- baseEntities: Object.values(baseEntities),
- stack: s,
- nextIDs: osmEntity.id.next,
- index: _index,
- // note the time the changes were saved
- timestamp: new Date().getTime()
- });
- },
- fromJSON: function fromJSON(json, loadChildNodes) {
- var h = JSON.parse(json);
- var loadComplete = true;
- osmEntity.id.next = h.nextIDs;
- _index = h.index;
+ if (existingVals.indexOf(replaceValue) === -1) {
+ existingVals.splice(semiIndex, 0, replaceValue);
+ tags[replaceKey] = existingVals.join(';');
+ }
+ } else {
+ tags[replaceKey] = replaceValue;
+ }
+ }
+ }
+ }
- if (h.version === 2 || h.version === 3) {
- var allEntities = {};
- h.entities.forEach(function (entity) {
- allEntities[osmEntity.key(entity)] = osmEntity(entity);
- });
+ return graph.replace(entity.update({
+ tags: tags
+ }));
+ };
+ }
- if (h.version === 3) {
- // This merges originals for changed entities into the base of
- // the _stack even if the current _stack doesn't have them (for
- // example when iD has been restarted in a different region)
- var baseEntities = h.baseEntities.map(function (d) {
- return osmEntity(d);
- });
+ function behaviorEdit(context) {
+ function behavior() {
+ context.map().minzoom(context.minEditableZoom());
+ }
- var stack = _stack.map(function (state) {
- return state.graph;
- });
+ behavior.off = function () {
+ context.map().minzoom(0);
+ };
- _stack[0].graph.rebase(baseEntities, stack, true);
+ return behavior;
+ }
- _tree.rebase(baseEntities, true); // When we restore a modified way, we also need to fetch any missing
- // childnodes that would normally have been downloaded with it.. #2142
+ /*
+ The hover behavior adds the `.hover` class on pointerover to all elements to which
+ the identical datum is bound, and removes it on pointerout.
+ 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.
+ */
- if (loadChildNodes) {
- var osm = context.connection();
- var baseWays = baseEntities.filter(function (e) {
- return e.type === 'way';
- });
- var nodeIDs = baseWays.reduce(function (acc, way) {
- return utilArrayUnion(acc, way.nodes);
- }, []);
- var missing = nodeIDs.filter(function (n) {
- return !_stack[0].graph.hasEntity(n);
- });
+ function behaviorHover(context) {
+ var dispatch = dispatch$8('hover');
- if (missing.length && osm) {
- loadComplete = false;
- context.map().redrawEnable(false);
- var loading = uiLoading(context).blocking(true);
- context.container().call(loading);
+ var _selection = select(null);
- var childNodesLoaded = function childNodesLoaded(err, result) {
- if (!err) {
- var visibleGroups = utilArrayGroupBy(result.data, 'visible');
- var visibles = visibleGroups["true"] || []; // alive nodes
+ var _newNodeId = null;
+ var _initialNodeID = null;
- var invisibles = visibleGroups["false"] || []; // deleted nodes
+ var _altDisables;
- if (visibles.length) {
- var visibleIDs = visibles.map(function (entity) {
- return entity.id;
- });
+ var _ignoreVertex;
- var stack = _stack.map(function (state) {
- return state.graph;
- });
+ var _targets = []; // use pointer events on supported platforms; fallback to mouse events
- missing = utilArrayDifference(missing, visibleIDs);
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
- _stack[0].graph.rebase(visibles, stack, true);
+ function keydown(d3_event) {
+ if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ _selection.selectAll('.hover').classed('hover-suppressed', true).classed('hover', false);
- _tree.rebase(visibles, true);
- } // fetch older versions of nodes that were deleted..
+ _selection.classed('hover-disabled', true);
+ dispatch.call('hover', this, null);
+ }
+ }
- invisibles.forEach(function (entity) {
- osm.loadEntityVersion(entity.id, +entity.version - 1, childNodesLoaded);
- });
- }
+ function keyup(d3_event) {
+ if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false).classed('hover', true);
- if (err || !missing.length) {
- loading.close();
- context.map().redrawEnable(true);
- dispatch.call('change');
- dispatch.call('restore', this);
- }
- };
+ _selection.classed('hover-disabled', false);
- osm.loadMultiple(missing, childNodesLoaded);
- }
- }
- }
+ dispatch.call('hover', this, _targets);
+ }
+ }
- _stack = h.stack.map(function (d) {
- var entities = {},
- entity;
+ function behavior(selection) {
+ _selection = selection;
+ _targets = [];
- if (d.modified) {
- d.modified.forEach(function (key) {
- entity = allEntities[key];
- entities[entity.id] = entity;
- });
- }
+ if (_initialNodeID) {
+ _newNodeId = _initialNodeID;
+ _initialNodeID = null;
+ } else {
+ _newNodeId = null;
+ }
- if (d.deleted) {
- d.deleted.forEach(function (id) {
- entities[id] = undefined;
- });
- }
+ _selection.on(_pointerPrefix + 'over.hover', pointerover).on(_pointerPrefix + 'out.hover', pointerout) // treat pointerdown as pointerover for touch devices
+ .on(_pointerPrefix + 'down.hover', pointerover);
- return {
- graph: coreGraph(_stack[0].graph).load(entities),
- annotation: d.annotation,
- imageryUsed: d.imageryUsed,
- photoOverlaysUsed: d.photoOverlaysUsed,
- transform: d.transform,
- selectedIDs: d.selectedIDs
- };
- });
- } else {
- // original version
- _stack = h.stack.map(function (d) {
- var entities = {};
+ select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', pointerout, true).on('keydown.hover', keydown).on('keyup.hover', keyup);
- for (var i in d.entities) {
- var entity = d.entities[i];
- entities[i] = entity === 'undefined' ? undefined : osmEntity(entity);
- }
+ function eventTarget(d3_event) {
+ var datum = d3_event.target && d3_event.target.__data__;
+ if (_typeof(datum) !== 'object') return null;
- d.graph = coreGraph(_stack[0].graph).load(entities);
- return d;
- });
+ if (!(datum instanceof osmEntity) && datum.properties && datum.properties.entity instanceof osmEntity) {
+ return datum.properties.entity;
}
- var transform = _stack[_index].transform;
+ return datum;
+ }
- if (transform) {
- context.map().transformEase(transform, 0); // 0 = immediate, no easing
- }
+ 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 (loadComplete) {
- dispatch.call('change');
- dispatch.call('restore', this);
- }
+ if (target && _targets.indexOf(target) === -1) {
+ _targets.push(target);
- return history;
- },
- lock: function lock() {
- return _lock.lock();
- },
- unlock: function unlock() {
- _lock.unlock();
- },
- save: function save() {
- if (_lock.locked() && // don't overwrite existing, unresolved changes
- !_hasUnresolvedRestorableChanges) {
- var success = corePreferences(getKey('saved_history'), history.toJSON() || null);
- if (!success) dispatch.call('storage_error');
+ updateHover(d3_event, _targets);
}
+ }
- return history;
- },
- // delete the history version saved in localStorage
- clearSaved: function clearSaved() {
- context.debouncedSave.cancel();
+ function pointerout(d3_event) {
+ var target = eventTarget(d3_event);
- if (_lock.locked()) {
- _hasUnresolvedRestorableChanges = false;
- corePreferences(getKey('saved_history'), null); // clear the changeset metadata associated with the saved history
+ var index = _targets.indexOf(target);
- corePreferences('comment', null);
- corePreferences('hashtags', null);
- corePreferences('source', null);
- }
+ if (index !== -1) {
+ _targets.splice(index);
- return history;
- },
- savedHistoryJSON: function savedHistoryJSON() {
- return corePreferences(getKey('saved_history'));
- },
- hasRestorableChanges: function hasRestorableChanges() {
- return _hasUnresolvedRestorableChanges;
- },
- // load history from a version stored in localStorage
- restore: function restore() {
- if (_lock.locked()) {
- _hasUnresolvedRestorableChanges = false;
- var json = this.savedHistoryJSON();
- if (json) history.fromJSON(json, true);
+ updateHover(d3_event, _targets);
}
- },
- _getKey: getKey
- };
- history.reset();
- return utilRebind(history, dispatch, 'on');
- }
+ }
- /**
- * Look for roads that can be connected to other roads with a short extension
- */
-
- function validationAlmostJunction(context) {
- var type = 'almost_junction';
- var EXTEND_TH_METERS = 5;
- var WELD_TH_METERS = 0.75; // Comes from considering bounding case of parallel ways
+ function allowsVertex(d) {
+ return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
+ }
- var CLOSE_NODE_TH = EXTEND_TH_METERS - WELD_TH_METERS; // Comes from considering bounding case of perpendicular ways
+ function modeAllowsHover(target) {
+ var mode = context.mode();
- var SIG_ANGLE_TH = Math.atan(WELD_TH_METERS / EXTEND_TH_METERS);
+ if (mode.id === 'add-point') {
+ return mode.preset.matchGeometry('vertex') || target.type !== 'way' && target.geometry(context.graph()) !== 'vertex';
+ }
- function isHighway(entity) {
- return entity.type === 'way' && osmRoutableHighwayTagValues[entity.tags.highway];
- }
+ return true;
+ }
- function isTaggedAsNotContinuing(node) {
- return node.tags.noexit === 'yes' || node.tags.amenity === 'parking_entrance' || node.tags.entrance && node.tags.entrance !== 'no';
- }
+ function updateHover(d3_event, targets) {
+ _selection.selectAll('.hover').classed('hover', false);
- var validation = function checkAlmostJunction(entity, graph) {
- if (!isHighway(entity)) return [];
- if (entity.isDegenerate()) return [];
- var tree = context.history().tree();
- var extendableNodeInfos = findConnectableEndNodesByExtension(entity);
- var issues = [];
- extendableNodeInfos.forEach(function (extendableNodeInfo) {
- issues.push(new validationIssue({
- type: type,
- subtype: 'highway-highway',
- severity: 'warning',
- message: function message(context) {
- var entity1 = context.hasEntity(this.entityIds[0]);
+ _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
- if (this.entityIds[0] === this.entityIds[2]) {
- return entity1 ? _t.html('issues.almost_junction.self.message', {
- feature: utilDisplayLabel(entity1, context.graph())
- }) : '';
- } else {
- var entity2 = context.hasEntity(this.entityIds[2]);
- return entity1 && entity2 ? _t.html('issues.almost_junction.message', {
- feature: utilDisplayLabel(entity1, context.graph()),
- feature2: utilDisplayLabel(entity2, context.graph())
- }) : '';
- }
- },
- reference: showReference,
- entityIds: [entity.id, extendableNodeInfo.node.id, extendableNodeInfo.wid],
- loc: extendableNodeInfo.node.loc,
- hash: JSON.stringify(extendableNodeInfo.node.loc),
- data: {
- midId: extendableNodeInfo.mid.id,
- edge: extendableNodeInfo.edge,
- cross_loc: extendableNodeInfo.cross_loc
- },
- dynamicFixes: makeFixes
- }));
- });
- return issues;
+ var mode = context.mode();
- function makeFixes(context) {
- var fixes = [new validationIssueFix({
- icon: 'iD-icon-abutment',
- title: _t.html('issues.fix.connect_features.title'),
- onClick: function onClick(context) {
- var annotation = _t('issues.fix.connect_almost_junction.annotation');
+ 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;
+ }
- var _this$issue$entityIds = _slicedToArray(this.issue.entityIds, 3),
- endNodeId = _this$issue$entityIds[1],
- crossWayId = _this$issue$entityIds[2];
+ 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);
+ }
- var midNode = context.entity(this.issue.data.midId);
- var endNode = context.entity(endNodeId);
- var crossWay = context.entity(crossWayId); // When endpoints are close, just join if resulting small change in angle (#7201)
+ return true;
+ });
+ var selector = '';
- var nearEndNodes = findNearbyEndNodes(endNode, crossWay);
+ for (var i in targets) {
+ var datum = targets[i]; // What are we hovering over?
- if (nearEndNodes.length > 0) {
- var collinear = findSmallJoinAngle(midNode, endNode, nearEndNodes);
+ 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;
- if (collinear) {
- context.perform(actionMergeNodes([collinear.id, endNode.id], collinear.loc), annotation);
- return;
+ if (datum.type === 'relation') {
+ for (var j in datum.members) {
+ selector += ', .' + datum.members[j].id;
}
}
-
- var targetEdge = this.issue.data.edge;
- var crossLoc = this.issue.data.cross_loc;
- var edgeNodes = [context.entity(targetEdge[0]), context.entity(targetEdge[1])];
- var closestNodeInfo = geoSphericalClosestNode(edgeNodes, crossLoc); // already a point nearby, just connect to that
-
- if (closestNodeInfo.distance < WELD_TH_METERS) {
- context.perform(actionMergeNodes([closestNodeInfo.node.id, endNode.id], closestNodeInfo.node.loc), annotation); // else add the end node to the edge way
- } else {
- context.perform(actionAddMidpoint({
- loc: crossLoc,
- edge: targetEdge
- }, endNode), annotation);
- }
}
- })];
- var node = context.hasEntity(this.entityIds[1]);
-
- if (node && !node.hasInterestingTags()) {
- // node has no descriptive tags, suggest noexit fix
- fixes.push(new validationIssueFix({
- icon: 'maki-barrier',
- title: _t.html('issues.fix.tag_as_disconnected.title'),
- onClick: function onClick(context) {
- var nodeID = this.issue.entityIds[1];
- var tags = Object.assign({}, context.entity(nodeID).tags);
- tags.noexit = 'yes';
- context.perform(actionChangeTags(nodeID, tags), _t('issues.fix.tag_as_disconnected.annotation'));
- }
- }));
- }
-
- return fixes;
- }
-
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.almost_junction.highway-highway.reference'));
- }
-
- function isExtendableCandidate(node, way) {
- // can not accurately test vertices on tiles not downloaded from osm - #5938
- var osm = services.osm;
-
- if (osm && !osm.isDataLoaded(node.loc)) {
- return false;
- }
-
- if (isTaggedAsNotContinuing(node) || graph.parentWays(node).length !== 1) {
- return false;
}
- var occurrences = 0;
+ var suppressed = _altDisables && d3_event && d3_event.altKey;
- for (var index in way.nodes) {
- if (way.nodes[index] === node.id) {
- occurrences += 1;
+ if (selector.trim().length) {
+ // remove the first comma
+ selector = selector.slice(1);
- if (occurrences > 1) {
- return false;
- }
- }
+ _selection.selectAll(selector).classed(suppressed ? 'hover-suppressed' : 'hover', true);
}
- return true;
+ dispatch.call('hover', this, !suppressed && targets);
}
+ }
- function findConnectableEndNodesByExtension(way) {
- var results = [];
- if (way.isClosed()) return results;
- var testNodes;
- var indices = [0, way.nodes.length - 1];
- indices.forEach(function (nodeIndex) {
- var nodeID = way.nodes[nodeIndex];
- var node = graph.entity(nodeID);
- if (!isExtendableCandidate(node, way)) return;
- var connectionInfo = canConnectByExtend(way, nodeIndex);
- if (!connectionInfo) return;
- testNodes = graph.childNodes(way).slice(); // shallow copy
-
- testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc); // don't flag issue if connecting the ways would cause self-intersection
-
- if (geoHasSelfIntersections(testNodes, nodeID)) return;
- results.push(connectionInfo);
- });
- return results;
- }
+ 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);
+ };
- function findNearbyEndNodes(node, way) {
- return [way.nodes[0], way.nodes[way.nodes.length - 1]].map(function (d) {
- return graph.entity(d);
- }).filter(function (d) {
- // Node cannot be near to itself, but other endnode of same way could be
- return d.id !== node.id && geoSphericalDistance(node.loc, d.loc) <= CLOSE_NODE_TH;
- });
- }
+ behavior.altDisables = function (val) {
+ if (!arguments.length) return _altDisables;
+ _altDisables = val;
+ return behavior;
+ };
- function findSmallJoinAngle(midNode, tipNode, endNodes) {
- // Both nodes could be close, so want to join whichever is closest to collinear
- var joinTo;
- var minAngle = Infinity; // Checks midNode -> tipNode -> endNode for collinearity
+ behavior.ignoreVertex = function (val) {
+ if (!arguments.length) return _ignoreVertex;
+ _ignoreVertex = val;
+ return behavior;
+ };
- endNodes.forEach(function (endNode) {
- var a1 = geoAngle(midNode, tipNode, context.projection) + Math.PI;
- var a2 = geoAngle(midNode, endNode, context.projection) + Math.PI;
- var diff = Math.max(a1, a2) - Math.min(a1, a2);
+ behavior.initialNodeID = function (nodeId) {
+ _initialNodeID = nodeId;
+ return behavior;
+ };
- if (diff < minAngle) {
- joinTo = endNode;
- minAngle = diff;
- }
- });
- /* Threshold set by considering right angle triangle
- based on node joining threshold and extension distance */
+ return utilRebind(behavior, dispatch, 'on');
+ }
- if (minAngle <= SIG_ANGLE_TH) return joinTo;
- return null;
- }
+ 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');
- function hasTag(tags, key) {
- return tags[key] !== undefined && tags[key] !== 'no';
- }
+ var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true).on('hover', context.ui().sidebar.hover);
- function canConnectWays(way, way2) {
- // allow self-connections
- if (way.id === way2.id) return true; // if one is bridge or tunnel, both must be bridge or tunnel
+ var _edit = behaviorEdit(context);
- if ((hasTag(way.tags, 'bridge') || hasTag(way2.tags, 'bridge')) && !(hasTag(way.tags, 'bridge') && hasTag(way2.tags, 'bridge'))) return false;
- if ((hasTag(way.tags, 'tunnel') || hasTag(way2.tags, 'tunnel')) && !(hasTag(way.tags, 'tunnel') && hasTag(way2.tags, 'tunnel'))) return false; // must have equivalent layers and levels
+ var _closeTolerance = 4;
+ var _tolerance = 12;
+ var _mouseLeave = false;
+ var _lastMouse = null;
- var layer1 = way.tags.layer || '0',
- layer2 = way2.tags.layer || '0';
- if (layer1 !== layer2) return false;
- var level1 = way.tags.level || '0',
- level2 = way2.tags.level || '0';
- if (level1 !== level2) return false;
- return true;
- }
+ var _lastPointerUpEvent;
- function canConnectByExtend(way, endNodeIdx) {
- var tipNid = way.nodes[endNodeIdx]; // the 'tip' node for extension point
+ var _downPointer; // use pointer events on supported platforms; fallback to mouse events
- var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2]; // the other node of the edge
- var tipNode = graph.entity(tipNid);
- var midNode = graph.entity(midNid);
- var lon = tipNode.loc[0];
- var lat = tipNode.loc[1];
- var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2;
- var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2;
- var queryExtent = geoExtent([[lon - lon_range, lat - lat_range], [lon + lon_range, lat + lat_range]]); // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // related code
+ // - `mode/drag_node.js` `datum()`
- var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc);
- var t = EXTEND_TH_METERS / edgeLen + 1.0;
- var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t); // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways
- var segmentInfos = tree.waySegments(queryExtent, graph);
+ function datum(d3_event) {
+ var mode = context.mode();
+ var isNote = mode && mode.id.indexOf('note') !== -1;
+ if (d3_event.altKey || isNote) return {};
+ var element;
- for (var i = 0; i < segmentInfos.length; i++) {
- var segmentInfo = segmentInfos[i];
- var way2 = graph.entity(segmentInfo.wayId);
- if (!isHighway(way2)) continue;
- if (!canConnectWays(way, way2)) continue;
- var nAid = segmentInfo.nodes[0],
- nBid = segmentInfo.nodes[1];
- if (nAid === tipNid || nBid === tipNid) continue;
- var nA = graph.entity(nAid),
- nB = graph.entity(nBid);
- var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]);
+ 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)
- if (crossLoc) {
- return {
- mid: midNode,
- node: tipNode,
- wid: way2.id,
- edge: [nA.id, nB.id],
- cross_loc: crossLoc
- };
- }
- }
- return null;
- }
- };
+ var d = element.__data__;
+ return d && d.properties && d.properties.target ? d : {};
+ }
- validation.type = type;
- return validation;
- }
+ 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 validationCloseNodes(context) {
- var type = 'close_nodes';
- var pointThresholdMeters = 0.2;
+ 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);
- var validation = function validation(entity, graph) {
- if (entity.type === 'node') {
- return getIssuesForNode(entity);
- } else if (entity.type === 'way') {
- return getIssuesForWay(entity);
+ 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);
}
+ }
- return [];
+ function pointermove(d3_event) {
+ if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse') && !_downPointer.isCancelled) {
+ var p2 = _downPointer.pointerLocGetter(d3_event);
- function getIssuesForNode(node) {
- var parentWays = graph.parentWays(node);
+ var dist = geoVecLength(_downPointer.downLoc, p2);
- if (parentWays.length) {
- return getIssuesForVertex(node, parentWays);
- } else {
- return getIssuesForDetachedPoint(node);
+ if (dist >= _closeTolerance) {
+ _downPointer.isCancelled = true;
+ dispatch.call('downcancel', this);
}
}
- function wayTypeFor(way) {
- if (way.tags.boundary && way.tags.boundary !== 'no') return 'boundary';
- if (way.tags.indoor && way.tags.indoor !== 'no') return 'indoor';
- if (way.tags.building && way.tags.building !== 'no' || way.tags['building:part'] && way.tags['building:part'] !== 'no') return 'building';
- if (osmPathHighwayTagValues[way.tags.highway]) return 'path';
- var parentRelations = graph.parentRelations(way);
+ 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.
- for (var i in parentRelations) {
- var relation = parentRelations[i];
- if (relation.tags.type === 'boundary') return 'boundary';
+ if (_lastPointerUpEvent && _lastPointerUpEvent.pointerType !== 'mouse' && d3_event.timeStamp - _lastPointerUpEvent.timeStamp < 100) return;
+ _lastMouse = d3_event;
+ dispatch.call('move', this, d3_event, datum(d3_event));
+ }
- if (relation.isMultipolygon()) {
- if (relation.tags.indoor && relation.tags.indoor !== 'no') return 'indoor';
- if (relation.tags.building && relation.tags.building !== 'no' || relation.tags['building:part'] && relation.tags['building:part'] !== 'no') return 'building';
- }
+ function pointercancel(d3_event) {
+ if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse')) {
+ if (!_downPointer.isCancelled) {
+ dispatch.call('downcancel', this);
}
- return 'other';
+ _downPointer = null;
}
+ }
- function shouldCheckWay(way) {
- // don't flag issues where merging would create degenerate ways
- if (way.nodes.length <= 2 || way.isClosed() && way.nodes.length <= 4) return false;
- var bbox = way.extent(graph).bbox();
- var hypotenuseMeters = geoSphericalDistance([bbox.minX, bbox.minY], [bbox.maxX, bbox.maxY]); // don't flag close nodes in very small ways
+ function mouseenter() {
+ _mouseLeave = false;
+ }
- if (hypotenuseMeters < 1.5) return false;
- return true;
- }
+ function mouseleave() {
+ _mouseLeave = true;
+ }
- function getIssuesForWay(way) {
- if (!shouldCheckWay(way)) return [];
- var issues = [],
- nodes = graph.childNodes(way);
+ 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()`
- for (var i = 0; i < nodes.length - 1; i++) {
- var node1 = nodes[i];
- var node2 = nodes[i + 1];
- var issue = getWayIssueIfAny(node1, node2, way);
- if (issue) issues.push(issue);
- }
- return issues;
- }
+ function click(d3_event, loc) {
+ var d = datum(d3_event);
+ var target = d && d.properties && d.properties.entity;
+ var mode = context.mode();
- function getIssuesForVertex(node, parentWays) {
- var issues = [];
+ 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());
- function checkForCloseness(node1, node2, way) {
- var issue = getWayIssueIfAny(node1, node2, way);
- if (issue) issues.push(issue);
+ 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);
+ }
+ } // treat a spacebar press like a click
- for (var i = 0; i < parentWays.length; i++) {
- var parentWay = parentWays[i];
- if (!shouldCheckWay(parentWay)) continue;
- var lastIndex = parentWay.nodes.length - 1;
- for (var j = 0; j < parentWay.nodes.length; j++) {
- if (j !== 0) {
- if (parentWay.nodes[j - 1] === node.id) {
- checkForCloseness(node, graph.entity(parentWay.nodes[j]), parentWay);
- }
- }
+ function space(d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ var currSpace = context.map().mouse();
- if (j !== lastIndex) {
- if (parentWay.nodes[j + 1] === node.id) {
- checkForCloseness(graph.entity(parentWay.nodes[j]), node, parentWay);
- }
- }
- }
- }
+ if (_disableSpace && _lastSpace) {
+ var dist = geoVecLength(_lastSpace, currSpace);
- return issues;
+ if (dist > _tolerance) {
+ _disableSpace = false;
+ }
}
- function thresholdMetersForWay(way) {
- if (!shouldCheckWay(way)) return 0;
- var wayType = wayTypeFor(way); // don't flag boundaries since they might be highly detailed and can't be easily verified
-
- if (wayType === 'boundary') return 0; // expect some features to be mapped with higher levels of detail
+ if (_disableSpace || _mouseLeave || !_lastMouse) return; // user must move mouse or release space bar to allow another click
- if (wayType === 'indoor') return 0.01;
- if (wayType === 'building') return 0.05;
- if (wayType === 'path') return 0.1;
- return 0.2;
- }
+ _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
- function getIssuesForDetachedPoint(node) {
- var issues = [];
- var lon = node.loc[0];
- var lat = node.loc[1];
- var lon_range = geoMetersToLon(pointThresholdMeters, lat) / 2;
- var lat_range = geoMetersToLat(pointThresholdMeters) / 2;
- var queryExtent = geoExtent([[lon - lon_range, lat - lat_range], [lon + lon_range, lat + lat_range]]);
- var intersected = context.history().tree().intersects(queryExtent, graph);
+ 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);
+ }
- for (var j = 0; j < intersected.length; j++) {
- var nearby = intersected[j];
- if (nearby.id === node.id) continue;
- if (nearby.type !== 'node' || nearby.geometry(graph) !== 'point') continue;
+ function backspace(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('undo');
+ }
- if (nearby.loc === node.loc || geoSphericalDistance(node.loc, nearby.loc) < pointThresholdMeters) {
- // allow very close points if tags indicate the z-axis might vary
- var zAxisKeys = {
- layer: true,
- level: true,
- 'addr:housenumber': true,
- 'addr:unit': true
- };
- var zAxisDifferentiates = false;
+ function del(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('cancel');
+ }
- for (var key in zAxisKeys) {
- var nodeValue = node.tags[key] || '0';
- var nearbyValue = nearby.tags[key] || '0';
+ function ret(d3_event) {
+ d3_event.preventDefault();
+ dispatch.call('finish');
+ }
- if (nodeValue !== nearbyValue) {
- zAxisDifferentiates = true;
- break;
- }
- }
+ 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;
+ }
- if (zAxisDifferentiates) continue;
- issues.push(new validationIssue({
- type: type,
- subtype: 'detached',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]),
- entity2 = context.hasEntity(this.entityIds[1]);
- return entity && entity2 ? _t.html('issues.close_nodes.detached.message', {
- feature: utilDisplayLabel(entity, context.graph()),
- feature2: utilDisplayLabel(entity2, context.graph())
- }) : '';
- },
- reference: showReference,
- entityIds: [node.id, nearby.id],
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-operation-disconnect',
- title: _t.html('issues.fix.move_points_apart.title')
- }), new validationIssueFix({
- icon: 'iD-icon-layers',
- title: _t.html('issues.fix.use_different_layers_or_levels.title')
- })];
- }
- }));
- }
- }
+ 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
- return issues;
+ select(document).call(keybinding.unbind);
+ };
- function showReference(selection) {
- var referenceText = _t('issues.close_nodes.detached.reference');
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(referenceText);
- }
- }
+ behavior.hover = function () {
+ return _hover;
+ };
- function getWayIssueIfAny(node1, node2, way) {
- if (node1.id === node2.id || node1.hasInterestingTags() && node2.hasInterestingTags()) {
- return null;
- }
+ return utilRebind(behavior, dispatch, 'on');
+ }
- if (node1.loc !== node2.loc) {
- var parentWays1 = graph.parentWays(node1);
- var parentWays2 = new Set(graph.parentWays(node2));
- var sharedWays = parentWays1.filter(function (parentWay) {
- return parentWays2.has(parentWay);
- });
- var thresholds = sharedWays.map(function (parentWay) {
- return thresholdMetersForWay(parentWay);
- });
- var threshold = Math.min.apply(Math, _toConsumableArray(thresholds));
- var distance = geoSphericalDistance(node1.loc, node2.loc);
- if (distance > threshold) return null;
- }
+ function initRange(domain, range) {
+ switch (arguments.length) {
+ case 0:
+ break;
- return new validationIssue({
- type: type,
- subtype: 'vertices',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.close_nodes.message', {
- way: utilDisplayLabel(entity, context.graph())
- }) : '';
- },
- reference: showReference,
- entityIds: [way.id, node1.id, node2.id],
- loc: node1.loc,
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-icon-plus',
- title: _t.html('issues.fix.merge_points.title'),
- onClick: function onClick(context) {
- var entityIds = this.issue.entityIds;
- var action = actionMergeNodes([entityIds[1], entityIds[2]]);
- context.perform(action, _t('issues.fix.merge_close_vertices.annotation'));
- }
- }), new validationIssueFix({
- icon: 'iD-operation-disconnect',
- title: _t.html('issues.fix.move_points_apart.title')
- })];
- }
- });
+ case 1:
+ this.range(domain);
+ break;
- function showReference(selection) {
- var referenceText = _t('issues.close_nodes.reference');
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(referenceText);
- }
- }
+ default:
+ this.range(range).domain(domain);
+ break;
+ }
+
+ return this;
+ }
+
+ function constants(x) {
+ return function () {
+ return x;
};
+ }
- validation.type = type;
- return validation;
+ function number(x) {
+ return +x;
}
- function validationCrossingWays(context) {
- var type = 'crossing_ways'; // returns the way or its parent relation, whichever has a useful feature type
+ var unit = [0, 1];
+ function identity$1(x) {
+ return x;
+ }
- function getFeatureWithFeatureTypeTagsForWay(way, graph) {
- if (getFeatureType(way, graph) === null) {
- // if the way doesn't match a feature type, check its parent relations
- var parentRels = graph.parentRelations(way);
+ function normalize(a, b) {
+ return (b -= a = +a) ? function (x) {
+ return (x - a) / b;
+ } : constants(isNaN(b) ? NaN : 0.5);
+ }
- for (var i = 0; i < parentRels.length; i++) {
- var rel = parentRels[i];
+ 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].
- if (getFeatureType(rel, graph) !== null) {
- return rel;
- }
- }
- }
- return way;
- }
+ 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 hasTag(tags, key) {
- return tags[key] !== undefined && tags[key] !== 'no';
+ 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 taggedAsIndoor(tags) {
- return hasTag(tags, 'indoor') || hasTag(tags, 'level') || tags.highway === 'corridor';
+ while (++i < j) {
+ d[i] = normalize(domain[i], domain[i + 1]);
+ r[i] = interpolate(range[i], range[i + 1]);
}
- function allowsBridge(featureType) {
- return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
+ return function (x) {
+ var i = bisectRight(domain, x, 1, j) - 1;
+ return r[i](d[i](x));
+ };
+ }
+
+ function copy(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;
+
+ 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;
}
- function allowsTunnel(featureType) {
- return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
- } // discard
+ 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)));
+ };
- var ignoredBuildings = {
- demolished: true,
- dismantled: true,
- proposed: true,
- razed: true
+ scale.domain = function (_) {
+ return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice();
};
- function getFeatureType(entity, graph) {
- var geometry = entity.geometry(graph);
- if (geometry !== 'line' && geometry !== 'area') return null;
- var tags = entity.tags;
- if (hasTag(tags, 'building') && !ignoredBuildings[tags.building]) return 'building';
- if (hasTag(tags, 'highway') && osmRoutableHighwayTagValues[tags.highway]) return 'highway'; // don't check railway or waterway areas
+ scale.range = function (_) {
+ return arguments.length ? (range = Array.from(_), rescale()) : range.slice();
+ };
- if (geometry !== 'line') return null;
- if (hasTag(tags, 'railway') && osmRailwayTrackTagValues[tags.railway]) return 'railway';
- if (hasTag(tags, 'waterway') && osmFlowingWaterwayTagValues[tags.waterway]) return 'waterway';
- return null;
- }
+ scale.rangeRound = function (_) {
+ return range = Array.from(_), interpolate = interpolateRound, rescale();
+ };
- function isLegitCrossing(tags1, featureType1, tags2, featureType2) {
- // assume 0 by default
- var level1 = tags1.level || '0';
- var level2 = tags2.level || '0';
+ scale.clamp = function (_) {
+ return arguments.length ? (clamp = _ ? true : identity$1, rescale()) : clamp !== identity$1;
+ };
- if (taggedAsIndoor(tags1) && taggedAsIndoor(tags2) && level1 !== level2) {
- // assume features don't interact if they're indoor on different levels
- return true;
- } // assume 0 by default; don't use way.layer() since we account for structures here
+ scale.interpolate = function (_) {
+ return arguments.length ? (interpolate = _, rescale()) : interpolate;
+ };
+ scale.unknown = function (_) {
+ return arguments.length ? (unknown = _, scale) : unknown;
+ };
- var layer1 = tags1.layer || '0';
- var layer2 = tags2.layer || '0';
+ return function (t, u) {
+ transform = t, untransform = u;
+ return rescale();
+ };
+ }
+ function continuous() {
+ return transformer()(identity$1, identity$1);
+ }
- if (allowsBridge(featureType1) && allowsBridge(featureType2)) {
- if (hasTag(tags1, 'bridge') && !hasTag(tags2, 'bridge')) return true;
- if (!hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge')) return true; // crossing bridges must use different layers
+ 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].
- if (hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge') && layer1 !== layer2) return true;
- } else if (allowsBridge(featureType1) && hasTag(tags1, 'bridge')) return true;else if (allowsBridge(featureType2) && hasTag(tags2, 'bridge')) return true;
+ function formatDecimalParts(x, p) {
+ if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
- if (allowsTunnel(featureType1) && allowsTunnel(featureType2)) {
- if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
- if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true; // crossing tunnels must use different layers
+ 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).
- if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && layer1 !== layer2) return true;
- } else if (allowsTunnel(featureType1) && hasTag(tags1, 'tunnel')) return true;else if (allowsTunnel(featureType2) && hasTag(tags2, 'tunnel')) return true; // don't flag crossing waterways and pier/highways
+ 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;
+ }
- if (featureType1 === 'waterway' && featureType2 === 'highway' && tags2.man_made === 'pier') return true;
- if (featureType2 === 'waterway' && featureType1 === 'highway' && tags1.man_made === 'pier') return true;
+ function formatGroup (grouping, thousands) {
+ return function (value, width) {
+ var i = value.length,
+ t = [],
+ j = 0,
+ g = grouping[0],
+ length = 0;
- if (featureType1 === 'building' || featureType2 === 'building') {
- // for building crossings, different layers are enough
- if (layer1 !== layer2) return true;
+ 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];
}
- return false;
- } // highway values for which we shouldn't recommend connecting to waterways
-
-
- var highwaysDisallowingFords = {
- motorway: true,
- motorway_link: true,
- trunk: true,
- trunk_link: true,
- primary: true,
- primary_link: true,
- secondary: true,
- secondary_link: true
+ return t.reverse().join(thousands);
};
- var nonCrossingHighways = {
- track: true
+ }
+
+ function formatNumerals (numerals) {
+ return function (value) {
+ return value.replace(/[0-9]/g, function (i) {
+ return numerals[+i];
+ });
};
+ }
- function tagsForConnectionNodeIfAllowed(entity1, entity2, graph) {
- var featureType1 = getFeatureType(entity1, graph);
- var featureType2 = getFeatureType(entity2, graph);
- var geometry1 = entity1.geometry(graph);
- var geometry2 = entity2.geometry(graph);
- var bothLines = geometry1 === 'line' && geometry2 === 'line';
+ // [[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
- if (featureType1 === featureType2) {
- if (featureType1 === 'highway') {
- var entity1IsPath = osmPathHighwayTagValues[entity1.tags.highway];
- var entity2IsPath = osmPathHighwayTagValues[entity2.tags.highway];
+ 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 + "";
+ }
- if ((entity1IsPath || entity2IsPath) && entity1IsPath !== entity2IsPath) {
- // one feature is a path but not both
- var roadFeature = entity1IsPath ? entity2 : entity1;
+ 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;
+ };
- if (nonCrossingHighways[roadFeature.tags.highway]) {
- // don't mark path connections with certain roads as crossings
- return {};
- }
+ // 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;
- var pathFeature = entity1IsPath ? entity1 : entity2;
+ case "0":
+ if (i0 === 0) i0 = i;
+ i1 = i;
+ break;
- if (['marked', 'unmarked'].indexOf(pathFeature.tags.crossing) !== -1) {
- // if the path is a crossing, match the crossing type
- return bothLines ? {
- highway: 'crossing',
- crossing: pathFeature.tags.crossing
- } : {};
- } // don't add a `crossing` subtag to ambiguous crossings
+ default:
+ if (!+s[i]) break out;
+ if (i0 > 0) i0 = 0;
+ break;
+ }
+ }
+ return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
+ }
- return bothLines ? {
- highway: 'crossing'
- } : {};
- }
+ var $$5 = _export;
+ var uncurryThis$3 = functionUncurryThis;
+ var fails$3 = fails$S;
+ var thisNumberValue = thisNumberValue$3;
- return {};
- }
+ var un$ToPrecision = uncurryThis$3(1.0.toPrecision);
- if (featureType1 === 'waterway') return {};
- if (featureType1 === 'railway') return {};
- } else {
- var featureTypes = [featureType1, featureType2];
+ var FORCED$1 = fails$3(function () {
+ // IE7-
+ return un$ToPrecision(1, undefined) !== '1';
+ }) || !fails$3(function () {
+ // V8 ~ Android 4.3-
+ un$ToPrecision({});
+ });
- if (featureTypes.indexOf('highway') !== -1) {
- if (featureTypes.indexOf('railway') !== -1) {
- if (!bothLines) return {};
- var isTram = entity1.tags.railway === 'tram' || entity2.tags.railway === 'tram';
+ // `Number.prototype.toPrecision` method
+ // https://tc39.es/ecma262/#sec-number.prototype.toprecision
+ $$5({ target: 'Number', proto: true, forced: FORCED$1 }, {
+ toPrecision: function toPrecision(precision) {
+ return precision === undefined
+ ? un$ToPrecision(thisNumberValue(this))
+ : un$ToPrecision(thisNumberValue(this), precision);
+ }
+ });
- if (osmPathHighwayTagValues[entity1.tags.highway] || osmPathHighwayTagValues[entity2.tags.highway]) {
- // path-tram connections use this tag
- if (isTram) return {
- railway: 'tram_crossing'
- }; // other path-rail connections use this tag
+ 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!
+ }
- return {
- railway: 'crossing'
- };
- } else {
- // path-tram connections use this tag
- if (isTram) return {
- railway: 'tram_level_crossing'
- }; // other road-rail connections use this tag
+ 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");
+ }
- return {
- railway: 'level_crossing'
- };
- }
- }
+ 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 (featureTypes.indexOf('waterway') !== -1) {
- // do not allow fords on structures
- if (hasTag(entity1.tags, 'tunnel') && hasTag(entity2.tags, 'tunnel')) return null;
- if (hasTag(entity1.tags, 'bridge') && hasTag(entity2.tags, 'bridge')) return null;
+ function identity (x) {
+ return x;
+ }
- if (highwaysDisallowingFords[entity1.tags.highway] || highwaysDisallowingFords[entity2.tags.highway]) {
- // do not allow fords on major highways
- return null;
- }
+ 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 + "";
- return bothLines ? {
- ford: 'yes'
- } : {};
- }
- }
- }
+ 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".
- return null;
- }
+ 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.
- function findCrossingsByWay(way1, graph, tree) {
- var edgeCrossInfos = [];
- if (way1.type !== 'way') return edgeCrossInfos;
- var taggedFeature1 = getFeatureWithFeatureTypeTagsForWay(way1, graph);
- var way1FeatureType = getFeatureType(taggedFeature1, graph);
- if (way1FeatureType === null) return edgeCrossInfos;
- var checkedSingleCrossingWays = {}; // declare vars ahead of time to reduce garbage collection
+ if (zero || fill === "0" && align === "=") zero = true, fill = "0", align = "="; // Compute the prefix and suffix.
+ // For SI-prefix, the suffix is lazily computed.
- var i, j;
- var extent;
- var n1, n2, nA, nB, nAId, nBId;
- var segment1, segment2;
- var oneOnly;
- var segmentInfos, segment2Info, way2, taggedFeature2, way2FeatureType;
- var way1Nodes = graph.childNodes(way1);
- var comparedWays = {};
+ 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?
- for (i = 0; i < way1Nodes.length - 1; i++) {
- n1 = way1Nodes[i];
- n2 = way1Nodes[i + 1];
- extent = 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])]]); // Optimize by only checking overlapping segments, not every segment
- // of overlapping ways
+ 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].
- segmentInfos = tree.waySegments(extent, graph);
+ precision = precision === undefined ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision));
- for (j = 0; j < segmentInfos.length; j++) {
- segment2Info = segmentInfos[j]; // don't check for self-intersection in this validation
+ function format(value) {
+ var valuePrefix = prefix,
+ valueSuffix = suffix,
+ i,
+ n,
+ c;
- if (segment2Info.wayId === way1.id) continue; // skip if this way was already checked and only one issue is needed
+ if (type === "c") {
+ valueSuffix = formatType(value) + valueSuffix;
+ value = "";
+ } else {
+ value = +value; // Determine the sign. -0 is not less than 0, but 1 / -0 is!
- if (checkedSingleCrossingWays[segment2Info.wayId]) continue; // mark this way as checked even if there are no crossings
+ var valueNegative = value < 0 || 1 / value < 0; // Perform the initial formatting.
- comparedWays[segment2Info.wayId] = true;
- way2 = graph.hasEntity(segment2Info.wayId);
- if (!way2) continue;
- taggedFeature2 = getFeatureWithFeatureTypeTagsForWay(way2, graph); // only check crossing highway, waterway, building, and railway
+ value = isNaN(value) ? nan : formatType(Math.abs(value), precision); // Trim insignificant zeros.
- way2FeatureType = getFeatureType(taggedFeature2, graph);
+ if (trim) value = formatTrim(value); // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
- if (way2FeatureType === null || isLegitCrossing(taggedFeature1.tags, way1FeatureType, taggedFeature2.tags, way2FeatureType)) {
- continue;
- } // create only one issue for building crossings
+ 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.
- oneOnly = way1FeatureType === 'building' || way2FeatureType === 'building';
- nAId = segment2Info.nodes[0];
- nBId = segment2Info.nodes[1];
+ if (maybeSuffix) {
+ i = -1, n = value.length;
- if (nAId === n1.id || nAId === n2.id || nBId === n1.id || nBId === n2.id) {
- // n1 or n2 is a connection node; skip
- continue;
+ 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.
- nA = graph.hasEntity(nAId);
- if (!nA) continue;
- nB = graph.hasEntity(nBId);
- if (!nB) continue;
- segment1 = [n1.loc, n2.loc];
- segment2 = [nA.loc, nB.loc];
- var point = geoLineIntersection(segment1, segment2);
- if (point) {
- edgeCrossInfos.push({
- wayInfos: [{
- way: way1,
- featureType: way1FeatureType,
- edge: [n1.id, n2.id]
- }, {
- way: way2,
- featureType: way2FeatureType,
- edge: [nA.id, nB.id]
- }],
- crossPoint: point
- });
+ if (comma && !zero) value = group(value, Infinity); // Compute the padding.
- if (oneOnly) {
- checkedSingleCrossingWays[way2.id] = true;
- break;
- }
- }
- }
- }
+ 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.
- return edgeCrossInfos;
- }
+ if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; // Reconstruct the final output based on the desired alignment.
- function waysToCheck(entity, graph) {
- var featureType = getFeatureType(entity, graph);
- if (!featureType) return [];
+ switch (align) {
+ case "<":
+ value = valuePrefix + value + valueSuffix + padding;
+ break;
- if (entity.type === 'way') {
- return [entity];
- } else if (entity.type === 'relation') {
- return entity.members.reduce(function (array, member) {
- if (member.type === 'way' && ( // only look at geometry ways
- !member.role || member.role === 'outer' || member.role === 'inner')) {
- var entity = graph.hasEntity(member.id); // don't add duplicates
+ case "=":
+ value = valuePrefix + padding + value + valueSuffix;
+ break;
- if (entity && array.indexOf(entity) === -1) {
- array.push(entity);
- }
- }
+ case "^":
+ value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
+ break;
- return array;
- }, []);
+ default:
+ value = padding + valuePrefix + value + valueSuffix;
+ break;
+ }
+
+ return numerals(value);
}
- return [];
- }
+ format.toString = function () {
+ return specifier + "";
+ };
- var validation = function checkCrossingWays(entity, graph) {
- var tree = context.history().tree();
- var ways = waysToCheck(entity, graph);
- var issues = []; // declare these here to reduce garbage collection
+ return format;
+ }
- var wayIndex, crossingIndex, crossings;
+ 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;
+ };
+ }
- for (wayIndex in ways) {
- crossings = findCrossingsByWay(ways[wayIndex], graph, tree);
+ return {
+ format: newFormat,
+ formatPrefix: formatPrefix
+ };
+ }
- for (crossingIndex in crossings) {
- issues.push(createIssue(crossings[crossingIndex], graph));
- }
- }
+ var locale;
+ var format$1;
+ var formatPrefix;
+ defaultLocale({
+ thousands: ",",
+ grouping: [3],
+ currency: ["$", ""]
+ });
+ function defaultLocale(definition) {
+ locale = formatLocale(definition);
+ format$1 = locale.format;
+ formatPrefix = locale.formatPrefix;
+ return locale;
+ }
- return issues;
- };
+ function precisionFixed (step) {
+ return Math.max(0, -exponent(Math.abs(step)));
+ }
- function createIssue(crossing, graph) {
- // use the entities with the tags that define the feature type
- crossing.wayInfos.sort(function (way1Info, way2Info) {
- var type1 = way1Info.featureType;
- var type2 = way2Info.featureType;
+ function precisionPrefix (step, value) {
+ return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));
+ }
- if (type1 === type2) {
- return utilDisplayLabel(way1Info.way, graph) > utilDisplayLabel(way2Info.way, graph);
- } else if (type1 === 'waterway') {
- return true;
- } else if (type2 === 'waterway') {
- return false;
- }
+ function precisionRound (step, max) {
+ step = Math.abs(step), max = Math.abs(max) - step;
+ return Math.max(0, exponent(max) - exponent(step)) + 1;
+ }
- return type1 < type2;
- });
- var entities = crossing.wayInfos.map(function (wayInfo) {
- return getFeatureWithFeatureTypeTagsForWay(wayInfo.way, graph);
- });
- var edges = [crossing.wayInfos[0].edge, crossing.wayInfos[1].edge];
- var featureTypes = [crossing.wayInfos[0].featureType, crossing.wayInfos[1].featureType];
- var connectionTags = tagsForConnectionNodeIfAllowed(entities[0], entities[1], graph);
- var featureType1 = crossing.wayInfos[0].featureType;
- var featureType2 = crossing.wayInfos[1].featureType;
- var isCrossingIndoors = taggedAsIndoor(entities[0].tags) && taggedAsIndoor(entities[1].tags);
- var isCrossingTunnels = allowsTunnel(featureType1) && hasTag(entities[0].tags, 'tunnel') && allowsTunnel(featureType2) && hasTag(entities[1].tags, 'tunnel');
- var isCrossingBridges = allowsBridge(featureType1) && hasTag(entities[0].tags, 'bridge') && allowsBridge(featureType2) && hasTag(entities[1].tags, 'bridge');
- var subtype = [featureType1, featureType2].sort().join('-');
- var crossingTypeID = subtype;
+ function tickFormat(start, stop, count, specifier) {
+ var step = tickStep(start, stop, count),
+ precision;
+ specifier = formatSpecifier(specifier == null ? ",f" : specifier);
- if (isCrossingIndoors) {
- crossingTypeID = 'indoor-indoor';
- } else if (isCrossingTunnels) {
- crossingTypeID = 'tunnel-tunnel';
- } else if (isCrossingBridges) {
- crossingTypeID = 'bridge-bridge';
- }
+ 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);
+ }
- if (connectionTags && (isCrossingIndoors || isCrossingTunnels || isCrossingBridges)) {
- crossingTypeID += '_connectable';
- } // Differentiate based on the loc rounded to 4 digits, since two ways can cross multiple times.
+ 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;
+ }
+ case "f":
+ case "%":
+ {
+ if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2;
+ break;
+ }
+ }
- var uniqueID = '' + crossing.crossPoint[0].toFixed(4) + ',' + crossing.crossPoint[1].toFixed(4);
- return new validationIssue({
- type: type,
- subtype: subtype,
- severity: 'warning',
- message: function message(context) {
- var graph = context.graph();
- var entity1 = graph.hasEntity(this.entityIds[0]),
- entity2 = graph.hasEntity(this.entityIds[1]);
- return entity1 && entity2 ? _t.html('issues.crossing_ways.message', {
- feature: utilDisplayLabel(entity1, graph),
- feature2: utilDisplayLabel(entity2, graph)
- }) : '';
- },
- reference: showReference,
- entityIds: entities.map(function (entity) {
- return entity.id;
- }),
- data: {
- edges: edges,
- featureTypes: featureTypes,
- connectionTags: connectionTags
- },
- hash: uniqueID,
- loc: crossing.crossPoint,
- dynamicFixes: function dynamicFixes(context) {
- var mode = context.mode();
- if (!mode || mode.id !== 'select' || mode.selectedIDs().length !== 1) return [];
- var selectedIndex = this.entityIds[0] === mode.selectedIDs()[0] ? 0 : 1;
- var selectedFeatureType = this.data.featureTypes[selectedIndex];
- var otherFeatureType = this.data.featureTypes[selectedIndex === 0 ? 1 : 0];
- var fixes = [];
+ return format$1(specifier);
+ }
- if (connectionTags) {
- fixes.push(makeConnectWaysFix(this.data.connectionTags));
- }
+ function linearish(scale) {
+ var domain = scale.domain;
- if (isCrossingIndoors) {
- fixes.push(new validationIssueFix({
- icon: 'iD-icon-layers',
- title: _t.html('issues.fix.use_different_levels.title')
- }));
- } else if (isCrossingTunnels || isCrossingBridges || featureType1 === 'building' || featureType2 === 'building') {
- fixes.push(makeChangeLayerFix('higher'));
- fixes.push(makeChangeLayerFix('lower')); // can only add bridge/tunnel if both features are lines
- } else if (context.graph().geometry(this.entityIds[0]) === 'line' && context.graph().geometry(this.entityIds[1]) === 'line') {
- // don't recommend adding bridges to waterways since they're uncommon
- if (allowsBridge(selectedFeatureType) && selectedFeatureType !== 'waterway') {
- fixes.push(makeAddBridgeOrTunnelFix('add_a_bridge', 'temaki-bridge', 'bridge'));
- } // don't recommend adding tunnels under waterways since they're uncommon
+ 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 skipTunnelFix = otherFeatureType === 'waterway' && selectedFeatureType !== 'waterway';
+ 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;
- if (allowsTunnel(selectedFeatureType) && !skipTunnelFix) {
- fixes.push(makeAddBridgeOrTunnelFix('add_a_tunnel', 'temaki-tunnel', 'tunnel'));
- }
- } // repositioning the features is always an option
+ if (stop < start) {
+ step = start, start = stop, stop = step;
+ step = i0, i0 = i1, i1 = step;
+ }
+ while (maxIter-- > 0) {
+ step = tickIncrement(start, stop, count);
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-move',
- title: _t.html('issues.fix.reposition_features.title')
- }));
- return fixes;
+ 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;
}
- });
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.crossing_ways.' + crossingTypeID + '.reference'));
+ prestep = step;
}
- }
-
- function makeAddBridgeOrTunnelFix(fixTitleID, iconName, bridgeOrTunnel) {
- return new validationIssueFix({
- icon: iconName,
- title: _t.html('issues.fix.' + fixTitleID + '.title'),
- onClick: function onClick(context) {
- var mode = context.mode();
- if (!mode || mode.id !== 'select') return;
- var selectedIDs = mode.selectedIDs();
- if (selectedIDs.length !== 1) return;
- var selectedWayID = selectedIDs[0];
- if (!context.hasEntity(selectedWayID)) return;
- var resultWayIDs = [selectedWayID];
- var edge, crossedEdge, crossedWayID;
- if (this.issue.entityIds[0] === selectedWayID) {
- edge = this.issue.data.edges[0];
- crossedEdge = this.issue.data.edges[1];
- crossedWayID = this.issue.entityIds[1];
- } else {
- edge = this.issue.data.edges[1];
- crossedEdge = this.issue.data.edges[0];
- crossedWayID = this.issue.entityIds[0];
- }
+ return scale;
+ };
- var crossingLoc = this.issue.loc;
- var projection = context.projection;
+ return scale;
+ }
+ function linear() {
+ var scale = continuous();
- var action = function actionAddStructure(graph) {
- var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])];
- var crossedWay = graph.hasEntity(crossedWayID); // use the explicit width of the crossed feature as the structure length, if available
+ scale.copy = function () {
+ return copy(scale, linear());
+ };
- var structLengthMeters = crossedWay && crossedWay.tags.width && parseFloat(crossedWay.tags.width);
+ initRange.apply(scale, arguments);
+ return linearish(scale);
+ }
- if (!structLengthMeters) {
- // if no explicit width is set, approximate the width based on the tags
- structLengthMeters = crossedWay && crossedWay.impliedLineWidthMeters();
- }
+ // eslint-disable-next-line es/no-math-expm1 -- safe
+ var $expm1 = Math.expm1;
+ var exp$1 = Math.exp;
- if (structLengthMeters) {
- if (getFeatureType(crossedWay, graph) === 'railway') {
- // bridges over railways are generally much longer than the rail bed itself, compensate
- structLengthMeters *= 2;
- }
- } else {
- // should ideally never land here since all rail/water/road tags should have an implied width
- structLengthMeters = 8;
- }
+ // `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;
- var a1 = geoAngle(edgeNodes[0], edgeNodes[1], projection) + Math.PI;
- var a2 = geoAngle(graph.entity(crossedEdge[0]), graph.entity(crossedEdge[1]), projection) + Math.PI;
- var crossingAngle = Math.max(a1, a2) - Math.min(a1, a2);
- if (crossingAngle > Math.PI) crossingAngle -= Math.PI; // lengthen the structure to account for the angle of the crossing
+ function quantize() {
+ var x0 = 0,
+ x1 = 1,
+ n = 1,
+ domain = [0.5],
+ range = [0, 1],
+ unknown;
- structLengthMeters = structLengthMeters / 2 / Math.sin(crossingAngle) * 2; // add padding since the structure must extend past the edges of the crossed feature
+ function scale(x) {
+ return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown;
+ }
- structLengthMeters += 4; // clamp the length to a reasonable range
+ function rescale() {
+ var i = -1;
+ domain = new Array(n);
- structLengthMeters = Math.min(Math.max(structLengthMeters, 4), 50);
+ while (++i < n) {
+ domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
+ }
- function geomToProj(geoPoint) {
- return [geoLonToMeters(geoPoint[0], geoPoint[1]), geoLatToMeters(geoPoint[1])];
- }
+ return scale;
+ }
- function projToGeom(projPoint) {
- var lat = geoMetersToLat(projPoint[1]);
- return [geoMetersToLon(projPoint[0], lat), lat];
- }
+ scale.domain = function (_) {
+ var _ref, _ref2;
- var projEdgeNode1 = geomToProj(edgeNodes[0].loc);
- var projEdgeNode2 = geomToProj(edgeNodes[1].loc);
- var projectedAngle = geoVecAngle(projEdgeNode1, projEdgeNode2);
- var projectedCrossingLoc = geomToProj(crossingLoc);
- var linearToSphericalMetersRatio = geoVecLength(projEdgeNode1, projEdgeNode2) / geoSphericalDistance(edgeNodes[0].loc, edgeNodes[1].loc);
+ return arguments.length ? ((_ref = _, _ref2 = _slicedToArray(_ref, 2), x0 = _ref2[0], x1 = _ref2[1], _ref), x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
+ };
- function locSphericalDistanceFromCrossingLoc(angle, distanceMeters) {
- var lengthSphericalMeters = distanceMeters * linearToSphericalMetersRatio;
- return projToGeom([projectedCrossingLoc[0] + Math.cos(angle) * lengthSphericalMeters, projectedCrossingLoc[1] + Math.sin(angle) * lengthSphericalMeters]);
- }
+ scale.range = function (_) {
+ return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice();
+ };
- var endpointLocGetter1 = function endpointLocGetter1(lengthMeters) {
- return locSphericalDistanceFromCrossingLoc(projectedAngle, lengthMeters);
- };
+ 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]];
+ };
- var endpointLocGetter2 = function endpointLocGetter2(lengthMeters) {
- return locSphericalDistanceFromCrossingLoc(projectedAngle + Math.PI, lengthMeters);
- }; // avoid creating very short edges from splitting too close to another node
+ scale.unknown = function (_) {
+ return arguments.length ? (unknown = _, scale) : scale;
+ };
+ scale.thresholds = function () {
+ return domain.slice();
+ };
- var minEdgeLengthMeters = 0.55; // decide where to bound the structure along the way, splitting as necessary
+ scale.copy = function () {
+ return quantize().domain([x0, x1]).range(range).unknown(unknown);
+ };
- function determineEndpoint(edge, endNode, locGetter) {
- var newNode;
- var idealLengthMeters = structLengthMeters / 2; // distance between the crossing location and the end of the edge,
- // the maximum length of this side of the structure
+ return initRange.apply(linearish(scale), arguments);
+ }
- var crossingToEdgeEndDistance = geoSphericalDistance(crossingLoc, endNode.loc);
+ var global$3 = global$1m;
+ var uncurryThis$2 = functionUncurryThis;
+ var fails$2 = fails$S;
+ var padStart = stringPad.start;
- if (crossingToEdgeEndDistance - idealLengthMeters > minEdgeLengthMeters) {
- // the edge is long enough to insert a new node
- // the loc that would result in the full expected length
- var idealNodeLoc = locGetter(idealLengthMeters);
- newNode = osmNode();
- graph = actionAddMidpoint({
- loc: idealNodeLoc,
- edge: edge
- }, newNode)(graph);
- } else {
- var edgeCount = 0;
- endNode.parentIntersectionWays(graph).forEach(function (way) {
- way.nodes.forEach(function (nodeID) {
- if (nodeID === endNode.id) {
- if (endNode.id === way.first() && endNode.id !== way.last() || endNode.id === way.last() && endNode.id !== way.first()) {
- edgeCount += 1;
- } else {
- edgeCount += 2;
- }
- }
- });
- });
+ var RangeError$2 = global$3.RangeError;
+ var abs$1 = Math.abs;
+ var DatePrototype = Date.prototype;
+ var n$DateToISOString = DatePrototype.toISOString;
+ var getTime = uncurryThis$2(DatePrototype.getTime);
+ var getUTCDate = uncurryThis$2(DatePrototype.getUTCDate);
+ var getUTCFullYear = uncurryThis$2(DatePrototype.getUTCFullYear);
+ var getUTCHours = uncurryThis$2(DatePrototype.getUTCHours);
+ var getUTCMilliseconds = uncurryThis$2(DatePrototype.getUTCMilliseconds);
+ var getUTCMinutes = uncurryThis$2(DatePrototype.getUTCMinutes);
+ var getUTCMonth = uncurryThis$2(DatePrototype.getUTCMonth);
+ var getUTCSeconds = uncurryThis$2(DatePrototype.getUTCSeconds);
- if (edgeCount >= 3) {
- // the end node is a junction, try to leave a segment
- // between it and the structure - #7202
- var insetLength = crossingToEdgeEndDistance - minEdgeLengthMeters;
+ // `Date.prototype.toISOString` method implementation
+ // https://tc39.es/ecma262/#sec-date.prototype.toisostring
+ // PhantomJS / old WebKit fails here:
+ var dateToIsoString = (fails$2(function () {
+ return n$DateToISOString.call(new Date(-5e13 - 1)) != '0385-07-25T07:06:39.999Z';
+ }) || !fails$2(function () {
+ n$DateToISOString.call(new Date(NaN));
+ })) ? function toISOString() {
+ if (!isFinite(getTime(this))) throw RangeError$2('Invalid time value');
+ var date = this;
+ var year = getUTCFullYear(date);
+ var milliseconds = getUTCMilliseconds(date);
+ var sign = year < 0 ? '-' : year > 9999 ? '+' : '';
+ return sign + padStart(abs$1(year), sign ? 6 : 4, 0) +
+ '-' + padStart(getUTCMonth(date) + 1, 2, 0) +
+ '-' + padStart(getUTCDate(date), 2, 0) +
+ 'T' + padStart(getUTCHours(date), 2, 0) +
+ ':' + padStart(getUTCMinutes(date), 2, 0) +
+ ':' + padStart(getUTCSeconds(date), 2, 0) +
+ '.' + padStart(milliseconds, 3, 0) +
+ 'Z';
+ } : n$DateToISOString;
- if (insetLength > minEdgeLengthMeters) {
- var insetNodeLoc = locGetter(insetLength);
- newNode = osmNode();
- graph = actionAddMidpoint({
- loc: insetNodeLoc,
- edge: edge
- }, newNode)(graph);
- }
- }
- } // if the edge is too short to subdivide as desired, then
- // just bound the structure at the existing end node
+ var $$4 = _export;
+ var toISOString = dateToIsoString;
+ // `Date.prototype.toISOString` method
+ // https://tc39.es/ecma262/#sec-date.prototype.toisostring
+ // PhantomJS / old WebKit has a broken implementations
+ $$4({ target: 'Date', proto: true, forced: Date.prototype.toISOString !== toISOString }, {
+ toISOString: toISOString
+ });
- if (!newNode) newNode = endNode;
- var splitAction = actionSplit([newNode.id]).limitWays(resultWayIDs); // only split selected or created ways
- // do the split
+ function behaviorBreathe() {
+ var duration = 800;
+ var steps = 4;
+ var selector = '.selected.shadow, .selected .shadow';
- graph = splitAction(graph);
+ var _selected = select(null);
- if (splitAction.getCreatedWayIDs().length) {
- resultWayIDs.push(splitAction.getCreatedWayIDs()[0]);
- }
+ var _classed = '';
+ var _params = {};
+ var _done = false;
- return newNode;
- }
+ var _timer;
- var structEndNode1 = determineEndpoint(edge, edgeNodes[1], endpointLocGetter1);
- var structEndNode2 = determineEndpoint([edgeNodes[0].id, structEndNode1.id], edgeNodes[0], endpointLocGetter2);
- var structureWay = resultWayIDs.map(function (id) {
- return graph.entity(id);
- }).find(function (way) {
- return way.nodes.indexOf(structEndNode1.id) !== -1 && way.nodes.indexOf(structEndNode2.id) !== -1;
- });
- var tags = Object.assign({}, structureWay.tags); // copy tags
+ 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 || '');
+ };
+ }
- if (bridgeOrTunnel === 'bridge') {
- tags.bridge = 'yes';
- tags.layer = '1';
- } else {
- var tunnelValue = 'yes';
+ function reset(selection) {
+ selection.style('stroke-opacity', null).style('stroke-width', null).style('fill-opacity', null).style('r', null);
+ }
- if (getFeatureType(structureWay, graph) === 'waterway') {
- // use `tunnel=culvert` for waterways by default
- tunnelValue = 'culvert';
- }
+ 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');
+ });
+ }
- tags.tunnel = tunnelValue;
- tags.layer = '-1';
- } // apply the structure tags to the way
+ 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..
- graph = actionChangeTags(structureWay.id, tags)(graph);
- return graph;
- };
- context.perform(action, _t('issues.fix.' + fixTitleID + '.annotation'));
- context.enter(modeSelect(context, resultWayIDs));
- }
+ 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 makeConnectWaysFix(connectionTags) {
- var fixTitleID = 'connect_features';
-
- if (connectionTags.ford) {
- fixTitleID = 'connect_using_ford';
- }
+ function run(surface, fromTo) {
+ var toFrom = fromTo === 'from' ? 'to' : 'from';
+ var currSelected = surface.selectAll(selector);
+ var currClassed = surface.attr('class');
- return new validationIssueFix({
- icon: 'iD-icon-crossing',
- title: _t.html('issues.fix.' + fixTitleID + '.title'),
- onClick: function onClick(context) {
- var loc = this.issue.loc;
- var connectionTags = this.issue.data.connectionTags;
- var edges = this.issue.data.edges;
- context.perform(function actionConnectCrossingWays(graph) {
- // create the new node for the points
- var node = osmNode({
- loc: loc,
- tags: connectionTags
- });
- graph = graph.replace(node);
- var nodesToMerge = [node.id];
- var mergeThresholdInMeters = 0.75;
- edges.forEach(function (edge) {
- var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])];
- var nearby = geoSphericalClosestNode(edgeNodes, loc); // if there is already a suitable node nearby, use that
- // use the node if node has no interesting tags or if it is a crossing node #8326
+ if (_done || currSelected.empty()) {
+ _selected.call(reset);
- if ((!nearby.node.hasInterestingTags() || nearby.node.isCrossing()) && nearby.distance < mergeThresholdInMeters) {
- nodesToMerge.push(nearby.node.id); // else add the new node to the way
- } else {
- graph = actionAddMidpoint({
- loc: loc,
- edge: edge
- }, node)(graph);
- }
- });
+ _selected = select(null);
+ return;
+ }
- if (nodesToMerge.length > 1) {
- // if we're using nearby nodes, merge them with the new node
- graph = actionMergeNodes(nodesToMerge, loc)(graph);
- }
+ if (!fastDeepEqual(currSelected.data(), _selected.data()) || currClassed !== _classed) {
+ _selected.call(reset);
- return graph;
- }, _t('issues.fix.connect_crossing_features.annotation'));
- }
- });
- }
+ _classed = currClassed;
+ _selected = currSelected.call(calcAnimationParams);
+ }
- function makeChangeLayerFix(higherOrLower) {
- return new validationIssueFix({
- icon: 'iD-icon-' + (higherOrLower === 'higher' ? 'up' : 'down'),
- title: _t.html('issues.fix.tag_this_as_' + higherOrLower + '.title'),
- onClick: function onClick(context) {
- var mode = context.mode();
- if (!mode || mode.id !== 'select') return;
- var selectedIDs = mode.selectedIDs();
- if (selectedIDs.length !== 1) return;
- var selectedID = selectedIDs[0];
- if (!this.issue.entityIds.some(function (entityId) {
- return entityId === selectedID;
- })) return;
- var entity = context.hasEntity(selectedID);
- if (!entity) return;
- var tags = Object.assign({}, entity.tags); // shallow copy
+ var didCallNextRun = false;
- var layer = tags.layer && Number(tags.layer);
+ _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
- if (layer && !isNaN(layer)) {
- if (higherOrLower === 'higher') {
- layer += 1;
- } else {
- layer -= 1;
- }
- } else {
- if (higherOrLower === 'higher') {
- layer = 1;
- } else {
- layer = -1;
- }
- }
- tags.layer = layer.toString();
- context.perform(actionChangeTags(entity.id, tags), _t('operations.change_tags.annotation'));
+ if (!select(this).classed('selected')) {
+ reset(select(this));
}
});
}
- validation.type = type;
- return validation;
- }
+ function behavior(surface) {
+ _done = false;
+ _timer = timer(function () {
+ // wait for elements to actually become selected
+ if (surface.selectAll(selector).empty()) {
+ return false;
+ }
- function behaviorDrawWay(context, wayID, mode, startGraph) {
- var keybinding = utilKeybinding('drawWay');
- var dispatch = dispatch$8('rejectedSelfIntersection');
- var behavior = behaviorDraw(context); // Must be set by `drawWay.nodeIndex` before each install of this behavior.
+ surface.call(run, 'from');
- var _nodeIndex;
+ _timer.stop();
- var _origWay;
+ return true;
+ }, 20);
+ }
- var _wayGeometry;
+ behavior.restartIfNeeded = function (surface) {
+ if (_selected.empty()) {
+ surface.call(run, 'from');
- var _headNodeID;
+ if (_timer) {
+ _timer.stop();
+ }
+ }
+ };
- var _annotation;
+ behavior.off = function () {
+ _done = true;
- var _pointerHasMoved = false; // The osmNode to be placed.
- // This is temporary and just follows the mouse cursor until an "add" event occurs.
+ if (_timer) {
+ _timer.stop();
+ }
- var _drawNode;
+ _selected.interrupt().call(reset);
+ };
- var _didResolveTempEdit = false;
+ return behavior;
+ }
- function createDrawNode(loc) {
- // don't make the draw node until we actually need it
- _drawNode = osmNode({
- loc: loc
- });
- context.pauseChangeDispatch();
- context.replace(function actionAddDrawNode(graph) {
- // add the draw node to the graph and insert it into the way
- var way = graph.entity(wayID);
- return graph.replace(_drawNode).replace(way.addNode(_drawNode.id, _nodeIndex));
- }, _annotation);
- context.resumeChangeDispatch();
- setActiveElements();
- }
+ /* Creates a keybinding behavior for an operation */
+ function behaviorOperation(context) {
+ var _operation;
- function removeDrawNode() {
- context.pauseChangeDispatch();
- context.replace(function actionDeleteDrawNode(graph) {
- var way = graph.entity(wayID);
- return graph.replace(way.removeNode(_drawNode.id)).remove(_drawNode);
- }, _annotation);
- _drawNode = undefined;
- context.resumeChangeDispatch();
- }
+ 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 keydown(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope')) {
- context.surface().classed('nope-suppressed', true);
- }
+ var disabled = _operation.disabled();
- context.surface().classed('nope', false).classed('nope-disabled', true);
+ 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);
+
+ _operation();
}
}
- function keyup(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope-suppressed')) {
- context.surface().classed('nope', true);
- }
-
- context.surface().classed('nope-suppressed', false).classed('nope-disabled', false);
+ function behavior() {
+ if (_operation && _operation.available()) {
+ context.keybinding().on(_operation.keys, keypress);
}
+
+ return behavior;
}
- 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()`
+ behavior.off = function () {
+ context.keybinding().off(_operation.keys);
+ };
+ behavior.which = function (_) {
+ if (!arguments.length) return _operation;
+ _operation = _;
+ return behavior;
+ };
- function move(d3_event, datum) {
- var loc = context.map().mouseCoordinates();
- if (!_drawNode) createDrawNode(loc);
- context.surface().classed('nope-disabled', d3_event.altKey);
- var targetLoc = datum && datum.properties && datum.properties.entity && allowsVertex(datum.properties.entity) && datum.properties.entity.loc;
- var targetNodes = datum && datum.properties && datum.properties.nodes;
+ return behavior;
+ }
- if (targetLoc) {
- // snap to node/vertex - a point target with `.loc`
- loc = targetLoc;
- } else if (targetNodes) {
- // snap to way - a line target with `.nodes`
- var choice = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, _drawNode.id);
+ function operationCircularize(context, selectedIDs) {
+ var _extent;
- if (choice) {
- loc = choice.loc;
- }
- }
+ var _actions = selectedIDs.map(getAction).filter(Boolean);
- context.replace(actionMoveNode(_drawNode.id, loc), _annotation);
- _drawNode = context.entity(_drawNode.id);
- checkGeometry(true
- /* includeDrawNode */
- );
- } // Check whether this edit causes the geometry to break.
- // If so, class the surface with a nope cursor.
- // `includeDrawNode` - Only check the relevant line segments if finishing drawing
+ var _amount = _actions.length === 1 ? 'single' : 'multiple';
+ var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
+ return n.loc;
+ });
- function checkGeometry(includeDrawNode) {
- var nopeDisabled = context.surface().classed('nope-disabled');
- var isInvalid = isInvalidGeometry(includeDrawNode);
+ function getAction(entityID) {
+ var entity = context.entity(entityID);
+ if (entity.type !== 'way' || new Set(entity.nodes).size <= 1) return null;
- if (nopeDisabled) {
- context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
+ if (!_extent) {
+ _extent = entity.extent(context.graph());
} else {
- context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
+ _extent = _extent.extend(entity.extent(context.graph()));
}
- }
- function isInvalidGeometry(includeDrawNode) {
- var testNode = _drawNode; // we only need to test the single way we're drawing
+ return actionCircularize(entityID, context.projection);
+ }
- var parentWay = context.graph().entity(wayID);
- var nodes = context.graph().childNodes(parentWay).slice(); // shallow copy
+ var operation = function operation() {
+ if (!_actions.length) return;
- if (includeDrawNode) {
- if (parentWay.isClosed()) {
- // don't test the last segment for closed ways - #4655
- // (still test the first segment)
- nodes.pop();
- }
- } else {
- // discount the draw node
- if (parentWay.isClosed()) {
- if (nodes.length < 3) return false;
- if (_drawNode) nodes.splice(-2, 1);
- testNode = nodes[nodes.length - 2];
- } else {
- // there's nothing we need to test if we ignore the draw node on open ways
- return false;
- }
- }
+ var combinedAction = function combinedAction(graph, t) {
+ _actions.forEach(function (action) {
+ if (!action.disabled(graph)) {
+ graph = action(graph, t);
+ }
+ });
- return testNode && geoHasSelfIntersections(nodes, testNode.id);
- }
+ return graph;
+ };
- function undone() {
- // undoing removed the temp edit
- _didResolveTempEdit = true;
- context.pauseChangeDispatch();
- var nextMode;
+ combinedAction.transitionable = true;
+ context.perform(combinedAction, operation.annotation());
+ window.setTimeout(function () {
+ context.validator().validate();
+ }, 300); // after any transition
+ };
- if (context.graph() === startGraph) {
- // We've undone back to the initial state before we started drawing.
- // Just exit the draw mode without undoing whatever we did before
- // we entered the draw mode.
- nextMode = modeSelect(context, [wayID]);
- } else {
- // The `undo` only removed the temporary edit, so here we have to
- // manually undo to actually remove the last node we added. We can't
- // use the `undo` function since the initial "add" graph doesn't have
- // an annotation and so cannot be undone to.
- context.pop(1); // continue drawing
+ operation.available = function () {
+ return _actions.length && selectedIDs.length === _actions.length;
+ }; // don't cache this because the visible extent could change
- nextMode = mode;
- } // clear the redo stack by adding and removing a blank edit
+ operation.disabled = function () {
+ if (!_actions.length) return '';
- context.perform(actionNoop());
- context.pop(1);
- context.resumeChangeDispatch();
- context.enter(nextMode);
- }
+ var actionDisableds = _actions.map(function (action) {
+ return action.disabled(context.graph());
+ }).filter(Boolean);
- function setActiveElements() {
- if (!_drawNode) return;
- context.surface().selectAll('.' + _drawNode.id).classed('active', true);
- }
+ if (actionDisableds.length === _actions.length) {
+ // none of the features can be circularized
+ if (new Set(actionDisableds).size > 1) {
+ return 'multiple_blockers';
+ }
- function resetToStartGraph() {
- while (context.graph() !== startGraph) {
- context.pop();
+ 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';
}
- }
- var drawWay = function drawWay(surface) {
- _drawNode = undefined;
- _didResolveTempEdit = false;
- _origWay = context.entity(wayID);
+ return false;
- if (typeof _nodeIndex === 'number') {
- _headNodeID = _origWay.nodes[_nodeIndex];
- } else if (_origWay.isClosed()) {
- _headNodeID = _origWay.nodes[_origWay.nodes.length - 2];
- } else {
- _headNodeID = _origWay.nodes[_origWay.nodes.length - 1];
- }
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
- _wayGeometry = _origWay.geometry(context.graph());
- _annotation = _t((_origWay.nodes.length === (_origWay.isClosed() ? 2 : 1) ? 'operations.start.annotation.' : 'operations.continue.annotation.') + _wayGeometry);
- _pointerHasMoved = false; // Push an annotated state for undo to return back to.
- // We must make sure to replace or remove it later.
+ if (osm) {
+ var missing = _coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
- context.pauseChangeDispatch();
- context.perform(actionNoop(), _annotation);
- context.resumeChangeDispatch();
- behavior.hover().initialNodeID(_headNodeID);
- behavior.on('move', function () {
- _pointerHasMoved = true;
- move.apply(this, arguments);
- }).on('down', function () {
- move.apply(this, arguments);
- }).on('downcancel', function () {
- if (_drawNode) removeDrawNode();
- }).on('click', drawWay.add).on('clickWay', drawWay.addWay).on('clickNode', drawWay.addNode).on('undo', context.undo).on('cancel', drawWay.cancel).on('finish', drawWay.finish);
- select(window).on('keydown.drawWay', keydown).on('keyup.drawWay', keyup);
- context.map().dblclickZoomEnable(false).on('drawn.draw', setActiveElements);
- setActiveElements();
- surface.call(behavior);
- context.history().on('undone.draw', undone);
- };
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
- drawWay.off = function (surface) {
- if (!_didResolveTempEdit) {
- // Drawing was interrupted unexpectedly.
- // This can happen if the user changes modes,
- // clicks geolocate button, a hashchange event occurs, etc.
- context.pauseChangeDispatch();
- resetToStartGraph();
- context.resumeChangeDispatch();
+ return false;
}
-
- _drawNode = undefined;
- _nodeIndex = undefined;
- context.map().on('drawn.draw', null);
- surface.call(behavior.off).selectAll('.active').classed('active', false);
- surface.classed('nope', false).classed('nope-suppressed', false).classed('nope-disabled', false);
- select(window).on('keydown.drawWay', null).on('keyup.drawWay', null);
- context.history().on('undone.draw', null);
};
- function attemptAdd(d, loc, doAdd) {
- if (_drawNode) {
- // move the node to the final loc in case move wasn't called
- // consistently (e.g. on touch devices)
- context.replace(actionMoveNode(_drawNode.id, loc), _annotation);
- _drawNode = context.entity(_drawNode.id);
- } else {
- createDrawNode(loc);
- }
-
- checkGeometry(true
- /* includeDrawNode */
- );
-
- if (d && d.properties && d.properties.nope || context.surface().classed('nope')) {
- if (!_pointerHasMoved) {
- // prevent the temporary draw node from appearing on touch devices
- removeDrawNode();
- }
-
- dispatch.call('rejectedSelfIntersection', this);
- return; // can't click here
- }
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.circularize.' + disable + '.' + _amount) : _t('operations.circularize.description.' + _amount);
+ };
- context.pauseChangeDispatch();
- doAdd(); // we just replaced the temporary edit with the real one
+ operation.annotation = function () {
+ return _t('operations.circularize.annotation.feature', {
+ n: _actions.length
+ });
+ };
- _didResolveTempEdit = true;
- context.resumeChangeDispatch();
- context.enter(mode);
- } // Accept the current position of the drawing node
+ 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
- drawWay.add = function (loc, d) {
- attemptAdd(d, loc, function () {// don't need to do anything extra
- });
- }; // Connect the way to an existing way
+ var uiCmd = function uiCmd(code) {
+ var detected = utilDetect();
+ if (detected.os === 'mac') {
+ return code;
+ }
- drawWay.addWay = function (loc, edge, d) {
- attemptAdd(d, loc, function () {
- context.replace(actionAddMidpoint({
- loc: loc,
- edge: edge
- }, _drawNode), _annotation);
- });
- }; // Connect the way to an existing node
+ if (detected.os === 'win') {
+ if (code === '⌘⇧Z') return 'Ctrl+Y';
+ }
+ var result = '',
+ replacements = {
+ '⌘': 'Ctrl',
+ '⇧': 'Shift',
+ '⌥': 'Alt',
+ '⌫': 'Backspace',
+ '⌦': 'Delete'
+ };
- drawWay.addNode = function (node, d) {
- // finish drawing if the mapper targets the prior node
- if (node.id === _headNodeID || // or the first node when drawing an area
- _origWay.isClosed() && node.id === _origWay.first()) {
- drawWay.finish();
- return;
+ 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];
}
+ }
- attemptAdd(d, node.loc, function () {
- context.replace(function actionReplaceDrawNode(graph) {
- // remove the temporary draw node and insert the existing node
- // at the same index
- graph = graph.replace(graph.entity(wayID).removeNode(_drawNode.id)).remove(_drawNode);
- return graph.replace(graph.entity(wayID).addNode(node.id, _nodeIndex));
- }, _annotation);
- });
+ return result;
+ }; // return a display-focused string for a given keyboard code
+
+ 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')
};
- /**
- * @param {(typeof osmWay)[]} ways
- * @returns {"line" | "area" | "generic"}
- */
+ 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());
- function getFeatureType(ways) {
- if (ways.every(function (way) {
- return way.isClosed();
- })) return 'area';
- if (ways.every(function (way) {
- return !way.isClosed();
- })) return 'line';
- return 'generic';
- }
- /** see PR #8671 */
+ var operation = function operation() {
+ var nextSelectedID;
+ var nextSelectedLoc;
+ 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.
- function followMode() {
- if (_didResolveTempEdit) return;
+ if (geometry === 'vertex') {
+ var nodes = parent.nodes;
+ var i = nodes.indexOf(id);
- try {
- // get the last 2 added nodes.
- // check if they are both part of only oneway (the same one)
- // check if the ways that they're part of are the same way
- // find index of the last two nodes, to determine the direction to travel around the existing way
- // add the next node to the way we are drawing
- // if we're drawing an area, the first node = last node.
- var isDrawingArea = _origWay.nodes[0] === _origWay.nodes.slice(-1)[0];
+ 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;
+ }
- var _origWay$nodes$slice = _origWay.nodes.slice(isDrawingArea ? -3 : -2),
- _origWay$nodes$slice2 = _slicedToArray(_origWay$nodes$slice, 2),
- secondLastNodeId = _origWay$nodes$slice2[0],
- lastNodeId = _origWay$nodes$slice2[1]; // Unlike startGraph, the full history graph may contain unsaved vertices to follow.
- // https://github.com/openstreetmap/iD/issues/8749
+ nextSelectedID = nodes[i];
+ nextSelectedLoc = context.entity(nextSelectedID).loc;
+ }
+ }
+ context.perform(action, operation.annotation());
+ context.validator().validate();
- var historyGraph = context.history().graph();
+ 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));
+ }
+ };
- if (!lastNodeId || !secondLastNodeId || !historyGraph.hasEntity(lastNodeId) || !historyGraph.hasEntity(secondLastNodeId)) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('operations.follow.error.needs_more_initial_nodes'))();
- return;
- } // If the way has looped over itself, follow some other way.
+ operation.available = function () {
+ return true;
+ };
+ 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';
+ }
- var lastNodesParents = historyGraph.parentWays(historyGraph.entity(lastNodeId)).filter(function (w) {
- return w.id !== wayID;
- });
- var secondLastNodesParents = historyGraph.parentWays(historyGraph.entity(secondLastNodeId)).filter(function (w) {
- return w.id !== wayID;
- });
- var featureType = getFeatureType(lastNodesParents);
+ return false;
- if (lastNodesParents.length !== 1 || secondLastNodesParents.length === 0) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t("operations.follow.error.intersection_of_multiple_ways.".concat(featureType)))();
- return;
- } // Check if the last node's parent is also the parent of the second last node.
- // The last node must only have one parent, but the second last node can have
- // multiple parents.
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
- if (!secondLastNodesParents.some(function (n) {
- return n.id === lastNodesParents[0].id;
- })) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t("operations.follow.error.intersection_of_different_ways.".concat(featureType)))();
- return;
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
}
- var way = lastNodesParents[0];
- var indexOfLast = way.nodes.indexOf(lastNodeId);
- var indexOfSecondLast = way.nodes.indexOf(secondLastNodeId); // for a closed way, the first/last node is the same so it appears twice in the array,
- // but indexOf always finds the first occurrence. This is only an issue when following a way
- // in descending order
-
- var isDescendingPastZero = indexOfLast === way.nodes.length - 2 && indexOfSecondLast === 0;
- var nextNodeIndex = indexOfLast + (indexOfLast > indexOfSecondLast && !isDescendingPastZero ? 1 : -1); // if we're following a closed way and we pass the first/last node, the next index will be -1
-
- if (nextNodeIndex === -1) nextNodeIndex = indexOfSecondLast === 1 ? way.nodes.length - 2 : 1;
- var nextNode = historyGraph.entity(way.nodes[nextNodeIndex]);
- drawWay.addNode(nextNode, {
- geometry: {
- type: 'Point',
- coordinates: nextNode.loc
- },
- id: nextNode.id,
- properties: {
- target: true,
- entity: nextNode
- }
- });
- } catch (ex) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('operations.follow.error.unknown'))();
+ return false;
}
- }
-
- keybinding.on(_t('operations.follow.key'), followMode);
- select(document).call(keybinding); // Finish the draw operation, removing the temporary edit.
- // If the way has enough nodes to be valid, it's selected.
- // Otherwise, delete everything and return to browse mode.
-
- drawWay.finish = function () {
- checkGeometry(false
- /* includeDrawNode */
- );
- if (context.surface().classed('nope')) {
- dispatch.call('rejectedSelfIntersection', this);
- return; // can't click here
+ function hasWikidataTag(id) {
+ var entity = context.entity(id);
+ return entity.tags.wikidata && entity.tags.wikidata.trim().length > 0;
}
- context.pauseChangeDispatch(); // remove the temporary edit
-
- context.pop(1);
- _didResolveTempEdit = true;
- context.resumeChangeDispatch();
- var way = context.hasEntity(wayID);
-
- if (!way || way.isDegenerate()) {
- drawWay.cancel();
- return;
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
}
- window.setTimeout(function () {
- context.map().dblclickZoomEnable(true);
- }, 1000);
- var isNewFeature = !mode.isContinuing;
- context.enter(modeSelect(context, [wayID]).newFeature(isNewFeature));
- }; // Cancel the draw operation, delete everything, and return to browse mode.
+ function protectedMember(id) {
+ var entity = context.entity(id);
+ if (entity.type !== 'way') return false;
+ var parents = context.graph().parentRelations(entity);
+ for (var i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+ var type = parent.tags.type;
+ var role = parent.memberById(id).role || 'outer';
- drawWay.cancel = function () {
- context.pauseChangeDispatch();
- resetToStartGraph();
- context.resumeChangeDispatch();
- window.setTimeout(function () {
- context.map().dblclickZoomEnable(true);
- }, 1000);
- context.surface().classed('nope', false).classed('nope-disabled', false).classed('nope-suppressed', false);
- context.enter(modeBrowse(context));
- };
+ if (type === 'route' || type === 'boundary' || type === 'multipolygon' && role === 'outer') {
+ return true;
+ }
+ }
- drawWay.nodeIndex = function (val) {
- if (!arguments.length) return _nodeIndex;
- _nodeIndex = val;
- return drawWay;
+ return false;
+ }
};
- drawWay.activeID = function () {
- if (!arguments.length) return _drawNode && _drawNode.id; // no assign
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.delete.' + disable + '.' + multi) : _t('operations.delete.description.' + multi);
+ };
- return drawWay;
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.delete.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.delete.annotation.feature', {
+ n: selectedIDs.length
+ });
};
- return utilRebind(drawWay, dispatch, 'on');
+ operation.id = 'delete';
+ operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦'), uiCmd('⌦')];
+ operation.title = _t('operations.delete.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
}
- function modeDrawLine(context, wayID, startGraph, button, affix, continuing) {
- var mode = {
- button: button,
- id: 'draw-line'
- };
- var behavior = behaviorDrawWay(context, wayID, mode, startGraph).on('rejectedSelfIntersection.modeDrawLine', function () {
- context.ui().flash.iconName('#iD-icon-no').label(_t('self_intersection.error.lines'))();
- });
- mode.wayID = wayID;
- mode.isContinuing = continuing;
+ function operationOrthogonalize(context, selectedIDs) {
+ var _extent;
- mode.enter = function () {
- behavior.nodeIndex(affix === 'prefix' ? 0 : undefined);
- context.install(behavior);
- };
+ var _type;
- mode.exit = function () {
- context.uninstall(behavior);
- };
+ var _actions = selectedIDs.map(chooseAction).filter(Boolean);
- mode.selectedIDs = function () {
- return [wayID];
- };
-
- mode.activeID = function () {
- return behavior && behavior.activeID() || [];
- };
+ var _amount = _actions.length === 1 ? 'single' : 'multiple';
- return mode;
- }
+ var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
+ return n.loc;
+ });
- function validationDisconnectedWay() {
- var type = 'disconnected_way';
+ function chooseAction(entityID) {
+ var entity = context.entity(entityID);
+ var geometry = entity.geometry(context.graph());
- function isTaggedAsHighway(entity) {
- return osmRoutableHighwayTagValues[entity.tags.highway];
- }
+ if (!_extent) {
+ _extent = entity.extent(context.graph());
+ } else {
+ _extent = _extent.extend(entity.extent(context.graph()));
+ } // square a line/area
- var validation = function checkDisconnectedWay(entity, graph) {
- var routingIslandWays = routingIslandForEntity(entity);
- if (!routingIslandWays) return [];
- return [new validationIssue({
- type: type,
- subtype: 'highway',
- severity: 'warning',
- message: function message(context) {
- var entity = this.entityIds.length && context.hasEntity(this.entityIds[0]);
- var label = entity && utilDisplayLabel(entity, context.graph());
- return _t.html('issues.disconnected_way.routable.message', {
- count: this.entityIds.length,
- highway: label
- });
- },
- reference: showReference,
- entityIds: Array.from(routingIslandWays).map(function (way) {
- return way.id;
- }),
- dynamicFixes: makeFixes
- })];
- function makeFixes(context) {
- var fixes = [];
- var singleEntity = this.entityIds.length === 1 && context.hasEntity(this.entityIds[0]);
+ 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);
- if (singleEntity) {
- if (singleEntity.type === 'way' && !singleEntity.isClosed()) {
- var textDirection = _mainLocalizer.textDirection();
- var startFix = makeContinueDrawingFixIfAllowed(textDirection, singleEntity.first(), 'start');
- if (startFix) fixes.push(startFix);
- var endFix = makeContinueDrawingFixIfAllowed(textDirection, singleEntity.last(), 'end');
- if (endFix) fixes.push(endFix);
- }
+ if (parents.length === 1) {
+ var way = parents[0];
- if (!fixes.length) {
- fixes.push(new validationIssueFix({
- title: _t.html('issues.fix.connect_feature.title')
- }));
+ if (way.nodes.indexOf(entityID) !== -1) {
+ return actionOrthogonalize(way.id, context.projection, entityID);
}
-
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.delete_feature.title'),
- entityIds: [singleEntity.id],
- onClick: function onClick(context) {
- var id = this.issue.entityIds[0];
- var operation = operationDelete(context, [id]);
-
- if (!operation.disabled()) {
- operation();
- }
- }
- }));
- } else {
- fixes.push(new validationIssueFix({
- title: _t.html('issues.fix.connect_features.title')
- }));
}
-
- return fixes;
- }
-
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.disconnected_way.routable.reference'));
}
- function routingIslandForEntity(entity) {
- var routingIsland = new Set(); // the interconnected routable features
+ return null;
+ }
- var waysToCheck = []; // the queue of remaining routable ways to traverse
+ var operation = function operation() {
+ if (!_actions.length) return;
- function queueParentWays(node) {
- graph.parentWays(node).forEach(function (parentWay) {
- if (!routingIsland.has(parentWay) && // only check each feature once
- isRoutableWay(parentWay, false)) {
- // only check routable features
- routingIsland.add(parentWay);
- waysToCheck.push(parentWay);
- }
- });
- }
+ var combinedAction = function combinedAction(graph, t) {
+ _actions.forEach(function (action) {
+ if (!action.disabled(graph)) {
+ graph = action(graph, t);
+ }
+ });
- if (entity.type === 'way' && isRoutableWay(entity, true)) {
- routingIsland.add(entity);
- waysToCheck.push(entity);
- } else if (entity.type === 'node' && isRoutableNode(entity)) {
- routingIsland.add(entity);
- queueParentWays(entity);
- } else {
- // this feature isn't routable, cannot be a routing island
- return null;
- }
+ return graph;
+ };
- while (waysToCheck.length) {
- var wayToCheck = waysToCheck.pop();
- var childNodes = graph.childNodes(wayToCheck);
+ combinedAction.transitionable = true;
+ context.perform(combinedAction, operation.annotation());
+ window.setTimeout(function () {
+ context.validator().validate();
+ }, 300); // after any transition
+ };
- for (var i in childNodes) {
- var vertex = childNodes[i];
+ operation.available = function () {
+ return _actions.length && selectedIDs.length === _actions.length;
+ }; // don't cache this because the visible extent could change
- if (isConnectedVertex(vertex)) {
- // found a link to the wider network, not a routing island
- return null;
- }
- if (isRoutableNode(vertex)) {
- routingIsland.add(vertex);
- }
+ operation.disabled = function () {
+ if (!_actions.length) return '';
- queueParentWays(vertex);
- }
- } // no network link found, this is a routing island, return its members
+ var actionDisableds = _actions.map(function (action) {
+ return action.disabled(context.graph());
+ }).filter(Boolean);
+ if (actionDisableds.length === _actions.length) {
+ // none of the features can be squared
+ if (new Set(actionDisableds).size > 1) {
+ return 'multiple_blockers';
+ }
- return routingIsland;
+ 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';
}
- function isConnectedVertex(vertex) {
- // assume ways overlapping unloaded tiles are connected to the wider road network - #5938
- var osm = services.osm;
- if (osm && !osm.isDataLoaded(vertex.loc)) return true; // entrances are considered connected
-
- if (vertex.tags.entrance && vertex.tags.entrance !== 'no') return true;
- if (vertex.tags.amenity === 'parking_entrance') return true;
- return false;
- }
+ return false;
- function isRoutableNode(node) {
- // treat elevators as distinct features in the highway network
- if (node.tags.highway === 'elevator') return true;
- return false;
- }
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
- function isRoutableWay(way, ignoreInnerWays) {
- if (isTaggedAsHighway(way) || way.tags.route === 'ferry') return true;
- return graph.parentRelations(way).some(function (parentRelation) {
- if (parentRelation.tags.type === 'route' && parentRelation.tags.route === 'ferry') return true;
- if (parentRelation.isMultipolygon() && isTaggedAsHighway(parentRelation) && (!ignoreInnerWays || parentRelation.memberById(way.id).role !== 'inner')) return true;
- return false;
- });
- }
+ if (osm) {
+ var missing = _coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
- function makeContinueDrawingFixIfAllowed(textDirection, vertexID, whichEnd) {
- var vertex = graph.hasEntity(vertexID);
- if (!vertex || vertex.tags.noexit === 'yes') return null;
- var useLeftContinue = whichEnd === 'start' && textDirection === 'ltr' || whichEnd === 'end' && textDirection === 'rtl';
- return new validationIssueFix({
- icon: 'iD-operation-continue' + (useLeftContinue ? '-left' : ''),
- title: _t.html('issues.fix.continue_from_' + whichEnd + '.title'),
- entityIds: [vertexID],
- onClick: function onClick(context) {
- var wayId = this.issue.entityIds[0];
- var way = context.hasEntity(wayId);
- var vertexId = this.entityIds[0];
- var vertex = context.hasEntity(vertexId);
- if (!way || !vertex) return; // make sure the vertex is actually visible and editable
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
- var map = context.map();
+ return false;
+ }
+ };
- if (!context.editable() || !map.trimmedExtent().contains(vertex.loc)) {
- map.zoomToEase(vertex);
- }
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.orthogonalize.' + disable + '.' + _amount) : _t('operations.orthogonalize.description.' + _type + '.' + _amount);
+ };
- context.enter(modeDrawLine(context, wayId, context.graph(), 'line', way.affix(vertexId), true));
- }
- });
- }
+ operation.annotation = function () {
+ return _t('operations.orthogonalize.annotation.' + _type, {
+ n: _actions.length
+ });
};
- validation.type = type;
- return validation;
+ operation.id = 'orthogonalize';
+ operation.keys = [_t('operations.orthogonalize.key')];
+ operation.title = _t('operations.orthogonalize.title');
+ operation.behavior = behaviorOperation(context).which(operation);
+ return operation;
}
- function validationFormatting() {
- var type = 'invalid_format';
-
- var validation = function validation(entity) {
- var issues = [];
+ 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());
- function isValidEmail(email) {
- // Emails in OSM are going to be official so they should be pretty simple
- // Using negated lists to better support all possible unicode characters (#6494)
- var valid_email = /^[^\(\)\\,":;<>@\[\]]+@[^\(\)\\,":;<>@\[\]\.]+(?:\.[a-z0-9-]+)*$/i; // An empty value is also acceptable
+ 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
+ };
- return !email || valid_email.test(email);
- }
- /*
- function isSchemePresent(url) {
- var valid_scheme = /^https?:\/\//i;
- return (!url || valid_scheme.test(url));
- }
- */
+ operation.available = function () {
+ return nodes.length >= 3;
+ }; // don't cache this because the visible extent could change
- function showReferenceEmail(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.invalid_format.email.reference'));
- }
- /*
- function showReferenceWebsite(selection) {
- selection.selectAll('.issue-reference')
- .data([0])
- .enter()
- .append('div')
- .attr('class', 'issue-reference')
- .html(t.html('issues.invalid_format.website.reference'));
- }
- if (entity.tags.website) {
- // Multiple websites are possible
- // If ever we support ES6, arrow functions make this nicer
- var websites = entity.tags.website
- .split(';')
- .map(function(s) { return s.trim(); })
- .filter(function(x) { return !isSchemePresent(x); });
- if (websites.length) {
- issues.push(new validationIssue({
- type: type,
- subtype: 'website',
- severity: 'warning',
- message: function(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? t.html('issues.invalid_format.website.message' + this.data,
- { feature: utilDisplayLabel(entity, context.graph()), site: websites.join(', ') }) : '';
- },
- reference: showReferenceWebsite,
- entityIds: [entity.id],
- hash: websites.join(),
- data: (websites.length > 1) ? '_multi' : ''
- }));
- }
+ 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;
- if (entity.tags.email) {
- // Multiple emails are possible
- var emails = entity.tags.email.split(';').map(function (s) {
- return s.trim();
- }).filter(function (x) {
- return !isValidEmail(x);
- });
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
- if (emails.length) {
- issues.push(new validationIssue({
- type: type,
- subtype: 'email',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.invalid_format.email.message' + this.data, {
- feature: utilDisplayLabel(entity, context.graph()),
- email: emails.join(', ')
- }) : '';
- },
- reference: showReferenceEmail,
- entityIds: [entity.id],
- hash: emails.join(),
- data: emails.length > 1 ? '_multi' : ''
- }));
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
+
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
}
+
+ return false;
}
- return issues;
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
+ }
};
- validation.type = type;
- return validation;
- }
-
- function validationHelpRequest(context) {
- var type = 'help_request';
-
- var validation = function checkFixmeTag(entity) {
- if (!entity.tags.fixme) return []; // don't flag fixmes on features added by the user
-
- if (entity.version === undefined) return [];
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.reflect.' + disable + '.' + multi) : _t('operations.reflect.description.' + axis + '.' + multi);
+ };
- if (entity.v !== undefined) {
- var baseEntity = context.history().base().hasEntity(entity.id); // don't flag fixmes added by the user on existing features
+ operation.annotation = function () {
+ return _t('operations.reflect.annotation.' + axis + '.feature', {
+ n: selectedIDs.length
+ });
+ };
- if (!baseEntity || !baseEntity.tags.fixme) return [];
- }
+ 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;
+ }
- return [new validationIssue({
- type: type,
- subtype: 'fixme_tag',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.fixme_tag.message', {
- feature: utilDisplayLabel(entity, context.graph(), true
- /* verbose */
- )
- }) : '';
- },
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- title: _t.html('issues.fix.address_the_concern.title')
- })];
- },
- reference: showReference,
- entityIds: [entity.id]
- })];
+ 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());
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.fixme_tag.reference'));
- }
+ var operation = function operation() {
+ context.enter(modeMove(context, selectedIDs));
};
- validation.type = type;
- return validation;
- }
+ operation.available = function () {
+ return selectedIDs.length > 0;
+ };
- function validationImpossibleOneway() {
- var type = 'impossible_oneway';
+ 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';
+ }
- var validation = function checkImpossibleOneway(entity, graph) {
- if (entity.type !== 'way' || entity.geometry(graph) !== 'line') return [];
- if (entity.isClosed()) return [];
- if (!typeForWay(entity)) return [];
- if (!isOneway(entity)) return [];
- var firstIssues = issuesForNode(entity, entity.first());
- var lastIssues = issuesForNode(entity, entity.last());
- return firstIssues.concat(lastIssues);
+ return false;
- function typeForWay(way) {
- if (way.geometry(graph) !== 'line') return null;
- if (osmRoutableHighwayTagValues[way.tags.highway]) return 'highway';
- if (osmFlowingWaterwayTagValues[way.tags.waterway]) return 'waterway';
- return null;
- }
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
- function isOneway(way) {
- if (way.tags.oneway === 'yes') return true;
- if (way.tags.oneway) return false;
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
- for (var key in way.tags) {
- if (osmOneWayTags[key] && osmOneWayTags[key][way.tags[key]]) {
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
return true;
}
}
return false;
}
- function nodeOccursMoreThanOnce(way, nodeID) {
- var occurrences = 0;
-
- for (var index in way.nodes) {
- if (way.nodes[index] === nodeID) {
- occurrences += 1;
- if (occurrences > 1) return true;
- }
- }
-
- return false;
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
}
+ };
- function isConnectedViaOtherTypes(way, node) {
- var wayType = typeForWay(way);
-
- if (wayType === 'highway') {
- // entrances are considered connected
- if (node.tags.entrance && node.tags.entrance !== 'no') return true;
- if (node.tags.amenity === 'parking_entrance') return true;
- } else if (wayType === 'waterway') {
- if (node.id === way.first()) {
- // multiple waterways may start at the same spring
- if (node.tags.natural === 'spring') return true;
- } else {
- // multiple waterways may end at the same drain
- if (node.tags.manhole === 'drain') return true;
- }
- }
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.move.' + disable + '.' + multi) : _t('operations.move.description.' + multi);
+ };
- return graph.parentWays(node).some(function (parentWay) {
- if (parentWay.id === way.id) return false;
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.move.annotation.feature', {
+ n: selectedIDs.length
+ });
+ };
- if (wayType === 'highway') {
- // allow connections to highway areas
- if (parentWay.geometry(graph) === 'area' && osmRoutableHighwayTagValues[parentWay.tags.highway]) return true; // count connections to ferry routes as connected
+ 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;
+ }
- if (parentWay.tags.route === 'ferry') return true;
- return graph.parentRelations(parentWay).some(function (parentRelation) {
- if (parentRelation.tags.type === 'route' && parentRelation.tags.route === 'ferry') return true; // allow connections to highway multipolygons
+ function modeRotate(context, entityIDs) {
+ var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeMove
- return parentRelation.isMultipolygon() && osmRoutableHighwayTagValues[parentRelation.tags.highway];
- });
- } else if (wayType === 'waterway') {
- // multiple waterways may start or end at a water body at the same node
- if (parentWay.tags.natural === 'water' || parentWay.tags.natural === 'coastline') return true;
- }
+ 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
+ });
- return false;
- });
- }
+ var _prevGraph;
- function issuesForNode(way, nodeID) {
- var isFirst = nodeID === way.first();
- var wayType = typeForWay(way); // ignore if this way is self-connected at this node
+ var _prevAngle;
- if (nodeOccursMoreThanOnce(way, nodeID)) return [];
- var osm = services.osm;
- if (!osm) return [];
- var node = graph.hasEntity(nodeID); // ignore if this node or its tile are unloaded
+ var _prevTransform;
- if (!node || !osm.isDataLoaded(node.loc)) return [];
- if (isConnectedViaOtherTypes(way, node)) return [];
- var attachedWaysOfSameType = graph.parentWays(node).filter(function (parentWay) {
- if (parentWay.id === way.id) return false;
- return typeForWay(parentWay) === wayType;
- }); // assume it's okay for waterways to start or end disconnected for now
+ var _pivot; // use pointer events on supported platforms; fallback to mouse events
- if (wayType === 'waterway' && attachedWaysOfSameType.length === 0) return [];
- var attachedOneways = attachedWaysOfSameType.filter(function (attachedWay) {
- return isOneway(attachedWay);
- }); // ignore if the way is connected to some non-oneway features
- if (attachedOneways.length < attachedWaysOfSameType.length) return [];
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
- if (attachedOneways.length) {
- var connectedEndpointsOkay = attachedOneways.some(function (attachedOneway) {
- if ((isFirst ? attachedOneway.first() : attachedOneway.last()) !== nodeID) return true;
- if (nodeOccursMoreThanOnce(attachedOneway, nodeID)) return true;
- return false;
- });
- if (connectedEndpointsOkay) return [];
- }
+ function doRotate(d3_event) {
+ var fn;
- var placement = isFirst ? 'start' : 'end',
- messageID = wayType + '.',
- referenceID = wayType + '.';
+ if (context.graph() !== _prevGraph) {
+ fn = context.perform;
+ } else {
+ fn = context.replace;
+ } // projection changed, recalculate _pivot
- if (wayType === 'waterway') {
- messageID += 'connected.' + placement;
- referenceID += 'connected';
- } else {
- messageID += placement;
- referenceID += placement;
- }
- return [new validationIssue({
- type: type,
- subtype: wayType,
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.impossible_oneway.' + messageID + '.message', {
- feature: utilDisplayLabel(entity, context.graph())
- }) : '';
- },
- reference: getReference(referenceID),
- entityIds: [way.id, node.id],
- dynamicFixes: function dynamicFixes() {
- var fixes = [];
+ var projection = context.projection;
+ var currTransform = projection.transform();
- if (attachedOneways.length) {
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-reverse',
- title: _t.html('issues.fix.reverse_feature.title'),
- entityIds: [way.id],
- onClick: function onClick(context) {
- var id = this.issue.entityIds[0];
- context.perform(actionReverse(id), _t('operations.reverse.annotation.line', {
- n: 1
- }));
- }
- }));
- }
+ 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;
+ }
- if (node.tags.noexit !== 'yes') {
- var textDirection = _mainLocalizer.textDirection();
- var useLeftContinue = isFirst && textDirection === 'ltr' || !isFirst && textDirection === 'rtl';
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-continue' + (useLeftContinue ? '-left' : ''),
- title: _t.html('issues.fix.continue_from_' + (isFirst ? 'start' : 'end') + '.title'),
- onClick: function onClick(context) {
- var entityID = this.issue.entityIds[0];
- var vertexID = this.issue.entityIds[1];
- var way = context.entity(entityID);
- var vertex = context.entity(vertexID);
- continueDrawing(way, vertex, context);
- }
- }));
- }
+ 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();
+ }
- return fixes;
- },
- loc: node.loc
- })];
+ function getPivot(points) {
+ var _pivot;
- function getReference(referenceID) {
- return function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.impossible_oneway.' + referenceID + '.reference'));
- };
+ 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 (polygonHull.length === 2) {
+ _pivot = geoVecInterp(points[0], points[1], 0.5);
+ } else {
+ _pivot = d3_polygonCentroid(d3_polygonHull(points));
}
}
- };
- function continueDrawing(way, vertex, context) {
- // make sure the vertex is actually visible and editable
- var map = context.map();
+ return _pivot;
+ }
- if (!context.editable() || !map.trimmedExtent().contains(vertex.loc)) {
- map.zoomToEase(vertex);
- }
+ function finish(d3_event) {
+ d3_event.stopPropagation();
+ context.replace(actionNoop(), annotation);
+ context.enter(modeSelect(context, entityIDs));
+ }
- context.enter(modeDrawLine(context, way.id, context.graph(), 'line', way.affix(vertex.id), true));
+ function cancel() {
+ if (_prevGraph) context.pop(); // remove the rotate
+
+ context.enter(modeSelect(context, entityIDs));
}
- validation.type = type;
- return validation;
- }
+ function undone() {
+ context.enter(modeBrowse(context));
+ }
- function validationIncompatibleSource() {
- var type = 'incompatible_source';
- var incompatibleRules = [{
- id: 'amap',
- regex: /(amap|autonavi|mapabc|高德)/i
- }, {
- id: 'baidu',
- regex: /(baidu|mapbar|百度)/i
- }, {
- id: 'google',
- regex: /google/i,
- exceptRegex: /((books|drive)\.google|google\s?(books|drive|plus))/i
- }];
+ 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);
+ };
- var validation = function checkIncompatibleSource(entity) {
- var entitySources = entity.tags && entity.tags.source && entity.tags.source.split(';');
- if (!entitySources) return [];
- var entityID = entity.id;
- return entitySources.map(function (source) {
- var matchRule = incompatibleRules.find(function (rule) {
- if (!rule.regex.test(source)) return false;
- if (rule.exceptRegex && rule.exceptRegex.test(source)) return false;
- return true;
- });
- if (!matchRule) return null;
- return new validationIssue({
- type: type,
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(entityID);
- return entity ? _t.html('issues.incompatible_source.feature.message', {
- feature: utilDisplayLabel(entity, context.graph(), true
- /* verbose */
- ),
- value: source
- }) : '';
- },
- reference: getReference(matchRule.id),
- entityIds: [entityID],
- hash: source,
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- title: _t.html('issues.fix.remove_proprietary_data.title')
- })];
- }
- });
- }).filter(Boolean);
+ 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([]);
+ };
- function getReference(id) {
- return function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html("issues.incompatible_source.reference.".concat(id)));
- };
- }
+ mode.selectedIDs = function () {
+ if (!arguments.length) return entityIDs; // no assign
+
+ return mode;
};
- validation.type = type;
- return validation;
+ return mode;
}
- function validationMaprules() {
- var type = 'maprules';
+ 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 validation = function checkMaprules(entity, graph) {
- if (!services.maprules) return [];
- var rules = services.maprules.validationRules();
- var issues = [];
+ var operation = function operation() {
+ context.enter(modeRotate(context, selectedIDs));
+ };
- for (var i = 0; i < rules.length; i++) {
- var rule = rules[i];
- rule.findIssues(entity, graph, issues);
+ operation.available = function () {
+ return nodes.length >= 2;
+ };
+
+ 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 issues;
- };
+ return false;
- validation.type = type;
- return validation;
- }
+ function someMissing() {
+ if (context.inIntro()) return false;
+ var osm = context.connection();
- function validationMismatchedGeometry() {
- var type = 'mismatched_geometry';
+ if (osm) {
+ var missing = coords.filter(function (loc) {
+ return !osm.isDataLoaded(loc);
+ });
- function tagSuggestingLineIsArea(entity) {
- if (entity.type !== 'way' || entity.isClosed()) return null;
- var tagSuggestingArea = entity.tagSuggestingArea();
+ if (missing.length) {
+ missing.forEach(function (loc) {
+ context.loadTileAtLoc(loc);
+ });
+ return true;
+ }
+ }
- if (!tagSuggestingArea) {
- return null;
+ return false;
}
- var asLine = _mainPresetIndex.matchTags(tagSuggestingArea, 'line');
- var asArea = _mainPresetIndex.matchTags(tagSuggestingArea, 'area');
-
- if (asLine && asArea && asLine === asArea) {
- // these tags also allow lines and making this an area wouldn't matter
- return null;
+ function incompleteRelation(id) {
+ var entity = context.entity(id);
+ return entity.type === 'relation' && !entity.isComplete(context.graph());
}
+ };
- return tagSuggestingArea;
- }
+ operation.tooltip = function () {
+ var disable = operation.disabled();
+ return disable ? _t('operations.rotate.' + disable + '.' + multi) : _t('operations.rotate.description.' + multi);
+ };
- function makeConnectEndpointsFixOnClick(way, graph) {
- // must have at least three nodes to close this automatically
- if (way.nodes.length < 3) return null;
- var nodes = graph.childNodes(way),
- testNodes;
- var firstToLastDistanceMeters = geoSphericalDistance(nodes[0].loc, nodes[nodes.length - 1].loc); // if the distance is very small, attempt to merge the endpoints
+ operation.annotation = function () {
+ return selectedIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.rotate.annotation.feature', {
+ n: selectedIDs.length
+ });
+ };
- if (firstToLastDistanceMeters < 0.75) {
- testNodes = nodes.slice(); // shallow copy
+ 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;
+ }
- testNodes.pop();
- testNodes.push(testNodes[0]); // make sure this will not create a self-intersection
+ function modeMove(context, entityIDs, baseGraph) {
+ var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeRotate
- if (!geoHasSelfIntersections(testNodes, testNodes[0].id)) {
- return function (context) {
- var way = context.entity(this.issue.entityIds[0]);
- context.perform(actionMergeNodes([way.nodes[0], way.nodes[way.nodes.length - 1]], nodes[0].loc), _t('issues.fix.connect_endpoints.annotation'));
- };
- }
- } // if the points were not merged, attempt to close the way
+ 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
+ });
+ var _prevGraph;
- testNodes = nodes.slice(); // shallow copy
+ var _cache;
- testNodes.push(testNodes[0]); // make sure this will not create a self-intersection
+ var _origin;
- if (!geoHasSelfIntersections(testNodes, testNodes[0].id)) {
- return function (context) {
- var wayId = this.issue.entityIds[0];
- var way = context.entity(wayId);
- var nodeId = way.nodes[0];
- var index = way.nodes.length;
- context.perform(actionAddVertex(wayId, nodeId, index), _t('issues.fix.connect_endpoints.annotation'));
- };
- }
- }
+ var _nudgeInterval; // use pointer events on supported platforms; fallback to mouse events
- function lineTaggedAsAreaIssue(entity) {
- var tagSuggestingArea = tagSuggestingLineIsArea(entity);
- if (!tagSuggestingArea) return null;
- return new validationIssue({
- type: type,
- subtype: 'area_as_line',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.tag_suggests_area.message', {
- feature: utilDisplayLabel(entity, 'area', true
- /* verbose */
- ),
- tag: utilTagText({
- tags: tagSuggestingArea
- })
- }) : '';
- },
- reference: showReference,
- entityIds: [entity.id],
- hash: JSON.stringify(tagSuggestingArea),
- dynamicFixes: function dynamicFixes(context) {
- var fixes = [];
- var entity = context.entity(this.entityIds[0]);
- var connectEndsOnClick = makeConnectEndpointsFixOnClick(entity, context.graph());
- fixes.push(new validationIssueFix({
- title: _t.html('issues.fix.connect_endpoints.title'),
- onClick: connectEndsOnClick
- }));
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.remove_tag.title'),
- onClick: function onClick(context) {
- var entityId = this.issue.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
- for (var key in tagSuggestingArea) {
- delete tags[key];
- }
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
- context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_tag.annotation'));
- }
- }));
- return fixes;
- }
- });
+ function doMove(nudge) {
+ nudge = nudge || [0, 0];
+ var fn;
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.tag_suggests_area.reference'));
+ if (_prevGraph !== context.graph()) {
+ _cache = {};
+ _origin = context.map().mouseCoordinates();
+ fn = context.perform;
+ } else {
+ fn = context.overwrite;
}
+
+ 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 vertexPointIssue(entity, graph) {
- // we only care about nodes
- if (entity.type !== 'node') return null; // ignore tagless points
+ function startNudge(nudge) {
+ if (_nudgeInterval) window.clearInterval(_nudgeInterval);
+ _nudgeInterval = window.setInterval(function () {
+ context.map().pan(nudge);
+ doMove(nudge);
+ }, 50);
+ }
- if (Object.keys(entity.tags).length === 0) return null; // address lines are special so just ignore them
+ function stopNudge() {
+ if (_nudgeInterval) {
+ window.clearInterval(_nudgeInterval);
+ _nudgeInterval = null;
+ }
+ }
- if (entity.isOnAddressLine(graph)) return null;
- var geometry = entity.geometry(graph);
- var allowedGeometries = osmNodeGeometriesForTags(entity.tags);
+ function move() {
+ doMove();
+ var nudge = geoViewportEdge(context.map().mouse(), context.map().dimensions());
- if (geometry === 'point' && !allowedGeometries.point && allowedGeometries.vertex) {
- return new validationIssue({
- type: type,
- subtype: 'vertex_as_point',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.vertex_as_point.message', {
- feature: utilDisplayLabel(entity, 'vertex', true
- /* verbose */
- )
- }) : '';
- },
- reference: function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.vertex_as_point.reference'));
- },
- entityIds: [entity.id]
- });
- } else if (geometry === 'vertex' && !allowedGeometries.vertex && allowedGeometries.point) {
- return new validationIssue({
- type: type,
- subtype: 'point_as_vertex',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.point_as_vertex.message', {
- feature: utilDisplayLabel(entity, 'point', true
- /* verbose */
- )
- }) : '';
- },
- reference: function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.point_as_vertex.reference'));
- },
- entityIds: [entity.id],
- dynamicFixes: extractPointDynamicFixes
- });
+ if (nudge) {
+ startNudge(nudge);
+ } else {
+ stopNudge();
}
+ }
- return null;
+ function finish(d3_event) {
+ d3_event.stopPropagation();
+ context.replace(actionNoop(), annotation);
+ context.enter(modeSelect(context, entityIDs));
+ stopNudge();
}
- function otherMismatchIssue(entity, graph) {
- // ignore boring features
- if (!entity.hasInterestingTags()) return null;
- if (entity.type !== 'node' && entity.type !== 'way') return null; // address lines are special so just ignore them
+ function cancel() {
+ if (baseGraph) {
+ while (context.graph() !== baseGraph) {
+ context.pop();
+ } // reset to baseGraph
- if (entity.type === 'node' && entity.isOnAddressLine(graph)) return null;
- var sourceGeom = entity.geometry(graph);
- var targetGeoms = entity.type === 'way' ? ['point', 'vertex'] : ['line', 'area'];
- if (sourceGeom === 'area') targetGeoms.unshift('line');
- var asSource = _mainPresetIndex.match(entity, graph);
- var targetGeom = targetGeoms.find(function (nodeGeom) {
- var asTarget = _mainPresetIndex.matchTags(entity.tags, nodeGeom);
- if (!asSource || !asTarget || asSource === asTarget || // sometimes there are two presets with the same tags for different geometries
- fastDeepEqual(asSource.tags, asTarget.tags)) return false;
- if (asTarget.isFallback()) return false;
- var primaryKey = Object.keys(asTarget.tags)[0]; // special case: buildings-as-points are discouraged by iD, but common in OSM, so ignore them
- if (primaryKey === 'building') return false;
- if (asTarget.tags[primaryKey] === '*') return false;
- return asSource.isFallback() || asSource.tags[primaryKey] === '*';
- });
- if (!targetGeom) return null;
- var subtype = targetGeom + '_as_' + sourceGeom;
- if (targetGeom === 'vertex') targetGeom = 'point';
- if (sourceGeom === 'vertex') sourceGeom = 'point';
- var referenceId = targetGeom + '_as_' + sourceGeom;
- var dynamicFixes;
+ context.enter(modeBrowse(context));
+ } else {
+ if (_prevGraph) context.pop(); // remove the move
- if (targetGeom === 'point') {
- dynamicFixes = extractPointDynamicFixes;
- } else if (sourceGeom === 'area' && targetGeom === 'line') {
- dynamicFixes = lineToAreaDynamicFixes;
+ context.enter(modeSelect(context, entityIDs));
}
- return new validationIssue({
- type: type,
- subtype: subtype,
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.' + referenceId + '.message', {
- feature: utilDisplayLabel(entity, targetGeom, true
- /* verbose */
- )
- }) : '';
- },
- reference: function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.mismatched_geometry.reference'));
- },
- entityIds: [entity.id],
- dynamicFixes: dynamicFixes
- });
+ stopNudge();
}
- function lineToAreaDynamicFixes(context) {
- var convertOnClick;
- var entityId = this.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ function undone() {
+ context.enter(modeBrowse(context));
+ }
- delete tags.area;
+ 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);
+ };
- if (!osmTagSuggestingArea(tags)) {
- // if removing the area tag would make this a line, offer that as a quick fix
- convertOnClick = function convertOnClick(context) {
- var entityId = this.issue.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ 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 (tags.area) {
- delete tags.area;
- }
+ mode.selectedIDs = function () {
+ if (!arguments.length) return entityIDs; // no assign
- context.perform(actionChangeTags(entityId, tags), _t('issues.fix.convert_to_line.annotation'));
- };
- }
+ return mode;
+ };
- return [new validationIssueFix({
- icon: 'iD-icon-line',
- title: _t.html('issues.fix.convert_to_line.title'),
- onClick: convertOnClick
- })];
- }
+ return mode;
+ }
- function extractPointDynamicFixes(context) {
- var entityId = this.entityIds[0];
- var extractOnClick = null;
+ 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);
+ });
- if (!context.hasHiddenConnections(entityId)) {
- extractOnClick = function extractOnClick(context) {
- var entityId = this.issue.entityIds[0];
- var action = actionExtract(entityId, context.projection);
- context.perform(action, _t('operations.extract.annotation', {
- n: 1
- })); // re-enter mode to trigger updates
+ for (var id in copies) {
+ var oldEntity = oldGraph.entity(id);
+ var newEntity = copies[id];
- context.enter(modeSelect(context, [action.getExtractedNodeID()]));
- };
- }
+ extent._extend(oldEntity.extent(oldGraph)); // Exclude child nodes from newIDs if their parent way was also copied.
- return [new validationIssueFix({
- icon: 'iD-operation-extract',
- title: _t.html('issues.fix.extract_point.title'),
- onClick: extractOnClick
- })];
- }
- function unclosedMultipolygonPartIssues(entity, graph) {
- if (entity.type !== 'relation' || !entity.isMultipolygon() || entity.isDegenerate() || // cannot determine issues for incompletely-downloaded relations
- !entity.isComplete(graph)) return [];
- var sequences = osmJoinWays(entity.members, graph);
- var issues = [];
+ var parents = context.graph().parentWays(newEntity);
+ var parentCopied = parents.some(function (parent) {
+ return originals.has(parent.id);
+ });
- for (var i in sequences) {
- var sequence = sequences[i];
- if (!sequence.nodes) continue;
- var firstNode = sequence.nodes[0];
- var lastNode = sequence.nodes[sequence.nodes.length - 1]; // part is closed if the first and last nodes are the same
+ if (!parentCopied) {
+ newIDs.push(newEntity.id);
+ }
+ } // Put pasted objects where mouse pointer is..
- if (firstNode === lastNode) continue;
- var issue = new validationIssue({
- type: type,
- subtype: 'unclosed_multipolygon_part',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.unclosed_multipolygon_part.message', {
- feature: utilDisplayLabel(entity, context.graph(), true
- /* verbose */
- )
- }) : '';
- },
- reference: showReference,
- loc: sequence.nodes[0].loc,
- entityIds: [entity.id],
- hash: sequence.map(function (way) {
- return way.id;
- }).join()
- });
- issues.push(issue);
- }
- return issues;
+ 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 showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.unclosed_multipolygon_part.reference'));
- }
+ function behavior() {
+ context.keybinding().on(uiCmd('⌘V'), doPaste);
+ return behavior;
}
- var validation = function checkMismatchedGeometry(entity, graph) {
- var vertexPoint = vertexPointIssue(entity, graph);
- if (vertexPoint) return [vertexPoint];
- var lineAsArea = lineTaggedAsAreaIssue(entity);
- if (lineAsArea) return [lineAsArea];
- var mismatch = otherMismatchIssue(entity, graph);
- if (mismatch) return [mismatch];
- return unclosedMultipolygonPartIssues(entity, graph);
+ behavior.off = function () {
+ context.keybinding().off(uiCmd('⌘V'));
};
- validation.type = type;
- return validation;
+ return behavior;
}
- function validationMissingRole() {
- var type = 'missing_role';
+ /*
+ `behaviorDrag` is like `d3_behavior.drag`, with the following differences:
- var validation = function checkMissingRole(entity, graph) {
- var issues = [];
+ * 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.
+ */
- if (entity.type === 'way') {
- graph.parentRelations(entity).forEach(function (relation) {
- if (!relation.isMultipolygon()) return;
- var member = relation.memberById(entity.id);
+ function behaviorDrag() {
+ var dispatch = dispatch$8('start', 'move', 'end'); // see also behaviorSelect
- if (member && isMissingRole(member)) {
- issues.push(makeIssue(entity, relation, member));
- }
- });
- } else if (entity.type === 'relation' && entity.isMultipolygon()) {
- entity.indexedMembers().forEach(function (member) {
- var way = graph.hasEntity(member.id);
+ var _tolerancePx = 1; // keep this low to facilitate pixel-perfect micromapping
- if (way && isMissingRole(member)) {
- issues.push(makeIssue(way, entity, member));
- }
- });
- }
+ var _penTolerancePx = 4; // styluses can be touchy so require greater movement - #1981
- return issues;
+ var _origin = null;
+ var _selector = '';
+
+ var _targetNode;
+
+ var _targetEntity;
+
+ var _surface;
+
+ var _pointerId; // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect');
+
+ 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 isMissingRole(member) {
- return !member.role || !member.role.trim().length;
- }
+ function pointerdown(d3_event) {
+ if (_pointerId) return;
+ _pointerId = d3_event.pointerId || 'mouse';
+ _targetNode = this; // only force reflow once per drag
- function makeIssue(way, relation, member) {
- return new validationIssue({
- type: type,
- severity: 'warning',
- message: function message(context) {
- var member = context.hasEntity(this.entityIds[1]),
- relation = context.hasEntity(this.entityIds[0]);
- return member && relation ? _t.html('issues.missing_role.message', {
- member: utilDisplayLabel(member, context.graph()),
- relation: utilDisplayLabel(relation, context.graph())
- }) : '';
- },
- reference: showReference,
- entityIds: [relation.id, way.id],
- data: {
- member: member
- },
- hash: member.index.toString(),
- dynamicFixes: function dynamicFixes() {
- return [makeAddRoleFix('inner'), makeAddRoleFix('outer'), new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.remove_from_relation.title'),
- onClick: function onClick(context) {
- context.perform(actionDeleteMember(this.issue.entityIds[0], this.issue.data.member.index), _t('operations.delete_member.annotation', {
- n: 1
- }));
- }
- })];
- }
- });
+ 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);
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.missing_role.multipolygon.reference'));
+ if (_origin) {
+ offset = _origin.call(_targetNode, _targetEntity);
+ offset = [offset[0] - startOrigin[0], offset[1] - startOrigin[1]];
+ } else {
+ offset = [0, 0];
}
- }
-
- function makeAddRoleFix(role) {
- return new validationIssueFix({
- title: _t.html('issues.fix.set_as_' + role + '.title'),
- onClick: function onClick(context) {
- var oldMember = this.issue.data.member;
- var member = {
- id: this.issue.entityIds[1],
- type: oldMember.type,
- role: role
- };
- context.perform(actionChangeMember(this.issue.entityIds[0], member, oldMember.index), _t('operations.change_role.annotation', {
- n: 1
- }));
- }
- });
- }
- validation.type = type;
- return validation;
- }
+ d3_event.stopPropagation();
- function validationMissingTag(context) {
- var type = 'missing_tag';
+ function pointermove(d3_event) {
+ if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
+ var p = pointerLocGetter(d3_event);
- function hasDescriptiveTags(entity, graph) {
- var onlyAttributeKeys = ['description', 'name', 'note', 'start_date'];
- var entityDescriptiveKeys = Object.keys(entity.tags).filter(function (k) {
- if (k === 'area' || !osmIsInterestingTag(k)) return false;
- return !onlyAttributeKeys.some(function (attributeKey) {
- return k === attributeKey || k.indexOf(attributeKey + ':') === 0;
- });
- });
+ 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
- if (entity.type === 'relation' && entityDescriptiveKeys.length === 1 && entity.tags.type === 'multipolygon') {
- // this relation's only interesting tag just says its a multipolygon,
- // which is not descriptive enough
- // It's okay for a simple multipolygon to have no descriptive tags
- // if its outer way has them (old model, see `outdated_tags.js`)
- return osmOldMultipolygonOuterMemberOfRelation(entity, graph);
+ 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]);
+ }
}
- return entityDescriptiveKeys.length > 0;
- }
+ function pointerup(d3_event) {
+ if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
+ _pointerId = null;
- function isUnknownRoad(entity) {
- return entity.type === 'way' && entity.tags.highway === 'road';
- }
+ if (started) {
+ dispatch.call('end', this, d3_event, _targetEntity);
+ d3_event.preventDefault();
+ }
- function isUntypedRelation(entity) {
- return entity.type === 'relation' && !entity.tags.type;
+ select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
+ selectEnable();
+ }
}
- var validation = function checkMissingTag(entity, graph) {
- var subtype;
- var osm = context.connection();
- var isUnloadedNode = entity.type === 'node' && osm && !osm.isDataLoaded(entity.loc); // we can't know if the node is a vertex if the tile is undownloaded
+ function behavior(selection) {
+ var matchesSelector = utilPrefixDOMProperty('matchesSelector');
+ var delegate = pointerdown;
- if (!isUnloadedNode && // allow untagged nodes that are part of ways
- entity.geometry(graph) !== 'vertex' && // allow untagged entities that are part of relations
- !entity.hasParentRelations(graph)) {
- if (Object.keys(entity.tags).length === 0) {
- subtype = 'any';
- } else if (!hasDescriptiveTags(entity, graph)) {
- subtype = 'descriptive';
- } else if (isUntypedRelation(entity)) {
- subtype = 'relation_type';
- }
- } // flag an unknown road even if it's a member of a relation
+ if (_selector) {
+ delegate = function delegate(d3_event) {
+ var root = this;
+ var target = d3_event.target;
+ for (; target && target !== root; target = target.parentNode) {
+ var datum = target.__data__;
+ _targetEntity = datum instanceof osmNote ? datum : datum && datum.properties && datum.properties.entity;
- if (!subtype && isUnknownRoad(entity)) {
- subtype = 'highway_classification';
+ if (_targetEntity && target[matchesSelector](_selector)) {
+ return pointerdown.call(target, d3_event);
+ }
+ }
+ };
}
- if (!subtype) return [];
- var messageID = subtype === 'highway_classification' ? 'unknown_road' : 'missing_tag.' + subtype;
- var referenceID = subtype === 'highway_classification' ? 'unknown_road' : 'missing_tag'; // can always delete if the user created it in the first place..
+ selection.on(_pointerPrefix + 'down.drag' + _selector, delegate);
+ }
- var canDelete = entity.version === undefined || entity.v !== undefined;
- var severity = canDelete && subtype !== 'highway_classification' ? 'error' : 'warning';
- return [new validationIssue({
- type: type,
- subtype: subtype,
- severity: severity,
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.' + messageID + '.message', {
- feature: utilDisplayLabel(entity, context.graph())
- }) : '';
- },
- reference: showReference,
- entityIds: [entity.id],
- dynamicFixes: function dynamicFixes(context) {
- var fixes = [];
- var selectFixType = subtype === 'highway_classification' ? 'select_road_type' : 'select_preset';
- fixes.push(new validationIssueFix({
- icon: 'iD-icon-search',
- title: _t.html('issues.fix.' + selectFixType + '.title'),
- onClick: function onClick(context) {
- context.ui().sidebar.showPresetList();
- }
- }));
- var deleteOnClick;
- var id = this.entityIds[0];
- var operation = operationDelete(context, [id]);
- var disabledReasonID = operation.disabled();
+ behavior.off = function (selection) {
+ selection.on(_pointerPrefix + 'down.drag' + _selector, null);
+ };
- if (!disabledReasonID) {
- deleteOnClick = function deleteOnClick(context) {
- var id = this.issue.entityIds[0];
- var operation = operationDelete(context, [id]);
+ behavior.selector = function (_) {
+ if (!arguments.length) return _selector;
+ _selector = _;
+ return behavior;
+ };
- if (!operation.disabled()) {
- operation();
- }
- };
- }
+ behavior.origin = function (_) {
+ if (!arguments.length) return _origin;
+ _origin = _;
+ return behavior;
+ };
- fixes.push(new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.delete_feature.title'),
- disabledReason: disabledReasonID ? _t('operations.delete.' + disabledReasonID + '.single') : undefined,
- onClick: deleteOnClick
- }));
- return fixes;
- }
- })];
+ behavior.cancel = function () {
+ select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
+ return behavior;
+ };
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.' + referenceID + '.reference'));
- }
+ behavior.targetNode = function (_) {
+ if (!arguments.length) return _targetNode;
+ _targetNode = _;
+ return behavior;
};
- validation.type = type;
- return validation;
- }
+ behavior.targetEntity = function (_) {
+ if (!arguments.length) return _targetEntity;
+ _targetEntity = _;
+ return behavior;
+ };
- function validationOutdatedTags() {
- var type = 'outdated_tags';
- var _waitingForDeprecated = true;
+ behavior.surface = function (_) {
+ if (!arguments.length) return _surface;
+ _surface = _;
+ return behavior;
+ };
- var _dataDeprecated; // fetch deprecated tags
+ return utilRebind(behavior, dispatch, 'on');
+ }
+ 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);
- _mainFileFetcher.get('deprecated').then(function (d) {
- return _dataDeprecated = d;
- })["catch"](function () {
- /* ignore */
- })["finally"](function () {
- return _waitingForDeprecated = false;
- });
+ var _nudgeInterval;
- function oldTagIssues(entity, graph) {
- var oldTags = Object.assign({}, entity.tags); // shallow copy
+ var _restoreSelectedIDs = [];
+ var _wasMidpoint = false;
+ var _isCancelled = false;
- var preset = _mainPresetIndex.match(entity, graph);
- var subtype = 'deprecated_tags';
- if (!preset) return [];
- if (!entity.hasInterestingTags()) return []; // Upgrade preset, if a replacement is available..
+ var _activeEntity;
- if (preset.replacement) {
- var newPreset = _mainPresetIndex.item(preset.replacement);
- graph = actionChangePreset(entity.id, preset, newPreset, true
- /* skip field defaults */
- )(graph);
- entity = graph.entity(entity.id);
- preset = newPreset;
- } // Upgrade deprecated tags..
+ var _startLoc;
+ var _lastLoc;
- if (_dataDeprecated) {
- var deprecatedTags = entity.deprecatedTags(_dataDeprecated);
+ 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);
+ }
- if (deprecatedTags.length) {
- deprecatedTags.forEach(function (tag) {
- graph = actionUpgradeTags(entity.id, tag.old, tag.replace)(graph);
- });
- entity = graph.entity(entity.id);
- }
- } // Add missing addTags from the detected preset
+ function stopNudge() {
+ if (_nudgeInterval) {
+ window.clearInterval(_nudgeInterval);
+ _nudgeInterval = null;
+ }
+ }
+ function moveAnnotation(entity) {
+ return _t('operations.move.annotation.' + entity.geometry(context.graph()));
+ }
- var newTags = Object.assign({}, entity.tags); // shallow copy
+ function connectAnnotation(nodeEntity, targetEntity) {
+ var nodeGeometry = nodeEntity.geometry(context.graph());
+ var targetGeometry = targetEntity.geometry(context.graph());
- if (preset.tags !== preset.addTags) {
- Object.keys(preset.addTags).forEach(function (k) {
- if (!newTags[k]) {
- if (preset.addTags[k] === '*') {
- newTags[k] = 'yes';
- } else {
- newTags[k] = preset.addTags[k];
- }
+ 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
+
+ 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');
}
- });
- } // Attempt to match a canonical record in the name-suggestion-index.
+ return _t('operations.connect.annotation.from_vertex.to_sibling_vertex');
+ }
+ }
- var nsi = services.nsi;
- var waitingForNsi = false;
- var nsiResult;
+ return _t('operations.connect.annotation.from_' + nodeGeometry + '.to_' + targetGeometry);
+ }
- if (nsi) {
- waitingForNsi = nsi.status() === 'loading';
+ function shouldSnapToNode(target) {
+ if (!_activeEntity) return false;
+ return _activeEntity.geometry(context.graph()) !== 'vertex' || target.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(target, context.graph());
+ }
- if (!waitingForNsi) {
- var loc = entity.extent(graph).center();
- nsiResult = nsi.upgradeTags(newTags, loc);
+ function origin(entity) {
+ return context.projection(entity.loc);
+ }
- if (nsiResult) {
- newTags = nsiResult.newTags;
- subtype = 'noncanonical_brand';
- }
+ function keydown(d3_event) {
+ if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ if (context.surface().classed('nope')) {
+ context.surface().classed('nope-suppressed', true);
}
- }
-
- var issues = [];
- issues.provisional = _waitingForDeprecated || waitingForNsi; // determine diff
-
- var tagDiff = utilTagDiff(oldTags, newTags);
- if (!tagDiff.length) return issues;
- var isOnlyAddingTags = tagDiff.every(function (d) {
- return d.type === '+';
- });
- var prefix = '';
- if (nsiResult) {
- prefix = 'noncanonical_brand.';
- } else if (subtype === 'deprecated_tags' && isOnlyAddingTags) {
- subtype = 'incomplete_tags';
- prefix = 'incomplete.';
- } // don't allow autofixing brand tags
+ 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);
+ }
- var autoArgs = subtype !== 'noncanonical_brand' ? [doUpgrade, _t('issues.fix.upgrade_tags.annotation')] : null;
- issues.push(new validationIssue({
- type: type,
- subtype: subtype,
- severity: 'warning',
- message: showMessage,
- reference: showReference,
- entityIds: [entity.id],
- hash: utilHashcode(JSON.stringify(tagDiff)),
- dynamicFixes: function dynamicFixes() {
- var fixes = [new validationIssueFix({
- autoArgs: autoArgs,
- title: _t.html('issues.fix.upgrade_tags.title'),
- onClick: function onClick(context) {
- context.perform(doUpgrade, _t('issues.fix.upgrade_tags.annotation'));
- }
- })];
- var item = nsiResult && nsiResult.matched;
+ context.surface().classed('nope-suppressed', false).classed('nope-disabled', false);
+ }
+ }
- if (item) {
- fixes.push(new validationIssueFix({
- title: _t.html('issues.fix.tag_as_not.title', {
- name: item.displayName
- }),
- onClick: function onClick(context) {
- context.perform(addNotTag, _t('issues.fix.tag_as_not.annotation'));
- }
- }));
- }
+ function start(d3_event, entity) {
+ _wasMidpoint = entity.type === 'midpoint';
+ var hasHidden = context.features().hasHiddenConnections(entity, context.graph());
+ _isCancelled = !context.editable() || d3_event.shiftKey || hasHidden;
- return fixes;
+ if (_isCancelled) {
+ if (hasHidden) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('modes.drag_node.connected_to_hidden'))();
}
- }));
- return issues;
- function doUpgrade(graph) {
- var currEntity = graph.hasEntity(entity.id);
- if (!currEntity) return graph;
- var newTags = Object.assign({}, currEntity.tags); // shallow copy
+ return drag.cancel();
+ }
- tagDiff.forEach(function (diff) {
- if (diff.type === '-') {
- delete newTags[diff.key];
- } else if (diff.type === '+') {
- newTags[diff.key] = diff.newVal;
- }
- });
- return actionChangeTags(currEntity.id, newTags)(graph);
+ if (_wasMidpoint) {
+ var midpoint = entity;
+ entity = osmNode();
+ context.perform(actionAddMidpoint(midpoint, entity));
+ entity = context.entity(entity.id); // get post-action entity
+
+ var vertex = context.surface().selectAll('.' + entity.id);
+ drag.targetNode(vertex.node()).targetEntity(entity);
+ } else {
+ context.perform(actionNoop());
}
- function addNotTag(graph) {
- var currEntity = graph.hasEntity(entity.id);
- if (!currEntity) return graph;
- var item = nsiResult && nsiResult.matched;
- if (!item) return graph;
- var newTags = Object.assign({}, currEntity.tags); // shallow copy
+ _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()`
- var wd = item.mainTag; // e.g. `brand:wikidata`
- var notwd = "not:".concat(wd); // e.g. `not:brand:wikidata`
+ 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 : {};
+ }
+ }
- var qid = item.tags[wd];
- newTags[notwd] = qid;
+ 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 (newTags[wd] === qid) {
- // if `brand:wikidata` was set to that qid
- var wp = item.mainTag.replace('wikidata', 'wikipedia');
- delete newTags[wd]; // remove `brand:wikidata`
+ 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;
- delete newTags[wp]; // remove `brand:wikipedia`
- }
+ if (targetLoc) {
+ // snap to node/vertex - a point target with `.loc`
+ if (shouldSnapToNode(target)) {
+ loc = targetLoc;
+ }
+ } else if (targetNodes) {
+ // snap to way - a line target with `.nodes`
+ edge = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, end.id);
- return actionChangeTags(currEntity.id, newTags)(graph);
+ if (edge) {
+ loc = edge.loc;
+ }
+ }
}
- function showMessage(context) {
- var currEntity = context.hasEntity(entity.id);
- if (!currEntity) return '';
- var messageID = "issues.outdated_tags.".concat(prefix, "message");
+ context.replace(actionMoveNode(entity.id, loc)); // Below here: validations
- if (subtype === 'noncanonical_brand' && isOnlyAddingTags) {
- messageID += '_incomplete';
- }
+ var isInvalid = false; // Check if this connection to `target` could cause relations to break..
- return _t.html(messageID, {
- feature: utilDisplayLabel(currEntity, context.graph(), true
- /* verbose */
- )
- });
- }
+ if (target) {
+ isInvalid = hasRelationConflict(entity, target, edge, context.graph());
+ } // Check if this drag causes the geometry to break..
- function showReference(selection) {
- var enter = selection.selectAll('.issue-reference').data([0]).enter();
- enter.append('div').attr('class', 'issue-reference').html(_t.html("issues.outdated_tags.".concat(prefix, "reference")));
- enter.append('strong').html(_t.html('issues.suggested'));
- enter.append('table').attr('class', 'tagDiff-table').selectAll('.tagDiff-row').data(tagDiff).enter().append('tr').attr('class', 'tagDiff-row').append('td').attr('class', function (d) {
- var klass = d.type === '+' ? 'add' : 'remove';
- return "tagDiff-cell tagDiff-cell-".concat(klass);
- }).html(function (d) {
- return d.display;
- });
+
+ if (!isInvalid) {
+ isInvalid = hasInvalidGeometry(entity, context.graph());
}
- }
- function oldMultipolygonIssues(entity, graph) {
- var multipolygon, outerWay;
+ var nope = context.surface().classed('nope');
- if (entity.type === 'relation') {
- outerWay = osmOldMultipolygonOuterMemberOfRelation(entity, graph);
- multipolygon = entity;
- } else if (entity.type === 'way') {
- multipolygon = osmIsOldMultipolygonOuterMember(entity, graph);
- outerWay = entity;
+ if (isInvalid === 'relation' || isInvalid === 'restriction') {
+ if (!nope) {
+ // about to nope - show hint
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html('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.html('self_intersection.error.' + errorID))();
} else {
- return [];
+ if (nope) {
+ // about to un-nope, remove hint
+ context.ui().flash.duration(1).label('')();
+ }
}
- if (!multipolygon || !outerWay) return [];
- return [new validationIssue({
- type: type,
- subtype: 'old_multipolygon',
- severity: 'warning',
- message: showMessage,
- reference: showReference,
- entityIds: [outerWay.id, multipolygon.id],
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- autoArgs: [doUpgrade, _t('issues.fix.move_tags.annotation')],
- title: _t.html('issues.fix.move_tags.title'),
- onClick: function onClick(context) {
- context.perform(doUpgrade, _t('issues.fix.move_tags.annotation'));
- }
- })];
- }
- })];
+ var nopeDisabled = context.surface().classed('nope-disabled');
- function doUpgrade(graph) {
- var currMultipolygon = graph.hasEntity(multipolygon.id);
- var currOuterWay = graph.hasEntity(outerWay.id);
- if (!currMultipolygon || !currOuterWay) return graph;
- currMultipolygon = currMultipolygon.mergeTags(currOuterWay.tags);
- graph = graph.replace(currMultipolygon);
- return actionChangeTags(currOuterWay.id, {})(graph);
+ if (nopeDisabled) {
+ context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
+ } else {
+ context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
}
- function showMessage(context) {
- var currMultipolygon = context.hasEntity(multipolygon.id);
- if (!currMultipolygon) return '';
- return _t.html('issues.old_multipolygon.message', {
- multipolygon: utilDisplayLabel(currMultipolygon, context.graph(), true
- /* verbose */
- )
- });
- }
+ _lastLoc = loc;
+ } // Uses `actionConnect.disabled()` to know whether this connection is ok..
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.old_multipolygon.reference'));
- }
- }
- var validation = function checkOutdatedTags(entity, graph) {
- var issues = oldMultipolygonIssues(entity, graph);
- if (!issues.length) issues = oldTagIssues(entity, graph);
- return issues;
- };
+ function hasRelationConflict(entity, target, edge, graph) {
+ var testGraph = graph.update(); // copy
+ // if snapping to way - add midpoint there and consider that the target..
- validation.type = type;
- return validation;
- }
+ 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?
- function validationPrivateData() {
- var type = 'private_data'; // assume that some buildings are private
- var privateBuildingValues = {
- detached: true,
- farm: true,
- house: true,
- houseboat: true,
- residential: true,
- semidetached_house: true,
- static_caravan: true
- }; // but they might be public if they have one of these other tags
+ var ids = [entity.id, target.id];
+ return actionConnect(ids).disabled(testGraph);
+ }
- var publicKeys = {
- amenity: true,
- craft: true,
- historic: true,
- leisure: true,
- office: true,
- shop: true,
- tourism: true
- }; // these tags may contain personally identifying info
+ function hasInvalidGeometry(entity, graph) {
+ var parents = graph.parentWays(entity);
+ var i, j, k;
- var personalTags = {
- 'contact:email': true,
- 'contact:fax': true,
- 'contact:phone': true,
- email: true,
- fax: true,
- phone: true
- };
+ 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 validation = function checkPrivateData(entity) {
- var tags = entity.tags;
- if (!tags.building || !privateBuildingValues[tags.building]) return [];
- var keepTags = {};
+ var relations = graph.parentRelations(parent);
- for (var k in tags) {
- if (publicKeys[k]) return []; // probably a public feature
+ 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 (!personalTags[k]) {
- keepTags[k] = tags[k];
- }
- }
+ for (k = 0; k < rings.length; k++) {
+ nodes = rings[k].nodes;
- var tagDiff = utilTagDiff(tags, keepTags);
- if (!tagDiff.length) return [];
- var fixID = tagDiff.length === 1 ? 'remove_tag' : 'remove_tags';
- return [new validationIssue({
- type: type,
- severity: 'warning',
- message: showMessage,
- reference: showReference,
- entityIds: [entity.id],
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.' + fixID + '.title'),
- onClick: function onClick(context) {
- context.perform(doUpgrade, _t('issues.fix.upgrade_tags.annotation'));
- }
- })];
- }
- })];
-
- function doUpgrade(graph) {
- var currEntity = graph.hasEntity(entity.id);
- if (!currEntity) return graph;
- var newTags = Object.assign({}, currEntity.tags); // shallow copy
-
- tagDiff.forEach(function (diff) {
- if (diff.type === '-') {
- delete newTags[diff.key];
- } else if (diff.type === '+') {
- newTags[diff.key] = diff.newVal;
- }
- });
- return actionChangeTags(currEntity.id, newTags)(graph);
- }
-
- function showMessage(context) {
- var currEntity = context.hasEntity(this.entityIds[0]);
- if (!currEntity) return '';
- return _t.html('issues.private_data.contact.message', {
- feature: utilDisplayLabel(currEntity, context.graph())
- });
- }
-
- function showReference(selection) {
- var enter = selection.selectAll('.issue-reference').data([0]).enter();
- enter.append('div').attr('class', 'issue-reference').html(_t.html('issues.private_data.reference'));
- enter.append('strong').html(_t.html('issues.suggested'));
- enter.append('table').attr('class', 'tagDiff-table').selectAll('.tagDiff-row').data(tagDiff).enter().append('tr').attr('class', 'tagDiff-row').append('td').attr('class', function (d) {
- var klass = d.type === '+' ? 'add' : 'remove';
- return 'tagDiff-cell tagDiff-cell-' + klass;
- }).html(function (d) {
- return d.display;
- });
- }
- };
-
- validation.type = type;
- return validation;
- }
-
- function validationSuspiciousName() {
- var type = 'suspicious_name';
- var keysToTestForGenericValues = ['aerialway', 'aeroway', 'amenity', 'building', 'craft', 'highway', 'leisure', 'railway', 'man_made', 'office', 'shop', 'tourism', 'waterway'];
- var _waitingForNsi = false; // Attempt to match a generic record in the name-suggestion-index.
+ if (nodes.find(function (n) {
+ return n.id === entity.id;
+ })) {
+ activeIndex = k;
- function isGenericMatchInNsi(tags) {
- var nsi = services.nsi;
+ if (geoHasSelfIntersections(nodes, entity.id)) {
+ return 'multipolygonMember';
+ }
+ }
- if (nsi) {
- _waitingForNsi = nsi.status() === 'loading';
+ rings[k].coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ } // test active ring for intersections with other rings in the multipolygon
- if (!_waitingForNsi) {
- return nsi.isGenericName(tags);
- }
- }
- return false;
- } // Test if the name is just the key or tag value (e.g. "park")
+ for (k = 0; k < rings.length; k++) {
+ if (k === activeIndex) continue; // make sure active ring doesn't cross passive rings
+ if (geoHasLineIntersections(rings[activeIndex].nodes, rings[k].nodes, entity.id)) {
+ return 'multipolygonRing';
+ }
+ }
+ } // 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.
- function nameMatchesRawTag(lowercaseName, tags) {
- for (var i = 0; i < keysToTestForGenericValues.length; i++) {
- var key = keysToTestForGenericValues[i];
- var val = tags[key];
- if (val) {
- val = val.toLowerCase();
+ if (activeIndex === null) {
+ nodes = parent.nodes.map(function (nodeID) {
+ return graph.entity(nodeID);
+ });
- if (key === lowercaseName || val === lowercaseName || key.replace(/\_/g, ' ') === lowercaseName || val.replace(/\_/g, ' ') === lowercaseName) {
- return true;
+ if (nodes.length && geoHasSelfIntersections(nodes, entity.id)) {
+ return parent.geometry(graph);
}
}
}
return false;
}
- function isGenericName(name, tags) {
- name = name.toLowerCase();
- return nameMatchesRawTag(name, tags) || isGenericMatchInNsi(tags);
- }
+ 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 makeGenericNameIssue(entityId, nameKey, genericName, langCode) {
- return new validationIssue({
- type: type,
- subtype: 'generic_name',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- if (!entity) return '';
- var preset = _mainPresetIndex.match(entity, context.graph());
- var langName = langCode && _mainLocalizer.languageName(langCode);
- return _t.html('issues.generic_name.message' + (langName ? '_language' : ''), {
- feature: preset.name(),
- name: genericName,
- language: langName
- });
- },
- reference: showReference,
- entityIds: [entityId],
- hash: "".concat(nameKey, "=").concat(genericName),
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.remove_the_name.title'),
- onClick: function onClick(context) {
- var entityId = this.issue.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ if (nudge) {
+ startNudge(d3_event, entity, nudge);
+ } else {
+ stopNudge();
+ }
+ }
- delete tags[nameKey];
- context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_generic_name.annotation'));
- }
- })];
- }
- });
+ 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
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.generic_name.reference'));
+ 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));
}
- }
- function makeIncorrectNameIssue(entityId, nameKey, incorrectName, langCode) {
- return new validationIssue({
- type: type,
- subtype: 'not_name',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- if (!entity) return '';
- var preset = _mainPresetIndex.match(entity, context.graph());
- var langName = langCode && _mainLocalizer.languageName(langCode);
- return _t.html('issues.incorrect_name.message' + (langName ? '_language' : ''), {
- feature: preset.name(),
- name: incorrectName,
- language: langName
- });
- },
- reference: showReference,
- entityIds: [entityId],
- hash: "".concat(nameKey, "=").concat(incorrectName),
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-operation-delete',
- title: _t.html('issues.fix.remove_the_name.title'),
- onClick: function onClick(context) {
- var entityId = this.issue.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ if (wasPoint) {
+ context.enter(modeSelect(context, [entity.id]));
+ } else {
+ var reselection = _restoreSelectedIDs.filter(function (id) {
+ return context.graph().hasEntity(id);
+ });
- delete tags[nameKey];
- context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_mistaken_name.annotation'));
- }
- })];
+ if (reselection.length) {
+ context.enter(modeSelect(context, reselection));
+ } else {
+ context.enter(modeBrowse(context));
}
- });
-
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.generic_name.reference'));
}
}
- var validation = function checkGenericName(entity) {
- var tags = entity.tags; // a generic name is allowed if it's a known brand or entity
-
- var hasWikidata = !!tags.wikidata || !!tags['brand:wikidata'] || !!tags['operator:wikidata'];
- if (hasWikidata) return [];
- var issues = [];
- var notNames = (tags['not:name'] || '').split(';');
-
- for (var key in tags) {
- var m = key.match(/^name(?:(?::)([a-zA-Z_-]+))?$/);
- if (!m) continue;
- var langCode = m.length >= 2 ? m[1] : null;
- var value = tags[key];
+ function _actionBounceBack(nodeID, toLoc) {
+ var moveNode = actionMoveNode(nodeID, toLoc);
- if (notNames.length) {
- for (var i in notNames) {
- var notName = notNames[i];
+ 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);
+ };
- if (notName && value === notName) {
- issues.push(makeIncorrectNameIssue(entity.id, key, value, langCode));
- continue;
- }
- }
- }
+ action.transitionable = true;
+ return action;
+ }
- if (isGenericName(value, tags)) {
- issues.provisional = _waitingForNsi; // retry later if we are waiting on NSI to finish loading
+ function cancel() {
+ drag.cancel();
+ context.enter(modeBrowse(context));
+ }
- issues.push(makeGenericNameIssue(entity.id, key, value, langCode));
- }
- }
+ 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 issues;
+ 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);
};
- validation.type = type;
- return validation;
- }
+ 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 validationUnsquareWay(context) {
- var type = 'unsquare_way';
- var DEFAULT_DEG_THRESHOLD = 5; // see also issues.js
- // use looser epsilon for detection to reduce warnings of buildings that are essentially square already
+ mode.selectedIDs = function () {
+ if (!arguments.length) return _activeEntity ? [_activeEntity.id] : []; // no assign
- var epsilon = 0.05;
- var nodeThreshold = 10;
+ return mode;
+ };
- function isBuilding(entity, graph) {
- if (entity.type !== 'way' || entity.geometry(graph) !== 'area') return false;
- return entity.tags.building && entity.tags.building !== 'no';
- }
+ mode.activeID = function () {
+ if (!arguments.length) return _activeEntity && _activeEntity.id; // no assign
- var validation = function checkUnsquareWay(entity, graph) {
- if (!isBuilding(entity, graph)) return []; // don't flag ways marked as physically unsquare
+ return mode;
+ };
- if (entity.tags.nonsquare === 'yes') return [];
- var isClosed = entity.isClosed();
- if (!isClosed) return []; // this building has bigger problems
- // don't flag ways with lots of nodes since they are likely detail-mapped
+ mode.restoreSelectedIDs = function (_) {
+ if (!arguments.length) return _restoreSelectedIDs;
+ _restoreSelectedIDs = _;
+ return mode;
+ };
- var nodes = graph.childNodes(entity).slice(); // shallow copy
+ mode.behavior = drag;
+ return mode;
+ }
- if (nodes.length > nodeThreshold + 1) return []; // +1 because closing node appears twice
- // ignore if not all nodes are fully downloaded
+ var $$3 = _export;
+ var NativePromise = nativePromiseConstructor;
+ var fails$1 = fails$S;
+ var getBuiltIn = getBuiltIn$b;
+ var isCallable = isCallable$r;
+ var speciesConstructor = speciesConstructor$5;
+ var promiseResolve = promiseResolve$2;
+ var redefine$1 = redefine$h.exports;
- var osm = services.osm;
- if (!osm || nodes.some(function (node) {
- return !osm.isDataLoaded(node.loc);
- })) return []; // don't flag connected ways to avoid unresolvable unsquare loops
+ // Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829
+ var NON_GENERIC = !!NativePromise && fails$1(function () {
+ NativePromise.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ });
+ });
- var hasConnectedSquarableWays = nodes.some(function (node) {
- return graph.parentWays(node).some(function (way) {
- if (way.id === entity.id) return false;
- if (isBuilding(way, graph)) return true;
- return graph.parentRelations(way).some(function (parentRelation) {
- return parentRelation.isMultipolygon() && parentRelation.tags.building && parentRelation.tags.building !== 'no';
- });
- });
- });
- if (hasConnectedSquarableWays) return []; // user-configurable square threshold
+ // `Promise.prototype.finally` method
+ // https://tc39.es/ecma262/#sec-promise.prototype.finally
+ $$3({ target: 'Promise', proto: true, real: true, forced: NON_GENERIC }, {
+ 'finally': function (onFinally) {
+ var C = speciesConstructor(this, getBuiltIn('Promise'));
+ var isFunction = isCallable(onFinally);
+ 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
+ );
+ }
+ });
- var storedDegreeThreshold = corePreferences('validate-square-degrees');
- var degreeThreshold = isNaN(storedDegreeThreshold) ? DEFAULT_DEG_THRESHOLD : parseFloat(storedDegreeThreshold);
- var points = nodes.map(function (node) {
- return context.projection(node.loc);
- });
- if (!geoOrthoCanOrthogonalize(points, isClosed, epsilon, degreeThreshold, true)) return [];
- var autoArgs; // don't allow autosquaring features linked to wikidata
+ // makes sure that native promise-based APIs `Promise#finally` properly works with patched `Promise#then`
+ if (isCallable(NativePromise)) {
+ var method = getBuiltIn('Promise').prototype['finally'];
+ if (NativePromise.prototype['finally'] !== method) {
+ redefine$1(NativePromise.prototype, 'finally', method, { unsafe: true });
+ }
+ }
- if (!entity.tags.wikidata) {
- // use same degree threshold as for detection
- var autoAction = actionOrthogonalize(entity.id, context.projection, undefined, degreeThreshold);
- autoAction.transitionable = false; // when autofixing, do it instantly
+ function quickselect(arr, k, left, right, compare) {
+ quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare);
+ }
- autoArgs = [autoAction, _t('operations.orthogonalize.annotation.feature', {
- n: 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);
}
- return [new validationIssue({
- type: type,
- subtype: 'building',
- severity: 'warning',
- message: function message(context) {
- var entity = context.hasEntity(this.entityIds[0]);
- return entity ? _t.html('issues.unsquare_way.message', {
- feature: utilDisplayLabel(entity, context.graph())
- }) : '';
- },
- reference: showReference,
- entityIds: [entity.id],
- hash: degreeThreshold,
- dynamicFixes: function dynamicFixes() {
- return [new validationIssueFix({
- icon: 'iD-operation-orthogonalize',
- title: _t.html('issues.fix.square_feature.title'),
- autoArgs: autoArgs,
- onClick: function onClick(context, completionHandler) {
- var entityId = this.issue.entityIds[0]; // use same degree threshold as for detection
+ var t = arr[k];
+ var i = left;
+ var j = right;
+ swap(arr, left, k);
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
- context.perform(actionOrthogonalize(entityId, context.projection, undefined, degreeThreshold), _t('operations.orthogonalize.annotation.feature', {
- n: 1
- })); // run after the squaring transition (currently 150ms)
+ while (i < j) {
+ swap(arr, i, j);
+ i++;
+ j--;
- window.setTimeout(function () {
- completionHandler();
- }, 175);
- }
- })
- /*
- new validationIssueFix({
- title: t.html('issues.fix.tag_as_unsquare.title'),
- onClick: function(context) {
- var entityId = this.issue.entityIds[0];
- var entity = context.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
- tags.nonsquare = 'yes';
- context.perform(
- actionChangeTags(entityId, tags),
- t('issues.fix.tag_as_unsquare.annotation')
- );
- }
- })
- */
- ];
+ while (compare(arr[i], t) < 0) {
+ i++;
}
- })];
- function showReference(selection) {
- selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(_t.html('issues.unsquare_way.buildings.reference'));
+ while (compare(arr[j], t) > 0) {
+ j--;
+ }
}
- };
- validation.type = type;
- return validation;
+ 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;
+ }
}
- var Validations = /*#__PURE__*/Object.freeze({
- __proto__: null,
- validationAlmostJunction: validationAlmostJunction,
- validationCloseNodes: validationCloseNodes,
- validationCrossingWays: validationCrossingWays,
- validationDisconnectedWay: validationDisconnectedWay,
- validationFormatting: validationFormatting,
- validationHelpRequest: validationHelpRequest,
- validationImpossibleOneway: validationImpossibleOneway,
- validationIncompatibleSource: validationIncompatibleSource,
- validationMaprules: validationMaprules,
- validationMismatchedGeometry: validationMismatchedGeometry,
- validationMissingRole: validationMissingRole,
- validationMissingTag: validationMissingTag,
- validationOutdatedTags: validationOutdatedTags,
- validationPrivateData: validationPrivateData,
- validationSuspiciousName: validationSuspiciousName,
- validationUnsquareWay: validationUnsquareWay
- });
+ function swap(arr, i, j) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+ }
- function coreValidator(context) {
- var _this = this;
+ function defaultCompare(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+ }
- var dispatch = dispatch$8('validated', 'focusedIssue');
- var validator = utilRebind({}, dispatch, 'on');
- var _rules = {};
- var _disabledRules = {};
+ var RBush = /*#__PURE__*/function () {
+ function RBush() {
+ var maxEntries = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 9;
- var _ignoredIssueIDs = new Set();
+ _classCallCheck$1(this, RBush);
- var _resolvedIssueIDs = new Set();
+ // 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();
+ }
- var _baseCache = validationCache('base'); // issues before any user edits
+ _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 = [];
+ while (node) {
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ var childBBox = node.leaf ? toBBox(child) : child;
- var _headCache = validationCache('head'); // issues after all user edits
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf) result.push(child);else if (contains(bbox, childBBox)) this._all(child, result);else nodesToSearch.push(child);
+ }
+ }
+ node = nodesToSearch.pop();
+ }
- var _completeDiff = {}; // complete diff base -> head of what the user changed
+ return result;
+ }
+ }, {
+ key: "collides",
+ value: function collides(bbox) {
+ var node = this.data;
+ if (!intersects(bbox, node)) return false;
+ var nodesToSearch = [];
- var _headIsCurrent = false;
+ while (node) {
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+ var childBBox = node.leaf ? this.toBBox(child) : child;
- var _deferredRIC = new Set(); // Set( RequestIdleCallback handles )
+ if (intersects(bbox, childBBox)) {
+ if (node.leaf || contains(bbox, childBBox)) return true;
+ nodesToSearch.push(child);
+ }
+ }
+ node = nodesToSearch.pop();
+ }
- var _deferredST = new Set(); // Set( SetTimeout handles )
+ 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]);
+ }
- var _headPromise; // Promise fulfilled when validation is performed up to headGraph snapshot
+ return this;
+ } // recursively build the tree with the given data from scratch using OMT algorithm
- var RETRY = 5000; // wait 5sec before revalidating provisional entities
- // Allow validation severity to be overridden by url queryparams...
- // See: https://github.com/openstreetmap/iD/pull/8243
- //
- // Each param should contain a urlencoded comma separated list of
- // `type/subtype` rules. `*` may be used as a wildcard..
- // Examples:
- // `validationError=disconnected_way/*`
- // `validationError=disconnected_way/highway`
- // `validationError=crossing_ways/bridge*`
- // `validationError=crossing_ways/bridge*,crossing_ways/tunnel*`
+ var node = this._build(data.slice(), 0, data.length - 1, 0);
- var _errorOverrides = parseHashParam(context.initialHashParams.validationError);
+ 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
- var _warningOverrides = parseHashParam(context.initialHashParams.validationWarning);
- var _disableOverrides = parseHashParam(context.initialHashParams.validationDisable); // `parseHashParam()` (private)
- // Checks hash parameters for severity overrides
- // Arguments
- // `param` - a url hash parameter (`validationError`, `validationWarning`, or `validationDisable`)
- // Returns
- // Array of Objects like { type: RegExp, subtype: RegExp }
- //
+ this._insert(node, this.data.height - node.height - 1, true);
+ }
+ 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
- function parseHashParam(param) {
- var result = [];
- var rules = (param || '').split(',');
- rules.forEach(function (rule) {
- rule = rule.trim();
- var parts = rule.split('/', 2); // "type/subtype"
+ while (node || path.length) {
+ if (!node) {
+ // go up
+ node = path.pop();
+ parent = path[path.length - 1];
+ i = indexes.pop();
+ goingUp = true;
+ }
- var type = parts[0];
- var subtype = parts[1] || '*';
- if (!type || !subtype) return;
- result.push({
- type: makeRegExp(type),
- subtype: makeRegExp(subtype)
- });
- });
- return result;
+ if (node.leaf) {
+ // check current node
+ var index = findItem(item, node.children, equalsFn);
- function makeRegExp(str) {
- var escaped = str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&') // escape all reserved chars except for the '*'
- .replace(/\*/g, '.*'); // treat a '*' like '.*'
+ if (index !== -1) {
+ // item found, remove the item and condense tree upwards
+ node.children.splice(index, 1);
+ path.push(node);
- return new RegExp('^' + escaped + '$');
- }
- } // `init()`
- // Initialize the validator, called once on iD startup
- //
+ this._condense(path);
+ return this;
+ }
+ }
- validator.init = function () {
- Object.values(Validations).forEach(function (validation) {
- if (typeof validation !== 'function') return;
- var fn = validation(context);
- var key = fn.type;
- _rules[key] = fn;
- });
- var disabledRules = corePreferences('validate-disabledRules');
+ 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
- if (disabledRules) {
- disabledRules.split(',').forEach(function (k) {
- return _disabledRules[k] = true;
- });
+ }
+
+ return this;
}
- }; // `reset()` (private)
- // Cancels deferred work and resets all caches
- //
- // Arguments
- // `resetIgnored` - `true` to clear the list of user-ignored issues
- //
+ }, {
+ 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 reset(resetIgnored) {
- // cancel deferred work
- _deferredRIC.forEach(window.cancelIdleCallback);
+ return result;
+ }
+ }, {
+ key: "_build",
+ value: function _build(items, left, right, height) {
+ var N = right - left + 1;
+ var M = this._maxEntries;
+ var node;
- _deferredRIC.clear();
+ if (N <= M) {
+ // reached leaf level; return leaf
+ node = createNode(items.slice(left, right + 1));
+ calcBBox(node, this.toBBox);
+ return node;
+ }
- _deferredST.forEach(window.clearTimeout);
+ 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
- _deferredST.clear(); // empty queues and resolve any pending promise
+ M = Math.ceil(N / Math.pow(M, height - 1));
+ }
+ node = createNode([]);
+ node.leaf = false;
+ node.height = height; // split the items into M mostly square tiles
- _baseCache.queue = [];
- _headCache.queue = [];
- processQueue(_headCache);
- processQueue(_baseCache); // clear caches
+ var N2 = Math.ceil(N / M);
+ var N1 = N2 * Math.ceil(Math.sqrt(M));
+ multiSelect(items, left, right, N1, this.compareMinX);
- if (resetIgnored) _ignoredIssueIDs.clear();
+ for (var i = left; i <= right; i += N1) {
+ var right2 = Math.min(i + N1 - 1, right);
+ multiSelect(items, i, right2, N2, this.compareMinY);
- _resolvedIssueIDs.clear();
+ for (var j = i; j <= right2; j += N2) {
+ var right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively
- _baseCache = validationCache('base');
- _headCache = validationCache('head');
- _completeDiff = {};
- _headIsCurrent = false;
- } // `reset()`
- // clear caches, called whenever iD resets after a save or switches sources
- // (clears out the _ignoredIssueIDs set also)
- //
+ node.children.push(this._build(items, j, right3, height - 1));
+ }
+ }
+ 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;
- validator.reset = function () {
- reset(true);
- }; // `resetIgnoredIssues()`
- // clears out the _ignoredIssueIDs Set
- //
+ 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
+ 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;
+ }
+ }
+ }
- validator.resetIgnoredIssues = function () {
- _ignoredIssueIDs.clear();
+ node = targetNode || node.children[0];
+ }
- dispatch.call('validated'); // redraw UI
- }; // `revalidateUnsquare()`
- // Called whenever the user changes the unsquare threshold
- // It reruns just the "unsquare_way" validation on all buildings.
- //
+ 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
- validator.revalidateUnsquare = function () {
- revalidateUnsquare(_headCache);
- revalidateUnsquare(_baseCache);
- dispatch.call('validated');
- };
- function revalidateUnsquare(cache) {
- var checkUnsquareWay = _rules.unsquare_way;
- if (!cache.graph || typeof checkUnsquareWay !== 'function') return; // uncache existing
+ node.children.push(item);
+ extend$1(node, bbox); // split on node overflow; propagate upwards if necessary
- cache.uncacheIssuesOfType('unsquare_way');
- var buildings = context.history().tree().intersects(geoExtent([-180, -90], [180, 90]), cache.graph) // everywhere
- .filter(function (entity) {
- return entity.type === 'way' && entity.tags.building && entity.tags.building !== 'no';
- }); // rerun for all buildings
+ while (level >= 0) {
+ if (insertPath[level].children.length > this._maxEntries) {
+ this._split(insertPath, level);
- buildings.forEach(function (entity) {
- var detected = checkUnsquareWay(entity, cache.graph);
- if (!detected.length) return;
- cache.cacheIssues(detected);
- });
- } // `getIssues()`
- // Gets all issues that match the given options
- // This is called by many other places
- //
- // Arguments
- // `options` Object like:
- // {
- // what: 'all', // 'all' or 'edited'
- // where: 'all', // 'all' or 'visible'
- // includeIgnored: false, // true, false, or 'only'
- // includeDisabledRules: false // true, false, or 'only'
- // }
- //
- // Returns
- // An Array containing the issues
- //
+ level--;
+ } else break;
+ } // adjust bboxes along the insertion path
- validator.getIssues = function (options) {
- var opts = Object.assign({
- what: 'all',
- where: 'all',
- includeIgnored: false,
- includeDisabledRules: false
- }, options);
- var view = context.map().extent();
- var seen = new Set();
- var results = []; // collect head issues - present in the user edits
+ this._adjustParentBBoxes(bbox, insertPath, level);
+ } // split overflowed node into two
- if (_headCache.graph && _headCache.graph !== _baseCache.graph) {
- Object.values(_headCache.issuesByIssueID).forEach(function (issue) {
- // In the head cache, only count features that the user is responsible for - #8632
- // For example, a user can undo some work and an issue will still present in the
- // head graph, but we don't want to credit the user for causing that issue.
- var userModified = (issue.entityIds || []).some(function (id) {
- return _completeDiff.hasOwnProperty(id);
- });
- if (opts.what === 'edited' && !userModified) return; // present in head but user didn't touch it
+ }, {
+ key: "_split",
+ value: function _split(insertPath, level) {
+ var node = insertPath[level];
+ var M = node.children.length;
+ var m = this._minEntries;
- if (!filter(issue)) return;
- seen.add(issue.id);
- results.push(issue);
- });
- } // collect base issues - present before user edits
+ this._chooseSplitAxis(node, m, M);
+ var splitIndex = this._chooseSplitIndex(node, m, M);
- if (opts.what === 'all') {
- Object.values(_baseCache.issuesByIssueID).forEach(function (issue) {
- if (!filter(issue)) return;
- seen.add(issue.id);
- results.push(issue);
- });
+ 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;
- return results; // Filter the issue set to include only what the calling code wants to see.
- // Note that we use `context.graph()`/`context.hasEntity()` here, not `cache.graph`,
- // because that is the graph that the calling code will be using.
-
- function filter(issue) {
- if (!issue) return false;
- if (seen.has(issue.id)) return false;
- if (_resolvedIssueIDs.has(issue.id)) return false;
- if (opts.includeDisabledRules === 'only' && !_disabledRules[issue.type]) return false;
- if (!opts.includeDisabledRules && _disabledRules[issue.type]) return false;
- if (opts.includeIgnored === 'only' && !_ignoredIssueIDs.has(issue.id)) return false;
- if (!opts.includeIgnored && _ignoredIssueIDs.has(issue.id)) return false; // This issue may involve an entity that doesn't exist in context.graph()
- // This can happen because validation is async and rendering the issue lists is async.
-
- if ((issue.entityIds || []).some(function (id) {
- return !context.hasEntity(id);
- })) return false;
+ 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
- if (opts.where === 'visible') {
- var extent = issue.extent(context.graph());
- if (!view.intersects(extent)) return false;
+ 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;
+ }
+ }
}
- return true;
- }
- }; // `getResolvedIssues()`
- // Gets the issues that have been fixed by the user.
- //
- // Resolved issues are tracked in the `_resolvedIssueIDs` Set,
- // and they should all be issues that exist in the _baseCache.
- //
- // Returns
- // An Array containing the issues
- //
-
+ return index || M - m;
+ } // sorts node children by the best axis for split
- validator.getResolvedIssues = function () {
- return Array.from(_resolvedIssueIDs).map(function (issueID) {
- return _baseCache.issuesByIssueID[issueID];
- }).filter(Boolean);
- }; // `focusIssue()`
- // Adjusts the map to focus on the given issue.
- // (requires the issue to have a reasonable extent defined)
- //
- // Arguments
- // `issue` - the issue to focus on
- //
+ }, {
+ 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);
- validator.focusIssue = function (issue) {
- // Note that we use `context.graph()`/`context.hasEntity()` here, not `cache.graph`,
- // because that is the graph that the calling code will be using.
- var graph = context.graph();
- var selectID;
- var focusCenter; // Try to focus the map at the center of the issue..
+ 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
- var issueExtent = issue.extent(graph);
- if (issueExtent) {
- focusCenter = issueExtent.center();
- } // Try to select the first entity in the issue..
+ if (xMargin < yMargin) node.children.sort(compareMinX);
+ } // total margin of all possible split distributions where each node is at least m full
+ }, {
+ 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);
- if (issue.entityIds && issue.entityIds.length) {
- selectID = issue.entityIds[0]; // If a relation, focus on one of its members instead.
- // Otherwise we might be focusing on a part of map where the relation is not visible.
-
- if (selectID && selectID.charAt(0) === 'r') {
- // relation
- var ids = utilEntityAndDeepMemberIDs([selectID], graph);
- var nodeID = ids.find(function (id) {
- return id.charAt(0) === 'n' && graph.hasEntity(id);
- });
-
- if (!nodeID) {
- // relation has no downloaded nodes to focus on
- var wayID = ids.find(function (id) {
- return id.charAt(0) === 'w' && graph.hasEntity(id);
- });
+ for (var i = m; i < M - m; i++) {
+ var child = node.children[i];
+ extend$1(leftBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(leftBBox);
+ }
- if (wayID) {
- nodeID = graph.entity(wayID).first(); // focus on the first node of this way
- }
- }
+ 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);
+ }
- if (nodeID) {
- focusCenter = graph.entity(nodeID).loc;
- }
+ 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);
}
}
-
- if (focusCenter) {
- // Adjust the view
- var setZoom = Math.max(context.map().zoom(), 19);
- context.map().unobscuredCenterZoomEase(focusCenter, setZoom);
+ }, {
+ 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);
+ }
}
+ }]);
- if (selectID) {
- // Enter select mode
- window.setTimeout(function () {
- context.enter(modeSelect(context, [selectID]));
- dispatch.call('focusedIssue', _this, issue);
- }, 250); // after ease
- }
- }; // `getIssuesBySeverity()`
- // Gets the issues then groups them by error/warning
- // (This just calls getIssues, then puts issues in groups)
- //
- // Arguments
- // `options` - (see `getIssues`)
- // Returns
- // Object result like:
- // {
- // error: Array of errors,
- // warning: Array of warnings
- // }
- //
+ return RBush;
+ }();
+ function findItem(item, items, equalsFn) {
+ if (!equalsFn) return items.indexOf(item);
- validator.getIssuesBySeverity = function (options) {
- var groups = utilArrayGroupBy(validator.getIssues(options), 'severity');
- groups.error = groups.error || [];
- groups.warning = groups.warning || [];
- return groups;
- }; // `getEntityIssues()`
- // Gets the issues that the given entity IDs have in common, matching the given options
- // (This just calls getIssues, then filters for the given entity IDs)
- // The issues are sorted for relevance
- //
- // Arguments
- // `entityIDs` - Array or Set of entityIDs to get issues for
- // `options` - (see `getIssues`)
- // Returns
- // An Array containing the issues
- //
+ 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
- validator.getSharedEntityIssues = function (entityIDs, options) {
- var orderedIssueTypes = [// Show some issue types in a particular order:
- 'missing_tag', 'missing_role', // - missing data first
- 'outdated_tags', 'mismatched_geometry', // - identity issues
- 'crossing_ways', 'almost_junction', // - geometry issues where fixing them might solve connectivity issues
- 'disconnected_way', 'impossible_oneway' // - finally connectivity issues
- ];
- var allIssues = validator.getIssues(options);
- var forEntityIDs = new Set(entityIDs);
- return allIssues.filter(function (issue) {
- return (issue.entityIds || []).some(function (entityID) {
- return forEntityIDs.has(entityID);
- });
- }).sort(function (issue1, issue2) {
- if (issue1.type === issue2.type) {
- // issues of the same type, sort deterministically
- return issue1.id < issue2.id ? -1 : 1;
- }
- var index1 = orderedIssueTypes.indexOf(issue1.type);
- var index2 = orderedIssueTypes.indexOf(issue2.type);
+ function calcBBox(node, toBBox) {
+ distBBox(node, 0, node.children.length, toBBox, node);
+ } // min bounding rectangle of node children from k to p-1
- if (index1 !== -1 && index2 !== -1) {
- // both issue types have explicit sort orders
- return index1 - index2;
- } else if (index1 === -1 && index2 === -1) {
- // neither issue type has an explicit sort order, sort by type
- return issue1.type < issue2.type ? -1 : 1;
- } else {
- // order explicit types before everything else
- return index1 !== -1 ? -1 : 1;
- }
- });
- }; // `getEntityIssues()`
- // Get an array of detected issues for the given entityID.
- // (This just calls getSharedEntityIssues for a single entity)
- //
- // Arguments
- // `entityID` - the entity ID to get the issues for
- // `options` - (see `getIssues`)
- // Returns
- // An Array containing the issues
- //
+ function distBBox(node, k, p, toBBox, destNode) {
+ if (!destNode) destNode = createNode(null);
+ destNode.minX = Infinity;
+ destNode.minY = Infinity;
+ destNode.maxX = -Infinity;
+ destNode.maxY = -Infinity;
- validator.getEntityIssues = function (entityID, options) {
- return validator.getSharedEntityIssues([entityID], options);
- }; // `getRuleKeys()`
- //
- // Returns
- // An Array containing the rule keys
- //
+ for (var i = k; i < p; i++) {
+ var child = node.children[i];
+ extend$1(destNode, node.leaf ? toBBox(child) : child);
+ }
+ return destNode;
+ }
- validator.getRuleKeys = function () {
- return Object.keys(_rules);
- }; // `isRuleEnabled()`
- //
- // Arguments
- // `key` - the rule to check (e.g. 'crossing_ways')
- // Returns
- // `true`/`false`
- //
+ 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;
+ }
+ function compareNodeMinX(a, b) {
+ return a.minX - b.minX;
+ }
- validator.isRuleEnabled = function (key) {
- return !_disabledRules[key];
- }; // `toggleRule()`
- // Toggles a single validation rule,
- // then reruns the validation so that the user sees something happen in the UI
- //
- // Arguments
- // `key` - the rule to toggle (e.g. 'crossing_ways')
- //
+ function compareNodeMinY(a, b) {
+ return a.minY - b.minY;
+ }
+ function bboxArea(a) {
+ return (a.maxX - a.minX) * (a.maxY - a.minY);
+ }
- validator.toggleRule = function (key) {
- if (_disabledRules[key]) {
- delete _disabledRules[key];
- } else {
- _disabledRules[key] = true;
- }
+ function bboxMargin(a) {
+ return a.maxX - a.minX + (a.maxY - a.minY);
+ }
- corePreferences('validate-disabledRules', Object.keys(_disabledRules).join(','));
- validator.validate();
- }; // `disableRules()`
- // Disables given validation rules,
- // then reruns the validation so that the user sees something happen in the UI
- //
- // Arguments
- // `keys` - Array or Set containing rule keys to disable
- //
+ 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 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);
+ }
- validator.disableRules = function (keys) {
- _disabledRules = {};
- keys.forEach(function (k) {
- return _disabledRules[k] = true;
- });
- corePreferences('validate-disabledRules', Object.keys(_disabledRules).join(','));
- validator.validate();
- }; // `ignoreIssue()`
- // Don't show the given issue in lists
- //
- // Arguments
- // `issueID` - the issueID
- //
+ function contains(a, b) {
+ return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
+ }
+ function intersects(a, b) {
+ return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
+ }
- validator.ignoreIssue = function (issueID) {
- _ignoredIssueIDs.add(issueID);
- }; // `validate()`
- // Validates anything that has changed in the head graph since the last time it was run.
- // (head graph contains user's edits)
- //
- // Returns
- // A Promise fulfilled when the validation has completed and then dispatches a `validated` event.
- // This may take time but happen in the background during browser idle time.
- //
+ 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
- validator.validate = function () {
- // Make sure the caches have graphs assigned to them.
- // (we don't do this in `reset` because context is still resetting things and `history.base()` is unstable then)
- var baseGraph = context.history().base();
- if (!_headCache.graph) _headCache.graph = baseGraph;
- if (!_baseCache.graph) _baseCache.graph = baseGraph;
- var prevGraph = _headCache.graph;
- var currGraph = context.graph();
+ function multiSelect(arr, left, right, n, compare) {
+ var stack = [left, right];
- if (currGraph === prevGraph) {
- // _headCache.graph is current - we are caught up
- _headIsCurrent = true;
- dispatch.call('validated');
- return Promise.resolve();
- }
+ 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);
+ }
+ }
- if (_headPromise) {
- // Validation already in process, but we aren't caught up to current
- _headIsCurrent = false; // We will need to catch up after the validation promise fulfills
+ function responseText(response) {
+ if (!response.ok) throw new Error(response.status + " " + response.statusText);
+ return response.text();
+ }
- return _headPromise;
- } // If we get here, its time to start validating stuff.
+ function d3_text (input, init) {
+ return fetch(input, init).then(responseText);
+ }
+ function responseJson(response) {
+ if (!response.ok) throw new Error(response.status + " " + response.statusText);
+ if (response.status === 204 || response.status === 205) return;
+ return response.json();
+ }
- _headCache.graph = currGraph; // take snapshot
+ function d3_json (input, init) {
+ return fetch(input, init).then(responseJson);
+ }
- _completeDiff = context.history().difference().complete();
- var incrementalDiff = coreDifference(prevGraph, currGraph);
- var entityIDs = Object.keys(incrementalDiff.complete());
- entityIDs = _headCache.withAllRelatedEntities(entityIDs); // expand set
+ function parser(type) {
+ return function (input, init) {
+ return d3_text(input, init).then(function (text) {
+ return new DOMParser().parseFromString(text, type);
+ });
+ };
+ }
- if (!entityIDs.size) {
- dispatch.call('validated');
- return Promise.resolve();
- }
+ var d3_xml = parser("application/xml");
+ var svg = parser("image/svg+xml");
- _headPromise = validateEntitiesAsync(entityIDs, _headCache).then(function () {
- return updateResolvedIssues(entityIDs);
- }).then(function () {
- return dispatch.call('validated');
- })["catch"](function () {
- /* ignore */
- }).then(function () {
- _headPromise = null;
+ 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
- if (!_headIsCurrent) {
- validator.validate(); // run it again to catch up to current graph
- }
+ var _cache$2;
+
+ 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 abortUnwantedRequests$3(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
});
- return _headPromise;
- }; // register event handlers:
- // WHEN TO RUN VALIDATION:
- // When history changes:
+ if (!wanted) {
+ abortRequest$6(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
- context.history().on('restore.validator', validator.validate) // on restore saved history
- .on('undone.validator', validator.validate) // on undo
- .on('redone.validator', validator.validate) // on redo
- .on('reset.validator', function () {
- // on history reset - happens after save, or enter/exit walkthrough
- reset(false); // cached issues aren't valid any longer if the history has been reset
+ 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
- validator.validate();
- }); // but not on 'change' (e.g. while drawing)
- // When user changes editing modes (to catch recent changes e.g. drawing)
- context.on('exit.validator', validator.validate); // When merging fetched data, validate base graph:
+ function updateRtree$3(item, replace) {
+ _cache$2.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
- context.history().on('merge.validator', function (entities) {
- if (!entities) return; // Make sure the caches have graphs assigned to them.
- // (we don't do this in `reset` because context is still resetting things and `history.base()` is unstable then)
+ if (replace) {
+ _cache$2.rtree.insert(item);
+ }
+ }
- var baseGraph = context.history().base();
- if (!_headCache.graph) _headCache.graph = baseGraph;
- if (!_baseCache.graph) _baseCache.graph = baseGraph;
- var entityIDs = entities.map(function (entity) {
- return entity.id;
- });
- entityIDs = _baseCache.withAllRelatedEntities(entityIDs); // expand set
+ function tokenReplacements(d) {
+ if (!(d instanceof QAItem)) return;
+ var htmlRegex = new RegExp(/<\/[a-z][\s\S]*>/);
+ var replacements = {};
+ var issueTemplate = _krData.errorTypes[d.whichType];
- validateEntitiesAsync(entityIDs, _baseCache);
- }); // `validateEntity()` (private)
- // Runs all validation rules on a single entity.
- // Some things to note:
- // - Graph is passed in from whenever the validation was started. Validators shouldn't use
- // `context.graph()` because this all happens async, and the graph might have changed
- // (for example, nodes getting deleted before the validation can run)
- // - Validator functions may still be waiting on something and return a "provisional" result.
- // In this situation, we will schedule to revalidate the entity sometime later.
- //
- // Arguments
- // `entity` - The entity
- // `graph` - graph containing the entity
- //
- // Returns
- // Object result like:
- // {
- // issues: Array of detected issues
- // provisional: `true` if provisional result, `false` if final result
- // }
- //
+ if (!issueTemplate) {
+ /* eslint-disable no-console */
+ console.log('No Template: ', d.whichType);
+ console.log(' ', d.description);
+ /* eslint-enable no-console */
- function validateEntity(entity, graph) {
- var result = {
- issues: [],
- provisional: false
- };
- Object.keys(_rules).forEach(runValidation); // run all rules
+ return;
+ } // some descriptions are just fixed text
- return result; // runs validation and appends resulting issues
- function runValidation(key) {
- var fn = _rules[key];
+ if (!issueTemplate.regex) return; // regex pattern should match description with variable details captured
- if (typeof fn !== 'function') {
- console.error('no such validation rule = ' + key); // eslint-disable-line no-console
+ var errorRegex = new RegExp(issueTemplate.regex, 'i');
+ var errorMatch = errorRegex.exec(d.description);
- return;
- }
+ if (!errorMatch) {
+ /* eslint-disable no-console */
+ console.log('Unmatched: ', d.whichType);
+ console.log(' ', d.description);
+ console.log(' ', errorRegex);
+ /* eslint-enable no-console */
- var detected = fn(entity, graph);
+ return;
+ }
- if (detected.provisional) {
- // this validation should be run again later
- result.provisional = true;
+ 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] : '';
+
+ 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 (_krData.localizeStrings[compare]) {
+ // some replacement strings can be localized
+ capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
}
+ }
- detected = detected.filter(applySeverityOverrides);
- result.issues = result.issues.concat(detected); // If there are any override rules that match the issue type/subtype,
- // adjust severity (or disable it) and keep/discard as quickly as possible.
+ replacements['var' + i] = capture;
+ }
- function applySeverityOverrides(issue) {
- var type = issue.type;
- var subtype = issue.subtype || '';
- var i;
+ return replacements;
+ }
- for (i = 0; i < _errorOverrides.length; i++) {
- if (_errorOverrides[i].type.test(type) && _errorOverrides[i].subtype.test(subtype)) {
- issue.severity = 'error';
- return true;
- }
- }
-
- for (i = 0; i < _warningOverrides.length; i++) {
- if (_warningOverrides[i].type.test(type) && _warningOverrides[i].subtype.test(subtype)) {
- issue.severity = 'warning';
- return true;
- }
- }
-
- for (i = 0; i < _disableOverrides.length; i++) {
- if (_disableOverrides[i].type.test(type) && _disableOverrides[i].subtype.test(subtype)) {
- return false;
- }
- }
-
- return true;
- }
- }
- } // `updateResolvedIssues()` (private)
- // Determine if any issues were resolved for the given entities.
- // This is called by `validate()` after validation of the head graph
- //
- // Give the user credit for fixing an issue if:
- // - the issue is in the base cache
- // - the issue is not in the head cache
- // - the user did something to one of the entities involved in the issue
- //
- // Arguments
- // `entityIDs` - Array or Set containing entity IDs.
- //
+ function parseError(capture, idType) {
+ var compare = capture.toLowerCase();
+ if (_krData.localizeStrings[compare]) {
+ // some replacement strings can be localized
+ capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
+ }
- function updateResolvedIssues(entityIDs) {
- entityIDs.forEach(function (entityID) {
- var baseIssues = _baseCache.issuesByEntityID[entityID];
- if (!baseIssues) return;
- baseIssues.forEach(function (issueID) {
- // Check if the user did something to one of the entities involved in this issue.
- // (This issue could involve multiple entities, e.g. disconnected routable features)
- var issue = _baseCache.issuesByIssueID[issueID];
- var userModified = (issue.entityIds || []).some(function (id) {
- return _completeDiff.hasOwnProperty(id);
- });
+ switch (idType) {
+ // link a string like "this node"
+ case 'this':
+ capture = linkErrorObject(capture);
+ break;
- if (userModified && !_headCache.issuesByIssueID[issueID]) {
- // issue seems fixed
- _resolvedIssueIDs.add(issueID);
- } else {
- // issue still not resolved
- _resolvedIssueIDs["delete"](issueID); // (did undo, or possibly fixed and then re-caused the issue)
+ case 'url':
+ capture = linkURL(capture);
+ break;
+ // link an entity ID
- }
- });
- });
- } // `validateEntitiesAsync()` (private)
- // Schedule validation for many entities.
- //
- // Arguments
- // `entityIDs` - Array or Set containing entityIDs.
- // `graph` - the graph to validate that contains those entities
- // `cache` - the cache to store results in (_headCache or _baseCache)
- //
- // Returns
- // A Promise fulfilled when the validation has completed.
- // This may take time but happen in the background during browser idle time.
- //
+ 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 validateEntitiesAsync(entityIDs, cache) {
- // Enqueue the work
- var jobs = Array.from(entityIDs).map(function (entityID) {
- if (cache.queuedEntityIDs.has(entityID)) return null; // queued already
+ case '211':
+ capture = parse211(capture);
+ break;
- cache.queuedEntityIDs.add(entityID); // Clear caches for existing issues related to this entity
+ case '231':
+ capture = parse231(capture);
+ break;
- cache.uncacheEntityID(entityID);
- return function () {
- cache.queuedEntityIDs["delete"](entityID);
- var graph = cache.graph;
- if (!graph) return; // was reset?
+ case '294':
+ capture = parse294(capture);
+ break;
- var entity = graph.hasEntity(entityID); // Sanity check: don't validate deleted entities
+ case '370':
+ capture = parse370(capture);
+ break;
+ }
- if (!entity) return; // detect new issues and update caches
+ return capture;
- var result = validateEntity(entity, graph);
+ function linkErrorObject(d) {
+ return "<a class=\"error_object_link\">".concat(d, "</a>");
+ }
- if (result.provisional) {
- // provisional result
- cache.provisionalEntityIDs.add(entityID); // we'll need to revalidate this entity again later
- }
+ function linkEntity(d) {
+ return "<a class=\"error_entity_link\">".concat(d, "</a>");
+ }
- cache.cacheIssues(result.issues); // update cache
- };
- }).filter(Boolean); // Perform the work in chunks.
- // Because this will happen during idle callbacks, we want to choose a chunk size
- // that won't make the browser stutter too badly.
+ function linkURL(d) {
+ return "<a class=\"kr_external_link\" target=\"_blank\" href=\"".concat(d, "\">").concat(d, "</a>");
+ } // arbitrary node list of form: #ID, #ID, #ID...
- cache.queue = cache.queue.concat(utilArrayChunk(jobs, 100)); // Perform the work
- if (cache.queuePromise) return cache.queuePromise;
- cache.queuePromise = processQueue(cache).then(function () {
- return revalidateProvisionalEntities(cache);
- })["catch"](function () {
- /* ignore */
- })["finally"](function () {
- return cache.queuePromise = null;
+ 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 cache.queuePromise;
- } // `revalidateProvisionalEntities()` (private)
- // Sometimes a validator will return a "provisional" result.
- // In this situation, we'll need to revalidate the entity later.
- // This function waits a delay, then places them back into the validation queue.
- //
- // Arguments
- // `cache` - The cache (_headCache or _baseCache)
- //
+ return newList.join(', ');
+ } // arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)...
- function revalidateProvisionalEntities(cache) {
- if (!cache.provisionalEntityIDs.size) return; // nothing to do
+ function parse231(capture) {
+ var newList = []; // unfortunately 'layer' can itself contain commas, so we split on '),'
- var handle = window.setTimeout(function () {
- _deferredST["delete"](handle);
+ var items = capture.split('),');
+ items.forEach(function (item) {
+ var match = item.match(/\#(\d+)\((.+)\)?/);
- if (!cache.provisionalEntityIDs.size) return; // nothing to do
+ 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...
- validateEntitiesAsync(Array.from(cache.provisionalEntityIDs), cache);
- }, RETRY);
- _deferredST.add(handle);
- } // `processQueue(queue)` (private)
- // Process the next chunk of deferred validation work
- //
- // Arguments
- // `cache` - The cache (_headCache or _baseCache)
- //
- // Returns
- // A Promise fulfilled when the validation has completed.
- // This may take time but happen in the background during browser idle time.
- //
+ 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
+ var role = "\"".concat(item[0], "\""); // first letter of node/relation provides the type
- function processQueue(cache) {
- // console.log(`${cache.which} queue length ${cache.queue.length}`);
- if (!cache.queue.length) return Promise.resolve(); // we're done
+ var idType = item[1].slice(0, 1); // ID has # at the front
- var chunk = cache.queue.pop();
- return new Promise(function (resolvePromise) {
- var handle = window.requestIdleCallback(function () {
- _deferredRIC["delete"](handle); // const t0 = performance.now();
+ 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')"
- chunk.forEach(function (job) {
- return job();
- }); // const t1 = performance.now();
- // console.log('chunk processed in ' + (t1 - t0) + ' ms');
+ function parse370(capture) {
+ if (!capture) return '';
+ var match = capture.match(/\(including the name (\'.+\')\)/);
- resolvePromise();
+ if (match && match.length) {
+ return _t('QA.keepRight.errorTypes.370.including_the_name', {
+ name: match[1]
});
+ }
- _deferredRIC.add(handle);
- }).then(function () {
- // dispatch an event sometimes to redraw various UI things
- if (cache.queue.length % 25 === 0) dispatch.call('validated');
- }).then(function () {
- return processQueue(cache);
- });
- }
-
- return validator;
- } // `validationCache()` (private)
- // Creates a cache to store validation state
- // We create 2 of these:
- // `_baseCache` for validation on the base graph (unedited)
- // `_headCache` for validation on the head graph (user edits applied)
- //
- // Arguments
- // `which` - just a String 'base' or 'head' to keep track of it
- //
-
- function validationCache(which) {
- var cache = {
- which: which,
- graph: null,
- queue: [],
- queuePromise: null,
- queuedEntityIDs: new Set(),
- provisionalEntityIDs: new Set(),
- issuesByIssueID: {},
- // issue.id -> issue
- issuesByEntityID: {} // entity.id -> Set(issue.id)
-
- };
+ return '';
+ } // arbitrary node list of form: #ID,#ID,#ID...
- cache.cacheIssue = function (issue) {
- (issue.entityIds || []).forEach(function (entityID) {
- if (!cache.issuesByEntityID[entityID]) {
- cache.issuesByEntityID[entityID] = new Set();
- }
- cache.issuesByEntityID[entityID].add(issue.id);
+ 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);
});
- cache.issuesByIssueID[issue.id] = issue;
- };
+ return newList.join(', ');
+ }
+ }
- cache.uncacheIssue = function (issue) {
- (issue.entityIds || []).forEach(function (entityID) {
- if (cache.issuesByEntityID[entityID]) {
- cache.issuesByEntityID[entityID]["delete"](issue.id);
- }
+ var serviceKeepRight = {
+ title: 'keepRight',
+ init: function init() {
+ _mainFileFetcher.get('keepRight').then(function (d) {
+ return _krData = d;
});
- delete cache.issuesByIssueID[issue.id];
- };
- cache.cacheIssues = function (issues) {
- issues.forEach(cache.cacheIssue);
- };
+ if (!_cache$2) {
+ this.reset();
+ }
- cache.uncacheIssues = function (issues) {
- issues.forEach(cache.uncacheIssue);
- };
+ this.event = utilRebind(this, dispatch$7, 'on');
+ },
+ reset: function reset() {
+ if (_cache$2) {
+ Object.values(_cache$2.inflightTile).forEach(abortRequest$6);
+ }
- cache.uncacheIssuesOfType = function (type) {
- var issuesOfType = Object.values(cache.issuesByIssueID).filter(function (issue) {
- return issue.type === type;
- });
- cache.uncacheIssues(issuesOfType);
- }; // Remove a single entity and all its related issues from the caches
+ _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;
+
+ var options = {
+ format: 'geojson',
+ ch: _krRuleset
+ }; // determine the needed tiles to cover the view
+ var tiles = tiler$6.zoomExtent([_tileZoom$3, _tileZoom$3]).getTiles(projection); // abort inflight requests that are no longer needed
- cache.uncacheEntityID = function (entityID) {
- var entityIssueIDs = cache.issuesByEntityID[entityID];
+ abortUnwantedRequests$3(_cache$2, tiles); // issue new requests..
- if (entityIssueIDs) {
- entityIssueIDs.forEach(function (issueID) {
- var issue = cache.issuesByIssueID[issueID];
+ tiles.forEach(function (tile) {
+ if (_cache$2.loadedTile[tile.id] || _cache$2.inflightTile[tile.id]) return;
- if (issue) {
- cache.uncacheIssue(issue);
- } else {
- // shouldn't happen, clean up
- delete cache.issuesByIssueID[issueID];
- }
+ 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];
+
+ 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;
- delete cache.issuesByEntityID[entityID];
- cache.provisionalEntityIDs["delete"](entityID);
- }; // Return the expandeded set of entityIDs related to issues for the given entityIDs
- //
- // Arguments
- // `entityIDs` - Array or Set containing entityIDs.
- //
+ if (!data || !data.features || !data.features.length) {
+ throw new Error('No Data');
+ }
+ 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)
- cache.withAllRelatedEntities = function (entityIDs) {
- var result = new Set();
- (entityIDs || []).forEach(function (entityID) {
- result.add(entityID); // include self
+ var issueTemplate = _krData.errorTypes[itemType];
+ var parentIssueType = (Math.floor(itemType / 10) * 10).toString(); // try to handle error type directly, fallback to parent error type.
- var entityIssueIDs = cache.issuesByEntityID[entityID];
+ 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 (entityIssueIDs) {
- entityIssueIDs.forEach(function (issueID) {
- var issue = cache.issuesByIssueID[issueID];
+ switch (whichType) {
+ case '170':
+ description = "This feature has a FIXME tag: ".concat(description);
+ break;
- if (issue) {
- (issue.entityIds || []).forEach(function (relatedID) {
- return result.add(relatedID);
- });
- } else {
- // shouldn't happen, clean up
- delete cache.issuesByIssueID[issueID];
- }
- });
- }
- });
- return result;
- };
+ case '292':
+ case '293':
+ description = description.replace('A turn-', 'This turn-');
+ break;
- return cache;
- }
+ case '294':
+ case '295':
+ case '296':
+ case '297':
+ case '298':
+ description = "This turn-restriction~".concat(description);
+ break;
- function coreUploader(context) {
- var dispatch = dispatch$8( // Start and end events are dispatched exactly once each per legitimate outside call to `save`
- 'saveStarted', // dispatched as soon as a call to `save` has been deemed legitimate
- 'saveEnded', // dispatched after the result event has been dispatched
- 'willAttemptUpload', // dispatched before the actual upload call occurs, if it will
- 'progressChanged', // Each save results in one of these outcomes:
- 'resultNoChanges', // upload wasn't attempted since there were no edits
- 'resultErrors', // upload failed due to errors
- 'resultConflicts', // upload failed due to data conflicts
- 'resultSuccess' // upload completed without errors
- );
- var _isSaving = false;
- var _conflicts = [];
- var _errors = [];
+ case '300':
+ description = 'This highway is missing a maxspeed tag';
+ break;
- var _origChanges;
+ 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
- var _discardTags = {};
- _mainFileFetcher.get('discarded').then(function (d) {
- _discardTags = d;
- })["catch"](function () {
- /* ignore */
- });
- var uploader = utilRebind({}, dispatch, 'on');
- uploader.isSaving = function () {
- return _isSaving;
- };
+ var coincident = false;
- uploader.save = function (changeset, tryAgain, checkConflicts) {
- // Guard against accidentally entering save code twice - #4641
- if (_isSaving && !tryAgain) {
- return;
- }
+ 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 osm = context.connection();
- if (!osm) return; // If user somehow got logged out mid-save, try to reauthenticate..
- // This can happen if they were logged in from before, but the tokens are no longer valid.
+ 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;
- if (!osm.authenticated()) {
- osm.authenticate(function (err) {
- if (!err) {
- uploader.save(changeset, tryAgain, checkConflicts); // continue where we left off..
- }
+ _cache$2.rtree.insert(encodeIssueRtree$2(d));
+ });
+ dispatch$7.call('loaded');
+ })["catch"](function () {
+ delete _cache$2.inflightTile[tile.id];
+ _cache$2.loadedTile[tile.id] = true;
});
- return;
+ });
+ },
+ postUpdate: function postUpdate(d, callback) {
+ var _this2 = this;
+
+ if (_cache$2.inflightPost[d.id]) {
+ return callback({
+ message: 'Error update already inflight',
+ status: -2
+ }, d);
}
- if (!_isSaving) {
- _isSaving = true;
- dispatch.call('saveStarted', this);
- }
-
- var history = context.history();
- _conflicts = [];
- _errors = []; // Store original changes, in case user wants to download them as an .osc file
+ var params = {
+ schema: d.schema,
+ id: d.id
+ };
- _origChanges = history.changes(actionDiscardTags(history.difference(), _discardTags)); // First time, `history.perform` a no-op action.
- // Any conflict resolutions will be done as `history.replace`
- // Remember to pop this later if needed
+ if (d.newStatus) {
+ params.st = d.newStatus;
+ }
- if (!tryAgain) {
- history.perform(actionNoop());
- } // Attempt a fast upload.. If there are conflicts, re-enter with `checkConflicts = true`
+ 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.
- if (!checkConflicts) {
- upload(changeset); // Do the full (slow) conflict check..
- } else {
- performFullConflictCheck(changeset);
- }
- };
+ 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)
- function performFullConflictCheck(changeset) {
- var osm = context.connection();
- if (!osm) return;
- var history = context.history();
- var localGraph = context.graph();
- var remoteGraph = coreGraph(history.base(), true);
- var summary = history.difference().summary();
- var _toCheck = [];
+ d3_json(url, {
+ signal: controller.signal
+ })["finally"](function () {
+ delete _cache$2.inflightPost[d.id];
- for (var i = 0; i < summary.length; i++) {
- var item = summary[i];
+ if (d.newStatus === 'ignore') {
+ // ignore permanently (false positive)
+ _this2.removeItem(d);
+ } else if (d.newStatus === 'ignore_t') {
+ // ignore temporarily (error fixed)
+ _this2.removeItem(d);
- if (item.changeType === 'modified') {
- _toCheck.push(item.entity.id);
+ _cache$2.closed["".concat(d.schema, ":").concat(d.id)] = true;
+ } else {
+ d = _this2.replaceItem(d.update({
+ comment: d.newComment,
+ newComment: undefined,
+ newState: undefined
+ }));
}
- }
- var _toLoad = withChildNodes(_toCheck, localGraph);
+ 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 _loaded = {};
- var _toLoadCount = 0;
- var _toLoadTotal = _toLoad.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 (_toCheck.length) {
- dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
+ 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
- _toLoad.forEach(function (id) {
- _loaded[id] = false;
- });
+ var _cache$1;
- osm.loadMultiple(_toLoad, loaded);
- } else {
- upload(changeset);
+ function abortRequest$5(i) {
+ Object.values(i).forEach(function (controller) {
+ if (controller) {
+ controller.abort();
}
+ });
+ }
- return;
-
- function withChildNodes(ids, graph) {
- var s = new Set(ids);
- ids.forEach(function (id) {
- var entity = graph.entity(id);
- if (entity.type !== 'way') return;
- graph.childNodes(entity).forEach(function (child) {
- if (child.version !== undefined) {
- s.add(child.id);
- }
- });
- });
- return Array.from(s);
- } // Reload modified entities into an alternate graph and check for conflicts..
-
+ function abortUnwantedRequests$2(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
+ });
- function loaded(err, result) {
- if (_errors.length) return;
+ if (!wanted) {
+ abortRequest$5(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
- if (err) {
- _errors.push({
- msg: err.message || err.responseText,
- details: [_t('save.status_code', {
- code: err.status
- })]
- });
+ 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
- didResultInErrors();
- } else {
- var loadMore = [];
- result.data.forEach(function (entity) {
- remoteGraph.replace(entity);
- _loaded[entity.id] = true;
- _toLoad = _toLoad.filter(function (val) {
- return val !== entity.id;
- });
- if (!entity.visible) return; // Because loadMultiple doesn't download /full like loadEntity,
- // need to also load children that aren't already being checked..
- var i, id;
+ function updateRtree$2(item, replace) {
+ _cache$1.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
- if (entity.type === 'way') {
- for (i = 0; i < entity.nodes.length; i++) {
- id = entity.nodes[i];
+ if (replace) {
+ _cache$1.rtree.insert(item);
+ }
+ }
- if (_loaded[id] === undefined) {
- _loaded[id] = false;
- loadMore.push(id);
- }
- }
- } else if (entity.type === 'relation' && entity.isMultipolygon()) {
- for (i = 0; i < entity.members.length; i++) {
- id = entity.members[i].id;
+ function linkErrorObject(d) {
+ return "<a class=\"error_object_link\">".concat(d, "</a>");
+ }
- if (_loaded[id] === undefined) {
- _loaded[id] = false;
- loadMore.push(id);
- }
- }
- }
- });
- _toLoadCount += result.data.length;
- _toLoadTotal += loadMore.length;
- dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
+ function linkEntity(d) {
+ return "<a class=\"error_entity_link\">".concat(d, "</a>");
+ }
- if (loadMore.length) {
- _toLoad.push.apply(_toLoad, loadMore);
+ 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];
+ }
+ }
- osm.loadMultiple(loadMore, loaded);
- }
+ function relativeBearing(p1, p2) {
+ var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
- if (!_toLoad.length) {
- detectConflicts();
- upload(changeset);
- }
- }
- }
+ if (angle < 0) {
+ angle += 2 * Math.PI;
+ } // Return degrees
- function detectConflicts() {
- function choice(id, text, _action) {
- return {
- id: id,
- text: text,
- action: function action() {
- history.replace(_action);
- }
- };
- }
- function formatUser(d) {
- return '<a href="' + osm.userURL(d) + '" target="_blank">' + d + '</a>';
- }
+ return angle * 180 / Math.PI;
+ } // Assuming range [0,360)
- function entityName(entity) {
- return utilDisplayName(entity) || utilDisplayType(entity.id) + ' ' + entity.id;
- }
- function sameVersions(local, remote) {
- if (local.version !== remote.version) return false;
+ 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
- if (local.type === 'way') {
- var children = utilArrayUnion(local.nodes, remote.nodes);
- for (var i = 0; i < children.length; i++) {
- var a = localGraph.hasEntity(children[i]);
- var b = remoteGraph.hasEntity(children[i]);
- if (a && b && a.version !== b.version) return false;
- }
- }
+ function preventCoincident$1(loc, bumpUp) {
+ var coincident = false;
- return true;
- }
+ 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);
- _toCheck.forEach(function (id) {
- var local = localGraph.entity(id);
- var remote = remoteGraph.entity(id);
- if (sameVersions(local, remote)) return;
- var merge = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags, formatUser);
- history.replace(merge);
- var mergeConflicts = merge.conflicts();
- if (!mergeConflicts.length) return; // merged safely
+ return loc;
+ }
- var forceLocal = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_local');
- var forceRemote = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_remote');
- var keepMine = _t('save.conflict.' + (remote.visible ? 'keep_local' : 'restore'));
- var keepTheirs = _t('save.conflict.' + (remote.visible ? 'keep_remote' : 'delete'));
+ var serviceImproveOSM = {
+ title: 'improveOSM',
+ init: function init() {
+ _mainFileFetcher.get('qa_data').then(function (d) {
+ return _impOsmData = d.improveOSM;
+ });
- _conflicts.push({
- id: id,
- name: entityName(local),
- details: mergeConflicts,
- chosen: 1,
- choices: [choice(id, keepMine, forceLocal), choice(id, keepTheirs, forceRemote)]
- });
- });
+ if (!_cache$1) {
+ this.reset();
}
- }
-
- function upload(changeset) {
- var osm = context.connection();
- if (!osm) {
- _errors.push({
- msg: 'No OSM Service'
- });
+ this.event = utilRebind(this, dispatch$6, 'on');
+ },
+ reset: function reset() {
+ if (_cache$1) {
+ Object.values(_cache$1.inflightTile).forEach(abortRequest$5);
}
- if (_conflicts.length) {
- didResultInConflicts(changeset);
- } else if (_errors.length) {
- didResultInErrors();
- } else {
- var history = context.history();
- var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
-
- if (changes.modified.length || changes.created.length || changes.deleted.length) {
- dispatch.call('willAttemptUpload', this);
- osm.putChangeset(changeset, changes, uploadCallback);
- } else {
- // changes were insignificant or reverted by user
- didResultInNoChanges();
- }
- }
- }
+ _cache$1 = {
+ data: {},
+ loadedTile: {},
+ inflightTile: {},
+ inflightPost: {},
+ closed: {},
+ rtree: new RBush()
+ };
+ },
+ loadIssues: function loadIssues(projection) {
+ var _this = this;
- function uploadCallback(err, changeset) {
- if (err) {
- if (err.status === 409) {
- // 409 Conflict
- uploader.save(changeset, true, true); // tryAgain = true, checkConflicts = true
- } else {
- _errors.push({
- msg: err.message || err.responseText,
- details: [_t('save.status_code', {
- code: err.status
- })]
- });
+ var options = {
+ client: 'iD',
+ status: 'OPEN',
+ zoom: '19' // Use a high zoom so that clusters aren't returned
- didResultInErrors();
- }
- } else {
- didResultInSuccess(changeset);
- }
- }
+ }; // determine the needed tiles to cover the view
- function didResultInNoChanges() {
- dispatch.call('resultNoChanges', this);
- endSave();
- context.flush(); // reset iD
- }
+ var tiles = tiler$5.zoomExtent([_tileZoom$2, _tileZoom$2]).getTiles(projection); // abort inflight requests that are no longer needed
- function didResultInErrors() {
- context.history().pop();
- dispatch.call('resultErrors', this, _errors);
- endSave();
- }
+ abortUnwantedRequests$2(_cache$1, tiles); // issue new requests..
- function didResultInConflicts(changeset) {
- _conflicts.sort(function (a, b) {
- return b.id.localeCompare(a.id);
- });
+ tiles.forEach(function (tile) {
+ if (_cache$1.loadedTile[tile.id] || _cache$1.inflightTile[tile.id]) return;
- dispatch.call('resultConflicts', this, changeset, _conflicts, _origChanges);
- endSave();
- }
+ 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];
- function didResultInSuccess(changeset) {
- // delete the edit stack cached to local storage
- context.history().clearSaved();
- dispatch.call('resultSuccess', this, changeset); // Add delay to allow for postgres replication #1646 #2678
+ var params = Object.assign({}, options, {
+ east: east,
+ south: south,
+ west: west,
+ north: north
+ }); // 3 separate requests to store for each tile
- window.setTimeout(function () {
- endSave();
- context.flush(); // reset iD
- }, 2500);
- }
+ 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];
- function endSave() {
- _isSaving = false;
- dispatch.call('saveEnded', this);
- }
+ 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
- uploader.cancelConflictResolution = function () {
- context.history().pop();
- };
- uploader.processResolvedConflicts = function (changeset) {
- var history = context.history();
+ 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
- for (var i = 0; i < _conflicts.length; i++) {
- if (_conflicts[i].chosen === 1) {
- // user chose "use theirs"
- var entity = context.hasEntity(_conflicts[i].id);
+ 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
- if (entity && entity.type === 'way') {
- var children = utilArrayUniq(entity.nodes);
- for (var j = 0; j < children.length; j++) {
- history.replace(actionRevert(children[j]));
- }
- }
+ 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
- history.replace(actionRevert(_conflicts[i].id));
- }
- }
+ 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;
- uploader.save(changeset, true, false); // tryAgain = true, checkConflicts = false
- };
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ });
+ } // Tiles at high zoom == missing roads
- uploader.reset = function () {};
- return uploader;
- }
+ 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
- var $$3 = _export;
- var fails = fails$N;
- var expm1 = mathExpm1;
+ 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
- var abs = Math.abs;
- var exp = Math.exp;
- var E = Math.E;
+ if (numberOfTrips === -1) {
+ d.desc = _t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
+ }
- var FORCED = fails(function () {
- // eslint-disable-next-line es/no-math-sinh -- required for testing
- return Math.sinh(-2e-17) != -2e-17;
- });
+ _cache$1.data[d.id] = d;
- // `Math.sinh` method
- // https://tc39.es/ecma262/#sec-math.sinh
- // V8 near Chromium 38 has a problem with very small numbers
- $$3({ target: 'Math', stat: true, forced: FORCED }, {
- sinh: function sinh(x) {
- return abs(x = +x) < 1 ? (expm1(x) - expm1(-x)) / 2 : (exp(x - 1) - exp(-x - 1)) * (E / 2);
- }
- });
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ });
+ } // Entities at high zoom == turn restrictions
- var isRetina = window.devicePixelRatio && window.devicePixelRatio >= 2; // listen for DPI change, e.g. when dragging a browser window from a retina to non-retina screen
- window.matchMedia("\n (-webkit-min-device-pixel-ratio: 2), /* Safari */\n (min-resolution: 2dppx), /* standard */\n (min-resolution: 192dpi) /* fallback */\n ").addListener(function () {
- isRetina = window.devicePixelRatio && window.devicePixelRatio >= 2;
- });
+ 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
- function localeDateString(s) {
- if (!s) return null;
- var options = {
- day: 'numeric',
- month: 'short',
- year: 'numeric'
- };
- var d = new Date(s);
- if (isNaN(d.getTime())) return null;
- return d.toLocaleDateString(_mainLocalizer.localeCode(), options);
- }
+ var loc = preventCoincident$1([point.lon, point.lat], true); // Elements are presented in a strange way
- function vintageRange(vintage) {
- var s;
+ 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
- if (vintage.start || vintage.end) {
- s = vintage.start || '?';
+ var _segments$0$points = _slicedToArray(segments[0].points, 2),
+ p1 = _segments$0$points[0],
+ p2 = _segments$0$points[1];
- if (vintage.start !== vintage.end) {
- s += ' - ' + (vintage.end || '?');
- }
- }
+ var dir_of_travel = cardinalDirection(relativeBearing(p1, p2)); // Variables used in the description
- return s;
- }
+ 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;
- function rendererBackgroundSource(data) {
- var source = Object.assign({}, data); // shallow copy
+ _cache$1.rtree.insert(encodeIssueRtree$1(d));
- var _offset = [0, 0];
- var _name = source.name;
- var _description = source.description;
+ dispatch$6.call('loaded');
+ });
+ }
+ })["catch"](function () {
+ delete _cache$1.inflightTile[tile.id][k];
- var _best = !!source.best;
+ 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;
- var _template = source.encrypted ? utilAesDecrypt(source.template) : source.template;
+ // If comments already retrieved no need to do so again
+ if (item.comments) {
+ return Promise.resolve(item);
+ }
- source.tileSize = data.tileSize || 256;
- source.zoomExtent = data.zoomExtent || [0, 22];
- source.overzoom = data.overzoom !== false;
+ var key = item.issueKey;
+ var qParams = {};
- source.offset = function (val) {
- if (!arguments.length) return _offset;
- _offset = val;
- return source;
- };
+ 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;
+ }
- source.nudge = function (val, zoomlevel) {
- _offset[0] += val[0] / Math.pow(2, zoomlevel);
- _offset[1] += val[1] / Math.pow(2, zoomlevel);
- return source;
- };
+ var url = "".concat(_impOsmUrls[key], "/retrieveComments?") + utilQsString(qParams);
- source.name = function () {
- var id_safe = source.id.replace(/\./g, '<TX_DOT>');
- return _t('imagery.' + id_safe + '.name', {
- "default": _name
- });
- };
+ var cacheComments = function cacheComments(data) {
+ // Assign directly for immediate use afterwards
+ // comments are served newest to oldest
+ item.comments = data.comments ? data.comments.reverse() : [];
- source.label = function () {
- var id_safe = source.id.replace(/\./g, '<TX_DOT>');
- return _t.html('imagery.' + id_safe + '.name', {
- "default": _name
- });
- };
+ _this2.replaceItem(item);
+ };
- source.description = function () {
- var id_safe = source.id.replace(/\./g, '<TX_DOT>');
- return _t.html('imagery.' + id_safe + '.description', {
- "default": _description
+ 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);
+ }
- source.best = function () {
- return _best;
- };
+ 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
- source.area = function () {
- if (!data.polygon) return Number.MAX_VALUE; // worldwide
- var area = d3_geoArea({
- type: 'MultiPolygon',
- coordinates: [data.polygon]
- });
- return isNaN(area) ? 0 : area;
- };
+ serviceOsm.userDetails(sendPayload.bind(this));
- source.imageryUsed = function () {
- return _name || source.id;
- };
+ function sendPayload(err, user) {
+ var _this3 = this;
- source.template = function (val) {
- if (!arguments.length) return _template;
+ if (err) {
+ return callback(err, d);
+ }
- if (source.id === 'custom' || source.id === 'Bing') {
- _template = val;
- }
+ var key = d.issueKey;
+ var url = "".concat(_impOsmUrls[key], "/comment");
+ var payload = {
+ username: user.display_name,
+ targetIds: [d.identifier]
+ };
- return source;
- };
+ if (d.newStatus) {
+ payload.status = d.newStatus;
+ payload.text = 'status changed';
+ } // Comment take place of default text
- source.url = function (coord) {
- var result = _template;
- if (result === '') return result; // source 'none'
- // Guess a type based on the tokens present in the template
- // (This is for 'custom' source, where we don't know)
- if (!source.type) {
- if (/SERVICE=WMS|\{(proj|wkid|bbox)\}/.test(_template)) {
- source.type = 'wms';
- source.projection = 'EPSG:3857'; // guess
- } else if (/\{(x|y)\}/.test(_template)) {
- source.type = 'tms';
- } else if (/\{u\}/.test(_template)) {
- source.type = 'bing';
+ if (d.newComment) {
+ payload.text = d.newComment;
}
- }
-
- if (source.type === 'wms') {
- var tileToProjectedCoords = function tileToProjectedCoords(x, y, z) {
- //polyfill for IE11, PhantomJS
- var sinh = Math.sinh || function (x) {
- var y = Math.exp(x);
- return (y - 1 / y) / 2;
- };
-
- var zoomSize = Math.pow(2, z);
- var lon = x / zoomSize * Math.PI * 2 - Math.PI;
- var lat = Math.atan(sinh(Math.PI * (1 - 2 * y / zoomSize)));
-
- switch (source.projection) {
- case 'EPSG:4326':
- return {
- x: lon * 180 / Math.PI,
- y: lat * 180 / Math.PI
- };
- default:
- // EPSG:3857 and synonyms
- var mercCoords = mercatorRaw(lon, lat);
- return {
- x: 20037508.34 / Math.PI * mercCoords[0],
- y: 20037508.34 / Math.PI * mercCoords[1]
- };
- }
+ 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
- var tileSize = source.tileSize;
- var projection = source.projection;
- var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]);
- var maxXminY = tileToProjectedCoords(coord[0] + 1, coord[1] + 1, coord[2]);
- result = result.replace(/\{(\w+)\}/g, function (token, key) {
- switch (key) {
- case 'width':
- case 'height':
- return tileSize;
-
- case 'proj':
- return projection;
+ 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
+ });
- case 'wkid':
- return projection.replace(/^EPSG:/, '');
+ _this3.replaceItem(d.update({
+ comments: comments,
+ newComment: undefined
+ }));
+ } else {
+ _this3.removeItem(d);
- case 'bbox':
- // WMS 1.3 flips x/y for some coordinate systems including EPSG:4326 - #7557
- if (projection === 'EPSG:4326' && // The CRS parameter implies version 1.3 (prior versions use SRS)
- /VERSION=1.3|CRS={proj}/.test(source.template().toUpperCase())) {
- return maxXminY.y + ',' + minXmaxY.x + ',' + minXmaxY.y + ',' + maxXminY.x;
- } else {
- return minXmaxY.x + ',' + maxXminY.y + ',' + maxXminY.x + ',' + minXmaxY.y;
+ 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;
}
- case 'w':
- return minXmaxY.x;
-
- case 's':
- return maxXminY.y;
-
- case 'n':
- return maxXminY.x;
-
- case 'e':
- return minXmaxY.y;
-
- default:
- return token;
- }
- });
- } else if (source.type === 'tms') {
- result = result.replace('{x}', coord[0]).replace('{y}', coord[1]) // TMS-flipped y coordinate
- .replace(/\{[t-]y\}/, Math.pow(2, coord[2]) - coord[1] - 1).replace(/\{z(oom)?\}/, coord[2]) // only fetch retina tiles for retina screens
- .replace(/\{@2x\}|\{r\}/, isRetina ? '@2x' : '');
- } else if (source.type === 'bing') {
- result = result.replace('{u}', function () {
- var u = '';
-
- for (var zoom = coord[2]; zoom > 0; zoom--) {
- var b = 0;
- var mask = 1 << zoom - 1;
- if ((coord[0] & mask) !== 0) b++;
- if ((coord[1] & mask) !== 0) b += 2;
- u += b.toString();
+ _cache$1.closed[d.issueKey] += 1;
+ }
}
- return u;
+ if (callback) callback(null, d);
+ })["catch"](function (err) {
+ delete _cache$1.inflightPost[d.id];
+ if (callback) callback(err.message);
});
- } // these apply to any type..
+ }
+ },
+ // 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
+ 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;
+ }
+ };
- result = result.replace(/\{switch:([^}]+)\}/, function (s, r) {
- var subdomains = r.split(',');
- return subdomains[(coord[0] + coord[1]) % subdomains.length];
- });
- return result;
- };
+ var defaults$5 = {exports: {}};
- source.validZoom = function (z) {
- return source.zoomExtent[0] <= z && (source.overzoom || source.zoomExtent[1] > z);
+ function getDefaults$1() {
+ 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
};
+ }
- source.isLocatorOverlay = function () {
- return source.id === 'mapbox_locator_overlay';
- };
- /* hides a source from the list, but leaves it available for use */
+ function changeDefaults$1(newDefaults) {
+ defaults$5.exports.defaults = newDefaults;
+ }
+ defaults$5.exports = {
+ defaults: getDefaults$1(),
+ getDefaults: getDefaults$1,
+ changeDefaults: changeDefaults$1
+ };
- source.isHidden = function () {
- return source.id === 'DigitalGlobe-Premium-vintage' || source.id === 'DigitalGlobe-Standard-vintage';
- };
+ var escapeTest = /[&<>"']/;
+ var escapeReplace = /[&<>"']/g;
+ var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
+ var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
+ var escapeReplacements = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
- source.copyrightNotices = function () {};
+ var getEscapeReplacement = function getEscapeReplacement(ch) {
+ return escapeReplacements[ch];
+ };
- source.getMetadata = function (center, tileCoord, callback) {
- var vintage = {
- start: localeDateString(source.startDate),
- end: localeDateString(source.endDate)
- };
- vintage.range = vintageRange(vintage);
- var metadata = {
- vintage: vintage
- };
- callback(null, metadata);
- };
+ 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);
+ }
+ }
- return source;
+ return html;
}
- rendererBackgroundSource.Bing = function (data, dispatch) {
- // https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata
- // https://docs.microsoft.com/en-us/bingmaps/rest-services/directly-accessing-the-bing-maps-tiles
- //fallback url template
- data.template = 'https://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&n=z';
- var bing = rendererBackgroundSource(data); //var key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU'; // P2, JOSM, etc
+ var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
- var key = 'Ak5oTE46TUbjRp08OFVcGpkARErDobfpuyNKa-W2mQ8wbt1K1KL8p1bIRwWwcF-Q'; // iD
+ 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 ':';
- /*
- missing tile image strictness param (n=)
- • n=f -> (Fail) returns a 404
- • n=z -> (Empty) returns a 200 with 0 bytes (no content)
- • n=t -> (Transparent) returns a 200 with a transparent (png) tile
- */
-
- var strictParam = 'n';
- var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&uriScheme=https&key=' + key;
- var cache = {};
- var inflight = {};
- var providers = [];
- d3_json(url).then(function (json) {
- var imageryResource = json.resourceSets[0].resources[0]; //retrieve and prepare up to date imagery template
-
- var template = imageryResource.imageUrl; //https://ecn.{subdomain}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=10339
-
- var subDomains = imageryResource.imageUrlSubdomains; //["t0, t1, t2, t3"]
-
- var subDomainNumbers = subDomains.map(function (subDomain) {
- return subDomain.substring(1);
- }).join(',');
- template = template.replace('{subdomain}', "t{switch:".concat(subDomainNumbers, "}")).replace('{quadkey}', '{u}');
-
- if (!new URLSearchParams(template).has(strictParam)) {
- template += "&".concat(strictParam, "=z");
+ if (n.charAt(0) === '#') {
+ return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
}
- bing.template(template);
- providers = imageryResource.imageryProviders.map(function (provider) {
- return {
- attribution: provider.attribution,
- areas: provider.coverageAreas.map(function (area) {
- return {
- zoom: [area.zoomMin, area.zoomMax],
- extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]])
- };
- })
- };
- });
- dispatch.call('change');
- })["catch"](function () {
- /* ignore */
+ return '';
});
+ }
- bing.copyrightNotices = function (zoom, extent) {
- zoom = Math.min(zoom, 21);
- return providers.filter(function (provider) {
- return provider.areas.some(function (area) {
- return extent.intersects(area.extent) && area.zoom[0] <= zoom && area.zoom[1] >= zoom;
- });
- }).map(function (provider) {
- return provider.attribution;
- }).join(', ');
+ var caret = /(^|[^\[])\^/g;
+
+ 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;
+ }
- bing.getMetadata = function (center, tileCoord, callback) {
- var tileID = tileCoord.slice(0, 3).join('/');
- var zoom = Math.min(tileCoord[2], 21);
- var centerPoint = center[1] + ',' + center[0]; // lat,lng
+ var nonWordAndColonTest = /[^\w:]/g;
+ var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
- var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + '?zl=' + zoom + '&key=' + key;
- if (inflight[tileID]) return;
+ function cleanUrl$1(sanitize, base, href) {
+ if (sanitize) {
+ var prot;
- if (!cache[tileID]) {
- cache[tileID] = {};
+ try {
+ prot = decodeURIComponent(unescape$2(href)).replace(nonWordAndColonTest, '').toLowerCase();
+ } catch (e) {
+ return null;
}
- if (cache[tileID] && cache[tileID].metadata) {
- return callback(null, cache[tileID].metadata);
+ if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
+ return null;
}
+ }
- inflight[tileID] = true;
- d3_json(url).then(function (result) {
- delete inflight[tileID];
+ if (base && !originIndependentUrl.test(href)) {
+ href = resolveUrl$2(base, href);
+ }
- if (!result) {
- throw new Error('Unknown Error');
- }
+ try {
+ href = encodeURI(href).replace(/%25/g, '%');
+ } catch (e) {
+ return null;
+ }
- var vintage = {
- start: localeDateString(result.resourceSets[0].resources[0].vintageStart),
- end: localeDateString(result.resourceSets[0].resources[0].vintageEnd)
- };
- vintage.range = vintageRange(vintage);
- var metadata = {
- vintage: vintage
- };
- cache[tileID].metadata = metadata;
- if (callback) callback(null, metadata);
- })["catch"](function (err) {
- delete inflight[tileID];
- if (callback) callback(err.message);
- });
- };
+ return href;
+ }
- bing.terms_url = 'https://blog.openstreetmap.org/2010/11/30/microsoft-imagery-details';
- return bing;
- };
+ var baseUrls = {};
+ var justDomain = /^[^:]+:\/*[^/]*$/;
+ var protocol = /^([^:]+:)[\s\S]*$/;
+ var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
- rendererBackgroundSource.Esri = function (data) {
- // in addition to using the tilemap at zoom level 20, overzoom real tiles - #4327 (deprecated technique, but it works)
- if (data.template.match(/blankTile/) === null) {
- data.template = data.template + '?blankTile=false';
+ function resolveUrl$2(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 esri = rendererBackgroundSource(data);
- var cache = {};
- var inflight = {};
-
- var _prevCenter; // use a tilemap service to set maximum zoom for esri tiles dynamically
- // https://developers.arcgis.com/documentation/tiled-elevation-service/
+ base = baseUrls[' ' + base];
+ var relativeBase = base.indexOf(':') === -1;
+ if (href.substring(0, 2) === '//') {
+ if (relativeBase) {
+ return href;
+ }
- esri.fetchTilemap = function (center) {
- // skip if we have already fetched a tilemap within 5km
- if (_prevCenter && geoSphericalDistance(center, _prevCenter) < 5000) return;
- _prevCenter = center; // tiles are available globally to zoom level 19, afterward they may or may not be present
+ return base.replace(protocol, '$1') + href;
+ } else if (href.charAt(0) === '/') {
+ if (relativeBase) {
+ return href;
+ }
- var z = 20; // first generate a random url using the template
+ return base.replace(domain, '$1') + href;
+ } else {
+ return base + href;
+ }
+ }
- var dummyUrl = esri.url([1, 2, 3]); // calculate url z/y/x from the lat/long of the center of the map
+ var noopTest$1 = {
+ exec: function noopTest() {}
+ };
- var x = Math.floor((center[0] + 180) / 360 * Math.pow(2, z));
- var y = Math.floor((1 - Math.log(Math.tan(center[1] * Math.PI / 180) + 1 / Math.cos(center[1] * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, z)); // fetch an 8x8 grid to leverage cache
+ function merge$2(obj) {
+ var i = 1,
+ target,
+ key;
- var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+\?blankTile=false/, 'tilemap') + '/' + z + '/' + y + '/' + x + '/8/8'; // make the request and introspect the response from the tilemap server
+ for (; i < arguments.length; i++) {
+ target = arguments[i];
- d3_json(tilemapUrl).then(function (tilemap) {
- if (!tilemap) {
- throw new Error('Unknown Error');
+ for (key in target) {
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
+ obj[key] = target[key];
}
+ }
+ }
- var hasTiles = true;
-
- for (var i = 0; i < tilemap.data.length; i++) {
- // 0 means an individual tile in the grid doesn't exist
- if (!tilemap.data[i]) {
- hasTiles = false;
- break;
- }
- } // if any tiles are missing at level 20 we restrict maxZoom to 19
-
-
- esri.zoomExtent[1] = hasTiles ? 22 : 19;
- })["catch"](function () {
- /* ignore */
- });
- };
-
- esri.getMetadata = function (center, tileCoord, callback) {
- var tileID = tileCoord.slice(0, 3).join('/');
- var zoom = Math.min(tileCoord[2], esri.zoomExtent[1]);
- var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be)
+ return obj;
+ }
- var unknown = _t('info_panels.background.unknown');
- var metadataLayer;
- var vintage = {};
- var metadata = {};
- if (inflight[tileID]) return;
+ 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;
- switch (true) {
- case zoom >= 20 && esri.id === 'EsriWorldImageryClarity':
- metadataLayer = 4;
- break;
+ while (--curr >= 0 && str[curr] === '\\') {
+ escaped = !escaped;
+ }
- case zoom >= 19:
- metadataLayer = 3;
- break;
+ 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;
- case zoom >= 17:
- metadataLayer = 2;
- break;
+ if (cells.length > count) {
+ cells.splice(count);
+ } else {
+ while (cells.length < count) {
+ cells.push('');
+ }
+ }
- case zoom >= 13:
- metadataLayer = 0;
- break;
+ for (; i < cells.length; i++) {
+ // leading or trailing whitespace is ignored per the gfm spec
+ cells[i] = cells[i].trim().replace(/\\\|/g, '|');
+ }
- default:
- metadataLayer = 99;
- }
+ 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.
- var url; // build up query using the layer appropriate to the current zoom
- if (esri.id === 'EsriWorldImagery') {
- url = 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/';
- } else if (esri.id === 'EsriWorldImageryClarity') {
- url = 'https://serviceslab.arcgisonline.com/arcgis/rest/services/Clarity_World_Imagery/MapServer/';
- }
+ function rtrim$1(str, c, invert) {
+ var l = str.length;
- url += metadataLayer + '/query?returnGeometry=false&geometry=' + centerPoint + '&inSR=4326&geometryType=esriGeometryPoint&outFields=*&f=json';
+ if (l === 0) {
+ return '';
+ } // Length of suffix matching the invert condition.
- if (!cache[tileID]) {
- cache[tileID] = {};
- }
- if (cache[tileID] && cache[tileID].metadata) {
- return callback(null, cache[tileID].metadata);
- } // accurate metadata is only available >= 13
+ var suffLen = 0; // Step left until we fail to match the invert condition.
+ while (suffLen < l) {
+ var currChar = str.charAt(l - suffLen - 1);
- if (metadataLayer === 99) {
- vintage = {
- start: null,
- end: null,
- range: null
- };
- metadata = {
- vintage: null,
- source: unknown,
- description: unknown,
- resolution: unknown,
- accuracy: unknown
- };
- callback(null, metadata);
+ if (currChar === c && !invert) {
+ suffLen++;
+ } else if (currChar !== c && invert) {
+ suffLen++;
} else {
- inflight[tileID] = true;
- d3_json(url).then(function (result) {
- delete inflight[tileID];
+ break;
+ }
+ }
- if (!result) {
- throw new Error('Unknown Error');
- } else if (result.features && result.features.length < 1) {
- throw new Error('No Results');
- } else if (result.error && result.error.message) {
- throw new Error(result.error.message);
- } // pass through the discrete capture date from metadata
-
-
- var captureDate = localeDateString(result.features[0].attributes.SRC_DATE2);
- vintage = {
- start: captureDate,
- end: captureDate,
- range: captureDate
- };
- metadata = {
- vintage: vintage,
- source: clean(result.features[0].attributes.NICE_NAME),
- description: clean(result.features[0].attributes.NICE_DESC),
- resolution: clean(+parseFloat(result.features[0].attributes.SRC_RES).toFixed(4)),
- accuracy: clean(+parseFloat(result.features[0].attributes.SRC_ACC).toFixed(4))
- }; // append units - meters
+ return str.substr(0, l - suffLen);
+ }
- if (isFinite(metadata.resolution)) {
- metadata.resolution += ' m';
- }
+ function findClosingBracket$1(str, b) {
+ if (str.indexOf(b[1]) === -1) {
+ return -1;
+ }
- if (isFinite(metadata.accuracy)) {
- metadata.accuracy += ' m';
- }
+ var l = str.length;
+ var level = 0,
+ i = 0;
- cache[tileID].metadata = metadata;
- if (callback) callback(null, metadata);
- })["catch"](function (err) {
- delete inflight[tileID];
- if (callback) callback(err.message);
- });
- }
+ for (; i < l; i++) {
+ if (str[i] === '\\') {
+ i++;
+ } else if (str[i] === b[0]) {
+ level++;
+ } else if (str[i] === b[1]) {
+ level--;
- function clean(val) {
- return String(val).trim() || unknown;
+ if (level < 0) {
+ return i;
+ }
}
- };
-
- return esri;
- };
-
- rendererBackgroundSource.None = function () {
- var source = rendererBackgroundSource({
- id: 'none',
- template: ''
- });
+ }
- source.name = function () {
- return _t('background.none');
- };
+ return -1;
+ }
- source.label = function () {
- return _t.html('background.none');
- };
+ 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
- source.imageryUsed = function () {
- return null;
- };
- source.area = function () {
- return -1; // sources in background pane are sorted by area
- };
+ function repeatString$1(pattern, count) {
+ if (count < 1) {
+ return '';
+ }
- return source;
- };
+ var result = '';
- rendererBackgroundSource.Custom = function (template) {
- var source = rendererBackgroundSource({
- id: 'custom',
- template: template
- });
+ while (count > 1) {
+ if (count & 1) {
+ result += pattern;
+ }
- source.name = function () {
- return _t('background.custom');
- };
+ count >>= 1;
+ pattern += pattern;
+ }
- source.label = function () {
- return _t.html('background.custom');
- };
+ return result + pattern;
+ }
- source.imageryUsed = function () {
- // sanitize personal connection tokens - #6801
- var cleaned = source.template(); // from query string parameters
+ var helpers = {
+ escape: escape$3,
+ unescape: unescape$2,
+ edit: edit$1,
+ cleanUrl: cleanUrl$1,
+ resolveUrl: resolveUrl$2,
+ noopTest: noopTest$1,
+ merge: merge$2,
+ splitCells: splitCells$1,
+ rtrim: rtrim$1,
+ findClosingBracket: findClosingBracket$1,
+ checkSanitizeDeprecation: checkSanitizeDeprecation$1,
+ repeatString: repeatString$1
+ };
- if (cleaned.indexOf('?') !== -1) {
- var parts = cleaned.split('?', 2);
- var qs = utilStringQs(parts[1]);
- ['access_token', 'connectId', 'token'].forEach(function (param) {
- if (qs[param]) {
- qs[param] = '{apikey}';
- }
- });
- cleaned = parts[0] + '?' + utilQsString(qs, true); // true = soft encode
- } // from wms/wmts api path parameters
+ var defaults$4 = defaults$5.exports.defaults;
+ var rtrim = helpers.rtrim,
+ splitCells = helpers.splitCells,
+ _escape = helpers.escape,
+ findClosingBracket = helpers.findClosingBracket;
+ function outputLink(cap, link, raw) {
+ var href = link.href;
+ var title = link.title ? _escape(link.title) : null;
+ var text = cap[1].replace(/\\([\[\]])/g, '$1');
- cleaned = cleaned.replace(/token\/(\w+)/, 'token/{apikey}');
- return 'Custom (' + cleaned + ' )';
- };
+ 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)
+ };
+ }
+ }
- source.area = function () {
- return -2; // sources in background pane are sorted by area
- };
+ function indentCodeCompensation(raw, text) {
+ var matchIndentToCode = raw.match(/^(\s+)(?:```)/);
- return source;
- };
+ if (matchIndentToCode === null) {
+ return text;
+ }
- function rendererTileLayer(context) {
- var transformProp = utilPrefixCSSProperty('Transform');
- var tiler = utilTiler();
- var _tileSize = 256;
+ var indentToCode = matchIndentToCode[1];
+ return text.split('\n').map(function (node) {
+ var matchIndentInNode = node.match(/^\s+/);
- var _projection;
+ if (matchIndentInNode === null) {
+ return node;
+ }
- var _cache = {};
+ var _matchIndentInNode = _slicedToArray(matchIndentInNode, 1),
+ indentInNode = _matchIndentInNode[0];
- var _tileOrigin;
+ if (indentInNode.length >= indentToCode.length) {
+ return node.slice(indentToCode.length);
+ }
- var _zoom;
+ return node;
+ }).join('\n');
+ }
+ /**
+ * Tokenizer
+ */
- var _source;
- function tileSizeAtZoom(d, z) {
- var EPSILON = 0.002; // close seams
+ var Tokenizer_1 = /*#__PURE__*/function () {
+ function Tokenizer(options) {
+ _classCallCheck$1(this, Tokenizer);
- return _tileSize * Math.pow(2, z - d[2]) / _tileSize + EPSILON;
+ this.options = options || defaults$4;
}
- function atZoom(t, distance) {
- var power = Math.pow(2, distance);
- return [Math.floor(t[0] * power), Math.floor(t[1] * power), t[2] + distance];
- }
+ _createClass$1(Tokenizer, [{
+ key: "space",
+ value: function space(src) {
+ var cap = this.rules.block.newline.exec(src);
- function lookUp(d) {
- for (var up = -1; up > -d[2]; up--) {
- var tile = atZoom(d, up);
+ if (cap) {
+ if (cap[0].length > 1) {
+ return {
+ type: 'space',
+ raw: cap[0]
+ };
+ }
- if (_cache[_source.url(tile)] !== false) {
- return tile;
+ return {
+ raw: '\n'
+ };
}
}
- }
-
- function uniqueBy(a, n) {
- var o = [];
- var seen = {};
+ }, {
+ key: "code",
+ value: function code(src) {
+ var cap = this.rules.block.code.exec(src);
- for (var i = 0; i < a.length; i++) {
- if (seen[a[i][n]] === undefined) {
- o.push(a[i]);
- seen[a[i][n]] = true;
+ 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);
- return o;
- }
+ 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
+ };
+ }
+ }
+ }, {
+ key: "heading",
+ value: function heading(src) {
+ var cap = this.rules.block.heading.exec(src);
- function addSource(d) {
- d.push(_source.url(d));
- return d;
- } // Update tiles based on current state of `projection`.
+ if (cap) {
+ var text = cap[2].trim(); // remove trailing #s
+ if (/#$/.test(text)) {
+ var trimmed = rtrim(text, '#');
- function background(selection) {
- _zoom = geoScaleToZoom(_projection.scale(), _tileSize);
- var pixelOffset;
+ if (this.options.pedantic) {
+ text = trimmed.trim();
+ } else if (!trimmed || / $/.test(trimmed)) {
+ // CommonMark requires space before trailing #s
+ text = trimmed.trim();
+ }
+ }
- if (_source) {
- pixelOffset = [_source.offset()[0] * Math.pow(2, _zoom), _source.offset()[1] * Math.pow(2, _zoom)];
- } else {
- pixelOffset = [0, 0];
+ return {
+ type: 'heading',
+ raw: cap[0],
+ depth: cap[1].length,
+ text: text
+ };
+ }
}
+ }, {
+ key: "nptable",
+ value: function nptable(src) {
+ var cap = this.rules.block.nptable.exec(src);
- var translate = [_projection.translate()[0] + pixelOffset[0], _projection.translate()[1] + pixelOffset[1]];
- tiler.scale(_projection.scale() * 2 * Math.PI).translate(translate);
- _tileOrigin = [_projection.scale() * Math.PI - translate[0], _projection.scale() * Math.PI - translate[1]];
- render(selection);
- } // Derive the tiles onscreen, remove those offscreen and position them.
- // Important that this part not depend on `_projection` because it's
- // rentered when tiles load/error (see #644).
+ 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 render(selection) {
- if (!_source) return;
- var requests = [];
- var showDebug = context.getDebug('tile') && !_source.overlay;
+ 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;
+ }
+ }
- if (_source.validZoom(_zoom)) {
- tiler.skipNullIsland(!!_source.overlay);
- tiler().forEach(function (d) {
- addSource(d);
- if (d[3] === '') return;
- if (typeof d[3] !== 'string') return; // Workaround for #2295
+ l = item.cells.length;
- requests.push(d);
+ for (i = 0; i < l; i++) {
+ item.cells[i] = splitCells(item.cells[i], item.header.length);
+ }
- if (_cache[d[3]] === false && lookUp(d)) {
- requests.push(addSource(lookUp(d)));
+ return item;
}
- });
- requests = uniqueBy(requests, 3).filter(function (r) {
- // don't re-request tiles which have failed in the past
- return _cache[r[3]] !== false;
- });
+ }
}
+ }, {
+ key: "hr",
+ value: function hr(src) {
+ var cap = this.rules.block.hr.exec(src);
- function load(d3_event, d) {
- _cache[d[3]] = true;
- select(this).on('error', null).on('load', null).classed('tile-loaded', true);
- render(selection);
+ if (cap) {
+ return {
+ type: 'hr',
+ raw: cap[0]
+ };
+ }
}
+ }, {
+ key: "blockquote",
+ value: function blockquote(src) {
+ var cap = this.rules.block.blockquote.exec(src);
- function error(d3_event, d) {
- _cache[d[3]] = false;
- select(this).on('error', null).on('load', null).remove();
- render(selection);
+ if (cap) {
+ var text = cap[0].replace(/^ *> ?/gm, '');
+ return {
+ type: 'blockquote',
+ raw: cap[0],
+ text: text
+ };
+ }
}
+ }, {
+ key: "list",
+ value: function list(src) {
+ var cap = this.rules.block.list.exec(src);
- function imageTransform(d) {
- var ts = _tileSize * Math.pow(2, _zoom - d[2]);
+ 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 scale = tileSizeAtZoom(d, _zoom);
- return 'translate(' + (d[0] * ts - _tileOrigin[0]) + 'px,' + (d[1] * ts - _tileOrigin[1]) + 'px) ' + 'scale(' + scale + ',' + scale + ')';
- }
+ 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 tileCenter(d) {
- var ts = _tileSize * Math.pow(2, _zoom - d[2]);
+ for (var i = 0; i < l; i++) {
+ item = itemMatch[i];
+ raw = item;
- return [d[0] * ts - _tileOrigin[0] + ts / 2, d[1] * ts - _tileOrigin[1] + ts / 2];
- }
+ 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'));
- function debugTransform(d) {
- var coord = tileCenter(d);
- return 'translate(' + coord[0] + 'px,' + coord[1] + 'px)';
- } // Pick a representative tile near the center of the viewport
- // (This is useful for sampling the imagery vintage)
+ 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.
- var dims = tiler.size();
- var mapCenter = [dims[0] / 2, dims[1] / 2];
- var minDist = Math.max(dims[0], dims[1]);
- var nearCenter;
- requests.forEach(function (d) {
- var c = tileCenter(d);
- var dist = geoVecLength(c, mapCenter);
+ if (i !== l - 1) {
+ bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
- if (dist < minDist) {
- minDist = dist;
- nearCenter = d;
- }
- });
- var image = selection.selectAll('img').data(requests, function (d) {
- return d[3];
- });
- image.exit().style(transformProp, imageTransform).classed('tile-removing', true).classed('tile-center', false).each(function () {
- var tile = select(this);
- window.setTimeout(function () {
- if (tile.classed('tile-removing')) {
- tile.remove();
- }
- }, 300);
- });
- image.enter().append('img').attr('class', 'tile').attr('draggable', 'false').style('width', _tileSize + 'px').style('height', _tileSize + 'px').attr('src', function (d) {
- return d[3];
- }).on('error', error).on('load', load).merge(image).style(transformProp, imageTransform).classed('tile-debug', showDebug).classed('tile-removing', false).classed('tile-center', function (d) {
- return d === nearCenter;
- });
- var debug = selection.selectAll('.tile-label-debug').data(showDebug ? requests : [], function (d) {
- return d[3];
- });
- debug.exit().remove();
+ 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;
+ }
- if (showDebug) {
- var debugEnter = debug.enter().append('div').attr('class', 'tile-label-debug');
- debugEnter.append('div').attr('class', 'tile-label-debug-coord');
- debugEnter.append('div').attr('class', 'tile-label-debug-vintage');
- debug = debug.merge(debugEnter);
- debug.style(transformProp, debugTransform);
- debug.selectAll('.tile-label-debug-coord').html(function (d) {
- return d[2] + ' / ' + d[0] + ' / ' + d[1];
- });
- debug.selectAll('.tile-label-debug-vintage').each(function (d) {
- var span = select(this);
- var center = context.projection.invert(tileCenter(d));
+ bcurr = bnext;
+ } // Remove the list item's bullet
+ // so it is seen as the next token.
- _source.getMetadata(center, d, function (err, result) {
- span.html(result && result.vintage && result.vintage.range || _t('info_panels.background.vintage') + ': ' + _t('info_panels.background.unknown'));
- });
- });
- }
- }
- background.projection = function (val) {
- if (!arguments.length) return _projection;
- _projection = val;
- return background;
- };
+ space = item.length;
+ item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the
+ // list item contains. Hacky.
- background.dimensions = function (val) {
- if (!arguments.length) return tiler.size();
- tiler.size(val);
- return background;
- };
+ 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
- background.source = function (val) {
- if (!arguments.length) return _source;
- _source = val;
- _tileSize = _source.tileSize;
- _cache = {};
- tiler.tileSize(_source.tileSize).zoomExtent(_source.zoomExtent);
- return background;
- };
- return background;
- }
+ item = rtrim(item, '\n');
- var _imageryIndex = null;
- function rendererBackground(context) {
- var dispatch = dispatch$8('change');
- var detected = utilDetect();
- var baseLayer = rendererTileLayer(context).projection(context.projection);
- var _isValid = true;
- var _overlayLayers = [];
- var _brightness = 1;
- var _contrast = 1;
- var _saturation = 1;
- var _sharpness = 1;
+ if (i !== l - 1) {
+ raw = raw + '\n';
+ } // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
- function ensureImageryIndex() {
- return _mainFileFetcher.get('imagery').then(function (sources) {
- if (_imageryIndex) return _imageryIndex;
- _imageryIndex = {
- imagery: sources,
- features: {}
- }; // use which-polygon to support efficient index and querying for imagery
- var features = sources.map(function (source) {
- if (!source.polygon) return null; // workaround for editor-layer-index weirdness..
- // Add an extra array nest to each element in `source.polygon`
- // so the rings are not treated as a bunch of holes:
- // what we have: [ [[outer],[hole],[hole]] ]
- // what we want: [ [[outer]],[[outer]],[[outer]] ]
+ loose = next || /\n\n(?!\s*$)/.test(raw);
- var rings = source.polygon.map(function (ring) {
- return [ring];
- });
- var feature = {
- type: 'Feature',
- properties: {
- id: source.id
- },
- geometry: {
- type: 'MultiPolygon',
- coordinates: rings
+ if (i !== l - 1) {
+ next = raw.slice(-2) === '\n\n';
+ if (!loose) loose = next;
}
- };
- _imageryIndex.features[source.id] = feature;
- return feature;
- }).filter(Boolean);
- _imageryIndex.query = whichPolygon_1({
- type: 'FeatureCollection',
- features: features
- }); // Instantiate `rendererBackgroundSource` objects for each source
- _imageryIndex.backgrounds = sources.map(function (source) {
- if (source.type === 'bing') {
- return rendererBackgroundSource.Bing(source, dispatch);
- } else if (/^EsriWorldImagery/.test(source.id)) {
- return rendererBackgroundSource.Esri(source);
- } else {
- return rendererBackgroundSource(source);
- }
- }); // Add 'None'
+ if (loose) {
+ list.loose = true;
+ } // Check for task list items
- _imageryIndex.backgrounds.unshift(rendererBackgroundSource.None()); // Add 'Custom'
+ if (this.options.gfm) {
+ istask = /^\[[ xX]\] /.test(item);
+ ischecked = undefined;
- var template = corePreferences('background-custom-template') || '';
- var custom = rendererBackgroundSource.Custom(template);
+ if (istask) {
+ ischecked = item[1] !== ' ';
+ item = item.replace(/^\[[ xX]\] +/, '');
+ }
+ }
- _imageryIndex.backgrounds.unshift(custom);
+ list.items.push({
+ type: 'list_item',
+ raw: raw,
+ task: istask,
+ checked: ischecked,
+ loose: loose,
+ text: item
+ });
+ }
- return _imageryIndex;
- });
- }
+ return list;
+ }
+ }
+ }, {
+ key: "html",
+ value: function html(src) {
+ var cap = this.rules.block.html.exec(src);
- function background(selection) {
- var currSource = baseLayer.source(); // If we are displaying an Esri basemap at high zoom,
- // check its tilemap to see how high the zoom can go
+ 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 (context.map().zoom() > 18) {
- if (currSource && /^EsriWorldImagery/.test(currSource.id)) {
- var center = context.map().center();
- currSource.fetchTilemap(center);
+ 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]
+ };
}
- } // Is the imagery valid here? - #4827
+ }
+ }, {
+ key: "table",
+ value: function table(src) {
+ var cap = this.rules.block.table.exec(src);
+ 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') : []
+ };
- var sources = background.sources(context.map().extent());
- var wasValid = _isValid;
- _isValid = !!sources.filter(function (d) {
- return d === currSource;
- }).length;
+ if (item.header.length === item.align.length) {
+ item.raw = cap[0];
+ var l = item.align.length;
+ var i;
- if (wasValid !== _isValid) {
- // change in valid status
- background.updateImagery();
- }
+ 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;
+ }
+ }
- var baseFilter = '';
+ l = item.cells.length;
- if (detected.cssfilters) {
- if (_brightness !== 1) {
- baseFilter += " brightness(".concat(_brightness, ")");
- }
+ for (i = 0; i < l; i++) {
+ item.cells[i] = splitCells(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length);
+ }
- if (_contrast !== 1) {
- baseFilter += " contrast(".concat(_contrast, ")");
+ return item;
+ }
}
+ }
+ }, {
+ key: "lheading",
+ value: function lheading(src) {
+ var cap = this.rules.block.lheading.exec(src);
- if (_saturation !== 1) {
- baseFilter += " saturate(".concat(_saturation, ")");
+ if (cap) {
+ return {
+ type: 'heading',
+ raw: cap[0],
+ depth: cap[2].charAt(0) === '=' ? 1 : 2,
+ text: cap[1]
+ };
}
+ }
+ }, {
+ key: "paragraph",
+ value: function paragraph(src) {
+ var cap = this.rules.block.paragraph.exec(src);
- if (_sharpness < 1) {
- // gaussian blur
- var blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness);
- baseFilter += " blur(".concat(blur, "px)");
+ if (cap) {
+ return {
+ type: 'paragraph',
+ raw: cap[0],
+ text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
+ };
}
}
+ }, {
+ key: "text",
+ value: function text(src) {
+ var cap = this.rules.block.text.exec(src);
- var base = selection.selectAll('.layer-background').data([0]);
- base = base.enter().insert('div', '.layer-data').attr('class', 'layer layer-background').merge(base);
-
- if (detected.cssfilters) {
- base.style('filter', baseFilter || null);
- } else {
- base.style('opacity', _brightness);
+ if (cap) {
+ return {
+ type: 'text',
+ raw: cap[0],
+ text: cap[0]
+ };
+ }
}
+ }, {
+ key: "escape",
+ value: function escape(src) {
+ var cap = this.rules.inline.escape.exec(src);
- var imagery = base.selectAll('.layer-imagery').data([0]);
- imagery.enter().append('div').attr('class', 'layer layer-imagery').merge(imagery).call(baseLayer);
- var maskFilter = '';
- var mixBlendMode = '';
-
- if (detected.cssfilters && _sharpness > 1) {
- // apply unsharp mask
- mixBlendMode = 'overlay';
- maskFilter = 'saturate(0) blur(3px) invert(1)';
- var contrast = _sharpness - 1;
- maskFilter += " contrast(".concat(contrast, ")");
- var brightness = d3_interpolateNumber(1, 0.85)(_sharpness - 1);
- maskFilter += " brightness(".concat(brightness, ")");
+ if (cap) {
+ return {
+ type: 'escape',
+ raw: cap[0],
+ text: _escape(cap[1])
+ };
+ }
}
+ }, {
+ key: "tag",
+ value: function tag(src, inLink, inRawBlock) {
+ var cap = this.rules.inline.tag.exec(src);
- var mask = base.selectAll('.layer-unsharp-mask').data(detected.cssfilters && _sharpness > 1 ? [0] : []);
- mask.exit().remove();
- mask.enter().append('div').attr('class', 'layer layer-mask layer-unsharp-mask').merge(mask).call(baseLayer).style('filter', maskFilter || null).style('mix-blend-mode', mixBlendMode || null);
- var overlays = selection.selectAll('.layer-overlay').data(_overlayLayers, function (d) {
- return d.source().name();
- });
- overlays.exit().remove();
- overlays.enter().insert('div', '.layer-data').attr('class', 'layer layer-overlay').merge(overlays).each(function (layer, i, nodes) {
- return select(nodes[i]).call(layer);
- });
- }
-
- background.updateImagery = function () {
- var currSource = baseLayer.source();
- if (context.inIntro() || !currSource) return;
-
- var o = _overlayLayers.filter(function (d) {
- return !d.source().isLocatorOverlay() && !d.source().isHidden();
- }).map(function (d) {
- return d.source().id;
- }).join(',');
+ if (cap) {
+ if (!inLink && /^<a /i.test(cap[0])) {
+ inLink = true;
+ } else if (inLink && /^<\/a>/i.test(cap[0])) {
+ inLink = false;
+ }
- var meters = geoOffsetToMeters(currSource.offset());
- var EPSILON = 0.01;
- var x = +meters[0].toFixed(2);
- var y = +meters[1].toFixed(2);
- var hash = utilStringQs(window.location.hash);
- var id = currSource.id;
+ 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;
+ }
- if (id === 'custom') {
- id = "custom:".concat(currSource.template());
+ 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]
+ };
+ }
}
+ }, {
+ key: "link",
+ value: function link(src) {
+ var cap = this.rules.inline.link.exec(src);
- if (id) {
- hash.background = id;
- } else {
- delete hash.background;
- }
+ if (cap) {
+ var trimmedUrl = cap[2].trim();
- if (o) {
- hash.overlays = o;
- } else {
- delete hash.overlays;
- }
+ if (!this.options.pedantic && /^</.test(trimmedUrl)) {
+ // commonmark requires matching angle brackets
+ if (!/>$/.test(trimmedUrl)) {
+ return;
+ } // ending angle bracket cannot be escaped
- if (Math.abs(x) > EPSILON || Math.abs(y) > EPSILON) {
- hash.offset = "".concat(x, ",").concat(y);
- } else {
- delete hash.offset;
- }
- if (!window.mocha) {
- window.location.replace('#' + utilQsString(hash, true));
- }
+ var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
- var imageryUsed = [];
- var photoOverlaysUsed = [];
- var currUsed = currSource.imageryUsed();
+ if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
+ return;
+ }
+ } else {
+ // find closing parenthesis
+ var lastParenIndex = findClosingBracket(cap[2], '()');
- if (currUsed && _isValid) {
- imageryUsed.push(currUsed);
- }
+ 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] = '';
+ }
+ }
- _overlayLayers.filter(function (d) {
- return !d.source().isLocatorOverlay() && !d.source().isHidden();
- }).forEach(function (d) {
- return imageryUsed.push(d.source().imageryUsed());
- });
+ var href = cap[2];
+ var title = '';
- var dataLayer = context.layers().layer('data');
+ if (this.options.pedantic) {
+ // split pedantic href and title
+ var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
- if (dataLayer && dataLayer.enabled() && dataLayer.hasData()) {
- imageryUsed.push(dataLayer.getSrc());
- }
+ if (link) {
+ href = link[1];
+ title = link[3];
+ }
+ } else {
+ title = cap[3] ? cap[3].slice(1, -1) : '';
+ }
- var photoOverlayLayers = {
- streetside: 'Bing Streetside',
- mapillary: 'Mapillary Images',
- 'mapillary-map-features': 'Mapillary Map Features',
- 'mapillary-signs': 'Mapillary Signs',
- openstreetcam: 'OpenStreetCam Images'
- };
+ href = href.trim();
- for (var layerID in photoOverlayLayers) {
- var layer = context.layers().layer(layerID);
+ if (/^</.test(href)) {
+ if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
+ // pedantic allows starting angle bracket without ending angle bracket
+ href = href.slice(1);
+ } else {
+ href = href.slice(1, -1);
+ }
+ }
- if (layer && layer.enabled()) {
- photoOverlaysUsed.push(layerID);
- imageryUsed.push(photoOverlayLayers[layerID]);
+ 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]);
}
}
+ }, {
+ key: "reflink",
+ value: function reflink(src, links) {
+ var cap;
- context.history().imageryUsed(imageryUsed);
- context.history().photoOverlaysUsed(photoOverlaysUsed);
- };
-
- var _checkedBlocklists;
-
- background.sources = function (extent, zoom, includeCurrent) {
- if (!_imageryIndex) return []; // called before init()?
-
- var visible = {};
- (_imageryIndex.query.bbox(extent.rectangle(), true) || []).forEach(function (d) {
- return visible[d.id] = true;
- });
- var currSource = baseLayer.source();
- var osm = context.connection();
- var blocklists = osm && osm.imageryBlocklists();
+ 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 (blocklists && blocklists !== _checkedBlocklists) {
- _imageryIndex.backgrounds.forEach(function (source) {
- source.isBlocked = blocklists.some(function (blocklist) {
- return blocklist.test(source.template());
- });
- });
+ if (!link || !link.href) {
+ var text = cap[0].charAt(0);
+ return {
+ type: 'text',
+ raw: text,
+ text: text
+ };
+ }
- _checkedBlocklists = blocklists;
+ return outputLink(cap, link, cap[0]);
+ }
}
+ }, {
+ 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
- return _imageryIndex.backgrounds.filter(function (source) {
- if (includeCurrent && currSource === source) return true; // optionally always include the current imagery
-
- if (source.isBlocked) return false; // even bundled sources may be blocked - #7905
+ 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\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\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\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\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-\u1711\u171F-\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-\u1B4C\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-\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-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\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\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\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\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\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\uDF40-\uDF46]|\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\uDEB0-\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]|\uD80B[\uDF90-\uDFF0]|[\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\uDE70-\uDEBE\uDEC0-\uDEC9\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]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\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]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\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-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\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 (!source.polygon) return true; // always include imagery with worldwide coverage
+ 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?)
- if (zoom && zoom < 6) return false; // optionally exclude local imagery at low zooms
+ maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
- return visible[source.id]; // include imagery visible in given extent
- });
- };
+ 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__
- background.dimensions = function (val) {
- if (!val) return;
- baseLayer.dimensions(val);
+ rLength = rDelim.length;
- _overlayLayers.forEach(function (layer) {
- return layer.dimensions(val);
- });
- };
+ 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
+ }
+ }
- background.baseLayerSource = function (d) {
- if (!arguments.length) return baseLayer.source(); // test source against OSM imagery blocklists..
+ delimTotal -= rLength;
+ if (delimTotal > 0) continue; // Haven't found enough closing delimiters
+ // Remove extra characters. *a*** -> *a*
- var osm = context.connection();
- if (!osm) return background;
- var blocklists = osm.imageryBlocklists();
- var template = d.template();
- var fail = false;
- var tested = 0;
- var regex;
+ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a***
- for (var i = 0; i < blocklists.length; i++) {
- regex = blocklists[i];
- fail = regex.test(template);
- tested++;
- if (fail) break;
- } // ensure at least one test was run.
+ 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***
- if (!tested) {
- regex = /.*\.google(apis)?\..*\/(vt|kh)[\?\/].*([xyz]=.*){3}.*/;
- fail = regex.test(template);
+ return {
+ type: 'strong',
+ raw: src.slice(0, lLength + match.index + rLength + 1),
+ text: src.slice(2, lLength + match.index + rLength - 1)
+ };
+ }
+ }
}
+ }, {
+ key: "codespan",
+ value: function codespan(src) {
+ var cap = this.rules.inline.code.exec(src);
- baseLayer.source(!fail ? d : background.findSource('none'));
- dispatch.call('change');
- background.updateImagery();
- return background;
- };
-
- background.findSource = function (id) {
- if (!id || !_imageryIndex) return null; // called before init()?
-
- return _imageryIndex.backgrounds.find(function (d) {
- return d.id && d.id === id;
- });
- };
-
- background.bing = function () {
- background.baseLayerSource(background.findSource('Bing'));
- };
-
- background.showsLayer = function (d) {
- var currSource = baseLayer.source();
- if (!d || !currSource) return false;
- return d.id === currSource.id || _overlayLayers.some(function (layer) {
- return d.id === layer.source().id;
- });
- };
-
- background.overlayLayerSources = function () {
- return _overlayLayers.map(function (layer) {
- return layer.source();
- });
- };
-
- background.toggleOverlayLayer = function (d) {
- var layer;
-
- for (var i = 0; i < _overlayLayers.length; i++) {
- layer = _overlayLayers[i];
+ if (cap) {
+ var text = cap[2].replace(/\n/g, ' ');
+ var hasNonSpaceChars = /[^ ]/.test(text);
+ var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
- if (layer.source() === d) {
- _overlayLayers.splice(i, 1);
+ if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
+ text = text.substring(1, text.length - 1);
+ }
- dispatch.call('change');
- background.updateImagery();
- return;
+ text = _escape(text, true);
+ return {
+ type: 'codespan',
+ raw: cap[0],
+ text: text
+ };
}
}
+ }, {
+ key: "br",
+ value: function br(src) {
+ var cap = this.rules.inline.br.exec(src);
- layer = rendererTileLayer(context).source(d).projection(context.projection).dimensions(baseLayer.dimensions());
-
- _overlayLayers.push(layer);
-
- dispatch.call('change');
- background.updateImagery();
- };
-
- background.nudge = function (d, zoom) {
- var currSource = baseLayer.source();
+ if (cap) {
+ return {
+ type: 'br',
+ raw: cap[0]
+ };
+ }
+ }
+ }, {
+ key: "del",
+ value: function del(src) {
+ var cap = this.rules.inline.del.exec(src);
- if (currSource) {
- currSource.nudge(d, zoom);
- dispatch.call('change');
- background.updateImagery();
+ if (cap) {
+ return {
+ type: 'del',
+ raw: cap[0],
+ text: cap[2]
+ };
+ }
}
+ }, {
+ key: "autolink",
+ value: function autolink(src, mangle) {
+ var cap = this.rules.inline.autolink.exec(src);
- return background;
- };
+ if (cap) {
+ var text, href;
- background.offset = function (d) {
- var currSource = baseLayer.source();
+ if (cap[2] === '@') {
+ text = _escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
+ href = 'mailto:' + text;
+ } else {
+ text = _escape(cap[1]);
+ href = text;
+ }
- if (!arguments.length) {
- return currSource && currSource.offset() || [0, 0];
+ return {
+ type: 'link',
+ raw: cap[0],
+ text: text,
+ href: href,
+ tokens: [{
+ type: 'text',
+ raw: text,
+ text: text
+ }]
+ };
+ }
}
+ }, {
+ key: "url",
+ value: function url(src, mangle) {
+ var cap;
- if (currSource) {
- currSource.offset(d);
- dispatch.call('change');
- background.updateImagery();
- }
+ if (cap = this.rules.inline.url.exec(src)) {
+ var text, href;
- return background;
- };
+ if (cap[2] === '@') {
+ text = _escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
+ href = 'mailto:' + text;
+ } else {
+ // do extended autolink path validation
+ var prevCapZero;
- background.brightness = function (d) {
- if (!arguments.length) return _brightness;
- _brightness = d;
- if (context.mode()) dispatch.call('change');
- return background;
- };
+ do {
+ prevCapZero = cap[0];
+ cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
+ } while (prevCapZero !== cap[0]);
- background.contrast = function (d) {
- if (!arguments.length) return _contrast;
- _contrast = d;
- if (context.mode()) dispatch.call('change');
- return background;
- };
+ text = _escape(cap[0]);
- background.saturation = function (d) {
- if (!arguments.length) return _saturation;
- _saturation = d;
- if (context.mode()) dispatch.call('change');
- return background;
- };
+ if (cap[1] === 'www.') {
+ href = 'http://' + text;
+ } else {
+ href = text;
+ }
+ }
- background.sharpness = function (d) {
- if (!arguments.length) return _sharpness;
- _sharpness = d;
- if (context.mode()) dispatch.call('change');
- return background;
- };
+ return {
+ type: 'link',
+ raw: cap[0],
+ text: text,
+ href: href,
+ tokens: [{
+ type: 'text',
+ raw: text,
+ text: text
+ }]
+ };
+ }
+ }
+ }, {
+ key: "inlineText",
+ value: function inlineText(src, inRawBlock, smartypants) {
+ var cap = this.rules.inline.text.exec(src);
- var _loadPromise;
+ if (cap) {
+ var text;
- background.ensureLoaded = function () {
- if (_loadPromise) return _loadPromise;
+ 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 parseMapParams(qmap) {
- if (!qmap) return false;
- var params = qmap.split('/').map(Number);
- if (params.length < 3 || params.some(isNaN)) return false;
- return geoExtent([params[2], params[1]]); // lon,lat
+ return {
+ type: 'text',
+ raw: cap[0],
+ text: text
+ };
+ }
}
+ }]);
- var hash = utilStringQs(window.location.hash);
- var requested = hash.background || hash.layer;
- var extent = parseMapParams(hash.map);
- return _loadPromise = ensureImageryIndex().then(function (imageryIndex) {
- var first = imageryIndex.backgrounds.length && imageryIndex.backgrounds[0];
- var best;
+ return Tokenizer;
+ }();
- if (!requested && extent) {
- best = background.sources(extent).find(function (s) {
- return s.best();
- });
- } // Decide which background layer to display
+ var noopTest = helpers.noopTest,
+ edit = helpers.edit,
+ merge$1 = helpers.merge;
+ /**
+ * Block-Level Grammar
+ */
+ 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)
+ + '|<![A-Z][\\s\\S]*?(?:>\\n*|$)' // (4)
+ + '|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\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 = /<!--(?!-?>)[\s\S]*?(?:-->|$)/;
+ 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
+ */
- if (requested && requested.indexOf('custom:') === 0) {
- var template = requested.replace(/^custom:/, '');
- var custom = background.findSource('custom');
- background.baseLayerSource(custom.template(template));
- corePreferences('background-custom-template', template);
- } else {
- background.baseLayerSource(background.findSource(requested) || best || background.findSource(corePreferences('background-last-used')) || background.findSource('Bing') || first || background.findSource('none'));
- }
+ block$1.normal = merge$1({}, block$1);
+ /**
+ * GFM Block Grammar
+ */
- var locator = imageryIndex.backgrounds.find(function (d) {
- return d.overlay && d["default"];
- });
+ 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
- if (locator) {
- background.toggleOverlayLayer(locator);
- }
+ });
+ 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 overlays = (hash.overlays || '').split(',');
- overlays.forEach(function (overlay) {
- overlay = background.findSource(overlay);
+ block$1.pedantic = merge$1({}, block$1.normal, {
+ html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
+ + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\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 (overlay) {
- background.toggleOverlayLayer(overlay);
- }
- });
+ 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. <?php ?>
+ + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html>
+ + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>',
+ // 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 _
- if (hash.gpx) {
- var gpx = context.layers().layer('data');
+ },
+ code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
+ br: /^( {2,}|\\)\n(?!\s*$)/,
+ del: noopTest,
+ text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,
+ punctuation: /^([\spunctuation])/
+ }; // list of punctuation marks from CommonMark spec
+ // without * and _ to handle the different emphasis markers * and _
- if (gpx) {
- gpx.url(hash.gpx, '.gpx');
- }
- }
+ inline$1._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
+ inline$1.punctuation = edit(inline$1.punctuation).replace(/punctuation/g, inline$1._punctuation).getRegex(); // sequences em should skip over [title](link), `code`, <html>
- if (hash.offset) {
- var offset = hash.offset.replace(/;/g, ',').split(',').map(function (n) {
- return !isNaN(n) && n;
- });
+ 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
+ */
- if (offset.length === 2) {
- background.offset(geoMetersToOffset(offset));
- }
- }
- })["catch"](function () {
- /* ignore */
- });
- };
+ inline$1.normal = merge$1({}, inline$1);
+ /**
+ * Pedantic Inline Grammar
+ */
- return utilRebind(background, dispatch, 'on');
- }
+ 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 rendererFeatures(context) {
- var dispatch = dispatch$8('change', 'redraw');
- var features = utilRebind({}, dispatch, 'on');
+ 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]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
+ });
+ inline$1.gfm.url = edit(inline$1.gfm.url, 'i').replace('email', inline$1.gfm._extended_email).getRegex();
+ /**
+ * GFM + Line Breaks Inline Grammar
+ */
- var _deferred = new Set();
+ inline$1.breaks = merge$1({}, inline$1.gfm, {
+ br: edit(inline$1.br).replace('{2,}', '*').getRegex(),
+ text: edit(inline$1.gfm.text).replace('\\b_', '\\b_| {2,}\\n').replace(/\{2,\}/g, '*').getRegex()
+ });
+ var rules = {
+ block: block$1,
+ inline: inline$1
+ };
- var traffic_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
- };
- var service_roads = {
- 'service': true,
- 'road': true,
- 'track': true
- };
- var paths = {
- 'path': true,
- 'footway': true,
- 'cycleway': true,
- 'bridleway': true,
- 'steps': true,
- 'pedestrian': true
- };
- var past_futures = {
- 'proposed': true,
- 'construction': true,
- 'abandoned': true,
- 'dismantled': true,
- 'disused': true,
- 'razed': true,
- 'demolished': true,
- 'obliterated': true
- };
- var _cullFactor = 1;
- var _cache = {};
- var _rules = {};
- var _stats = {};
- var _keys = [];
- var _hidden = [];
- var _forceVisible = {};
+ var Tokenizer$1 = Tokenizer_1;
+ var defaults$3 = defaults$5.exports.defaults;
+ var block = rules.block,
+ inline = rules.inline;
+ var repeatString = helpers.repeatString;
+ /**
+ * smartypants text replacement
+ */
- function update() {
- if (!window.mocha) {
- var hash = utilStringQs(window.location.hash);
- var disabled = features.disabled();
+ 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
+ */
- if (disabled.length) {
- hash.disable_features = disabled.join(',');
- } else {
- delete hash.disable_features;
- }
- window.location.replace('#' + utilQsString(hash, true));
- corePreferences('disabled-features', disabled.join(','));
+ function mangle(text) {
+ var out = '',
+ i,
+ ch;
+ var l = text.length;
+
+ for (i = 0; i < l; i++) {
+ ch = text.charCodeAt(i);
+
+ if (Math.random() > 0.5) {
+ ch = 'x' + ch.toString(16);
}
- _hidden = features.hidden();
- dispatch.call('change');
- dispatch.call('redraw');
+ out += '&#' + ch + ';';
}
- function defineRule(k, filter, max) {
- var isEnabled = true;
+ return out;
+ }
+ /**
+ * Block Lexer
+ */
- _keys.push(k);
- _rules[k] = {
- filter: filter,
- enabled: isEnabled,
- // whether the user wants it enabled..
- count: 0,
- currentMax: max || Infinity,
- defaultMax: max || Infinity,
- enable: function enable() {
- this.enabled = true;
- this.currentMax = this.defaultMax;
- },
- disable: function disable() {
- this.enabled = false;
- this.currentMax = 0;
- },
- hidden: function hidden() {
- return this.count === 0 && !this.enabled || this.count > this.currentMax * _cullFactor;
- },
- autoHidden: function autoHidden() {
- return this.hidden() && this.currentMax > 0;
- }
+ 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
};
- }
- defineRule('points', function isPoint(tags, geometry) {
- return geometry === 'point';
- }, 200);
- defineRule('traffic_roads', function isTrafficRoad(tags) {
- return traffic_roads[tags.highway];
- });
- defineRule('service_roads', function isServiceRoad(tags) {
- return service_roads[tags.highway];
- });
- defineRule('paths', function isPath(tags) {
- return paths[tags.highway];
- });
- defineRule('buildings', function isBuilding(tags) {
- return !!tags.building && tags.building !== 'no' || tags.parking === 'multi-storey' || tags.parking === 'sheds' || tags.parking === 'carports' || tags.parking === 'garage_boxes';
- }, 250);
- defineRule('building_parts', function isBuildingPart(tags) {
- return tags['building:part'];
- });
- defineRule('indoor', function isIndoor(tags) {
- return tags.indoor;
- });
- defineRule('landuse', function isLanduse(tags, geometry) {
- return geometry === 'area' && !_rules.buildings.filter(tags) && !_rules.building_parts.filter(tags) && !_rules.indoor.filter(tags) && !_rules.water.filter(tags) && !_rules.pistes.filter(tags);
- });
- defineRule('boundaries', function isBoundary(tags) {
- return !!tags.boundary && !(traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway] || tags.waterway || tags.railway || tags.landuse || tags.natural || tags.building || tags.power);
- });
- defineRule('water', function isWater(tags) {
- return !!tags.waterway || tags.natural === 'water' || tags.natural === 'coastline' || tags.natural === 'bay' || tags.landuse === 'pond' || tags.landuse === 'basin' || tags.landuse === 'reservoir' || tags.landuse === 'salt_pond';
- });
- defineRule('rail', function isRail(tags) {
- return (!!tags.railway || tags.landuse === 'railway') && !(traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway]);
- });
- defineRule('pistes', function isPiste(tags) {
- return tags['piste:type'];
- });
- defineRule('aerialways', function isPiste(tags) {
- return tags.aerialway && tags.aerialway !== 'yes' && tags.aerialway !== 'station';
- });
- defineRule('power', function isPower(tags) {
- return !!tags.power;
- }); // contains a past/future tag, but not in active use as a road/path/cycleway/etc..
+ if (this.options.pedantic) {
+ rules.block = block.pedantic;
+ rules.inline = inline.pedantic;
+ } else if (this.options.gfm) {
+ rules.block = block.gfm;
- defineRule('past_future', function isPastFuture(tags) {
- if (traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway]) {
- return false;
+ if (this.options.breaks) {
+ rules.inline = inline.breaks;
+ } else {
+ rules.inline = inline.gfm;
+ }
}
- var strings = Object.keys(tags);
+ this.tokenizer.rules = rules;
+ }
+ /**
+ * Expose Rules
+ */
- for (var i = 0; i < strings.length; i++) {
- var s = strings[i];
- if (past_futures[s] || past_futures[tags[s]]) {
- return true;
- }
+ _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
+ */
- return false;
- }); // Lines or areas that don't match another feature filter.
- // IMPORTANT: The 'others' feature must be the last one defined,
- // so that code in getMatches can skip this test if `hasMatch = true`
+ }, {
+ 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;
- defineRule('others', function isOther(tags, geometry) {
- return geometry === 'line' || geometry === 'area';
- });
+ if (this.options.pedantic) {
+ src = src.replace(/^ +$/gm, '');
+ }
- features.features = function () {
- return _rules;
- };
+ var token, i, l, lastToken;
- features.keys = function () {
- return _keys;
- };
+ while (src) {
+ // newline
+ if (token = this.tokenizer.space(src)) {
+ src = src.substring(token.raw.length);
- features.enabled = function (k) {
- if (!arguments.length) {
- return _keys.filter(function (k) {
- return _rules[k].enabled;
- });
- }
+ if (token.type) {
+ tokens.push(token);
+ }
- return _rules[k] && _rules[k].enabled;
- };
+ continue;
+ } // code
- features.disabled = function (k) {
- if (!arguments.length) {
- return _keys.filter(function (k) {
- return !_rules[k].enabled;
- });
- }
- return _rules[k] && !_rules[k].enabled;
- };
+ 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.
- features.hidden = function (k) {
- if (!arguments.length) {
- return _keys.filter(function (k) {
- return _rules[k].hidden();
- });
- }
+ if (lastToken && lastToken.type === 'paragraph') {
+ lastToken.raw += '\n' + token.raw;
+ lastToken.text += '\n' + token.text;
+ } else {
+ tokens.push(token);
+ }
- return _rules[k] && _rules[k].hidden();
- };
+ continue;
+ } // fences
- features.autoHidden = function (k) {
- if (!arguments.length) {
- return _keys.filter(function (k) {
- return _rules[k].autoHidden();
- });
- }
- return _rules[k] && _rules[k].autoHidden();
- };
+ if (token = this.tokenizer.fences(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // heading
- features.enable = function (k) {
- if (_rules[k] && !_rules[k].enabled) {
- _rules[k].enable();
- update();
- }
- };
+ if (token = this.tokenizer.heading(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // table no leading pipe (gfm)
- features.enableAll = function () {
- var didEnable = false;
- for (var k in _rules) {
- if (!_rules[k].enabled) {
- didEnable = true;
+ if (token = this.tokenizer.nptable(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // hr
- _rules[k].enable();
- }
- }
- if (didEnable) update();
- };
+ if (token = this.tokenizer.hr(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // blockquote
- features.disable = function (k) {
- if (_rules[k] && _rules[k].enabled) {
- _rules[k].disable();
- update();
- }
- };
+ if (token = this.tokenizer.blockquote(src)) {
+ src = src.substring(token.raw.length);
+ token.tokens = this.blockTokens(token.text, [], top);
+ tokens.push(token);
+ continue;
+ } // list
- features.disableAll = function () {
- var didDisable = false;
- for (var k in _rules) {
- if (_rules[k].enabled) {
- didDisable = true;
+ if (token = this.tokenizer.list(src)) {
+ src = src.substring(token.raw.length);
+ l = token.items.length;
- _rules[k].disable();
- }
- }
+ for (i = 0; i < l; i++) {
+ token.items[i].tokens = this.blockTokens(token.items[i].text, [], false);
+ }
- if (didDisable) update();
- };
+ tokens.push(token);
+ continue;
+ } // html
- features.toggle = function (k) {
- if (_rules[k]) {
- (function (f) {
- return f.enabled ? f.disable() : f.enable();
- })(_rules[k]);
- update();
- }
- };
+ if (token = this.tokenizer.html(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // def
- features.resetStats = function () {
- for (var i = 0; i < _keys.length; i++) {
- _rules[_keys[i]].count = 0;
- }
- dispatch.call('change');
- };
+ if (top && (token = this.tokenizer.def(src))) {
+ src = src.substring(token.raw.length);
- features.gatherStats = function (d, resolver, dimensions) {
- var needsRedraw = false;
- var types = utilArrayGroupBy(d, 'type');
- var entities = [].concat(types.relation || [], types.way || [], types.node || []);
- var currHidden, geometry, matches, i, j;
+ if (!this.tokens.links[token.tag]) {
+ this.tokens.links[token.tag] = {
+ href: token.href,
+ title: token.title
+ };
+ }
- for (i = 0; i < _keys.length; i++) {
- _rules[_keys[i]].count = 0;
- } // adjust the threshold for point/building culling based on viewport size..
- // a _cullFactor of 1 corresponds to a 1000x1000px viewport..
+ continue;
+ } // table (gfm)
- _cullFactor = dimensions[0] * dimensions[1] / 1000000;
+ if (token = this.tokenizer.table(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // lheading
- for (i = 0; i < entities.length; i++) {
- geometry = entities[i].geometry(resolver);
- matches = Object.keys(features.getMatches(entities[i], resolver, geometry));
- for (j = 0; j < matches.length; j++) {
- _rules[matches[j]].count++;
- }
- }
+ if (token = this.tokenizer.lheading(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // top-level paragraph
- currHidden = features.hidden();
- if (currHidden !== _hidden) {
- _hidden = currHidden;
- needsRedraw = true;
- dispatch.call('change');
- }
+ if (top && (token = this.tokenizer.paragraph(src))) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // text
- return needsRedraw;
- };
- features.stats = function () {
- for (var i = 0; i < _keys.length; i++) {
- _stats[_keys[i]] = _rules[_keys[i]].count;
- }
+ if (token = this.tokenizer.text(src)) {
+ src = src.substring(token.raw.length);
+ lastToken = tokens[tokens.length - 1];
- return _stats;
- };
+ if (lastToken && lastToken.type === 'text') {
+ lastToken.raw += '\n' + token.raw;
+ lastToken.text += '\n' + token.text;
+ } else {
+ tokens.push(token);
+ }
- features.clear = function (d) {
- for (var i = 0; i < d.length; i++) {
- features.clearEntity(d[i]);
+ continue;
+ }
+
+ if (src) {
+ var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ break;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
+
+ return tokens;
}
- };
+ }, {
+ key: "inline",
+ value: function inline(tokens) {
+ var i, j, k, l2, row, token;
+ var l = tokens.length;
- features.clearEntity = function (entity) {
- delete _cache[osmEntity.key(entity)];
- };
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
- features.reset = function () {
- Array.from(_deferred).forEach(function (handle) {
- window.cancelIdleCallback(handle);
+ switch (token.type) {
+ case 'paragraph':
+ case 'text':
+ case 'heading':
+ {
+ token.tokens = [];
+ this.inlineTokens(token.text, token.tokens);
+ break;
+ }
- _deferred["delete"](handle);
- });
- _cache = {};
- }; // only certain relations are worth checking
+ case 'table':
+ {
+ token.tokens = {
+ header: [],
+ cells: []
+ }; // header
+ l2 = token.header.length;
- function relationShouldBeChecked(relation) {
- // multipolygon features have `area` geometry and aren't checked here
- return relation.tags.type === 'boundary';
- }
+ for (j = 0; j < l2; j++) {
+ token.tokens.header[j] = [];
+ this.inlineTokens(token.header[j], token.tokens.header[j]);
+ } // cells
- features.getMatches = function (entity, resolver, geometry) {
- if (geometry === 'vertex' || geometry === 'relation' && !relationShouldBeChecked(entity)) return {};
- var ent = osmEntity.key(entity);
- if (!_cache[ent]) {
- _cache[ent] = {};
- }
+ l2 = token.cells.length;
- if (!_cache[ent].matches) {
- var matches = {};
- var hasMatch = false;
+ for (j = 0; j < l2; j++) {
+ row = token.cells[j];
+ token.tokens.cells[j] = [];
- for (var i = 0; i < _keys.length; i++) {
- if (_keys[i] === 'others') {
- if (hasMatch) continue; // If an entity...
- // 1. is a way that hasn't matched other 'interesting' feature rules,
+ for (k = 0; k < row.length; k++) {
+ token.tokens.cells[j][k] = [];
+ this.inlineTokens(row[k], token.tokens.cells[j][k]);
+ }
+ }
- if (entity.type === 'way') {
- var parents = features.getParents(entity, resolver, geometry); // 2a. belongs only to a single multipolygon relation
+ break;
+ }
- if (parents.length === 1 && parents[0].isMultipolygon() || // 2b. or belongs only to boundary relations
- parents.length > 0 && parents.every(function (parent) {
- return parent.tags.type === 'boundary';
- })) {
- // ...then match whatever feature rules the parent relation has matched.
- // see #2548, #2887
- //
- // IMPORTANT:
- // For this to work, getMatches must be called on relations before ways.
- //
- var pkey = osmEntity.key(parents[0]);
+ case 'blockquote':
+ {
+ this.inline(token.tokens);
+ break;
+ }
- if (_cache[pkey] && _cache[pkey].matches) {
- matches = Object.assign({}, _cache[pkey].matches); // shallow copy
+ case 'list':
+ {
+ l2 = token.items.length;
- continue;
+ for (j = 0; j < l2; j++) {
+ this.inline(token.items[j].tokens);
}
- }
- }
- }
- if (_rules[_keys[i]].filter(entity.tags, geometry)) {
- matches[_keys[i]] = hasMatch = true;
+ break;
+ }
}
}
- _cache[ent].matches = matches;
+ return tokens;
}
+ /**
+ * Lexing/Compiling
+ */
- return _cache[ent].matches;
- };
+ }, {
+ 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
- features.getParents = function (entity, resolver, geometry) {
- if (geometry === 'point') return [];
- var ent = osmEntity.key(entity);
+ var maskedSrc = src;
+ var match;
+ var keepPrevChar, prevChar; // Mask out reflinks
- if (!_cache[ent]) {
- _cache[ent] = {};
- }
+ if (this.tokens.links) {
+ var links = Object.keys(this.tokens.links);
- if (!_cache[ent].parents) {
- var parents = [];
+ 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);
+ }
+ }
+ }
+ } // Mask out other blocks
- if (geometry === 'vertex') {
- parents = resolver.parentWays(entity);
- } else {
- // 'line', 'area', 'relation'
- parents = resolver.parentRelations(entity);
- }
- _cache[ent].parents = parents;
- }
+ 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 _cache[ent].parents;
- };
- features.isHiddenPreset = function (preset, geometry) {
- if (!_hidden.length) return false;
- if (!preset.tags) return false;
- var test = preset.setTags({}, geometry);
+ while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) {
+ maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
+ }
- for (var key in _rules) {
- if (_rules[key].filter(test, geometry)) {
- if (_hidden.indexOf(key) !== -1) {
- return key;
+ while (src) {
+ if (!keepPrevChar) {
+ prevChar = '';
}
- return false;
- }
- }
+ keepPrevChar = false; // escape
- return false;
- };
+ if (token = this.tokenizer.escape(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // tag
- features.isHiddenFeature = function (entity, resolver, geometry) {
- if (!_hidden.length) return false;
- if (!entity.version) return false;
- if (_forceVisible[entity.id]) return false;
- var matches = Object.keys(features.getMatches(entity, resolver, geometry));
- return matches.length && matches.every(function (k) {
- return features.hidden(k);
- });
- };
- features.isHiddenChild = function (entity, resolver, geometry) {
- if (!_hidden.length) return false;
- if (!entity.version || geometry === 'point') return false;
- if (_forceVisible[entity.id]) return false;
- var parents = features.getParents(entity, resolver, geometry);
- if (!parents.length) return false;
+ 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];
- for (var i = 0; i < parents.length; i++) {
- if (!features.isHidden(parents[i], resolver, parents[i].geometry(resolver))) {
- return false;
- }
- }
+ if (_lastToken && token.type === 'text' && _lastToken.type === 'text') {
+ _lastToken.raw += token.raw;
+ _lastToken.text += token.text;
+ } else {
+ tokens.push(token);
+ }
- return true;
- };
+ continue;
+ } // link
- features.hasHiddenConnections = function (entity, resolver) {
- if (!_hidden.length) return false;
- var childNodes, connections;
- if (entity.type === 'midpoint') {
- childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
- connections = [];
- } else {
- childNodes = entity.nodes ? resolver.childNodes(entity) : [];
- connections = features.getParents(entity, resolver, entity.geometry(resolver));
- } // gather ways connected to child nodes..
+ if (token = this.tokenizer.link(src)) {
+ src = src.substring(token.raw.length);
+ if (token.type === 'link') {
+ token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
+ }
- connections = childNodes.reduce(function (result, e) {
- return resolver.isShared(e) ? utilArrayUnion(result, resolver.parentWays(e)) : result;
- }, connections);
- return connections.some(function (e) {
- return features.isHidden(e, resolver, e.geometry(resolver));
- });
- };
+ tokens.push(token);
+ continue;
+ } // reflink, nolink
- features.isHidden = function (entity, resolver, geometry) {
- if (!_hidden.length) return false;
- if (!entity.version) return false;
- var fn = geometry === 'vertex' ? features.isHiddenChild : features.isHiddenFeature;
- return fn(entity, resolver, geometry);
- };
- features.filter = function (d, resolver) {
- if (!_hidden.length) return d;
- var result = [];
+ if (token = this.tokenizer.reflink(src, this.tokens.links)) {
+ src = src.substring(token.raw.length);
+ var _lastToken2 = tokens[tokens.length - 1];
- for (var i = 0; i < d.length; i++) {
- var entity = d[i];
+ 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 (!features.isHidden(entity, resolver, entity.geometry(resolver))) {
- result.push(entity);
- }
- }
+ continue;
+ } // em & strong
- return result;
- };
- features.forceVisible = function (entityIDs) {
- if (!arguments.length) return Object.keys(_forceVisible);
- _forceVisible = {};
+ 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
- for (var i = 0; i < entityIDs.length; i++) {
- _forceVisible[entityIDs[i]] = true;
- var entity = context.hasEntity(entityIDs[i]);
- if (entity && entity.type === 'relation') {
- // also show relation members (one level deep)
- for (var j in entity.members) {
- _forceVisible[entity.members[j].id] = true;
- }
- }
- }
+ if (token = this.tokenizer.codespan(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // br
- return features;
- };
- features.init = function () {
- var storage = corePreferences('disabled-features');
+ if (token = this.tokenizer.br(src)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // del (gfm)
- if (storage) {
- var storageDisabled = storage.replace(/;/g, ',').split(',');
- storageDisabled.forEach(features.disable);
- }
- var hash = utilStringQs(window.location.hash);
+ 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
- if (hash.disable_features) {
- var hashDisabled = hash.disable_features.replace(/;/g, ',').split(',');
- hashDisabled.forEach(features.disable);
- }
- }; // warm up the feature matching cache upon merging fetched data
+ if (token = this.tokenizer.autolink(src, mangle)) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // url (gfm)
- context.history().on('merge.features', function (newEntities) {
- if (!newEntities) return;
- var handle = window.requestIdleCallback(function () {
- var graph = context.graph();
- var types = utilArrayGroupBy(newEntities, 'type'); // ensure that getMatches is called on relations before ways
- var entities = [].concat(types.relation || [], types.way || [], types.node || []);
+ if (!inLink && (token = this.tokenizer.url(src, mangle))) {
+ src = src.substring(token.raw.length);
+ tokens.push(token);
+ continue;
+ } // text
- for (var i = 0; i < entities.length; i++) {
- var geometry = entities[i].geometry(graph);
- features.getMatches(entities[i], graph, geometry);
- }
- });
- _deferred.add(handle);
- });
- return features;
- }
-
- /** Error message constants. */
-
- var FUNC_ERROR_TEXT = 'Expected a function';
- /**
- * 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);
- */
-
- function throttle(func, wait, options) {
- var leading = true,
- trailing = true;
-
- if (typeof func != 'function') {
- throw new TypeError(FUNC_ERROR_TEXT);
- }
-
- if (isObject$2(options)) {
- leading = 'leading' in options ? !!options.leading : leading;
- trailing = 'trailing' in options ? !!options.trailing : trailing;
- }
+ if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
+ src = src.substring(token.raw.length);
- return debounce(func, wait, {
- 'leading': leading,
- 'maxWait': wait,
- 'trailing': trailing
- });
- }
+ if (token.raw.slice(-1) !== '_') {
+ // Track prevChar before string of ____ started
+ prevChar = token.raw.slice(-1);
+ }
- //
- // - the activeID - nope
- // - 1 away (adjacent) to the activeID - yes (vertices will be merged)
- // - 2 away from the activeID - nope (would create a self intersecting segment)
- // - all others on a linear way - yes
- // - all others on a closed way - nope (would create a self intersecting polygon)
- //
- // returns
- // 0 = active vertex - no touch/connect
- // 1 = passive vertex - yes touch/connect
- // 2 = adjacent vertex - yes but pay attention segmenting a line here
- //
+ keepPrevChar = true;
+ lastToken = tokens[tokens.length - 1];
- function svgPassiveVertex(node, graph, activeID) {
- if (!activeID) return 1;
- if (activeID === node.id) return 0;
- var parents = graph.parentWays(node);
- var i, j, nodes, isClosed, ix1, ix2, ix3, ix4, max;
+ if (lastToken && lastToken.type === 'text') {
+ lastToken.raw += token.raw;
+ lastToken.text += token.text;
+ } else {
+ tokens.push(token);
+ }
- for (i = 0; i < parents.length; i++) {
- nodes = parents[i].nodes;
- isClosed = parents[i].isClosed();
+ continue;
+ }
- for (j = 0; j < nodes.length; j++) {
- // find this vertex, look nearby
- if (nodes[j] === node.id) {
- ix1 = j - 2;
- ix2 = j - 1;
- ix3 = j + 1;
- ix4 = j + 2;
+ if (src) {
+ var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
- if (isClosed) {
- // wraparound if needed
- max = nodes.length - 1;
- if (ix1 < 0) ix1 = max + ix1;
- if (ix2 < 0) ix2 = max + ix2;
- if (ix3 > max) ix3 = ix3 - max;
- if (ix4 > max) ix4 = ix4 - max;
+ if (this.options.silent) {
+ console.error(errMsg);
+ break;
+ } else {
+ throw new Error(errMsg);
+ }
}
-
- if (nodes[ix1] === activeID) return 0; // no - prevent self intersect
- else if (nodes[ix2] === activeID) return 2; // ok - adjacent
- else if (nodes[ix3] === activeID) return 2; // ok - adjacent
- else if (nodes[ix4] === activeID) return 0; // no - prevent self intersect
- else if (isClosed && nodes.indexOf(activeID) !== -1) return 0; // no - prevent self intersect
}
- }
- }
-
- return 1; // ok
- }
- function svgMarkerSegments(projection, graph, dt, shouldReverse, bothDirections) {
- return function (entity) {
- var i = 0;
- var offset = dt;
- var segments = [];
- var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
- var coordinates = graph.childNodes(entity).map(function (n) {
- return n.loc;
- });
- var a, b;
- if (shouldReverse(entity)) {
- coordinates.reverse();
+ return tokens;
+ }
+ }], [{
+ key: "rules",
+ get: function get() {
+ return {
+ block: block,
+ inline: inline
+ };
}
+ /**
+ * Static Lex Method
+ */
- d3_geoStream({
- type: 'LineString',
- coordinates: coordinates
- }, projection.stream(clip({
- lineStart: function lineStart() {},
- lineEnd: function lineEnd() {
- a = null;
- },
- point: function point(x, y) {
- b = [x, y];
+ }, {
+ key: "lex",
+ value: function lex(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.lex(src);
+ }
+ /**
+ * Static Lex Inline Method
+ */
- if (a) {
- var span = geoVecLength(a, b) - offset;
+ }, {
+ key: "lexInline",
+ value: function lexInline(src, options) {
+ var lexer = new Lexer(options);
+ return lexer.inlineTokens(src);
+ }
+ }]);
- if (span >= 0) {
- var heading = geoVecAngle(a, b);
- var dx = dt * Math.cos(heading);
- var dy = dt * Math.sin(heading);
- var p = [a[0] + offset * Math.cos(heading), a[1] + offset * Math.sin(heading)]; // gather coordinates
+ return Lexer;
+ }();
- var coord = [a, p];
+ var defaults$2 = defaults$5.exports.defaults;
+ var cleanUrl = helpers.cleanUrl,
+ escape$2 = helpers.escape;
+ /**
+ * Renderer
+ */
- for (span -= dt; span >= 0; span -= dt) {
- p = geoVecAdd(p, [dx, dy]);
- coord.push(p);
- }
+ var Renderer_1 = /*#__PURE__*/function () {
+ function Renderer(options) {
+ _classCallCheck$1(this, Renderer);
- coord.push(b); // generate svg paths
+ this.options = options || defaults$2;
+ }
- var segment = '';
- var j;
+ _createClass$1(Renderer, [{
+ key: "code",
+ value: function code(_code, infostring, escaped) {
+ var lang = (infostring || '').match(/\S*/)[0];
- for (j = 0; j < coord.length; j++) {
- segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
- }
+ if (this.options.highlight) {
+ var out = this.options.highlight(_code, lang);
- segments.push({
- id: entity.id,
- index: i++,
- d: segment
- });
+ if (out != null && out !== _code) {
+ escaped = true;
+ _code = out;
+ }
+ }
- if (bothDirections(entity)) {
- segment = '';
+ _code = _code.replace(/\n$/, '') + '\n';
- for (j = coord.length - 1; j >= 0; j--) {
- segment += (j === coord.length - 1 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
- }
+ if (!lang) {
+ return '<pre><code>' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
+ }
- segments.push({
- id: entity.id,
- index: i++,
- d: segment
- });
- }
- }
+ return '<pre><code class="' + this.options.langPrefix + escape$2(lang, true) + '">' + (escaped ? _code : escape$2(_code, true)) + '</code></pre>\n';
+ }
+ }, {
+ key: "blockquote",
+ value: function blockquote(quote) {
+ return '<blockquote>\n' + quote + '</blockquote>\n';
+ }
+ }, {
+ key: "html",
+ value: function html(_html) {
+ return _html;
+ }
+ }, {
+ key: "heading",
+ value: function heading(text, level, raw, slugger) {
+ if (this.options.headerIds) {
+ return '<h' + level + ' id="' + this.options.headerPrefix + slugger.slug(raw) + '">' + text + '</h' + level + '>\n';
+ } // ignore IDs
- offset = -span;
- }
- a = b;
- }
- })));
- return segments;
- };
- }
- function svgPath(projection, graph, isArea) {
- // Explanation of magic numbers:
- // "padding" here allows space for strokes to extend beyond the viewport,
- // so that the stroke isn't drawn along the edge of the viewport when
- // the shape is clipped.
- //
- // When drawing lines, pad viewport by 5px.
- // When drawing areas, pad viewport by 65px in each direction to allow
- // for 60px area fill stroke (see ".fill-partial path.fill" css rule)
- var cache = {};
- var padding = isArea ? 65 : 5;
- var viewport = projection.clipExtent();
- var paddedExtent = [[viewport[0][0] - padding, viewport[0][1] - padding], [viewport[1][0] + padding, viewport[1][1] + padding]];
- var clip = d3_geoIdentity().clipExtent(paddedExtent).stream;
- var project = projection.stream;
- var path = d3_geoPath().projection({
- stream: function stream(output) {
- return project(clip(output));
+ return '<h' + level + '>' + text + '</h' + level + '>\n';
}
- });
+ }, {
+ key: "hr",
+ value: function hr() {
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\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 '<li>' + text + '</li>\n';
+ }
+ }, {
+ key: "checkbox",
+ value: function checkbox(checked) {
+ return '<input ' + (checked ? 'checked="" ' : '') + 'disabled="" type="checkbox"' + (this.options.xhtml ? ' /' : '') + '> ';
+ }
+ }, {
+ key: "paragraph",
+ value: function paragraph(text) {
+ return '<p>' + text + '</p>\n';
+ }
+ }, {
+ key: "table",
+ value: function table(header, body) {
+ if (body) body = '<tbody>' + body + '</tbody>';
+ return '<table>\n' + '<thead>\n' + header + '</thead>\n' + body + '</table>\n';
+ }
+ }, {
+ key: "tablerow",
+ value: function tablerow(content) {
+ return '<tr>\n' + content + '</tr>\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 svgpath = function svgpath(entity) {
- if (entity.id in cache) {
- return cache[entity.id];
- } else {
- return cache[entity.id] = path(entity.asGeoJSON(graph));
+ }, {
+ key: "strong",
+ value: function strong(text) {
+ return '<strong>' + text + '</strong>';
}
- };
+ }, {
+ key: "em",
+ value: function em(text) {
+ return '<em>' + text + '</em>';
+ }
+ }, {
+ key: "codespan",
+ value: function codespan(text) {
+ return '<code>' + text + '</code>';
+ }
+ }, {
+ key: "br",
+ value: function br() {
+ return this.options.xhtml ? '<br/>' : '<br>';
+ }
+ }, {
+ key: "del",
+ value: function del(text) {
+ return '<del>' + text + '</del>';
+ }
+ }, {
+ key: "link",
+ value: function link(href, title, text) {
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
- svgpath.geojson = function (d) {
- if (d.__featurehash__ !== undefined) {
- if (d.__featurehash__ in cache) {
- return cache[d.__featurehash__];
- } else {
- return cache[d.__featurehash__] = path(d);
+ if (href === null) {
+ return text;
}
- } else {
- return path(d);
- }
- };
- return svgpath;
- }
- function svgPointTransform(projection) {
- var svgpoint = function svgpoint(entity) {
- // http://jsperf.com/short-array-join
- var pt = projection(entity.loc);
- return 'translate(' + pt[0] + ',' + pt[1] + ')';
- };
+ var out = '<a href="' + escape$2(href) + '"';
- svgpoint.geojson = function (d) {
- return svgpoint(d.properties.entity);
- };
+ if (title) {
+ out += ' title="' + title + '"';
+ }
- return svgpoint;
- }
- function svgRelationMemberTags(graph) {
- return function (entity) {
- var tags = entity.tags;
- var shouldCopyMultipolygonTags = !entity.hasInterestingTags();
- graph.parentRelations(entity).forEach(function (relation) {
- var type = relation.tags.type;
+ out += '>' + text + '</a>';
+ return out;
+ }
+ }, {
+ key: "image",
+ value: function image(href, title, text) {
+ href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
- if (type === 'multipolygon' && shouldCopyMultipolygonTags || type === 'boundary') {
- tags = Object.assign({}, relation.tags, tags);
+ if (href === null) {
+ return text;
}
- });
- return tags;
- };
- }
- function svgSegmentWay(way, graph, activeID) {
- // When there is no activeID, we can memoize this expensive computation
- if (activeID === undefined) {
- return graph["transient"](way, 'waySegments', getWaySegments);
- } else {
- return getWaySegments();
- }
-
- function getWaySegments() {
- var isActiveWay = way.nodes.indexOf(activeID) !== -1;
- var features = {
- passive: [],
- active: []
- };
- var start = {};
- var end = {};
- var node, type;
- for (var i = 0; i < way.nodes.length; i++) {
- node = graph.entity(way.nodes[i]);
- type = svgPassiveVertex(node, graph, activeID);
- end = {
- node: node,
- type: type
- };
+ var out = '<img src="' + href + '" alt="' + text + '"';
- if (start.type !== undefined) {
- if (start.node.id === activeID || end.node.id === activeID) ; else if (isActiveWay && (start.type === 2 || end.type === 2)) {
- // one adjacent vertex
- pushActive(start, end, i);
- } else if (start.type === 0 && end.type === 0) {
- // both active vertices
- pushActive(start, end, i);
- } else {
- pushPassive(start, end, i);
- }
+ if (title) {
+ out += ' title="' + title + '"';
}
- start = end;
+ out += this.options.xhtml ? '/>' : '>';
+ return out;
}
-
- return features;
-
- function pushActive(start, end, index) {
- features.active.push({
- type: 'Feature',
- id: way.id + '-' + index + '-nope',
- properties: {
- nope: true,
- target: true,
- entity: way,
- nodes: [start.node, end.node],
- index: index
- },
- geometry: {
- type: 'LineString',
- coordinates: [start.node.loc, end.node.loc]
- }
- });
+ }, {
+ key: "text",
+ value: function text(_text) {
+ return _text;
}
+ }]);
- function pushPassive(start, end, index) {
- features.passive.push({
- type: 'Feature',
- id: way.id + '-' + index,
- properties: {
- target: true,
- entity: way,
- nodes: [start.node, end.node],
- index: index
- },
- geometry: {
- type: 'LineString',
- coordinates: [start.node.loc, end.node.loc]
- }
- });
- }
+ return Renderer;
+ }();
+
+ var TextRenderer_1 = /*#__PURE__*/function () {
+ function TextRenderer() {
+ _classCallCheck$1(this, TextRenderer);
}
- }
- function svgTagClasses() {
- var primaries = ['building', 'highway', 'railway', 'waterway', 'aeroway', 'aerialway', 'piste:type', 'boundary', 'power', 'amenity', 'natural', 'landuse', 'leisure', 'military', 'place', 'man_made', 'route', 'attraction', 'building:part', 'indoor'];
- var statuses = [// nonexistent, might be built
- 'proposed', 'planned', // under maintentance or between groundbreaking and opening
- 'construction', // existent but not functional
- 'disused', // dilapidated to nonexistent
- 'abandoned', // nonexistent, still may appear in imagery
- 'dismantled', 'razed', 'demolished', 'obliterated', // existent occasionally, e.g. stormwater drainage basin
- 'intermittent'];
- var secondaries = ['oneway', 'bridge', 'tunnel', 'embankment', 'cutting', 'barrier', 'surface', 'tracktype', 'footway', 'crossing', 'service', 'sport', 'public_transport', 'location', 'parking', 'golf', 'type', 'leisure', 'man_made', 'indoor'];
-
- var _tags = function _tags(entity) {
- return entity.tags;
- };
-
- var tagClasses = function tagClasses(selection) {
- selection.each(function tagClassesEach(entity) {
- var value = this.className;
+ _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 '';
+ }
+ }]);
- if (value.baseVal !== undefined) {
- value = value.baseVal;
- }
+ return TextRenderer;
+ }();
- var t = _tags(entity);
+ var Slugger_1 = /*#__PURE__*/function () {
+ function Slugger() {
+ _classCallCheck$1(this, Slugger);
- var computed = tagClasses.getClassesString(t, value);
+ this.seen = {};
+ }
- if (computed !== value) {
- select(this).attr('class', computed);
- }
- });
- };
+ _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
+ */
- tagClasses.getClassesString = function (t, value) {
- var primary, status;
- var i, j, k, v; // in some situations we want to render perimeter strokes a certain way
+ }, {
+ key: "getNextSafeSlug",
+ value: function getNextSafeSlug(originalSlug, isDryRun) {
+ var slug = originalSlug;
+ var occurenceAccumulator = 0;
- var overrideGeometry;
+ if (this.seen.hasOwnProperty(slug)) {
+ occurenceAccumulator = this.seen[originalSlug];
- if (/\bstroke\b/.test(value)) {
- if (!!t.barrier && t.barrier !== 'no') {
- overrideGeometry = 'line';
+ do {
+ occurenceAccumulator++;
+ slug = originalSlug + '-' + occurenceAccumulator;
+ } while (this.seen.hasOwnProperty(slug));
}
- } // preserve base classes (nothing with `tag-`)
-
-
- var classes = value.trim().split(/\s+/).filter(function (klass) {
- return klass.length && !/^tag-/.test(klass);
- }).map(function (klass) {
- // special overrides for some perimeter strokes
- return klass === 'line' || klass === 'area' ? overrideGeometry || klass : klass;
- }); // pick at most one primary classification tag..
-
- for (i = 0; i < primaries.length; i++) {
- k = primaries[i];
- v = t[k];
- if (!v || v === 'no') continue;
- if (k === 'piste:type') {
- // avoid a ':' in the class name
- k = 'piste';
- } else if (k === 'building:part') {
- // avoid a ':' in the class name
- k = 'building_part';
+ if (!isDryRun) {
+ this.seen[originalSlug] = occurenceAccumulator;
+ this.seen[slug] = 0;
}
- primary = k;
-
- if (statuses.indexOf(v) !== -1) {
- // e.g. `railway=abandoned`
- status = v;
- classes.push('tag-' + k);
- } else {
- classes.push('tag-' + k);
- classes.push('tag-' + k + '-' + v);
- }
+ return slug;
+ }
+ /**
+ * Convert string to unique id
+ * @param {object} options
+ * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator.
+ */
- break;
+ }, {
+ 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);
}
+ }]);
- if (!primary) {
- for (i = 0; i < statuses.length; i++) {
- for (j = 0; j < primaries.length; j++) {
- k = statuses[i] + ':' + primaries[j]; // e.g. `demolished:building=yes`
+ return Slugger;
+ }();
- v = t[k];
- if (!v || v === 'no') continue;
- status = statuses[i];
- break;
- }
- }
- } // add at most one status tag, only if relates to primary tag..
+ var Renderer$1 = Renderer_1;
+ var TextRenderer$1 = TextRenderer_1;
+ var Slugger$1 = Slugger_1;
+ var defaults$1 = defaults$5.exports.defaults;
+ var unescape$1 = helpers.unescape;
+ /**
+ * Parsing & Compiling
+ */
+ var Parser_1 = /*#__PURE__*/function () {
+ function Parser(options) {
+ _classCallCheck$1(this, Parser);
- if (!status) {
- for (i = 0; i < statuses.length; i++) {
- k = statuses[i];
- v = t[k];
- if (!v || v === 'no') continue;
+ 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
+ */
- if (v === 'yes') {
- // e.g. `railway=rail + abandoned=yes`
- status = k;
- } else if (primary && primary === v) {
- // e.g. `railway=rail + abandoned=railway`
- status = k;
- } else if (!primary && primaries.indexOf(v) !== -1) {
- // e.g. `abandoned=railway`
- status = k;
- primary = v;
- classes.push('tag-' + v);
- } // else ignore e.g. `highway=path + abandoned=railway`
+ _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;
- if (status) break;
- }
- }
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
- if (status) {
- classes.push('tag-status');
- classes.push('tag-status-' + status);
- } // add any secondary tags
+ switch (token.type) {
+ case 'space':
+ {
+ continue;
+ }
+ case 'hr':
+ {
+ out += this.renderer.hr();
+ continue;
+ }
- for (i = 0; i < secondaries.length; i++) {
- k = secondaries[i];
- v = t[k];
- if (!v || v === 'no' || k === primary) continue;
- classes.push('tag-' + k);
- classes.push('tag-' + k + '-' + v);
- } // For highways, look for surface tagging..
+ case 'heading':
+ {
+ out += this.renderer.heading(this.parseInline(token.tokens), token.depth, unescape$1(this.parseInline(token.tokens, this.textRenderer)), this.slugger);
+ continue;
+ }
+ case 'code':
+ {
+ out += this.renderer.code(token.text, token.lang, token.escaped);
+ continue;
+ }
- if (primary === 'highway' && !osmPathHighwayTagValues[t.highway] || primary === 'aeroway') {
- var surface = t.highway === 'track' ? 'unpaved' : 'paved';
+ case 'table':
+ {
+ header = ''; // header
- for (k in t) {
- v = t[k];
+ cell = '';
+ l2 = token.header.length;
- if (k in osmPavedTags) {
- surface = osmPavedTags[k][v] ? 'paved' : 'unpaved';
- }
+ for (j = 0; j < l2; j++) {
+ cell += this.renderer.tablecell(this.parseInline(token.tokens.header[j]), {
+ header: true,
+ align: token.align[j]
+ });
+ }
- if (k in osmSemipavedTags && !!osmSemipavedTags[k][v]) {
- surface = 'semipaved';
- }
- }
+ header += this.renderer.tablerow(cell);
+ body = '';
+ l2 = token.cells.length;
- classes.push('tag-' + surface);
- } // If this is a wikidata-tagged item, add a class for that..
+ for (j = 0; j < l2; j++) {
+ row = token.tokens.cells[j];
+ cell = '';
+ l3 = row.length;
+ for (k = 0; k < l3; k++) {
+ cell += this.renderer.tablecell(this.parseInline(row[k]), {
+ header: false,
+ align: token.align[k]
+ });
+ }
- var qid = t.wikidata || t['flag:wikidata'] || t['brand:wikidata'] || t['network:wikidata'] || t['operator:wikidata'];
+ body += this.renderer.tablerow(cell);
+ }
- if (qid) {
- classes.push('tag-wikidata');
- }
+ out += this.renderer.table(header, body);
+ continue;
+ }
- return classes.join(' ').trim();
- };
+ case 'blockquote':
+ {
+ body = this.parse(token.tokens);
+ out += this.renderer.blockquote(body);
+ continue;
+ }
- tagClasses.tags = function (val) {
- if (!arguments.length) return _tags;
- _tags = val;
- return tagClasses;
- };
+ case 'list':
+ {
+ ordered = token.ordered;
+ start = token.start;
+ loose = token.loose;
+ l2 = token.items.length;
+ body = '';
- return tagClasses;
- }
+ for (j = 0; j < l2; j++) {
+ item = token.items[j];
+ checked = item.checked;
+ task = item.task;
+ itemBody = '';
- // Patterns only work in Firefox when set directly on element.
- // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632)
- var patterns = {
- // tag - pattern name
- // -or-
- // tag - value - pattern name
- // -or-
- // tag - value - rules (optional tag-values, pattern name)
- // (matches earlier rules first, so fallback should be last entry)
- amenity: {
- grave_yard: 'cemetery',
- fountain: 'water_standing'
- },
- landuse: {
- cemetery: [{
- religion: 'christian',
- pattern: 'cemetery_christian'
- }, {
- religion: 'buddhist',
- pattern: 'cemetery_buddhist'
- }, {
- religion: 'muslim',
- pattern: 'cemetery_muslim'
- }, {
- religion: 'jewish',
- pattern: 'cemetery_jewish'
- }, {
- pattern: 'cemetery'
- }],
- construction: 'construction',
- farmland: 'farmland',
- farmyard: 'farmyard',
- forest: [{
- leaf_type: 'broadleaved',
- pattern: 'forest_broadleaved'
- }, {
- leaf_type: 'needleleaved',
- pattern: 'forest_needleleaved'
- }, {
- leaf_type: 'leafless',
- pattern: 'forest_leafless'
- }, {
- pattern: 'forest'
- } // same as 'leaf_type:mixed'
- ],
- grave_yard: 'cemetery',
- grass: [{
- golf: 'green',
- pattern: 'golf_green'
- }, {
- pattern: 'grass'
- }],
- landfill: 'landfill',
- meadow: 'meadow',
- military: 'construction',
- orchard: 'orchard',
- quarry: 'quarry',
- vineyard: 'vineyard'
- },
- natural: {
- beach: 'beach',
- grassland: 'grass',
- sand: 'beach',
- scrub: 'scrub',
- water: [{
- water: 'pond',
- pattern: 'pond'
- }, {
- water: 'reservoir',
- pattern: 'water_standing'
- }, {
- pattern: 'waves'
- }],
- wetland: [{
- wetland: 'marsh',
- pattern: 'wetland_marsh'
- }, {
- wetland: 'swamp',
- pattern: 'wetland_swamp'
- }, {
- wetland: 'bog',
- pattern: 'wetland_bog'
- }, {
- wetland: 'reedbed',
- pattern: 'wetland_reedbed'
- }, {
- pattern: 'wetland'
- }],
- wood: [{
- leaf_type: 'broadleaved',
- pattern: 'forest_broadleaved'
- }, {
- leaf_type: 'needleleaved',
- pattern: 'forest_needleleaved'
- }, {
- leaf_type: 'leafless',
- pattern: 'forest_leafless'
- }, {
- pattern: 'forest'
- } // same as 'leaf_type:mixed'
- ]
- },
- traffic_calming: {
- island: [{
- surface: 'grass',
- pattern: 'grass'
- }],
- chicane: [{
- surface: 'grass',
- pattern: 'grass'
- }],
- choker: [{
- surface: 'grass',
- pattern: 'grass'
- }]
- }
- };
- function svgTagPattern(tags) {
- // Skip pattern filling if this is a building (buildings don't get patterns applied)
- if (tags.building && tags.building !== 'no') {
- return null;
- }
+ if (item.task) {
+ checkbox = this.renderer.checkbox(checked);
- for (var tag in patterns) {
- var entityValue = tags[tag];
- if (!entityValue) continue;
+ if (loose) {
+ if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
+ item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
- if (typeof patterns[tag] === 'string') {
- // extra short syntax (just tag) - pattern name
- return 'pattern-' + patterns[tag];
- } else {
- var values = patterns[tag];
+ 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;
+ }
+ }
- for (var value in values) {
- if (entityValue !== value) continue;
- var rules = values[value];
+ itemBody += this.parse(item.tokens, loose);
+ body += this.renderer.listitem(itemBody, task, checked);
+ }
- if (typeof rules === 'string') {
- // short syntax - pattern name
- return 'pattern-' + rules;
- } // long syntax - rule array
+ out += this.renderer.list(body, ordered, start);
+ continue;
+ }
+ case 'html':
+ {
+ // TODO parse inline content if parameter markdown=1
+ out += this.renderer.html(token.text);
+ continue;
+ }
- for (var ruleKey in rules) {
- var rule = rules[ruleKey];
- var pass = true;
+ case 'paragraph':
+ {
+ out += this.renderer.paragraph(this.parseInline(token.tokens));
+ continue;
+ }
- for (var criterion in rule) {
- if (criterion !== 'pattern') {
- // reserved for pattern name
- // The only rule is a required tag-value pair
- var v = tags[criterion];
+ case 'text':
+ {
+ body = token.tokens ? this.parseInline(token.tokens) : token.text;
- if (!v || v !== rule[criterion]) {
- pass = false;
- break;
+ 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;
}
- }
- if (pass) {
- return 'pattern-' + rule.pattern;
- }
+ default:
+ {
+ var errMsg = 'Token with "' + token.type + '" type was not found.';
+
+ if (this.options.silent) {
+ console.error(errMsg);
+ return;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
}
}
- }
- }
- return null;
- }
-
- function svgAreas(projection, context) {
- function getPatternStyle(tags) {
- var imageID = svgTagPattern(tags);
-
- if (imageID) {
- return 'url("#ideditor-' + imageID + '")';
+ return out;
}
+ /**
+ * Parse Inline Tokens
+ */
- return '';
- }
-
- function drawTargets(selection, graph, entities, filter) {
- var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
- var getPath = svgPath(projection).geojson;
- var activeID = context.activeID();
- var base = context.history().base(); // The targets and nopes will be MultiLineString sub-segments of the ways
-
- var data = {
- targets: [],
- nopes: []
- };
- entities.forEach(function (way) {
- var features = svgSegmentWay(way, graph, activeID);
- data.targets.push.apply(data.targets, features.passive);
- data.nopes.push.apply(data.nopes, features.active);
- }); // Targets allow hover and vertex snapping
+ }, {
+ key: "parseInline",
+ value: function parseInline(tokens, renderer) {
+ renderer = renderer || this.renderer;
+ var out = '',
+ i,
+ token;
+ var l = tokens.length;
- var targetData = data.targets.filter(getPath);
- var targets = selection.selectAll('.area.target-allowed').filter(function (d) {
- return filter(d.properties.entity);
- }).data(targetData, function key(d) {
- return d.id;
- }); // exit
+ for (i = 0; i < l; i++) {
+ token = tokens[i];
- targets.exit().remove();
+ switch (token.type) {
+ case 'escape':
+ {
+ out += renderer.text(token.text);
+ break;
+ }
- var segmentWasEdited = function segmentWasEdited(d) {
- var wayID = d.properties.entity.id; // if the whole line was edited, don't draw segment changes
+ case 'html':
+ {
+ out += renderer.html(token.text);
+ break;
+ }
- if (!base.entities[wayID] || !fastDeepEqual(graph.entities[wayID].nodes, base.entities[wayID].nodes)) {
- return false;
- }
+ case 'link':
+ {
+ out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
+ break;
+ }
- return d.properties.nodes.some(function (n) {
- return !base.entities[n.id] || !fastDeepEqual(graph.entities[n.id].loc, base.entities[n.id].loc);
- });
- }; // enter/update
+ case 'image':
+ {
+ out += renderer.image(token.href, token.title, token.text);
+ break;
+ }
+ case 'strong':
+ {
+ out += renderer.strong(this.parseInline(token.tokens, renderer));
+ break;
+ }
- targets.enter().append('path').merge(targets).attr('d', getPath).attr('class', function (d) {
- return 'way area target target-allowed ' + targetClass + d.id;
- }).classed('segment-edited', segmentWasEdited); // NOPE
+ case 'em':
+ {
+ out += renderer.em(this.parseInline(token.tokens, renderer));
+ break;
+ }
- var nopeData = data.nopes.filter(getPath);
- var nopes = selection.selectAll('.area.target-nope').filter(function (d) {
- return filter(d.properties.entity);
- }).data(nopeData, function key(d) {
- return d.id;
- }); // exit
+ case 'codespan':
+ {
+ out += renderer.codespan(token.text);
+ break;
+ }
- nopes.exit().remove(); // enter/update
+ case 'br':
+ {
+ out += renderer.br();
+ break;
+ }
- nopes.enter().append('path').merge(nopes).attr('d', getPath).attr('class', function (d) {
- return 'way area target target-nope ' + nopeClass + d.id;
- }).classed('segment-edited', segmentWasEdited);
- }
+ case 'del':
+ {
+ out += renderer.del(this.parseInline(token.tokens, renderer));
+ break;
+ }
- function drawAreas(selection, graph, entities, filter) {
- var path = svgPath(projection, graph, true);
- var areas = {};
- var multipolygon;
- var base = context.history().base();
+ case 'text':
+ {
+ out += renderer.text(token.text);
+ break;
+ }
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- if (entity.geometry(graph) !== 'area') continue;
- multipolygon = osmIsOldMultipolygonOuterMember(entity, graph);
+ default:
+ {
+ var errMsg = 'Token with "' + token.type + '" type was not found.';
- if (multipolygon) {
- areas[multipolygon.id] = {
- entity: multipolygon.mergeTags(entity.tags),
- area: Math.abs(entity.area(graph))
- };
- } else if (!areas[entity.id]) {
- areas[entity.id] = {
- entity: entity,
- area: Math.abs(entity.area(graph))
- };
+ if (this.options.silent) {
+ console.error(errMsg);
+ return;
+ } else {
+ throw new Error(errMsg);
+ }
+ }
+ }
}
- }
-
- var fills = Object.values(areas).filter(function hasPath(a) {
- return path(a.entity);
- });
- fills.sort(function areaSort(a, b) {
- return b.area - a.area;
- });
- fills = fills.map(function (a) {
- return a.entity;
- });
- var strokes = fills.filter(function (area) {
- return area.type === 'way';
- });
- var data = {
- clip: fills,
- shadow: strokes,
- stroke: strokes,
- fill: fills
- };
- var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath-osm').filter(filter).data(data.clip, osmEntity.key);
- clipPaths.exit().remove();
- var clipPathsEnter = clipPaths.enter().append('clipPath').attr('class', 'clipPath-osm').attr('id', function (entity) {
- return 'ideditor-' + entity.id + '-clippath';
- });
- clipPathsEnter.append('path');
- clipPaths.merge(clipPathsEnter).selectAll('path').attr('d', path);
- var drawLayer = selection.selectAll('.layer-osm.areas');
- var touchLayer = selection.selectAll('.layer-touch.areas'); // Draw areas..
- var areagroup = drawLayer.selectAll('g.areagroup').data(['fill', 'shadow', 'stroke']);
- areagroup = areagroup.enter().append('g').attr('class', function (d) {
- return 'areagroup area-' + d;
- }).merge(areagroup);
- var paths = areagroup.selectAll('path').filter(filter).data(function (layer) {
- return data[layer];
- }, osmEntity.key);
- paths.exit().remove();
- var fillpaths = selection.selectAll('.area-fill path.area').nodes();
- var bisect = d3_bisector(function (node) {
- return -node.__data__.area(graph);
- }).left;
+ return out;
+ }
+ }], [{
+ key: "parse",
+ value: function parse(tokens, options) {
+ var parser = new Parser(options);
+ return parser.parse(tokens);
+ }
+ /**
+ * Static Parse Inline Method
+ */
- function sortedByArea(entity) {
- if (this._parent.__data__ === 'fill') {
- return fillpaths[bisect(fillpaths, -entity.area(graph))];
- }
+ }, {
+ key: "parseInline",
+ value: function parseInline(tokens, options) {
+ var parser = new Parser(options);
+ return parser.parseInline(tokens);
}
+ }]);
- paths = paths.enter().insert('path', sortedByArea).merge(paths).each(function (entity) {
- var layer = this.parentNode.__data__;
- this.setAttribute('class', entity.type + ' area ' + layer + ' ' + entity.id);
+ return Parser;
+ }();
- if (layer === 'fill') {
- this.setAttribute('clip-path', 'url(#ideditor-' + entity.id + '-clippath)');
- this.style.fill = this.style.stroke = getPatternStyle(entity.tags);
- }
- }).classed('added', function (d) {
- return !base.entities[d.id];
- }).classed('geometry-edited', function (d) {
- return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].nodes, base.entities[d.id].nodes);
- }).classed('retagged', function (d) {
- return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
- }).call(svgTagClasses()).attr('d', path); // Draw touch targets..
+ var Lexer = Lexer_1;
+ var Parser = Parser_1;
+ var Tokenizer = Tokenizer_1;
+ var Renderer = Renderer_1;
+ var TextRenderer = TextRenderer_1;
+ var Slugger = Slugger_1;
+ var merge = helpers.merge,
+ checkSanitizeDeprecation = helpers.checkSanitizeDeprecation,
+ escape$1 = helpers.escape;
+ var getDefaults = defaults$5.exports.getDefaults,
+ changeDefaults = defaults$5.exports.changeDefaults,
+ defaults = defaults$5.exports.defaults;
+ /**
+ * Marked
+ */
- touchLayer.call(drawTargets, graph, data.stroke, filter);
+ 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 drawAreas;
- }
+ if (typeof src !== 'string') {
+ throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
+ }
- var fastJsonStableStringify = function fastJsonStableStringify(data, opts) {
- if (!opts) opts = {};
- if (typeof opts === 'function') opts = {
- cmp: opts
- };
- var cycles = typeof opts.cycles === 'boolean' ? opts.cycles : false;
+ if (typeof opt === 'function') {
+ callback = opt;
+ opt = null;
+ }
- var cmp = opts.cmp && function (f) {
- return function (node) {
- return function (a, b) {
- var aobj = {
- key: a,
- value: node[a]
- };
- var bobj = {
- key: b,
- value: node[b]
- };
- return f(aobj, bobj);
- };
- };
- }(opts.cmp);
+ opt = merge({}, marked.defaults, opt || {});
+ checkSanitizeDeprecation(opt);
- var seen = [];
- return function stringify(node) {
- if (node && node.toJSON && typeof node.toJSON === 'function') {
- node = node.toJSON();
+ if (callback) {
+ var highlight = opt.highlight;
+ var tokens;
+
+ try {
+ tokens = Lexer.lex(src, opt);
+ } catch (e) {
+ return callback(e);
}
- if (node === undefined) return;
- if (typeof node == 'number') return isFinite(node) ? '' + node : 'null';
- if (_typeof(node) !== 'object') return JSON.stringify(node);
- var i, out;
+ var done = function done(err) {
+ var out;
- if (Array.isArray(node)) {
- out = '[';
+ if (!err) {
+ try {
+ if (opt.walkTokens) {
+ marked.walkTokens(tokens, opt.walkTokens);
+ }
- for (i = 0; i < node.length; i++) {
- if (i) out += ',';
- out += stringify(node[i]) || 'null';
+ out = Parser.parse(tokens, opt);
+ } catch (e) {
+ err = e;
+ }
}
- return out + ']';
- }
-
- if (node === null) return 'null';
+ opt.highlight = highlight;
+ return err ? callback(err) : callback(null, out);
+ };
- if (seen.indexOf(node) !== -1) {
- if (cycles) return JSON.stringify('__cycle__');
- throw new TypeError('Converting circular structure to JSON');
+ if (!highlight || highlight.length < 3) {
+ return done();
}
- var seenIndex = seen.push(node) - 1;
- var keys = Object.keys(node).sort(cmp && cmp(node));
- out = '';
+ 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);
+ }
- for (i = 0; i < keys.length; i++) {
- var key = keys[i];
- var value = stringify(node[key]);
- if (!value) continue;
- if (out) out += ',';
- out += JSON.stringify(key) + ':' + value;
- }
+ if (code != null && code !== token.text) {
+ token.text = code;
+ token.escaped = true;
+ }
- seen.splice(seenIndex, 1);
- return '{' + out + '}';
- }(data);
- };
+ pending--;
- var $$2 = _export;
- var $entries = objectToArray.entries;
+ if (pending === 0) {
+ done();
+ }
+ });
+ }, 0);
+ }
+ });
- // `Object.entries` method
- // https://tc39.es/ecma262/#sec-object.entries
- $$2({ target: 'Object', stat: true }, {
- entries: function entries(O) {
- return $entries(O);
+ if (pending === 0) {
+ done();
+ }
+
+ return;
}
- });
- var _marked = /*#__PURE__*/regeneratorRuntime.mark(gpxGen),
- _marked3 = /*#__PURE__*/regeneratorRuntime.mark(kmlGen);
+ try {
+ var _tokens = Lexer.lex(src, opt);
- // cast array x into numbers
- // get the content of a text node, if any
- function nodeVal(x) {
- if (x && x.normalize) {
- x.normalize();
- }
+ if (opt.walkTokens) {
+ marked.walkTokens(_tokens, opt.walkTokens);
+ }
- return x && x.textContent || "";
- } // one Y child of X, if any, otherwise null
+ return Parser.parse(_tokens, opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if (opt.silent) {
+ return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
+ }
- function get1(x, y) {
- var n = x.getElementsByTagName(y);
- return n.length ? n[0] : null;
+ throw e;
+ }
}
+ /**
+ * Options
+ */
- function getLineStyle(extensions) {
- var style = {};
-
- if (extensions) {
- var lineStyle = get1(extensions, "line");
- if (lineStyle) {
- var color = nodeVal(get1(lineStyle, "color")),
- opacity = parseFloat(nodeVal(get1(lineStyle, "opacity"))),
- width = parseFloat(nodeVal(get1(lineStyle, "width")));
- if (color) style.stroke = color;
- if (!isNaN(opacity)) style["stroke-opacity"] = opacity; // GPX width is in mm, convert to px with 96 px per inch
+ marked.options = marked.setOptions = function (opt) {
+ merge(marked.defaults, opt);
+ changeDefaults(marked.defaults);
+ return marked;
+ };
- if (!isNaN(width)) style["stroke-width"] = width * 96 / 25.4;
- }
- }
+ marked.getDefaults = getDefaults;
+ marked.defaults = defaults;
+ /**
+ * Use Extension
+ */
- return style;
- } // get the contents of multiple text nodes, if present
+ marked.use = function (extension) {
+ var opts = merge({}, extension);
+ if (extension.renderer) {
+ (function () {
+ var renderer = marked.defaults.renderer || new Renderer();
- function getMulti(x, ys) {
- var o = {};
- var n;
- var k;
+ var _loop = function _loop(prop) {
+ var prevRenderer = renderer[prop];
- for (k = 0; k < ys.length; k++) {
- n = get1(x, ys[k]);
- if (n) o[ys[k]] = nodeVal(n);
- }
+ renderer[prop] = function () {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
- return o;
- }
+ var ret = extension.renderer[prop].apply(renderer, args);
- function getProperties$1(node) {
- var prop = getMulti(node, ["name", "cmt", "desc", "type", "time", "keywords"]); // Parse additional data from our Garmin extension(s)
+ if (ret === false) {
+ ret = prevRenderer.apply(renderer, args);
+ }
- var extensions = node.getElementsByTagNameNS("http://www.garmin.com/xmlschemas/GpxExtensions/v3", "*");
+ return ret;
+ };
+ };
- for (var i = 0; i < extensions.length; i++) {
- var extension = extensions[i]; // Ignore nested extensions, like those on routepoints or trackpoints
+ for (var prop in extension.renderer) {
+ _loop(prop);
+ }
- if (extension.parentNode.parentNode === node) {
- prop[extension.tagName.replace(":", "_")] = nodeVal(extension);
- }
+ opts.renderer = renderer;
+ })();
}
- var links = node.getElementsByTagName("link");
- if (links.length) prop.links = [];
-
- for (var _i = 0; _i < links.length; _i++) {
- prop.links.push(Object.assign({
- href: links[_i].getAttribute("href")
- }, getMulti(links[_i], ["text", "type"])));
- }
+ if (extension.tokenizer) {
+ (function () {
+ var tokenizer = marked.defaults.tokenizer || new Tokenizer();
- return prop;
- }
+ var _loop2 = function _loop2(prop) {
+ var prevTokenizer = tokenizer[prop];
- function coordPair$1(x) {
- var ll = [parseFloat(x.getAttribute("lon")), parseFloat(x.getAttribute("lat"))];
- var ele = get1(x, "ele"); // handle namespaced attribute in browser
+ tokenizer[prop] = function () {
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
- var heart = get1(x, "gpxtpx:hr") || get1(x, "hr");
- var time = get1(x, "time");
- var e;
+ var ret = extension.tokenizer[prop].apply(tokenizer, args);
- if (ele) {
- e = parseFloat(nodeVal(ele));
+ if (ret === false) {
+ ret = prevTokenizer.apply(tokenizer, args);
+ }
- if (!isNaN(e)) {
- ll.push(e);
- }
- }
+ return ret;
+ };
+ };
- var result = {
- coordinates: ll,
- time: time ? nodeVal(time) : null,
- extendedValues: []
- };
+ for (var prop in extension.tokenizer) {
+ _loop2(prop);
+ }
- if (heart) {
- result.extendedValues.push(["heart", parseFloat(nodeVal(heart))]);
+ opts.tokenizer = tokenizer;
+ })();
}
- var extensions = get1(x, "extensions");
+ if (extension.walkTokens) {
+ var walkTokens = marked.defaults.walkTokens;
- if (extensions !== null) {
- for (var _i2 = 0, _arr = ["speed", "course", "hAcc", "vAcc"]; _i2 < _arr.length; _i2++) {
- var name = _arr[_i2];
- var v = parseFloat(nodeVal(get1(extensions, name)));
+ opts.walkTokens = function (token) {
+ extension.walkTokens(token);
- if (!isNaN(v)) {
- result.extendedValues.push([name, v]);
+ if (walkTokens) {
+ walkTokens(token);
}
- }
+ };
}
- return result;
- }
+ marked.setOptions(opts);
+ };
+ /**
+ * Run callback for every token
+ */
- function getRoute(node) {
- var line = getPoints$1(node, "rtept");
- if (!line) return;
- return {
- type: "Feature",
- properties: Object.assign(getProperties$1(node), getLineStyle(get1(node, "extensions")), {
- _gpxType: "rte"
- }),
- geometry: {
- type: "LineString",
- coordinates: line.line
- }
- };
- }
- function getPoints$1(node, pointname) {
- var pts = node.getElementsByTagName(pointname);
- if (pts.length < 2) return; // Invalid line in GeoJSON
+ marked.walkTokens = function (tokens, callback) {
+ var _iterator = _createForOfIteratorHelper(tokens),
+ _step;
- var line = [];
- var times = [];
- var extendedValues = {};
+ try {
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
+ var token = _step.value;
+ callback(token);
- for (var i = 0; i < pts.length; i++) {
- var c = coordPair$1(pts[i]);
- line.push(c.coordinates);
- if (c.time) times.push(c.time);
+ switch (token.type) {
+ case 'table':
+ {
+ var _iterator2 = _createForOfIteratorHelper(token.tokens.header),
+ _step2;
- for (var j = 0; j < c.extendedValues.length; j++) {
- var _c$extendedValues$j = _slicedToArray(c.extendedValues[j], 2),
- name = _c$extendedValues$j[0],
- val = _c$extendedValues$j[1];
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var cell = _step2.value;
+ marked.walkTokens(cell, callback);
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
- var plural = name === "heart" ? name : name + "s";
+ var _iterator3 = _createForOfIteratorHelper(token.tokens.cells),
+ _step3;
- if (!extendedValues[plural]) {
- extendedValues[plural] = Array(pts.length).fill(null);
- }
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var row = _step3.value;
- extendedValues[plural][i] = val;
- }
- }
+ var _iterator4 = _createForOfIteratorHelper(row),
+ _step4;
- return {
- line: line,
- times: times,
- extendedValues: extendedValues
- };
- }
+ 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();
+ }
- function getTrack(node) {
- var segments = node.getElementsByTagName("trkseg");
- var track = [];
- var times = [];
- var extractedLines = [];
+ break;
+ }
- for (var i = 0; i < segments.length; i++) {
- var line = getPoints$1(segments[i], "trkpt");
+ case 'list':
+ {
+ marked.walkTokens(token.items, callback);
+ break;
+ }
- if (line) {
- extractedLines.push(line);
- if (line.times && line.times.length) times.push(line.times);
+ default:
+ {
+ if (token.tokens) {
+ marked.walkTokens(token.tokens, callback);
+ }
+ }
+ }
}
+ } catch (err) {
+ _iterator.e(err);
+ } finally {
+ _iterator.f();
}
+ };
+ /**
+ * Parse Inline
+ */
- if (extractedLines.length === 0) return;
- var multi = extractedLines.length > 1;
- var properties = Object.assign(getProperties$1(node), getLineStyle(get1(node, "extensions")), {
- _gpxType: "trk"
- }, times.length ? {
- coordinateProperties: {
- times: multi ? times : times[0]
- }
- } : {});
-
- for (var _i3 = 0; _i3 < extractedLines.length; _i3++) {
- var _line = extractedLines[_i3];
- track.push(_line.line);
- for (var _i4 = 0, _Object$entries = Object.entries(_line.extendedValues); _i4 < _Object$entries.length; _i4++) {
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i4], 2),
- name = _Object$entries$_i[0],
- val = _Object$entries$_i[1];
+ 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');
+ }
- var props = properties;
+ if (typeof src !== 'string') {
+ throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
+ }
- if (name === "heart") {
- if (!properties.coordinateProperties) {
- properties.coordinateProperties = {};
- }
+ opt = merge({}, marked.defaults, opt || {});
+ checkSanitizeDeprecation(opt);
- props = properties.coordinateProperties;
- }
+ try {
+ var tokens = Lexer.lexInline(src, opt);
- if (multi) {
- if (!props[name]) props[name] = extractedLines.map(function (line) {
- return new Array(line.line.length).fill(null);
- });
- props[name][_i3] = val;
- } else {
- props[name] = val;
- }
+ if (opt.walkTokens) {
+ marked.walkTokens(tokens, opt.walkTokens);
}
- }
- return {
- type: "Feature",
- properties: properties,
- geometry: multi ? {
- type: "MultiLineString",
- coordinates: track
- } : {
- type: "LineString",
- coordinates: track[0]
- }
- };
- }
+ return Parser.parseInline(tokens, opt);
+ } catch (e) {
+ e.message += '\nPlease report this to https://github.com/markedjs/marked.';
- function getPoint(node) {
- return {
- type: "Feature",
- properties: Object.assign(getProperties$1(node), getMulti(node, ["sym"])),
- geometry: {
- type: "Point",
- coordinates: coordPair$1(node).coordinates
+ if (opt.silent) {
+ return '<p>An error occurred:</p><pre>' + escape$1(e.message + '', true) + '</pre>';
}
- };
- }
- function gpxGen(doc) {
- var tracks, routes, waypoints, i, feature, _i5, _feature, _i6;
+ throw e;
+ }
+ };
+ /**
+ * Expose
+ */
- return regeneratorRuntime.wrap(function gpxGen$(_context) {
- while (1) {
- switch (_context.prev = _context.next) {
- case 0:
- tracks = doc.getElementsByTagName("trk");
- routes = doc.getElementsByTagName("rte");
- waypoints = doc.getElementsByTagName("wpt");
- i = 0;
- case 4:
- if (!(i < tracks.length)) {
- _context.next = 12;
- break;
- }
+ marked.Parser = Parser;
+ marked.parser = Parser.parse;
+ marked.Renderer = Renderer;
+ marked.TextRenderer = TextRenderer;
+ marked.Lexer = Lexer;
+ marked.lexer = Lexer.lex;
+ marked.Tokenizer = Tokenizer;
+ marked.Slugger = Slugger;
+ marked.parse = marked;
+ var marked_1 = marked;
- feature = getTrack(tracks[i]);
+ 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
- if (!feature) {
- _context.next = 9;
- break;
- }
+ var _cache;
- _context.next = 9;
- return feature;
+ function abortRequest$4(controller) {
+ if (controller) {
+ controller.abort();
+ }
+ }
- case 9:
- i++;
- _context.next = 4;
- break;
+ function abortUnwantedRequests$1(cache, tiles) {
+ Object.keys(cache.inflightTile).forEach(function (k) {
+ var wanted = tiles.find(function (tile) {
+ return k === tile.id;
+ });
- case 12:
- _i5 = 0;
+ if (!wanted) {
+ abortRequest$4(cache.inflightTile[k]);
+ delete cache.inflightTile[k];
+ }
+ });
+ }
- case 13:
- if (!(_i5 < routes.length)) {
- _context.next = 21;
- break;
- }
+ 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
- _feature = getRoute(routes[_i5]);
- if (!_feature) {
- _context.next = 18;
- break;
- }
+ function updateRtree$1(item, replace) {
+ _cache.rtree.remove(item, function (a, b) {
+ return a.data.id === b.data.id;
+ });
- _context.next = 18;
- return _feature;
+ if (replace) {
+ _cache.rtree.insert(item);
+ }
+ } // Issues shouldn't obscure each other
- case 18:
- _i5++;
- _context.next = 13;
- break;
- case 21:
- _i6 = 0;
+ function preventCoincident(loc) {
+ var coincident = false;
- case 22:
- if (!(_i6 < waypoints.length)) {
- _context.next = 28;
- break;
- }
+ 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);
- _context.next = 25;
- return getPoint(waypoints[_i6]);
+ return loc;
+ }
- case 25:
- _i6++;
- _context.next = 22;
- break;
+ 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]);
+ }, []);
+ });
- case 28:
- case "end":
- return _context.stop();
- }
+ if (!_cache) {
+ this.reset();
}
- }, _marked);
- }
- function gpx(doc) {
- return {
- type: "FeatureCollection",
- features: Array.from(gpxGen(doc))
- };
- }
+ this.event = utilRebind(this, dispatch$5, 'on');
+ },
+ reset: function reset() {
+ var _strings = {};
+ var _colors = {};
- var removeSpace = /\s*/g;
- var trimSpace = /^\s*|\s*$/g;
- var splitSpace = /\s+/; // generate a short, numeric hash of a string
+ if (_cache) {
+ Object.values(_cache.inflightTile).forEach(abortRequest$4); // Strings and colors are static and should not be re-populated
- function okhash(x) {
- if (!x || !x.length) return 0;
- var h = 0;
+ _strings = _cache.strings;
+ _colors = _cache.colors;
+ }
- for (var i = 0; i < x.length; i++) {
- h = (h << 5) - h + x.charCodeAt(i) | 0;
- }
+ _cache = {
+ data: {},
+ loadedTile: {},
+ inflightTile: {},
+ inflightPost: {},
+ closed: {},
+ rtree: new RBush(),
+ strings: _strings,
+ colors: _colors
+ };
+ },
+ loadIssues: function loadIssues(projection) {
+ var _this = this;
- return h;
- } // get one coordinate from a coordinate array, if any
+ 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
- function coord1(v) {
- return v.replace(removeSpace, "").split(",").map(parseFloat);
- } // get all coordinates from a coordinate array as [[],[]]
+ abortUnwantedRequests$1(_cache, tiles); // issue new requests..
+ tiles.forEach(function (tile) {
+ if (_cache.loadedTile[tile.id] || _cache.inflightTile[tile.id]) return;
- function coord(v) {
- return v.replace(trimSpace, "").split(splitSpace).map(coord1);
- }
+ var _tile$xyz = _slicedToArray(tile.xyz, 3),
+ x = _tile$xyz[0],
+ y = _tile$xyz[1],
+ z = _tile$xyz[2];
- function xml2str(node) {
- if (node.xml !== undefined) return node.xml;
+ 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;
- if (node.tagName) {
- var output = node.tagName;
+ 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) */
- for (var i = 0; i < node.attributes.length; i++) {
- output += node.attributes[i].name + node.attributes[i].value;
- }
+ var itemType = "".concat(item, "-").concat(cl); // Filter out unsupported issue types (some are too specific or advanced)
- for (var _i9 = 0; _i9 < node.childNodes.length; _i9++) {
- output += xml2str(node.childNodes[_i9]);
- }
+ if (itemType in _osmoseData.icons) {
+ var loc = issue.geometry.coordinates; // lon, lat
- return output;
- }
+ loc = preventCoincident(loc);
+ var d = new QAItem(loc, _this, itemType, id, {
+ item: item
+ }); // Setting elems here prevents UI detail requests
- if (node.nodeName === "#text") {
- return (node.nodeValue || node.value || "").trim();
- }
+ if (item === 8300 || item === 8360) {
+ d.elems = [];
+ }
- if (node.nodeName === "#cdata-section") {
- return node.nodeValue;
- }
+ _cache.data[d.id] = d;
- return "";
- }
+ _cache.rtree.insert(encodeIssueRtree(d));
+ }
+ });
+ }
- var geotypes = ["Polygon", "LineString", "Point", "Track", "gx:Track"];
+ dispatch$5.call('loaded');
+ })["catch"](function () {
+ delete _cache.inflightTile[tile.id];
+ _cache.loadedTile[tile.id] = true;
+ });
+ });
+ },
+ loadIssueDetail: function loadIssueDetail(issue) {
+ var _this2 = this;
- function kmlColor(properties, elem, prefix) {
- var v = nodeVal(get1(elem, "color")) || "";
- var colorProp = prefix == "stroke" || prefix === "fill" ? prefix : prefix + "-color";
+ // Issue details only need to be fetched once
+ if (issue.elems !== undefined) {
+ return Promise.resolve(issue);
+ }
- if (v.substr(0, 1) === "#") {
- v = v.substr(1);
- }
+ var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "?langs=").concat(_mainLocalizer.localeCode());
- if (v.length === 6 || v.length === 3) {
- properties[colorProp] = v;
- } else if (v.length === 8) {
- properties[prefix + "-opacity"] = parseInt(v.substr(0, 2), 16) / 255;
- properties[colorProp] = "#" + v.substr(6, 2) + v.substr(4, 2) + v.substr(2, 2);
- }
- }
+ 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
- function numericProperty(properties, elem, source, target) {
- var val = parseFloat(nodeVal(get1(elem, source)));
- if (!isNaN(val)) properties[target] = val;
- }
+ issue.detail = data.subtitle ? marked_1(data.subtitle.auto) : '';
- function gxCoords(root) {
- var elems = root.getElementsByTagName("coord");
- var coords = [];
- var times = [];
- if (elems.length === 0) elems = root.getElementsByTagName("gx:coord");
+ _this2.replaceItem(issue);
+ };
- for (var i = 0; i < elems.length; i++) {
- coords.push(nodeVal(elems[i]).split(" ").map(parseFloat));
- }
+ 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);
- var timeElems = root.getElementsByTagName("when");
+ 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
- for (var j = 0; j < timeElems.length; j++) {
- times.push(nodeVal(timeElems[j]));
- }
- return {
- coords: coords,
- times: times
- };
- }
+ 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
- function getGeometry(root) {
- var geomNode;
- var geomNodes;
- var i;
- var j;
- var k;
- var geoms = [];
- var coordTimes = [];
- if (get1(root, "MultiGeometry")) {
- return getGeometry(get1(root, "MultiGeometry"));
- }
+ var allRequests = items.map(function (itemType) {
+ // No need to request data we already have
+ if (itemType in _cache.strings[locale]) return null;
- if (get1(root, "MultiTrack")) {
- return getGeometry(get1(root, "MultiTrack"));
- }
+ 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$;
- if (get1(root, "gx:MultiTrack")) {
- return getGeometry(get1(root, "gx:MultiTrack"));
- }
+ var _cat$items = _slicedToArray(cat.items, 1),
+ _cat$items$ = _cat$items[0],
+ item = _cat$items$ === void 0 ? {
+ "class": []
+ } : _cat$items$;
- for (i = 0; i < geotypes.length; i++) {
- geomNodes = root.getElementsByTagName(geotypes[i]);
+ 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)
- if (geomNodes) {
- for (j = 0; j < geomNodes.length; j++) {
- geomNode = geomNodes[j];
- if (geotypes[i] === "Point") {
- geoms.push({
- type: "Point",
- coordinates: coord1(nodeVal(get1(geomNode, "coordinates")))
- });
- } else if (geotypes[i] === "LineString") {
- geoms.push({
- type: "LineString",
- coordinates: coord(nodeVal(get1(geomNode, "coordinates")))
- });
- } else if (geotypes[i] === "Polygon") {
- var rings = geomNode.getElementsByTagName("LinearRing"),
- coords = [];
+ if (!cl) {
+ /* eslint-disable no-console */
+ console.log("Osmose strings request (".concat(itemType, ") had unexpected data"));
+ /* eslint-enable no-console */
- for (k = 0; k < rings.length; k++) {
- coords.push(coord(nodeVal(get1(rings[k], "coordinates"))));
- }
+ return;
+ } // Cache served item colors to automatically style issue markers later
- geoms.push({
- type: "Polygon",
- coordinates: coords
- });
- } else if (geotypes[i] === "Track" || geotypes[i] === "gx:Track") {
- var track = gxCoords(geomNode);
- geoms.push({
- type: "LineString",
- coordinates: track.coords
- });
- if (track.times.length) coordTimes.push(track.times);
- }
- }
- }
- }
- return {
- geoms: geoms,
- coordTimes: coordTimes
- };
- }
+ var itemInt = item.item,
+ color = item.color;
- function getPlacemark(root, styleIndex, styleMapIndex, styleByHash) {
- var geomsAndTimes = getGeometry(root);
- var i;
- var properties = {};
- var name = nodeVal(get1(root, "name"));
- var address = nodeVal(get1(root, "address"));
- var styleUrl = nodeVal(get1(root, "styleUrl"));
- var description = nodeVal(get1(root, "description"));
- var timeSpan = get1(root, "TimeSpan");
- var timeStamp = get1(root, "TimeStamp");
- var extendedData = get1(root, "ExtendedData");
- var iconStyle = get1(root, "IconStyle");
- var labelStyle = get1(root, "LabelStyle");
- var lineStyle = get1(root, "LineStyle");
- var polyStyle = get1(root, "PolyStyle");
- var visibility = get1(root, "visibility");
- if (name) properties.name = name;
- if (address) properties.address = address;
+ 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 (styleUrl) {
- if (styleUrl[0] !== "#") {
- styleUrl = "#" + styleUrl;
- }
- properties.styleUrl = styleUrl;
+ var title = cl.title,
+ detail = cl.detail,
+ fix = cl.fix,
+ trap = cl.trap; // Osmose titles shouldn't contain markdown
- if (styleIndex[styleUrl]) {
- properties.styleHash = styleIndex[styleUrl];
- }
+ 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;
+ };
- if (styleMapIndex[styleUrl]) {
- properties.styleMapHash = styleMapIndex[styleUrl];
- properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal];
- } // Try to populate the lineStyle or polyStyle since we got the style hash
+ 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
- var style = styleByHash[properties.styleHash];
+ 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;
- if (style) {
- if (!iconStyle) iconStyle = get1(style, "IconStyle");
- if (!labelStyle) labelStyle = get1(style, "LabelStyle");
- if (!lineStyle) lineStyle = get1(style, "LineStyle");
- if (!polyStyle) polyStyle = get1(style, "PolyStyle");
- }
- }
+ if (_cache.inflightPost[issue.id]) {
+ return callback({
+ message: 'Issue update already inflight',
+ status: -2
+ }, issue);
+ } // UI sets the status to either 'done' or 'false'
- if (description) properties.description = description;
- if (timeSpan) {
- var begin = nodeVal(get1(timeSpan, "begin"));
- var end = nodeVal(get1(timeSpan, "end"));
- properties.timespan = {
- begin: begin,
- end: end
+ var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "/").concat(issue.newStatus);
+ var controller = new AbortController();
+
+ var after = function after() {
+ delete _cache.inflightPost[issue.id];
+
+ _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;
+ }
+
+ _cache.closed[issue.item] += 1;
+ }
+
+ if (callback) callback(null, issue);
};
- }
- if (timeStamp) {
- properties.timestamp = nodeVal(get1(timeStamp, "when"));
+ _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);
}
+ };
- if (iconStyle) {
- kmlColor(properties, iconStyle, "icon");
- numericProperty(properties, iconStyle, "scale", "icon-scale");
- numericProperty(properties, iconStyle, "heading", "icon-heading");
- var hotspot = get1(iconStyle, "hotSpot");
+ var ieee754$1 = {};
- if (hotspot) {
- var left = parseFloat(hotspot.getAttribute("x"));
- var top = parseFloat(hotspot.getAttribute("y"));
- if (!isNaN(left) && !isNaN(top)) properties["icon-offset"] = [left, top];
- }
+ /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
- var icon = get1(iconStyle, "Icon");
+ ieee754$1.read = function (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;
- if (icon) {
- var href = nodeVal(get1(icon, "href"));
- if (href) properties.icon = href;
- }
- }
+ for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
- if (labelStyle) {
- kmlColor(properties, labelStyle, "label");
- numericProperty(properties, labelStyle, "scale", "label-scale");
- }
+ m = e & (1 << -nBits) - 1;
+ e >>= -nBits;
+ nBits += mLen;
- if (lineStyle) {
- kmlColor(properties, lineStyle, "stroke");
- numericProperty(properties, lineStyle, "width", "stroke-width");
- }
+ for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
- if (polyStyle) {
- kmlColor(properties, polyStyle, "fill");
- var fill = nodeVal(get1(polyStyle, "fill"));
- var outline = nodeVal(get1(polyStyle, "outline"));
- if (fill) properties["fill-opacity"] = fill === "1" ? properties["fill-opacity"] || 1 : 0;
- if (outline) properties["stroke-opacity"] = outline === "1" ? properties["stroke-opacity"] || 1 : 0;
+ 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;
}
- if (extendedData) {
- var datas = extendedData.getElementsByTagName("Data"),
- simpleDatas = extendedData.getElementsByTagName("SimpleData");
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
+ };
- for (i = 0; i < datas.length; i++) {
- properties[datas[i].getAttribute("name")] = nodeVal(get1(datas[i], "value"));
+ ieee754$1.write = function (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);
+
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--;
+ c *= 2;
}
- for (i = 0; i < simpleDatas.length; i++) {
- properties[simpleDatas[i].getAttribute("name")] = nodeVal(simpleDatas[i]);
+ if (e + eBias >= 1) {
+ value += rt / c;
+ } else {
+ value += rt * Math.pow(2, 1 - eBias);
}
- }
- if (visibility) {
- properties.visibility = nodeVal(visibility);
- }
+ if (value * c >= 2) {
+ e++;
+ c /= 2;
+ }
- if (geomsAndTimes.coordTimes.length) {
- properties.coordinateProperties = {
- times: geomsAndTimes.coordTimes.length === 1 ? geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes
- };
+ 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 feature = {
- type: "Feature",
- geometry: geomsAndTimes.geoms.length === 0 ? null : geomsAndTimes.geoms.length === 1 ? geomsAndTimes.geoms[0] : {
- type: "GeometryCollection",
- geometries: geomsAndTimes.geoms
- },
- properties: properties
- };
- if (root.getAttribute("id")) feature.id = root.getAttribute("id");
- return feature;
- }
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
- function kmlGen(doc) {
- var styleIndex, styleByHash, styleMapIndex, placemarks, styles, styleMaps, k, hash, l, pairs, pairsMap, m, j, feature;
- return regeneratorRuntime.wrap(function kmlGen$(_context3) {
- while (1) {
- switch (_context3.prev = _context3.next) {
- case 0:
- // styleindex keeps track of hashed styles in order to match feature
- styleIndex = {};
- styleByHash = {}; // stylemapindex keeps track of style maps to expose in properties
+ e = e << mLen | m;
+ eLen += mLen;
- styleMapIndex = {}; // atomic geospatial types supported by KML - MultiGeometry is
- // handled separately
- // all root placemarks in the file
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
- placemarks = doc.getElementsByTagName("Placemark");
- styles = doc.getElementsByTagName("Style");
- styleMaps = doc.getElementsByTagName("StyleMap");
+ buffer[offset + i - d] |= s * 128;
+ };
- for (k = 0; k < styles.length; k++) {
- hash = okhash(xml2str(styles[k])).toString(16);
- styleIndex["#" + styles[k].getAttribute("id")] = hash;
- styleByHash[hash] = styles[k];
- }
+ var pbf = Pbf;
+ var ieee754 = ieee754$1;
- for (l = 0; l < styleMaps.length; l++) {
- styleIndex["#" + styleMaps[l].getAttribute("id")] = okhash(xml2str(styleMaps[l])).toString(16);
- pairs = styleMaps[l].getElementsByTagName("Pair");
- pairsMap = {};
+ 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;
+ }
- for (m = 0; m < pairs.length; m++) {
- pairsMap[nodeVal(get1(pairs[m], "key"))] = nodeVal(get1(pairs[m], "styleUrl"));
- }
+ Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
- styleMapIndex["#" + styleMaps[l].getAttribute("id")] = pairsMap;
- }
+ Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
- j = 0;
+ Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
- case 9:
- if (!(j < placemarks.length)) {
- _context3.next = 17;
- break;
- }
+ Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
- feature = getPlacemark(placemarks[j], styleIndex, styleMapIndex, styleByHash);
+ 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)
- if (!feature) {
- _context3.next = 14;
- break;
- }
+ 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;
- _context3.next = 14;
- return feature;
-
- case 14:
- j++;
- _context3.next = 9;
- break;
-
- case 17:
- case "end":
- return _context3.stop();
- }
+ 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);
}
- }, _marked3);
- }
-
- function kml(doc) {
- return {
- type: "FeatureCollection",
- features: Array.from(kmlGen(doc))
- };
- }
- var _initialized = false;
- var _enabled = false;
+ 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;
- var _geojson;
+ 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
- function svgData(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
- var _showLabels = true;
- var detected = utilDetect();
- var layer = select(null);
+ 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 || [];
- var _vtService;
+ while (this.pos < end) {
+ arr.push(this.readVarint(isSigned));
+ }
- var _fileList;
+ return arr;
+ },
+ readPackedSVarint: function readPackedSVarint(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- var _template;
+ while (this.pos < end) {
+ arr.push(this.readSVarint());
+ }
- var _src;
+ return arr;
+ },
+ readPackedBoolean: function readPackedBoolean(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- function init() {
- if (_initialized) return; // run once
+ while (this.pos < end) {
+ arr.push(this.readBoolean());
+ }
- _geojson = {};
- _enabled = true;
+ return arr;
+ },
+ readPackedFloat: function readPackedFloat(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- function over(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- d3_event.dataTransfer.dropEffect = 'copy';
+ while (this.pos < end) {
+ arr.push(this.readFloat());
}
- context.container().attr('dropzone', 'copy').on('drop.svgData', function (d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- if (!detected.filedrop) return;
- drawData.fileList(d3_event.dataTransfer.files);
- }).on('dragenter.svgData', over).on('dragexit.svgData', over).on('dragover.svgData', over);
- _initialized = true;
- }
+ return arr;
+ },
+ readPackedDouble: function readPackedDouble(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- function getService() {
- if (services.vectorTile && !_vtService) {
- _vtService = services.vectorTile;
+ while (this.pos < end) {
+ arr.push(this.readDouble());
+ }
- _vtService.event.on('loadedData', throttledRedraw);
- } else if (!services.vectorTile && _vtService) {
- _vtService = null;
+ return arr;
+ },
+ readPackedFixed32: function readPackedFixed32(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+
+ while (this.pos < end) {
+ arr.push(this.readFixed32());
}
- return _vtService;
- }
+ return arr;
+ },
+ readPackedSFixed32: function readPackedSFixed32(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- function showLayer() {
- layerOn();
- layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
- dispatch.call('change');
- });
- }
+ while (this.pos < end) {
+ arr.push(this.readSFixed32());
+ }
- function hideLayer() {
- throttledRedraw.cancel();
- layer.transition().duration(250).style('opacity', 0).on('end', layerOff);
- }
+ return arr;
+ },
+ readPackedFixed64: function readPackedFixed64(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
- function layerOn() {
- layer.style('display', 'block');
- }
+ while (this.pos < end) {
+ arr.push(this.readFixed64());
+ }
- function layerOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
- } // ensure that all geojson features in a collection have IDs
+ return arr;
+ },
+ readPackedSFixed64: function readPackedSFixed64(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) {
+ arr.push(this.readSFixed64());
+ }
- function ensureIDs(gj) {
- if (!gj) return null;
+ 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;
- if (gj.type === 'FeatureCollection') {
- for (var i = 0; i < gj.features.length; i++) {
- ensureFeatureID(gj.features[i]);
- }
- } else {
- ensureFeatureID(gj);
+ while (length < this.pos + min) {
+ length *= 2;
}
- return gj;
- } // ensure that each single Feature object has a unique ID
+ 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;
+ if (val > 0xfffffff || val < 0) {
+ writeBigVarint(val, this);
+ return;
+ }
- function ensureFeatureID(feature) {
- if (!feature) return;
- feature.__featurehash__ = utilHashcode(fastJsonStableStringify(feature));
- return feature;
- } // Prefer an array of Features instead of a FeatureCollection
+ 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
+ var startPos = this.pos; // write the string directly to the buffer and see how much was written
- function getFeatures(gj) {
- if (!gj) return [];
+ 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
- if (gj.type === 'FeatureCollection') {
- return gj.features;
- } else {
- return [gj];
+ 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);
+
+ 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
- function featureKey(d) {
- return d.__featurehash__;
- }
+ 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
- function isPolygon(d) {
- return d.geometry.type === 'Polygon' || d.geometry.type === 'MultiPolygon';
+ 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 clipPathID(d) {
- return 'ideditor-data-' + d.__featurehash__ + '-clippath';
- }
+ 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 featureClasses(d) {
- return ['data' + d.__featurehash__, d.geometry.type, isPolygon(d) ? 'area' : '', d.__layerID__ || ''].filter(Boolean).join(' ');
+ function readPackedEnd(pbf) {
+ return pbf.type === Pbf.Bytes ? pbf.readVarint() + pbf.pos : pbf.pos + 1;
+ }
+
+ function toNum(low, high, isSigned) {
+ if (isSigned) {
+ return high * 0x100000000 + (low >>> 0);
}
- function drawData(selection) {
- var vtService = getService();
- var getPath = svgPath(projection).geojson;
- var getAreaPath = svgPath(projection, null, true).geojson;
- var hasData = drawData.hasData();
- layer = selection.selectAll('.layer-mapdata').data(_enabled && hasData ? [0] : []);
- layer.exit().remove();
- layer = layer.enter().append('g').attr('class', 'layer-mapdata').merge(layer);
- var surface = context.surface();
- if (!surface || surface.empty()) return; // not ready to draw yet, starting up
- // Gather data
+ return (high >>> 0) * 0x100000000 + (low >>> 0);
+ }
- var geoData, polygonData;
+ function writeBigVarint(val, pbf) {
+ var low, high;
- if (_template && vtService) {
- // fetch data from vector tile service
- var sourceID = _template;
- vtService.loadTiles(sourceID, _template, projection);
- geoData = vtService.data(sourceID, projection);
+ if (val >= 0) {
+ low = val % 0x100000000 | 0;
+ high = val / 0x100000000 | 0;
+ } else {
+ low = ~(-val % 0x100000000);
+ high = ~(-val / 0x100000000);
+
+ if (low ^ 0xffffffff) {
+ low = low + 1 | 0;
} else {
- geoData = getFeatures(_geojson);
+ low = 0;
+ high = high + 1 | 0;
}
+ }
- geoData = geoData.filter(getPath);
- polygonData = geoData.filter(isPolygon); // Draw clip paths for polygons
+ if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
+ throw new Error('Given varint doesn\'t fit into 10 bytes');
+ }
- var clipPaths = surface.selectAll('defs').selectAll('.clipPath-data').data(polygonData, featureKey);
- clipPaths.exit().remove();
- var clipPathsEnter = clipPaths.enter().append('clipPath').attr('class', 'clipPath-data').attr('id', clipPathID);
- clipPathsEnter.append('path');
- clipPaths.merge(clipPathsEnter).selectAll('path').attr('d', getAreaPath); // Draw fill, shadow, stroke layers
+ pbf.realloc(10);
+ writeBigVarintLow(low, high, pbf);
+ writeBigVarintHigh(high, pbf);
+ }
- var datagroups = layer.selectAll('g.datagroup').data(['fill', 'shadow', 'stroke']);
- datagroups = datagroups.enter().append('g').attr('class', function (d) {
- return 'datagroup datagroup-' + d;
- }).merge(datagroups); // Draw paths
+ 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;
+ }
- var pathData = {
- fill: polygonData,
- shadow: geoData,
- stroke: geoData
- };
- var paths = datagroups.selectAll('path').data(function (layer) {
- return pathData[layer];
- }, featureKey); // exit
+ 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;
+ }
- paths.exit().remove(); // enter/update
+ 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
- paths = paths.enter().append('path').attr('class', function (d) {
- var datagroup = this.parentNode.__data__;
- return 'pathdata ' + datagroup + ' ' + featureClasses(d);
- }).attr('clip-path', function (d) {
- var datagroup = this.parentNode.__data__;
- return datagroup === 'fill' ? 'url(#' + clipPathID(d) + ')' : null;
- }).merge(paths).attr('d', function (d) {
- var datagroup = this.parentNode.__data__;
- return datagroup === 'fill' ? getAreaPath(d) : getPath(d);
- }); // Draw labels
+ pbf.realloc(extraLen);
- layer.call(drawLabels, 'label-halo', geoData).call(drawLabels, 'label', geoData);
+ for (var i = pbf.pos - 1; i >= startPos; i--) {
+ pbf.buf[i + extraLen] = pbf.buf[i];
+ }
+ }
- function drawLabels(selection, textClass, data) {
- var labelPath = d3_geoPath(projection);
- var labelData = data.filter(function (d) {
- return _showLabels && d.properties && (d.properties.desc || d.properties.name);
- });
- var labels = selection.selectAll('text.' + textClass).data(labelData, featureKey); // exit
+ function _writePackedVarint(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeVarint(arr[i]);
+ }
+ }
- labels.exit().remove(); // enter/update
+ function _writePackedSVarint(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeSVarint(arr[i]);
+ }
+ }
- labels = labels.enter().append('text').attr('class', function (d) {
- return textClass + ' ' + featureClasses(d);
- }).merge(labels).text(function (d) {
- return d.properties.desc || d.properties.name;
- }).attr('x', function (d) {
- var centroid = labelPath.centroid(d);
- return centroid[0] + 11;
- }).attr('y', function (d) {
- var centroid = labelPath.centroid(d);
- return centroid[1];
- });
- }
+ function _writePackedFloat(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeFloat(arr[i]);
}
+ }
- function getExtension(fileName) {
- if (!fileName) return;
- var re = /\.(gpx|kml|(geo)?json)$/i;
- var match = fileName.toLowerCase().match(re);
- return match && match.length && match[0];
+ function _writePackedDouble(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeDouble(arr[i]);
}
+ }
- function xmlToDom(textdata) {
- return new DOMParser().parseFromString(textdata, 'text/xml');
+ function _writePackedBoolean(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeBoolean(arr[i]);
}
+ }
- drawData.setFile = function (extension, data) {
- _template = null;
- _fileList = null;
- _geojson = null;
- _src = null;
- var gj;
+ function _writePackedFixed(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeFixed32(arr[i]);
+ }
+ }
- switch (extension) {
- case '.gpx':
- gj = gpx(xmlToDom(data));
- break;
+ function _writePackedSFixed(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeSFixed32(arr[i]);
+ }
+ }
- case '.kml':
- gj = kml(xmlToDom(data));
- break;
+ function _writePackedFixed2(arr, pbf) {
+ for (var i = 0; i < arr.length; i++) {
+ pbf.writeFixed64(arr[i]);
+ }
+ }
- case '.geojson':
- case '.json':
- gj = JSON.parse(data);
- break;
- }
+ 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
- gj = gj || {};
- if (Object.keys(gj).length) {
- _geojson = ensureIDs(gj);
- _src = extension + ' data file';
- this.fitZoom();
- }
+ function readUInt32(buf, pos) {
+ return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + buf[pos + 3] * 0x1000000;
+ }
- dispatch.call('change');
- return this;
- };
+ function writeInt32(buf, val, pos) {
+ buf[pos] = val;
+ buf[pos + 1] = val >>> 8;
+ buf[pos + 2] = val >>> 16;
+ buf[pos + 3] = val >>> 24;
+ }
- drawData.showLabels = function (val) {
- if (!arguments.length) return _showLabels;
- _showLabels = val;
- return this;
- };
+ function readInt32(buf, pos) {
+ return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + (buf[pos + 3] << 24);
+ }
- drawData.enabled = function (val) {
- if (!arguments.length) return _enabled;
- _enabled = val;
+ function readUtf8(buf, pos, end) {
+ var str = '';
+ var i = pos;
- if (_enabled) {
- showLayer();
- } else {
- hideLayer();
- }
+ while (i < end) {
+ var b0 = buf[i];
+ var c = null; // codepoint
- dispatch.call('change');
- return this;
- };
+ var bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1;
+ if (i + bytesPerSequence > end) break;
+ var b1, b2, b3;
- drawData.hasData = function () {
- var gj = _geojson || {};
- return !!(_template || Object.keys(gj).length);
- };
+ if (bytesPerSequence === 1) {
+ if (b0 < 0x80) {
+ c = b0;
+ }
+ } else if (bytesPerSequence === 2) {
+ b1 = buf[i + 1];
- drawData.template = function (val, src) {
- if (!arguments.length) return _template; // test source against OSM imagery blocklists..
+ if ((b1 & 0xC0) === 0x80) {
+ c = (b0 & 0x1F) << 0x6 | b1 & 0x3F;
- var osm = context.connection();
+ if (c <= 0x7F) {
+ c = null;
+ }
+ }
+ } else if (bytesPerSequence === 3) {
+ b1 = buf[i + 1];
+ b2 = buf[i + 2];
- if (osm) {
- var blocklists = osm.imageryBlocklists();
- var fail = false;
- var tested = 0;
- var regex;
+ if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
+ c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | b2 & 0x3F;
- for (var i = 0; i < blocklists.length; i++) {
- regex = blocklists[i];
- fail = regex.test(val);
- tested++;
- if (fail) break;
- } // ensure at least one test was run.
+ if (c <= 0x7FF || c >= 0xD800 && c <= 0xDFFF) {
+ c = null;
+ }
+ }
+ } else if (bytesPerSequence === 4) {
+ b1 = buf[i + 1];
+ b2 = buf[i + 2];
+ b3 = buf[i + 3];
+ if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
+ c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | b3 & 0x3F;
- if (!tested) {
- regex = /.*\.google(apis)?\..*\/(vt|kh)[\?\/].*([xyz]=.*){3}.*/;
- fail = regex.test(val);
+ if (c <= 0xFFFF || c >= 0x110000) {
+ c = null;
+ }
}
}
- _template = val;
- _fileList = null;
- _geojson = null; // strip off the querystring/hash from the template,
- // it often includes the access token
-
- _src = src || 'vectortile:' + val.split(/[?#]/)[0];
- dispatch.call('change');
- return this;
- };
-
- drawData.geojson = function (gj, src) {
- if (!arguments.length) return _geojson;
- _template = null;
- _fileList = null;
- _geojson = null;
- _src = null;
- gj = gj || {};
-
- if (Object.keys(gj).length) {
- _geojson = ensureIDs(gj);
- _src = src || 'unknown.geojson';
+ if (c === null) {
+ c = 0xFFFD;
+ bytesPerSequence = 1;
+ } else if (c > 0xFFFF) {
+ c -= 0x10000;
+ str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
+ c = 0xDC00 | c & 0x3FF;
}
- dispatch.call('change');
- return this;
- };
-
- drawData.fileList = function (fileList) {
- if (!arguments.length) return _fileList;
- _template = null;
- _fileList = fileList;
- _geojson = null;
- _src = null;
- if (!fileList || !fileList.length) return this;
- var f = fileList[0];
- var extension = getExtension(f.name);
- var reader = new FileReader();
+ str += String.fromCharCode(c);
+ i += bytesPerSequence;
+ }
- reader.onload = function () {
- return function (e) {
- drawData.setFile(extension, e.target.result);
- };
- }();
+ return str;
+ }
- reader.readAsText(f);
- return this;
- };
+ function readUtf8TextDecoder(buf, pos, end) {
+ return utf8TextDecoder.decode(buf.subarray(pos, end));
+ }
- drawData.url = function (url, defaultExtension) {
- _template = null;
- _fileList = null;
- _geojson = null;
- _src = null; // strip off any querystring/hash from the url before checking extension
+ function writeUtf8(buf, str, pos) {
+ for (var i = 0, c, lead; i < str.length; i++) {
+ c = str.charCodeAt(i); // code point
- var testUrl = url.split(/[?#]/)[0];
- var extension = getExtension(testUrl) || defaultExtension;
+ 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;
+ }
+ } else {
+ if (c > 0xDBFF || i + 1 === str.length) {
+ buf[pos++] = 0xEF;
+ buf[pos++] = 0xBF;
+ buf[pos++] = 0xBD;
+ } else {
+ lead = c;
+ }
- if (extension) {
- _template = null;
- d3_text(url).then(function (data) {
- drawData.setFile(extension, data);
- })["catch"](function () {
- /* ignore */
- });
- } else {
- drawData.template(url);
+ continue;
+ }
+ } else if (lead) {
+ buf[pos++] = 0xEF;
+ buf[pos++] = 0xBF;
+ buf[pos++] = 0xBD;
+ lead = null;
}
- return this;
- };
+ 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;
+ }
- drawData.getSrc = function () {
- return _src || '';
- };
+ buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+ }
- drawData.fitZoom = function () {
- var features = getFeatures(_geojson);
- if (!features.length) return;
- var map = context.map();
- var viewport = map.trimmedExtent().polygon();
- var coords = features.reduce(function (coords, feature) {
- var geom = feature.geometry;
- if (!geom) return coords;
- var c = geom.coordinates;
- /* eslint-disable no-fallthrough */
+ buf[pos++] = c & 0x3F | 0x80;
+ }
+ }
- switch (geom.type) {
- case 'Point':
- c = [c];
+ return pos;
+ }
- case 'MultiPoint':
- case 'LineString':
- break;
+ var vectorTile = {};
- case 'MultiPolygon':
- c = utilArrayFlatten(c);
+ var pointGeometry = Point$1;
+ /**
+ * 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);
+ */
- case 'Polygon':
- case 'MultiLineString':
- c = utilArrayFlatten(c);
- break;
- }
- /* eslint-enable no-fallthrough */
+ function Point$1(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+ Point$1.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$1(this.x, this.y);
+ },
- return utilArrayUnion(coords, c);
- }, []);
+ /**
+ * 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);
+ },
- if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
- var extent = geoExtent(d3_geoBounds({
- type: 'LineString',
- coordinates: coords
- }));
- map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
- }
+ /**
+ * 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);
+ },
- return this;
- };
+ /**
+ * 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);
+ },
- init();
- return drawData;
- }
+ /**
+ * 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);
+ },
- function svgDebug(projection, context) {
- function drawDebug(selection) {
- var showTile = context.getDebug('tile');
- var showCollision = context.getDebug('collision');
- var showImagery = context.getDebug('imagery');
- var showTouchTargets = context.getDebug('target');
- var showDownloaded = context.getDebug('downloaded');
- var debugData = [];
+ /**
+ * 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);
+ },
- if (showTile) {
- debugData.push({
- "class": 'red',
- label: 'tile'
- });
- }
+ /**
+ * 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);
+ },
- if (showCollision) {
- debugData.push({
- "class": 'yellow',
- label: 'collision'
- });
- }
+ /**
+ * 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);
+ },
- if (showImagery) {
- debugData.push({
- "class": 'orange',
- label: 'imagery'
- });
- }
+ /**
+ * 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);
+ },
- if (showTouchTargets) {
- debugData.push({
- "class": 'pink',
- label: 'touchTargets'
- });
- }
+ /**
+ * Multiply this point by a 4x1 transformation matrix
+ * @param {Array<Number>} m transformation matrix
+ * @return {Point} output point
+ */
+ matMult: function matMult(m) {
+ return this.clone()._matMult(m);
+ },
- if (showDownloaded) {
- debugData.push({
- "class": 'purple',
- label: 'downloaded'
- });
- }
+ /**
+ * 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();
+ },
- var legend = context.container().select('.main-content').selectAll('.debug-legend').data(debugData.length ? [0] : []);
- legend.exit().remove();
- legend = legend.enter().append('div').attr('class', 'fillD debug-legend').merge(legend);
- var legendItems = legend.selectAll('.debug-legend-item').data(debugData, function (d) {
- return d.label;
- });
- legendItems.exit().remove();
- legendItems.enter().append('span').attr('class', function (d) {
- return "debug-legend-item ".concat(d["class"]);
- }).text(function (d) {
- return d.label;
- });
- var layer = selection.selectAll('.layer-debug').data(showImagery || showDownloaded ? [0] : []);
- layer.exit().remove();
- layer = layer.enter().append('g').attr('class', 'layer-debug').merge(layer); // imagery
+ /**
+ * 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();
+ },
- var extent = context.map().extent();
- _mainFileFetcher.get('imagery').then(function (d) {
- var hits = showImagery && d.query.bbox(extent.rectangle(), true) || [];
- var features = hits.map(function (d) {
- return d.features[d.id];
- });
- var imagery = layer.selectAll('path.debug-imagery').data(features);
- imagery.exit().remove();
- imagery.enter().append('path').attr('class', 'debug-imagery debug orange');
- })["catch"](function () {
- /* ignore */
- }); // downloaded
+ /**
+ * 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 osm = context.connection();
- var dataDownloaded = [];
+ /**
+ * 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 (osm && showDownloaded) {
- var rtree = osm.caches('get').tile.rtree;
- dataDownloaded = rtree.all().map(function (bbox) {
- return {
- type: 'Feature',
- properties: {
- id: bbox.id
- },
- geometry: {
- type: 'Polygon',
- coordinates: [[[bbox.minX, bbox.minY], [bbox.minX, bbox.maxY], [bbox.maxX, bbox.maxY], [bbox.maxX, bbox.minY], [bbox.minX, bbox.minY]]]
- }
- };
- });
- }
+ /**
+ * 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;
+ },
- var downloaded = layer.selectAll('path.debug-downloaded').data(showDownloaded ? dataDownloaded : []);
- downloaded.exit().remove();
- downloaded.enter().append('path').attr('class', 'debug-downloaded debug purple'); // update
+ /**
+ * 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));
+ },
- layer.selectAll('path').attr('d', svgPath(projection).geojson);
- } // This looks strange because `enabled` methods on other layers are
- // chainable getter/setters, and this one is just a getter.
+ /**
+ * 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;
+ },
+ /**
+ * 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);
+ },
- drawDebug.enabled = function () {
- if (!arguments.length) {
- return context.getDebug('tile') || context.getDebug('collision') || context.getDebug('imagery') || context.getDebug('target') || context.getDebug('downloaded');
- } else {
- return this;
- }
- };
+ /**
+ * 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);
+ },
- return drawDebug;
- }
+ /**
+ * 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);
+ },
- /*
- A standalone SVG element that contains only a `defs` sub-element. To be
- used once globally, since defs IDs must be unique within a document.
- */
+ /*
+ * 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());
- function svgDefs(context) {
- var _defsSelection = select(null);
+ 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<Number>|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);
+ */
- var _spritesheetIds = ['iD-sprite', 'maki-sprite', 'temaki-sprite', 'fa-sprite', 'community-sprite'];
+ Point$1.convert = function (a) {
+ if (a instanceof Point$1) {
+ return a;
+ }
- function drawDefs(selection) {
- _defsSelection = selection.append('defs'); // add markers
+ if (Array.isArray(a)) {
+ return new Point$1(a[0], a[1]);
+ }
- _defsSelection.append('marker').attr('id', 'ideditor-oneway-marker').attr('viewBox', '0 0 10 5').attr('refX', 2.5).attr('refY', 2.5).attr('markerWidth', 2).attr('markerHeight', 2).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'oneway-marker-path').attr('d', 'M 5,3 L 0,3 L 0,2 L 5,2 L 5,0 L 10,2.5 L 5,5 z').attr('stroke', 'none').attr('fill', '#000').attr('opacity', '0.75'); // SVG markers have to be given a colour where they're defined
- // (they can't inherit it from the line they're attached to),
- // so we need to manually define markers for each color of tag
- // (also, it's slightly nicer if we can control the
- // positioning for different tags)
+ return a;
+ };
+ var Point = pointGeometry;
+ var vectortilefeature = VectorTileFeature$1;
- function addSidedMarker(name, color, offset) {
- _defsSelection.append('marker').attr('id', 'ideditor-sided-marker-' + name).attr('viewBox', '0 0 2 2').attr('refX', 1).attr('refY', -offset).attr('markerWidth', 1.5).attr('markerHeight', 1.5).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'sided-marker-path sided-marker-' + name + '-path').attr('d', 'M 0,0 L 1,1 L 2,0 z').attr('stroke', 'none').attr('fill', color);
- }
+ function VectorTileFeature$1(pbf, end, extent, keys, values) {
+ // Public
+ this.properties = {};
+ this.extent = extent;
+ this.type = 0; // Private
- addSidedMarker('natural', 'rgb(170, 170, 170)', 0); // for a coastline, the arrows are (somewhat unintuitively) on
- // the water side, so let's color them blue (with a gap) to
- // give a stronger indication
+ this._pbf = pbf;
+ this._geometry = -1;
+ this._keys = keys;
+ this._values = values;
+ pbf.readFields(readFeature, this, end);
+ }
- addSidedMarker('coastline', '#77dede', 1);
- addSidedMarker('waterway', '#77dede', 1); // barriers have a dashed line, and separating the triangle
- // from the line visually suits that
+ 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;
+ }
- addSidedMarker('barrier', '#ddd', 1);
- addSidedMarker('man_made', '#fff', 0);
+ function readTag(pbf, feature) {
+ var end = pbf.readVarint() + pbf.pos;
- _defsSelection.append('marker').attr('id', 'ideditor-viewfield-marker').attr('viewBox', '0 0 16 16').attr('refX', 8).attr('refY', 16).attr('markerWidth', 4).attr('markerHeight', 4).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'viewfield-marker-path').attr('d', 'M 6,14 C 8,13.4 8,13.4 10,14 L 16,3 C 12,0 4,0 0,3 z').attr('fill', '#333').attr('fill-opacity', '0.75').attr('stroke', '#fff').attr('stroke-width', '0.5px').attr('stroke-opacity', '0.75');
+ while (pbf.pos < end) {
+ var key = feature._keys[pbf.readVarint()],
+ value = feature._values[pbf.readVarint()];
- _defsSelection.append('marker').attr('id', 'ideditor-viewfield-marker-wireframe').attr('viewBox', '0 0 16 16').attr('refX', 8).attr('refY', 16).attr('markerWidth', 4).attr('markerHeight', 4).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'viewfield-marker-path').attr('d', 'M 6,14 C 8,13.4 8,13.4 10,14 L 16,3 C 12,0 4,0 0,3 z').attr('fill', 'none').attr('stroke', '#fff').attr('stroke-width', '0.5px').attr('stroke-opacity', '0.75'); // add patterns
+ feature.properties[key] = value;
+ }
+ }
+ VectorTileFeature$1.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
- var patterns = _defsSelection.selectAll('pattern').data([// pattern name, pattern image name
- ['beach', 'dots'], ['construction', 'construction'], ['cemetery', 'cemetery'], ['cemetery_christian', 'cemetery_christian'], ['cemetery_buddhist', 'cemetery_buddhist'], ['cemetery_muslim', 'cemetery_muslim'], ['cemetery_jewish', 'cemetery_jewish'], ['farmland', 'farmland'], ['farmyard', 'farmyard'], ['forest', 'forest'], ['forest_broadleaved', 'forest_broadleaved'], ['forest_needleleaved', 'forest_needleleaved'], ['forest_leafless', 'forest_leafless'], ['golf_green', 'grass'], ['grass', 'grass'], ['landfill', 'landfill'], ['meadow', 'grass'], ['orchard', 'orchard'], ['pond', 'pond'], ['quarry', 'quarry'], ['scrub', 'bushes'], ['vineyard', 'vineyard'], ['water_standing', 'lines'], ['waves', 'waves'], ['wetland', 'wetland'], ['wetland_marsh', 'wetland_marsh'], ['wetland_swamp', 'wetland_swamp'], ['wetland_bog', 'wetland_bog'], ['wetland_reedbed', 'wetland_reedbed']]).enter().append('pattern').attr('id', function (d) {
- return 'ideditor-pattern-' + d[0];
- }).attr('width', 32).attr('height', 32).attr('patternUnits', 'userSpaceOnUse');
+ 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;
- patterns.append('rect').attr('x', 0).attr('y', 0).attr('width', 32).attr('height', 32).attr('class', function (d) {
- return 'pattern-color-' + d[0];
- });
- patterns.append('image').attr('x', 0).attr('y', 0).attr('width', 32).attr('height', 32).attr('xlink:href', function (d) {
- return context.imagePath('pattern/' + d[1] + '.png');
- }); // add clip paths
+ while (pbf.pos < end) {
+ if (length <= 0) {
+ var cmdLen = pbf.readVarint();
+ cmd = cmdLen & 0x7;
+ length = cmdLen >> 3;
+ }
- _defsSelection.selectAll('clipPath').data([12, 18, 20, 32, 45]).enter().append('clipPath').attr('id', function (d) {
- return 'ideditor-clip-square-' + d;
- }).append('rect').attr('x', 0).attr('y', 0).attr('width', function (d) {
- return d;
- }).attr('height', function (d) {
- return d;
- }); // add symbol spritesheets
+ length--;
+ if (cmd === 1 || cmd === 2) {
+ x += pbf.readSVarint();
+ y += pbf.readSVarint();
- addSprites(_spritesheetIds, true);
+ if (cmd === 1) {
+ // moveTo
+ if (line) lines.push(line);
+ line = [];
+ }
+
+ line.push(new Point(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);
+ }
}
- function addSprites(ids, overrideColors) {
- _spritesheetIds = utilArrayUniq(_spritesheetIds.concat(ids));
+ if (line) lines.push(line);
+ return lines;
+ };
- var spritesheets = _defsSelection.selectAll('.spritesheet').data(_spritesheetIds);
+ 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;
- spritesheets.enter().append('g').attr('class', function (d) {
- return 'spritesheet spritesheet-' + d;
- }).each(function (d) {
- var url = context.imagePath(d + '.svg');
- var node = select(this).node();
- svg(url).then(function (svg) {
- node.appendChild(select(svg.documentElement).attr('id', 'ideditor-' + d).node());
+ while (pbf.pos < end) {
+ if (length <= 0) {
+ var cmdLen = pbf.readVarint();
+ cmd = cmdLen & 0x7;
+ length = cmdLen >> 3;
+ }
- if (overrideColors && d !== 'iD-sprite') {
- // allow icon colors to be overridden..
- select(node).selectAll('path').attr('fill', 'currentColor');
- }
- })["catch"](function () {
- /* ignore */
- });
- });
- spritesheets.exit().remove();
- }
+ length--;
- drawDefs.addSprites = addSprites;
- return drawDefs;
- }
+ 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 _layerEnabled$2 = false;
+ return [x1, y1, x2, y2];
+ };
- var _qaService$2;
+ 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;
- function svgKeepRight(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- return dispatch.call('change');
- }, 1000);
+ 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];
+ }
+ }
- var minZoom = 12;
- var touchLayer = select(null);
- var drawLayer = select(null);
- var layerVisible = false;
+ switch (this.type) {
+ case 1:
+ var points = [];
- function markerPath(selection, klass) {
- selection.attr('class', klass).attr('transform', 'translate(-4, -24)').attr('d', 'M11.6,6.2H7.1l1.4-5.1C8.6,0.6,8.1,0,7.5,0H2.2C1.7,0,1.3,0.3,1.3,0.8L0,10.2c-0.1,0.6,0.4,1.1,0.9,1.1h4.6l-1.8,7.6C3.6,19.4,4.1,20,4.7,20c0.3,0,0.6-0.2,0.8-0.5l6.9-11.9C12.7,7,12.3,6.2,11.6,6.2z');
- } // Loosely-coupled keepRight service for fetching issues.
+ for (i = 0; i < coords.length; i++) {
+ points[i] = coords[i][0];
+ }
+ coords = points;
+ project(coords);
+ break;
- function getService() {
- if (services.keepRight && !_qaService$2) {
- _qaService$2 = services.keepRight;
-
- _qaService$2.on('loaded', throttledRedraw);
- } else if (!services.keepRight && _qaService$2) {
- _qaService$2 = null;
- }
-
- return _qaService$2;
- } // Show the markers
-
+ case 2:
+ for (i = 0; i < coords.length; i++) {
+ project(coords[i]);
+ }
- function editOn() {
- if (!layerVisible) {
- layerVisible = true;
- drawLayer.style('display', 'block');
- }
- } // Immediately remove the markers and their touch targets
+ break;
+ case 3:
+ coords = classifyRings(coords);
- function editOff() {
- if (layerVisible) {
- layerVisible = false;
- drawLayer.style('display', 'none');
- drawLayer.selectAll('.qaItem.keepRight').remove();
- touchLayer.selectAll('.qaItem.keepRight').remove();
- }
- } // Enable the layer. This shows the markers and transitions them to visible.
+ for (i = 0; i < coords.length; i++) {
+ for (j = 0; j < coords[i].length; j++) {
+ project(coords[i][j]);
+ }
+ }
+ break;
+ }
- function layerOn() {
- editOn();
- drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
- return dispatch.call('change');
- });
- } // Disable the layer. This transitions the layer invisible and then hides the markers.
+ if (coords.length === 1) {
+ coords = coords[0];
+ } else {
+ type = 'Multi' + type;
+ }
+ var result = {
+ type: "Feature",
+ geometry: {
+ type: type,
+ coordinates: coords
+ },
+ properties: this.properties
+ };
- function layerOff() {
- throttledRedraw.cancel();
- drawLayer.interrupt();
- touchLayer.selectAll('.qaItem.keepRight').remove();
- drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
- editOff();
- dispatch.call('change');
- });
- } // Update the issue markers
+ if ('id' in this) {
+ result.id = this.id;
+ }
+ return result;
+ }; // classifies an array of rings into polygons with outer rings and holes
- function updateMarkers() {
- if (!layerVisible || !_layerEnabled$2) return;
- var service = getService();
- var selectedID = context.selectedErrorID();
- var data = service ? service.getItems(projection) : [];
- var getTransform = svgPointTransform(projection); // Draw markers..
- var markers = drawLayer.selectAll('.qaItem.keepRight').data(data, function (d) {
- return d.id;
- }); // exit
+ function classifyRings(rings) {
+ var len = rings.length;
+ if (len <= 1) return [rings];
+ var polygons = [],
+ polygon,
+ ccw;
- markers.exit().remove(); // enter
+ for (var i = 0; i < len; i++) {
+ var area = signedArea(rings[i]);
+ if (area === 0) continue;
+ if (ccw === undefined) ccw = area < 0;
- var markersEnter = markers.enter().append('g').attr('class', function (d) {
- return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.parentIssueType);
- });
- markersEnter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
- markersEnter.append('path').call(markerPath, 'shadow');
- markersEnter.append('use').attr('class', 'qaItem-fill').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').attr('xlink:href', '#iD-icon-bolt'); // update
+ if (ccw === area < 0) {
+ if (polygon) polygons.push(polygon);
+ polygon = [rings[i]];
+ } else {
+ polygon.push(rings[i]);
+ }
+ }
- markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
- return d.id === selectedID;
- }).attr('transform', getTransform); // Draw targets..
+ if (polygon) polygons.push(polygon);
+ return polygons;
+ }
- if (touchLayer.empty()) return;
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var targets = touchLayer.selectAll('.qaItem.keepRight').data(data, function (d) {
- return d.id;
- }); // exit
+ function signedArea(ring) {
+ var sum = 0;
- targets.exit().remove(); // enter/update
+ 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);
+ }
- targets.enter().append('rect').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').merge(targets).sort(sortY).attr('class', function (d) {
- return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
- }).attr('transform', getTransform);
+ return sum;
+ }
- function sortY(a, b) {
- return a.id === selectedID ? 1 : b.id === selectedID ? -1 : a.severity === 'error' && b.severity !== 'error' ? 1 : b.severity === 'error' && a.severity !== 'error' ? -1 : b.loc[1] - a.loc[1];
- }
- } // Draw the keepRight layer and schedule loading issues and updating markers.
+ var VectorTileFeature = vectortilefeature;
+ var vectortilelayer = VectorTileLayer$1;
+ function VectorTileLayer$1(pbf, end) {
+ // Public
+ this.version = 1;
+ this.name = null;
+ this.extent = 4096;
+ this.length = 0; // Private
- function drawKeepRight(selection) {
- var service = getService();
- var surface = context.surface();
+ this._pbf = pbf;
+ this._keys = [];
+ this._values = [];
+ this._features = [];
+ pbf.readFields(readLayer, this, end);
+ this.length = this._features.length;
+ }
- if (surface && !surface.empty()) {
- touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
- }
+ 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));
+ }
- drawLayer = selection.selectAll('.layer-keepRight').data(service ? [0] : []);
- drawLayer.exit().remove();
- drawLayer = drawLayer.enter().append('g').attr('class', 'layer-keepRight').style('display', _layerEnabled$2 ? 'block' : 'none').merge(drawLayer);
+ function readValueMessage(pbf) {
+ var value = null,
+ end = pbf.readVarint() + pbf.pos;
- if (_layerEnabled$2) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- service.loadIssues(projection);
- updateMarkers();
- } else {
- editOff();
- }
- }
- } // Toggles the layer on and off
+ 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;
+ }
+ return value;
+ } // return feature `i` from this layer as a `VectorTileFeature`
- drawKeepRight.enabled = function (val) {
- if (!arguments.length) return _layerEnabled$2;
- _layerEnabled$2 = val;
- if (_layerEnabled$2) {
- layerOn();
- } else {
- layerOff();
+ 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];
- if (context.selectedErrorID()) {
- context.enter(modeBrowse(context));
- }
- }
+ var end = this._pbf.readVarint() + this._pbf.pos;
- dispatch.call('change');
- return this;
- };
+ return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
+ };
- drawKeepRight.supported = function () {
- return !!getService();
- };
+ var VectorTileLayer = vectortilelayer;
+ var vectortile = VectorTile$1;
- return drawKeepRight;
+ function VectorTile$1(pbf, end) {
+ this.layers = pbf.readFields(readTile, {}, end);
}
- function svgGeolocate(projection) {
- var layer = select(null);
+ 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 _position;
+ var VectorTile = vectorTile.VectorTile = vectortile;
+ vectorTile.VectorTileFeature = vectortilefeature;
+ vectorTile.VectorTileLayer = vectortilelayer;
- function init() {
- if (svgGeolocate.initialized) return; // run once
+ 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');
- svgGeolocate.enabled = false;
- svgGeolocate.initialized = true;
- }
+ var _loadViewerPromise$2;
- function showLayer() {
- layer.style('display', 'block');
- }
+ var _mlyActiveImage;
- function hideLayer() {
- layer.transition().duration(250).style('opacity', 0);
- }
+ var _mlyCache;
- function layerOn() {
- layer.style('opacity', 0).transition().duration(250).style('opacity', 1);
- }
+ var _mlyFallback = false;
- function layerOff() {
- layer.style('display', 'none');
- }
+ var _mlyHighlightedDetection;
- function transform(d) {
- return svgPointTransform(projection)(d);
- }
+ var _mlyShowFeatureDetections = false;
+ var _mlyShowSignDetections = false;
- function accuracy(accuracy, loc) {
- // converts accuracy to pixels...
- var degreesRadius = geoMetersToLat(accuracy),
- tangentLoc = [loc[0], loc[1] + degreesRadius],
- projectedTangent = projection(tangentLoc),
- projectedLoc = projection([loc[0], loc[1]]); // southern most point will have higher pixel value...
+ var _mlyViewer;
- return Math.round(projectedLoc[1] - projectedTangent[1]).toString();
- }
+ var _mlyViewerFilter = ['all']; // Load all data for the specified type from Mapillary vector tiles
- function update() {
- var geolocation = {
- loc: [_position.coords.longitude, _position.coords.latitude]
- };
- var groups = layer.selectAll('.geolocations').selectAll('.geolocation').data([geolocation]);
- groups.exit().remove();
- var pointsEnter = groups.enter().append('g').attr('class', 'geolocation');
- pointsEnter.append('circle').attr('class', 'geolocate-radius').attr('dx', '0').attr('dy', '0').attr('fill', 'rgb(15,128,225)').attr('fill-opacity', '0.3').attr('r', '0');
- pointsEnter.append('circle').attr('dx', '0').attr('dy', '0').attr('fill', 'rgb(15,128,225)').attr('stroke', 'white').attr('stroke-width', '1.5').attr('r', '6');
- groups.merge(pointsEnter).attr('transform', transform);
- layer.select('.geolocate-radius').attr('r', accuracy(_position.coords.accuracy, geolocation.loc));
- }
+ 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 drawLocation(selection) {
- var enabled = svgGeolocate.enabled;
- layer = selection.selectAll('.layer-geolocate').data([0]);
- layer.exit().remove();
- var layerEnter = layer.enter().append('g').attr('class', 'layer-geolocate').style('display', enabled ? 'block' : 'none');
- layerEnter.append('g').attr('class', 'geolocations');
- layer = layerEnter.merge(layer);
- if (enabled) {
- update();
- } else {
- layerOff();
+ 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);
}
- }
-
- drawLocation.enabled = function (position, enabled) {
- if (!arguments.length) return svgGeolocate.enabled;
- _position = position;
- svgGeolocate.enabled = enabled;
- if (svgGeolocate.enabled) {
- showLayer();
- layerOn();
- } else {
- hideLayer();
+ cache.loaded[tileId] = true;
+ delete cache.inflight[tileId];
+ return response.arrayBuffer();
+ }).then(function (data) {
+ if (!data) {
+ throw new Error('No Data');
}
- return this;
- };
-
- init();
- return drawLocation;
- }
-
- function svgLabels(projection, context) {
- var path = d3_geoPath(projection);
- var detected = utilDetect();
- var baselineHack = detected.ie || detected.browser.toLowerCase() === 'edge' || detected.browser.toLowerCase() === 'firefox' && detected.version >= 70;
+ loadTileDataToCache(data, tile, which);
- var _rdrawn = new RBush();
+ 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
- var _rskipped = new RBush();
- var _textWidthCache = {};
- var _entitybboxes = {}; // Listed from highest to lowest priority
+ function loadTileDataToCache(data, tile, which) {
+ var vectorTile = new VectorTile(new pbf(data));
+ var features, cache, layer, i, feature, loc, d;
- var labelStack = [['line', 'aeroway', '*', 12], ['line', 'highway', 'motorway', 12], ['line', 'highway', 'trunk', 12], ['line', 'highway', 'primary', 12], ['line', 'highway', 'secondary', 12], ['line', 'highway', 'tertiary', 12], ['line', 'highway', '*', 12], ['line', 'railway', '*', 12], ['line', 'waterway', '*', 12], ['area', 'aeroway', '*', 12], ['area', 'amenity', '*', 12], ['area', 'building', '*', 12], ['area', 'historic', '*', 12], ['area', 'leisure', '*', 12], ['area', 'man_made', '*', 12], ['area', 'natural', '*', 12], ['area', 'shop', '*', 12], ['area', 'tourism', '*', 12], ['area', 'camp_site', '*', 12], ['point', 'aeroway', '*', 10], ['point', 'amenity', '*', 10], ['point', 'building', '*', 10], ['point', 'historic', '*', 10], ['point', 'leisure', '*', 10], ['point', 'man_made', '*', 10], ['point', 'natural', '*', 10], ['point', 'shop', '*', 10], ['point', 'tourism', '*', 10], ['point', 'camp_site', '*', 10], ['line', 'name', '*', 12], ['area', 'name', '*', 12], ['point', 'name', '*', 10]];
+ if (vectorTile.layers.hasOwnProperty('image')) {
+ features = [];
+ cache = _mlyCache.images;
+ layer = vectorTile.layers.image;
- function shouldSkipIcon(preset) {
- var noIcons = ['building', 'landuse', 'natural'];
- return noIcons.some(function (s) {
- return preset.id.indexOf(s) >= 0;
- });
- }
+ 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
+ });
+ }
- function get(array, prop) {
- return function (d, i) {
- return array[i][prop];
- };
+ if (cache.rtree) {
+ cache.rtree.load(features);
+ }
}
- function textWidth(text, size, elem) {
- var c = _textWidthCache[size];
- if (!c) c = _textWidthCache[size] = {};
+ if (vectorTile.layers.hasOwnProperty('sequence')) {
+ features = [];
+ cache = _mlyCache.sequences;
+ layer = vectorTile.layers.sequence;
- if (c[text]) {
- return c[text];
- } else if (elem) {
- c[text] = elem.getComputedTextLength();
- return c[text];
- } else {
- var str = encodeURIComponent(text).match(/%[CDEFcdef]/g);
+ for (i = 0; i < layer.length; i++) {
+ feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
- if (str === null) {
- return size / 3 * 2 * text.length;
+ if (cache.lineString[feature.properties.id]) {
+ cache.lineString[feature.properties.id].push(feature);
} else {
- return size / 3 * (2 * text.length + str.length);
+ cache.lineString[feature.properties.id] = [feature];
}
}
}
- function drawLinePaths(selection, entities, filter, classes, labels) {
- var paths = selection.selectAll('path').filter(filter).data(entities, osmEntity.key); // exit
-
- paths.exit().remove(); // enter/update
-
- paths.enter().append('path').style('stroke-width', get(labels, 'font-size')).attr('id', function (d) {
- return 'ideditor-labelpath-' + d.id;
- }).attr('class', classes).merge(paths).attr('d', get(labels, 'lineString'));
- }
-
- function drawLineLabels(selection, entities, filter, classes, labels) {
- var texts = selection.selectAll('text.' + classes).filter(filter).data(entities, osmEntity.key); // exit
-
- texts.exit().remove(); // enter
+ if (vectorTile.layers.hasOwnProperty('point')) {
+ features = [];
+ cache = _mlyCache[which];
+ layer = vectorTile.layers.point;
- texts.enter().append('text').attr('class', function (d, i) {
- return classes + ' ' + labels[i].classes + ' ' + d.id;
- }).attr('dy', baselineHack ? '0.35em' : null).append('textPath').attr('class', 'textpath'); // update
+ 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
+ });
+ }
- selection.selectAll('text.' + classes).selectAll('.textpath').filter(filter).data(entities, osmEntity.key).attr('startOffset', '50%').attr('xlink:href', function (d) {
- return '#ideditor-labelpath-' + d.id;
- }).text(utilDisplayNameForPath);
+ if (cache.rtree) {
+ cache.rtree.load(features);
+ }
}
- function drawPointLabels(selection, entities, filter, classes, labels) {
- var texts = selection.selectAll('text.' + classes).filter(filter).data(entities, osmEntity.key); // exit
+ if (vectorTile.layers.hasOwnProperty('traffic_sign')) {
+ features = [];
+ cache = _mlyCache[which];
+ layer = vectorTile.layers.traffic_sign;
- texts.exit().remove(); // enter/update
+ 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
+ });
+ }
- texts.enter().append('text').attr('class', function (d, i) {
- return classes + ' ' + labels[i].classes + ' ' + d.id;
- }).merge(texts).attr('x', get(labels, 'x')).attr('y', get(labels, 'y')).style('text-anchor', get(labels, 'textAnchor')).text(utilDisplayName).each(function (d, i) {
- textWidth(utilDisplayName(d), labels[i].height, this);
- });
+ if (cache.rtree) {
+ cache.rtree.load(features);
+ }
}
+ } // Get data from the API
- function drawAreaLabels(selection, entities, filter, classes, labels) {
- entities = entities.filter(hasText);
- labels = labels.filter(hasText);
- drawPointLabels(selection, entities, filter, classes, labels);
- function hasText(d, i) {
- return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
+ function loadData(url) {
+ return fetch(url).then(function (response) {
+ if (!response.ok) {
+ throw new Error(response.status + ' ' + response.statusText);
}
- }
- function drawAreaIcons(selection, entities, filter, classes, labels) {
- var icons = selection.selectAll('use.' + classes).filter(filter).data(entities, osmEntity.key); // exit
-
- icons.exit().remove(); // enter/update
-
- icons.enter().append('use').attr('class', 'icon ' + classes).attr('width', '17px').attr('height', '17px').merge(icons).attr('transform', get(labels, 'transform')).attr('xlink:href', function (d) {
- var preset = _mainPresetIndex.match(d, context.graph());
- var picon = preset && preset.icon;
-
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return '#' + picon + (isMaki ? '-15' : '');
- }
- });
- }
+ return response.json();
+ }).then(function (result) {
+ if (!result) {
+ return [];
+ }
- function drawCollisionBoxes(selection, rtree, which) {
- var classes = 'debug ' + which + ' ' + (which === 'debug-skipped' ? 'orange' : 'yellow');
- var gj = [];
+ return result.data || [];
+ });
+ } // Partition viewport into higher zoom tiles
- if (context.getDebug('collision')) {
- gj = rtree.all().map(function (d) {
- return {
- type: 'Polygon',
- coordinates: [[[d.minX, d.minY], [d.maxX, d.minY], [d.maxX, d.maxY], [d.minX, d.maxY], [d.minX, d.minY]]]
- };
- });
- }
- var boxes = selection.selectAll('.' + which).data(gj); // exit
+ 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
- boxes.exit().remove(); // enter/update
+ var tiler = utilTiler().zoomExtent([z2, z2]);
+ return tiler.getTiles(projection).map(function (tile) {
+ return tile.extent;
+ });
+ } // Return no more than `limit` results per partition.
- boxes.enter().append('path').attr('class', classes).merge(boxes).attr('d', d3_geoPath());
- }
- function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) {
- var wireframe = context.surface().classed('fill-wireframe');
- var zoom = geoScaleToZoom(projection.scale());
- var labelable = [];
- var renderNodeAs = {};
- var i, j, k, entity, geometry;
+ 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;
+ }, []);
+ }
- for (i = 0; i < labelStack.length; i++) {
- labelable.push([]);
+ var serviceMapillary = {
+ // Initialize Mapillary
+ init: function init() {
+ if (!_mlyCache) {
+ this.reset();
}
- if (fullRedraw) {
- _rdrawn.clear();
-
- _rskipped.clear();
+ 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();
+ });
+ }
- _entitybboxes = {};
- } else {
- for (i = 0; i < entities.length; i++) {
- entity = entities[i];
- var toRemove = [].concat(_entitybboxes[entity.id] || []).concat(_entitybboxes[entity.id + 'I'] || []);
+ _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: {}
+ }
+ };
+ _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 = [];
- for (j = 0; j < toRemove.length; j++) {
- _rdrawn.remove(toRemove[j]);
+ _mlyCache.images.rtree.search(bbox).forEach(function (d) {
+ if (d.data.sequence_id) {
+ sequenceIds[d.data.sequence_id] = true;
+ }
+ });
- _rskipped.remove(toRemove[j]);
- }
+ Object.keys(sequenceIds).forEach(function (sequenceId) {
+ if (_mlyCache.sequences.lineString[sequenceId]) {
+ lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceId]);
}
- } // Loop through all the entities to do some preprocessing
+ });
+ 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
+ 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;
- for (i = 0; i < entities.length; i++) {
- entity = entities[i];
- geometry = entity.geometry(graph); // Insert collision boxes around interesting points/vertices
+ function loaded() {
+ loadedCount += 1; // wait until both files are loaded
- if (geometry === 'point' || geometry === 'vertex' && isInterestingVertex(entity)) {
- var hasDirections = entity.directions(graph, projection).length;
- var markerPadding;
+ if (loadedCount === 2) resolve();
+ }
- if (!wireframe && geometry === 'point' && !(zoom >= 18 && hasDirections)) {
- renderNodeAs[entity.id] = 'point';
- markerPadding = 20; // extra y for marker height
- } else {
- renderNodeAs[entity.id] = 'vertex';
- markerPadding = 0;
- }
+ var head = select('head'); // load mapillary-viewercss
- var coord = projection(entity.loc);
- var nodePadding = 10;
- var bbox = {
- minX: coord[0] - nodePadding,
- minY: coord[1] - nodePadding - markerPadding,
- maxX: coord[0] + nodePadding,
- maxY: coord[1] + nodePadding
- };
- doInsert(bbox, entity.id + 'P');
- } // From here on, treat vertices like points
+ 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
+ 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 (geometry === 'vertex') {
- geometry = 'point';
- } // Determine which entities are label-able
+ if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
+ this.resetTags();
+ }
+ },
+ // Show traffic sign detections in image viewer
+ showSignDetections: function showSignDetections(value) {
+ _mlyShowSignDetections = value;
+ 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]);
- var preset = geometry === 'area' && _mainPresetIndex.match(entity, graph);
- var icon = preset && !shouldSkipIcon(preset) && preset.icon;
- if (!icon && !utilDisplayName(entity)) continue;
+ if (fromDate) {
+ filter.push(['>=', 'capturedAt', new Date(fromDate).getTime()]);
+ }
- for (k = 0; k < labelStack.length; k++) {
- var matchGeom = labelStack[k][0];
- var matchKey = labelStack[k][1];
- var matchVal = labelStack[k][2];
- var hasVal = entity.tags[matchKey];
+ if (toDate) {
+ filter.push(['>=', 'capturedAt', new Date(toDate).getTime()]);
+ }
- if (geometry === matchGeom && hasVal && (matchVal === '*' || matchVal === hasVal)) {
- labelable[k].push(entity);
- break;
- }
- }
+ if (_mlyViewer) {
+ _mlyViewer.setFilter(filter);
}
- var positions = {
- point: [],
- line: [],
- area: []
- };
- var labelled = {
- point: [],
- line: [],
- area: []
- }; // Try and find a valid label for labellable entities
+ _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();
- for (k = 0; k < labelable.length; k++) {
- var fontSize = labelStack[k][3];
+ if (isHidden && _mlyViewer) {
+ wrap.selectAll('.photo-wrapper:not(.mly-wrapper)').classed('hide', true);
+ wrap.selectAll('.photo-wrapper.mly-wrapper').classed('hide', false);
- for (i = 0; i < labelable[k].length; i++) {
- entity = labelable[k][i];
- geometry = entity.geometry(graph);
- var getName = geometry === 'line' ? utilDisplayNameForPath : utilDisplayName;
- var name = getName(entity);
- var width = name && textWidth(name, fontSize);
- var p = null;
+ _mlyViewer.resize();
+ }
- if (geometry === 'point' || geometry === 'vertex') {
- // no point or vertex labels in wireframe mode
- // no vertex labels at low zooms (vertices have no icons)
- if (wireframe) continue;
- var renderAs = renderNodeAs[entity.id];
- if (renderAs === 'vertex' && zoom < 17) continue;
- p = getPointLabel(entity, width, fontSize, renderAs);
- } else if (geometry === 'line') {
- p = getLineLabel(entity, width, fontSize);
- } else if (geometry === 'area') {
- p = getAreaLabel(entity, width, fontSize);
- }
+ return this;
+ },
+ // Hide the image viewer and resets map markers
+ hideViewer: function hideViewer(context) {
+ _mlyActiveImage = null;
- if (p) {
- if (geometry === 'vertex') {
- geometry = 'point';
- } // treat vertex like point
+ if (!_mlyFallback && _mlyViewer) {
+ _mlyViewer.getComponent('sequence').stop();
+ }
+ 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);
- p.classes = geometry + ' tag-' + labelStack[k][1];
- positions[geometry].push(p);
- labelled[geometry].push(entity);
- }
+ if (imageId) {
+ hash.photo = 'mapillary/' + imageId;
+ } else {
+ delete hash.photo;
}
- }
- function isInterestingVertex(entity) {
- var selectedIDs = context.selectedIDs();
- return entity.hasInterestingTags() || entity.isEndpoint(graph) || entity.isConnected(graph) || selectedIDs.indexOf(entity.id) !== -1 || graph.parentWays(entity).some(function (parent) {
- return selectedIDs.indexOf(parent.id) !== -1;
- });
+ 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;
}
- function getPointLabel(entity, width, height, geometry) {
- var y = geometry === 'point' ? -12 : 0;
- var pointOffsets = {
- ltr: [15, y, 'start'],
- rtl: [-15, y, 'end']
- };
- var textDirection = _mainLocalizer.textDirection();
- var coord = projection(entity.loc);
- var textPadding = 2;
- var offset = pointOffsets[textDirection];
- var p = {
- height: height,
- width: width,
- x: coord[0] + offset[0],
- y: coord[1] + offset[1],
- textAnchor: offset[2]
- }; // insert a collision box for the text label..
-
- var bbox;
+ 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
- if (textDirection === 'rtl') {
- bbox = {
- minX: p.x - width - textPadding,
- minY: p.y - height / 2 - textPadding,
- maxX: p.x + textPadding,
- maxY: p.y + height / 2 + textPadding
- };
- } else {
- bbox = {
- minX: p.x - textPadding,
- minY: p.y - height / 2 - textPadding,
- maxX: p.x + width + textPadding,
- maxY: p.y + height / 2 + textPadding
- };
- }
+ 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
- if (tryInsert([bbox], entity.id, true)) {
- return p;
- }
+ };
}
- function getLineLabel(entity, width, height) {
- var viewport = geoExtent(context.projection.clipExtent()).polygon();
- var points = graph.childNodes(entity).map(function (node) {
- return projection(node.loc);
- });
- var length = geoPathLength(points);
- if (length < width + 20) return; // % along the line to attempt to place the label
+ _mlyViewer = new mapillary.Viewer(opts);
- var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25, 75, 20, 80, 15, 95, 10, 90, 5, 95];
- var padding = 3;
+ _mlyViewer.on('image', imageChanged);
- for (var i = 0; i < lineOffsets.length; i++) {
- var offset = lineOffsets[i];
- var middle = offset / 100 * length;
- var start = middle - width / 2;
- if (start < 0 || start + width > length) continue; // generate subpath and ignore paths that are invalid or don't cross viewport.
+ _mlyViewer.on('bearing', bearingChanged);
- var sub = subpath(points, start, start + width);
+ if (_mlyViewerFilter) {
+ _mlyViewer.setFilter(_mlyViewerFilter);
+ } // Register viewer resize handler
- if (!sub || !geoPolygonIntersectsPolygon(viewport, sub, true)) {
- continue;
- }
- var isReverse = reverse(sub);
+ context.ui().photoviewer.on('resize.mapillary', function () {
+ if (_mlyViewer) _mlyViewer.resize();
+ }); // imageChanged: called after the viewer has changed images and is ready.
- if (isReverse) {
- sub = sub.reverse();
- }
+ 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 bboxes = [];
- var boxsize = (height + 2) / 2;
+ if (_mlyShowFeatureDetections || _mlyShowSignDetections) {
+ that.updateDetections(image.id, "".concat(apiUrl, "/").concat(image.id, "/detections?access_token=").concat(accessToken, "&fields=id,image,geometry,value"));
+ }
- for (var j = 0; j < sub.length - 1; j++) {
- var a = sub[j];
- var b = sub[j + 1]; // split up the text into small collision boxes
+ dispatch$4.call('imageChanged');
+ } // bearingChanged: called when the bearing changes in the image viewer.
- var num = Math.max(1, Math.floor(geoVecLength(a, b) / boxsize / 2));
- for (var box = 0; box < num; box++) {
- var p = geoVecInterp(a, b, box / num);
- var x0 = p[0] - boxsize - padding;
- var y0 = p[1] - boxsize - padding;
- var x1 = p[0] + boxsize + padding;
- var y1 = p[1] + boxsize + padding;
- bboxes.push({
- minX: Math.min(x0, x1),
- minY: Math.min(y0, y1),
- maxX: Math.max(x0, x1),
- maxY: Math.max(y0, y1)
- });
- }
- }
+ function bearingChanged(e) {
+ dispatch$4.call('bearingChanged', undefined, e);
+ }
+ },
+ // Move to an image
+ selectImage: function selectImage(context, imageId) {
+ if (_mlyViewer && imageId) {
+ _mlyViewer.moveTo(imageId)["catch"](function (e) {
+ console.error('mly3', e); // eslint-disable-line no-console
+ });
+ }
- if (tryInsert(bboxes, entity.id, false)) {
- // accept this one
- return {
- 'font-size': height + 2,
- lineString: lineString(sub),
- startOffset: offset + '%'
- };
- }
- }
-
- function reverse(p) {
- var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
- return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI / 2 && angle > -Math.PI / 2);
- }
-
- function lineString(points) {
- return 'M' + points.join('L');
- }
-
- function subpath(points, from, to) {
- var sofar = 0;
- var start, end, i0, i1;
-
- for (var i = 0; i < points.length - 1; i++) {
- var a = points[i];
- var b = points[i + 1];
- var current = geoVecLength(a, b);
- var portion;
+ return this;
+ },
+ // Return the currently displayed image
+ getActiveImage: function getActiveImage() {
+ return _mlyActiveImage;
+ },
+ // Return a list of detection objects for the given id
+ getDetections: function getDetections(id) {
+ return loadData("".concat(apiUrl, "/").concat(id, "/detections?access_token=").concat(accessToken, "&fields=id,value,image"));
+ },
+ // Set the currently visible image
+ setActiveImage: function setActiveImage(image) {
+ if (image) {
+ _mlyActiveImage = {
+ ca: image.originalCompassAngle,
+ id: image.id,
+ loc: [image.originalLngLat.lng, image.originalLngLat.lat],
+ is_pano: image.cameraType === 'spherical',
+ sequence_id: image.sequenceId
+ };
+ } else {
+ _mlyActiveImage = null;
+ }
+ },
+ // Update the currently highlighted sequence and selected bubble.
+ setStyles: function setStyles(context, hovered) {
+ var hoveredImageId = hovered && hovered.id;
+ var hoveredSequenceId = hovered && hovered.sequence_id;
+ var selectedSequenceId = _mlyActiveImage && _mlyActiveImage.sequence_id;
+ context.container().selectAll('.layer-mapillary .viewfield-group').classed('highlighted', function (d) {
+ return d.sequence_id === selectedSequenceId || d.id === hoveredImageId;
+ }).classed('hovered', function (d) {
+ return d.id === hoveredImageId;
+ });
+ context.container().selectAll('.layer-mapillary .sequence').classed('highlighted', function (d) {
+ return d.properties.id === hoveredSequenceId;
+ }).classed('currentView', function (d) {
+ return d.properties.id === selectedSequenceId;
+ });
+ return this;
+ },
+ // Get detections for the current image and shows them in the image viewer
+ updateDetections: function updateDetections(imageId, url) {
+ if (!_mlyViewer || _mlyFallback) return;
+ if (!imageId) return;
+ var cache = _mlyCache.image_detections;
- if (!start && sofar + current >= from) {
- portion = (from - sofar) / current;
- start = [a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1])];
- i0 = i + 1;
+ if (cache.forImageId[imageId]) {
+ showDetections(_mlyCache.image_detections.forImageId[imageId]);
+ } else {
+ loadData(url).then(function (detections) {
+ detections.forEach(function (detection) {
+ if (!cache.forImageId[imageId]) {
+ cache.forImageId[imageId] = [];
}
- if (!end && sofar + current >= to) {
- portion = (to - sofar) / current;
- end = [a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1])];
- i1 = i + 1;
- }
+ cache.forImageId[imageId].push({
+ geometry: detection.geometry,
+ id: detection.id,
+ image_id: imageId,
+ value: detection.value
+ });
+ });
+ showDetections(_mlyCache.image_detections.forImageId[imageId] || []);
+ });
+ } // Create a tag for each detection and shows it in the image viewer
- sofar += current;
- }
- var result = points.slice(i0, i1);
- result.unshift(start);
- result.push(end);
- return result;
- }
- }
+ function showDetections(detections) {
+ var tagComponent = _mlyViewer.getComponent('tag');
- function getAreaLabel(entity, width, height) {
- var centroid = path.centroid(entity.asGeoJSON(graph));
- var extent = entity.extent(graph);
- var areaWidth = projection(extent[1])[0] - projection(extent[0])[0];
- if (isNaN(centroid[0]) || areaWidth < 20) return;
- var preset = _mainPresetIndex.match(entity, context.graph());
- var picon = preset && preset.icon;
- var iconSize = 17;
- var padding = 2;
- var p = {};
+ detections.forEach(function (data) {
+ var tag = makeTag(data);
- if (picon) {
- // icon and label..
- if (addIcon()) {
- addLabel(iconSize + padding);
- return p;
- }
- } else {
- // label only..
- if (addLabel(0)) {
- return p;
+ if (tag) {
+ tagComponent.add([tag]);
}
- }
-
- function addIcon() {
- var iconX = centroid[0] - iconSize / 2;
- var iconY = centroid[1] - iconSize / 2;
- var bbox = {
- minX: iconX,
- minY: iconY,
- maxX: iconX + iconSize,
- maxY: iconY + iconSize
- };
+ });
+ } // Create a Mapillary JS tag object
- if (tryInsert([bbox], entity.id + 'I', true)) {
- p.transform = 'translate(' + iconX + ',' + iconY + ')';
- return true;
- }
- return false;
- }
+ function makeTag(data) {
+ var valueParts = data.value.split('--');
+ if (!valueParts.length) return;
+ var tag;
+ var text;
+ var color = 0xffffff;
- function addLabel(yOffset) {
- if (width && areaWidth >= width + 20) {
- var labelX = centroid[0];
- var labelY = centroid[1] + yOffset;
- var bbox = {
- minX: labelX - width / 2 - padding,
- minY: labelY - height / 2 - padding,
- maxX: labelX + width / 2 + padding,
- maxY: labelY + height / 2 + padding
- };
+ if (_mlyHighlightedDetection === data.id) {
+ color = 0xffff00;
+ text = valueParts[1];
- if (tryInsert([bbox], entity.id, true)) {
- p.x = labelX;
- p.y = labelY;
- p.textAnchor = 'middle';
- p.height = height;
- return true;
- }
+ if (text === 'flat' || text === 'discrete' || text === 'sign') {
+ text = valueParts[2];
}
- return false;
+ text = text.replace(/-/g, ' ');
+ text = text.charAt(0).toUpperCase() + text.slice(1);
+ _mlyHighlightedDetection = null;
}
- } // force insert a singular bounding box
- // singular box only, no array, id better be unique
-
- function doInsert(bbox, id) {
- bbox.id = id;
- var oldbox = _entitybboxes[id];
+ var decodedGeometry = window.atob(data.geometry);
+ var uintArray = new Uint8Array(decodedGeometry.length);
- if (oldbox) {
- _rdrawn.remove(oldbox);
+ for (var i = 0; i < decodedGeometry.length; i++) {
+ uintArray[i] = decodedGeometry.charCodeAt(i);
}
- _entitybboxes[id] = bbox;
-
- _rdrawn.insert(bbox);
+ var tile = new VectorTile(new pbf(uintArray.buffer));
+ var layer = tile.layers['mpy-or'];
+ var geometries = layer.feature(0).loadGeometry();
+ var polygon = geometries.map(function (ring) {
+ return ring.map(function (point) {
+ return [point.x / layer.extent, point.y / layer.extent];
+ });
+ });
+ tag = new mapillary.OutlineTag(data.id, new mapillary.PolygonGeometry(polygon[0]), {
+ text: text,
+ textColor: color,
+ lineColor: color,
+ lineWidth: 2,
+ fillColor: color,
+ fillOpacity: 0.3
+ });
+ return tag;
}
+ },
+ // Return the current cache
+ cache: function cache() {
+ return _mlyCache;
+ }
+ };
- function tryInsert(bboxes, id, saveSkipped) {
- var skipped = false;
-
- for (var i = 0; i < bboxes.length; i++) {
- var bbox = bboxes[i];
- bbox.id = id; // Check that label is visible
+ function validationIssue(attrs) {
+ this.type = attrs.type; // required - name of rule that created the issue (e.g. 'missing_tag')
- if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) {
- skipped = true;
- break;
- }
+ this.subtype = attrs.subtype; // optional - category of the issue within the type (e.g. 'relation_type' under 'missing_tag')
- if (_rdrawn.collides(bbox)) {
- skipped = true;
- break;
- }
- }
+ this.severity = attrs.severity; // required - 'warning' or 'error'
- _entitybboxes[id] = bboxes;
+ this.message = attrs.message; // required - function returning localized string
- if (skipped) {
- if (saveSkipped) {
- _rskipped.load(bboxes);
- }
- } else {
- _rdrawn.load(bboxes);
- }
+ this.reference = attrs.reference; // optional - function(selection) to render reference information
- return !skipped;
- }
+ this.entityIds = attrs.entityIds; // optional - array of IDs of entities involved in the issue
- var layer = selection.selectAll('.layer-osm.labels');
- layer.selectAll('.labels-group').data(['halo', 'label', 'debug']).enter().append('g').attr('class', function (d) {
- return 'labels-group ' + d;
- });
- var halo = layer.selectAll('.labels-group.halo');
- var label = layer.selectAll('.labels-group.label');
- var debug = layer.selectAll('.labels-group.debug'); // points
+ this.loc = attrs.loc; // optional - [lon, lat] to zoom in on to see the issue
- drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
- drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point); // lines
+ this.data = attrs.data; // optional - object containing extra data for the fixes
- drawLinePaths(layer, labelled.line, filter, '', positions.line);
- drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
- drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line); // areas
+ this.dynamicFixes = attrs.dynamicFixes; // optional - function(context) returning fixes
- drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
- drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
- drawAreaIcons(label, labelled.area, filter, 'areaicon', positions.area);
- drawAreaIcons(halo, labelled.area, filter, 'areaicon-halo', positions.area); // debug
+ this.hash = attrs.hash; // optional - string to further differentiate the issue
- drawCollisionBoxes(debug, _rskipped, 'debug-skipped');
- drawCollisionBoxes(debug, _rdrawn, 'debug-drawn');
- layer.call(filterLabels);
- }
+ this.id = generateID.apply(this); // generated - see below
- function filterLabels(selection) {
- var drawLayer = selection.selectAll('.layer-osm.labels');
- var layers = drawLayer.selectAll('.labels-group.halo, .labels-group.label');
- layers.selectAll('.nolabel').classed('nolabel', false);
- var mouse = context.map().mouse();
- var graph = context.graph();
- var selectedIDs = context.selectedIDs();
- var ids = [];
- var pad, bbox; // hide labels near the mouse
+ this.key = generateKey.apply(this); // generated - see below (call after generating this.id)
- if (mouse) {
- pad = 20;
- bbox = {
- minX: mouse[0] - pad,
- minY: mouse[1] - pad,
- maxX: mouse[0] + pad,
- maxY: mouse[1] + pad
- };
+ this.autoFix = null; // generated - if autofix exists, will be set below
+ // A unique, deterministic string hash.
+ // Issues with identical id values are considered identical.
- var nearMouse = _rdrawn.search(bbox).map(function (entity) {
- return entity.id;
- });
+ function generateID() {
+ var parts = [this.type];
- ids.push.apply(ids, nearMouse);
- } // hide labels on selected nodes (they look weird when dragging / haloed)
+ if (this.hash) {
+ // subclasses can pass in their own differentiator
+ parts.push(this.hash);
+ }
+ if (this.subtype) {
+ parts.push(this.subtype);
+ } // include the entities this issue is for
+ // (sort them so the id is deterministic)
- for (var i = 0; i < selectedIDs.length; i++) {
- var entity = graph.hasEntity(selectedIDs[i]);
- if (entity && entity.type === 'node') {
- ids.push(selectedIDs[i]);
- }
+ if (this.entityIds) {
+ var entityKeys = this.entityIds.slice().sort();
+ parts.push.apply(parts, entityKeys);
}
- layers.selectAll(utilEntitySelector(ids)).classed('nolabel', true); // draw the mouse bbox if debugging is on..
+ return parts.join(':');
+ } // An identifier suitable for use as the second argument to d3.selection#data().
+ // (i.e. this should change whenever the data needs to be refreshed)
- var debug = selection.selectAll('.labels-group.debug');
- var gj = [];
- if (context.getDebug('collision')) {
- gj = bbox ? [{
- type: 'Polygon',
- coordinates: [[[bbox.minX, bbox.minY], [bbox.maxX, bbox.minY], [bbox.maxX, bbox.maxY], [bbox.minX, bbox.maxY], [bbox.minX, bbox.minY]]]
- }] : [];
+ function generateKey() {
+ return this.id + ':' + Date.now().toString(); // include time of creation
+ }
+
+ this.extent = function (resolver) {
+ if (this.loc) {
+ return geoExtent(this.loc);
}
- var box = debug.selectAll('.debug-mouse').data(gj); // exit
+ if (this.entityIds && this.entityIds.length) {
+ return this.entityIds.reduce(function (extent, entityId) {
+ return extent.extend(resolver.entity(entityId).extent(resolver));
+ }, geoExtent());
+ }
- box.exit().remove(); // enter/update
+ return null;
+ };
- box.enter().append('path').attr('class', 'debug debug-mouse yellow').merge(box).attr('d', d3_geoPath());
- }
+ this.fixes = function (context) {
+ var fixes = this.dynamicFixes ? this.dynamicFixes(context) : [];
+ var issue = this;
- var throttleFilterLabels = throttle(filterLabels, 100);
+ if (issue.severity === 'warning') {
+ // allow ignoring any issue that's not an error
+ fixes.push(new validationIssueFix({
+ title: _t.html('issues.fix.ignore_issue.title'),
+ icon: 'iD-icon-close',
+ onClick: function onClick() {
+ context.validator().ignoreIssue(this.issue.id);
+ }
+ }));
+ }
- drawLabels.observe = function (selection) {
- var listener = function listener() {
- throttleFilterLabels(selection);
- };
+ fixes.forEach(function (fix) {
+ // the id doesn't matter as long as it's unique to this issue/fix
+ fix.id = fix.title; // add a reference to the issue for use in actions
- selection.on('mousemove.hidelabels', listener);
- context.on('enter.hidelabels', listener);
- };
+ fix.issue = issue;
- drawLabels.off = function (selection) {
- throttleFilterLabels.cancel();
- selection.on('mousemove.hidelabels', null);
- context.on('enter.hidelabels', null);
+ if (fix.autoArgs) {
+ issue.autoFix = fix;
+ }
+ });
+ return fixes;
};
-
- return drawLabels;
}
+ function validationIssueFix(attrs) {
+ this.title = attrs.title; // Required
- var _layerEnabled$1 = false;
+ this.onClick = attrs.onClick; // Optional - the function to run to apply the fix
- var _qaService$1;
+ this.disabledReason = attrs.disabledReason; // Optional - a string explaining why the fix is unavailable, if any
- function svgImproveOSM(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- return dispatch.call('change');
- }, 1000);
+ this.icon = attrs.icon; // Optional - shows 'iD-icon-wrench' if not set
- var minZoom = 12;
- var touchLayer = select(null);
- var drawLayer = select(null);
- var layerVisible = false;
+ this.entityIds = attrs.entityIds || []; // Optional - used for hover-higlighting.
- function markerPath(selection, klass) {
- selection.attr('class', klass).attr('transform', 'translate(-10, -28)').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
- } // Loosely-coupled improveOSM service for fetching issues
+ this.autoArgs = attrs.autoArgs; // Optional - pass [actions, annotation] arglist if this fix can automatically run
+ this.issue = null; // Generated link - added by validationIssue
+ }
- function getService() {
- if (services.improveOSM && !_qaService$1) {
- _qaService$1 = services.improveOSM;
+ var buildRuleChecks = function buildRuleChecks() {
+ return {
+ equals: function equals(_equals) {
+ return function (tags) {
+ return Object.keys(_equals).every(function (k) {
+ return _equals[k] === tags[k];
+ });
+ };
+ },
+ notEquals: function notEquals(_notEquals) {
+ return function (tags) {
+ return Object.keys(_notEquals).some(function (k) {
+ return _notEquals[k] !== tags[k];
+ });
+ };
+ },
+ absence: function absence(_absence) {
+ return function (tags) {
+ return Object.keys(tags).indexOf(_absence) === -1;
+ };
+ },
+ presence: function presence(_presence) {
+ return function (tags) {
+ return Object.keys(tags).indexOf(_presence) > -1;
+ };
+ },
+ greaterThan: function greaterThan(_greaterThan) {
+ var key = Object.keys(_greaterThan)[0];
+ var value = _greaterThan[key];
+ return function (tags) {
+ return tags[key] > value;
+ };
+ },
+ greaterThanEqual: function greaterThanEqual(_greaterThanEqual) {
+ var key = Object.keys(_greaterThanEqual)[0];
+ var value = _greaterThanEqual[key];
+ return function (tags) {
+ return tags[key] >= value;
+ };
+ },
+ lessThan: function lessThan(_lessThan) {
+ var key = Object.keys(_lessThan)[0];
+ var value = _lessThan[key];
+ return function (tags) {
+ return tags[key] < value;
+ };
+ },
+ lessThanEqual: function lessThanEqual(_lessThanEqual) {
+ var key = Object.keys(_lessThanEqual)[0];
+ var value = _lessThanEqual[key];
+ return function (tags) {
+ return tags[key] <= value;
+ };
+ },
+ positiveRegex: function positiveRegex(_positiveRegex) {
+ var tagKey = Object.keys(_positiveRegex)[0];
- _qaService$1.on('loaded', throttledRedraw);
- } else if (!services.improveOSM && _qaService$1) {
- _qaService$1 = null;
- }
+ var expression = _positiveRegex[tagKey].join('|');
- return _qaService$1;
- } // Show the markers
+ var regex = new RegExp(expression);
+ return function (tags) {
+ return regex.test(tags[tagKey]);
+ };
+ },
+ negativeRegex: function negativeRegex(_negativeRegex) {
+ var tagKey = Object.keys(_negativeRegex)[0];
+ var expression = _negativeRegex[tagKey].join('|');
- function editOn() {
- if (!layerVisible) {
- layerVisible = true;
- drawLayer.style('display', 'block');
+ var regex = new RegExp(expression);
+ return function (tags) {
+ return !regex.test(tags[tagKey]);
+ };
}
- } // Immediately remove the markers and their touch targets
+ };
+ };
+ var buildLineKeys = function buildLineKeys() {
+ return {
+ highway: {
+ rest_area: true,
+ services: true
+ },
+ railway: {
+ roundhouse: true,
+ station: true,
+ traverser: true,
+ turntable: true,
+ wash: true
+ }
+ };
+ };
- function editOff() {
- if (layerVisible) {
- layerVisible = false;
- drawLayer.style('display', 'none');
- drawLayer.selectAll('.qaItem.improveOSM').remove();
- touchLayer.selectAll('.qaItem.improveOSM').remove();
- }
- } // Enable the layer. This shows the markers and transitions them to visible.
-
+ var serviceMapRules = {
+ init: function init() {
+ this._ruleChecks = buildRuleChecks();
+ this._validationRules = [];
+ this._areaKeys = osmAreaKeys;
+ this._lineKeys = buildLineKeys();
+ },
+ // list of rules only relevant to tag checks...
+ filterRuleChecks: function filterRuleChecks(selector) {
+ var _ruleChecks = this._ruleChecks;
+ return Object.keys(selector).reduce(function (rules, key) {
+ if (['geometry', 'error', 'warning'].indexOf(key) === -1) {
+ rules.push(_ruleChecks[key](selector[key]));
+ }
- function layerOn() {
- editOn();
- drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
- return dispatch.call('change');
- });
- } // Disable the layer. This transitions the layer invisible and then hides the markers.
+ return rules;
+ }, []);
+ },
+ // builds tagMap from mapcss-parse selector object...
+ buildTagMap: function buildTagMap(selector) {
+ var getRegexValues = function getRegexValues(regexes) {
+ return regexes.map(function (regex) {
+ return regex.replace(/\$|\^/g, '');
+ });
+ };
+ var tagMap = Object.keys(selector).reduce(function (expectedTags, key) {
+ var values;
+ var isRegex = /regex/gi.test(key);
+ var isEqual = /equals/gi.test(key);
- function layerOff() {
- throttledRedraw.cancel();
- drawLayer.interrupt();
- touchLayer.selectAll('.qaItem.improveOSM').remove();
- drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
- editOff();
- dispatch.call('change');
- });
- } // Update the issue markers
+ if (isRegex || isEqual) {
+ Object.keys(selector[key]).forEach(function (selectorKey) {
+ values = isEqual ? [selector[key][selectorKey]] : getRegexValues(selector[key][selectorKey]);
+ if (expectedTags.hasOwnProperty(selectorKey)) {
+ values = values.concat(expectedTags[selectorKey]);
+ }
- function updateMarkers() {
- if (!layerVisible || !_layerEnabled$1) return;
- var service = getService();
- var selectedID = context.selectedErrorID();
- var data = service ? service.getItems(projection) : [];
- var getTransform = svgPointTransform(projection); // Draw markers..
+ expectedTags[selectorKey] = values;
+ });
+ } else if (/(greater|less)Than(Equal)?|presence/g.test(key)) {
+ var tagKey = /presence/.test(key) ? selector[key] : Object.keys(selector[key])[0];
+ values = [selector[key][tagKey]];
- var markers = drawLayer.selectAll('.qaItem.improveOSM').data(data, function (d) {
- return d.id;
- }); // exit
+ if (expectedTags.hasOwnProperty(tagKey)) {
+ values = values.concat(expectedTags[tagKey]);
+ }
- markers.exit().remove(); // enter
+ expectedTags[tagKey] = values;
+ }
- var markersEnter = markers.enter().append('g').attr('class', function (d) {
- return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
- });
- markersEnter.append('polygon').call(markerPath, 'shadow');
- markersEnter.append('ellipse').attr('cx', 0).attr('cy', 0).attr('rx', 4.5).attr('ry', 2).attr('class', 'stroke');
- markersEnter.append('polygon').attr('fill', 'currentColor').call(markerPath, 'qaItem-fill');
- markersEnter.append('use').attr('transform', 'translate(-6.5, -23)').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('xlink:href', function (d) {
- var picon = d.icon;
+ return expectedTags;
+ }, {});
+ return tagMap;
+ },
+ // inspired by osmWay#isArea()
+ inferGeometry: function inferGeometry(tagMap) {
+ var _lineKeys = this._lineKeys;
+ var _areaKeys = this._areaKeys;
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return "#".concat(picon).concat(isMaki ? '-11' : '');
- }
- }); // update
+ var keyValueDoesNotImplyArea = function keyValueDoesNotImplyArea(key) {
+ return utilArrayIntersection(tagMap[key], Object.keys(_areaKeys[key])).length > 0;
+ };
- markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
- return d.id === selectedID;
- }).attr('transform', getTransform); // Draw targets..
+ var keyValueImpliesLine = function keyValueImpliesLine(key) {
+ return utilArrayIntersection(tagMap[key], Object.keys(_lineKeys[key])).length > 0;
+ };
- if (touchLayer.empty()) return;
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var targets = touchLayer.selectAll('.qaItem.improveOSM').data(data, function (d) {
- return d.id;
- }); // exit
+ if (tagMap.hasOwnProperty('area')) {
+ if (tagMap.area.indexOf('yes') > -1) {
+ return 'area';
+ }
- targets.exit().remove(); // enter/update
+ if (tagMap.area.indexOf('no') > -1) {
+ return 'line';
+ }
+ }
- targets.enter().append('rect').attr('width', '20px').attr('height', '30px').attr('x', '-10px').attr('y', '-28px').merge(targets).sort(sortY).attr('class', function (d) {
- return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
- }).attr('transform', getTransform);
+ for (var key in tagMap) {
+ if (key in _areaKeys && !keyValueDoesNotImplyArea(key)) {
+ return 'area';
+ }
- function sortY(a, b) {
- return a.id === selectedID ? 1 : b.id === selectedID ? -1 : b.loc[1] - a.loc[1];
+ if (key in _lineKeys && keyValueImpliesLine(key)) {
+ return 'area';
+ }
}
- } // Draw the ImproveOSM layer and schedule loading issues and updating markers.
+ return 'line';
+ },
+ // adds from mapcss-parse selector check...
+ addRule: function addRule(selector) {
+ var rule = {
+ // checks relevant to mapcss-selector
+ checks: this.filterRuleChecks(selector),
+ // true if all conditions for a tag error are true..
+ matches: function matches(entity) {
+ return this.checks.every(function (check) {
+ return check(entity.tags);
+ });
+ },
+ // borrowed from Way#isArea()
+ inferredGeometry: this.inferGeometry(this.buildTagMap(selector), this._areaKeys),
+ geometryMatches: function geometryMatches(entity, graph) {
+ if (entity.type === 'node' || entity.type === 'relation') {
+ return selector.geometry === entity.type;
+ } else if (entity.type === 'way') {
+ return this.inferredGeometry === entity.geometry(graph);
+ }
+ },
+ // when geometries match and tag matches are present, return a warning...
+ findIssues: function findIssues(entity, graph, issues) {
+ if (this.geometryMatches(entity, graph) && this.matches(entity)) {
+ var severity = Object.keys(selector).indexOf('error') > -1 ? 'error' : 'warning';
+ var _message = selector[severity];
+ issues.push(new validationIssue({
+ type: 'maprules',
+ severity: severity,
+ message: function message() {
+ return _message;
+ },
+ entityIds: [entity.id]
+ }));
+ }
+ }
+ };
- function drawImproveOSM(selection) {
- var service = getService();
- var surface = context.surface();
+ this._validationRules.push(rule);
+ },
+ clearRules: function clearRules() {
+ this._validationRules = [];
+ },
+ // returns validationRules...
+ validationRules: function validationRules() {
+ return this._validationRules;
+ },
+ // returns ruleChecks
+ ruleChecks: function ruleChecks() {
+ return this._ruleChecks;
+ }
+ };
- if (surface && !surface.empty()) {
- touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
- }
+ var apibase$2 = 'https://nominatim.openstreetmap.org/';
+ var _inflight$2 = {};
- drawLayer = selection.selectAll('.layer-improveOSM').data(service ? [0] : []);
- drawLayer.exit().remove();
- drawLayer = drawLayer.enter().append('g').attr('class', 'layer-improveOSM').style('display', _layerEnabled$1 ? 'block' : 'none').merge(drawLayer);
+ var _nominatimCache;
- if (_layerEnabled$1) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- service.loadIssues(projection);
- updateMarkers();
+ var serviceNominatim = {
+ init: function init() {
+ _inflight$2 = {};
+ _nominatimCache = new RBush();
+ },
+ reset: function reset() {
+ Object.values(_inflight$2).forEach(function (controller) {
+ controller.abort();
+ });
+ _inflight$2 = {};
+ _nominatimCache = new RBush();
+ },
+ countryCode: function countryCode(location, callback) {
+ this.reverse(location, function (err, result) {
+ if (err) {
+ return callback(err);
+ } else if (result.address) {
+ return callback(null, result.address.country_code);
} else {
- editOff();
+ return callback('Unable to geocode', null);
}
+ });
+ },
+ reverse: function reverse(loc, callback) {
+ var cached = _nominatimCache.search({
+ minX: loc[0],
+ minY: loc[1],
+ maxX: loc[0],
+ maxY: loc[1]
+ });
+
+ if (cached.length > 0) {
+ if (callback) callback(null, cached[0].data);
+ return;
}
- } // Toggles the layer on and off
+ var params = {
+ zoom: 13,
+ format: 'json',
+ addressdetails: 1,
+ lat: loc[1],
+ lon: loc[0]
+ };
+ var url = apibase$2 + 'reverse?' + utilQsString(params);
+ if (_inflight$2[url]) return;
+ var controller = new AbortController();
+ _inflight$2[url] = controller;
+ d3_json(url, {
+ signal: controller.signal
+ }).then(function (result) {
+ delete _inflight$2[url];
- drawImproveOSM.enabled = function (val) {
- if (!arguments.length) return _layerEnabled$1;
- _layerEnabled$1 = val;
+ if (result && result.error) {
+ throw new Error(result.error);
+ }
- if (_layerEnabled$1) {
- layerOn();
- } else {
- layerOff();
+ var extent = geoExtent(loc).padByMeters(200);
- if (context.selectedErrorID()) {
- context.enter(modeBrowse(context));
+ _nominatimCache.insert(Object.assign(extent.bbox(), {
+ data: result
+ }));
+
+ if (callback) callback(null, result);
+ })["catch"](function (err) {
+ delete _inflight$2[url];
+ if (err.name === 'AbortError') return;
+ if (callback) callback(err.message);
+ });
+ },
+ search: function search(val, callback) {
+ var searchVal = encodeURIComponent(val);
+ var url = apibase$2 + 'search/' + searchVal + '?limit=10&format=json';
+ if (_inflight$2[url]) return;
+ var controller = new AbortController();
+ _inflight$2[url] = controller;
+ d3_json(url, {
+ signal: controller.signal
+ }).then(function (result) {
+ delete _inflight$2[url];
+
+ if (result && result.error) {
+ throw new Error(result.error);
}
- }
- dispatch.call('change');
- return this;
- };
+ if (callback) callback(null, result);
+ })["catch"](function (err) {
+ delete _inflight$2[url];
+ if (err.name === 'AbortError') return;
+ if (callback) callback(err.message);
+ });
+ }
+ };
- drawImproveOSM.supported = function () {
- return !!getService();
- };
+ // for punction see https://stackoverflow.com/a/21224179
- return drawImproveOSM;
+ function simplify$1(str) {
+ if (typeof str !== 'string') return '';
+ return diacritics.remove(str.replace(/&/g, 'and').replace(/İ/ig, 'i') // for BİM, İşbank - #5017
+ .replace(/[\s\-=_!"#%'*{},.\/:;?\(\)\[\]@\\$\^*+<>«»~`’\u00a1\u00a7\u00b6\u00b7\u00bf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d\u166e\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u2000-\u206f\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00-\u2e7f\u3001-\u3003\u303d\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a\ufe6b\ufeff\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65]+/g, '').toLowerCase());
}
- var _layerEnabled = false;
+ var matchGroups$1 = {adult_gaming_centre:["amenity/casino","amenity/gambling","leisure/adult_gaming_centre"],beauty:["shop/beauty","shop/hairdresser_supply"],bed:["shop/bed","shop/furniture"],beverages:["shop/alcohol","shop/beer","shop/beverages","shop/wine"],camping:["leisure/park","tourism/camp_site","tourism/caravan_site"],car_parts:["shop/car_parts","shop/car_repair","shop/tires","shop/tyres"],clinic:["amenity/clinic","amenity/doctors","healthcare/clinic","healthcare/dialysis"],confectionery:["shop/candy","shop/chocolate","shop/confectionery"],convenience:["shop/beauty","shop/chemist","shop/convenience","shop/cosmetics","shop/grocery","shop/newsagent","shop/perfumery"],coworking:["amenity/coworking_space","office/coworking","office/coworking_space"],dentist:["amenity/dentist","amenity/doctors","healthcare/dentist"],electronics:["office/telecommunication","shop/computer","shop/electronics","shop/hifi","shop/mobile","shop/mobile_phone","shop/telecommunication"],fabric:["shop/fabric","shop/haberdashery","shop/sewing"],fashion:["shop/accessories","shop/bag","shop/botique","shop/clothes","shop/department_store","shop/fashion","shop/fashion_accessories","shop/sports","shop/shoes"],financial:["amenity/bank","office/accountant","office/financial","office/financial_advisor","office/tax_advisor","shop/tax"],fitness:["leisure/fitness_centre","leisure/fitness_center","leisure/sports_centre","leisure/sports_center"],food:["amenity/pub","amenity/bar","amenity/cafe","amenity/fast_food","amenity/ice_cream","amenity/restaurant","shop/bakery","shop/ice_cream","shop/pastry","shop/tea","shop/coffee"],fuel:["amenity/fuel","shop/gas","shop/convenience;gas","shop/gas;convenience"],gift:["shop/gift","shop/card","shop/cards","shop/stationery"],hardware:["shop/bathroom_furnishing","shop/carpet","shop/diy","shop/doityourself","shop/doors","shop/electrical","shop/flooring","shop/hardware","shop/hardware_store","shop/power_tools","shop/tool_hire","shop/tools","shop/trade"],health_food:["shop/health","shop/health_food","shop/herbalist","shop/nutrition_supplements"],hobby:["shop/electronics","shop/hobby","shop/books","shop/games","shop/collector","shop/toys","shop/model","shop/video_games","shop/anime"],hospital:["amenity/doctors","amenity/hospital","healthcare/hospital"],houseware:["shop/houseware","shop/interior_decoration"],lifeboat_station:["amenity/lifeboat_station","emergency/lifeboat_station","emergency/marine_rescue"],lodging:["tourism/hotel","tourism/motel"],money_transfer:["amenity/money_transfer","shop/money_transfer"],office_supplies:["shop/office_supplies","shop/stationary","shop/stationery"],outdoor:["shop/outdoor","shop/sports"],pharmacy:["amenity/doctors","amenity/pharmacy","healthcare/pharmacy"],playground:["amenity/theme_park","leisure/amusement_arcade","leisure/playground"],rental:["amenity/bicycle_rental","amenity/boat_rental","amenity/car_rental","amenity/truck_rental","amenity/vehicle_rental","shop/rental"],school:["amenity/childcare","amenity/college","amenity/kindergarten","amenity/language_school","amenity/prep_school","amenity/school","amenity/university"],storage:["shop/storage_units","shop/storage_rental"],substation:["power/station","power/substation","power/sub_station"],supermarket:["shop/food","shop/frozen_food","shop/greengrocer","shop/grocery","shop/supermarket","shop/wholesale"],variety_store:["shop/variety_store","shop/discount","shop/convenience"],vending:["amenity/vending_machine","shop/vending_machine"],weight_loss:["amenity/doctors","amenity/weight_clinic","healthcare/counselling","leisure/fitness_centre","office/therapist","shop/beauty","shop/diet","shop/food","shop/health_food","shop/herbalist","shop/nutrition","shop/nutrition_supplements","shop/weight_loss"],wholesale:["shop/wholesale","shop/supermarket","shop/department_store"]};
+ var matchGroupsJSON = {
+ matchGroups: matchGroups$1
+ };
- var _qaService;
+ var genericWords = ["^(barn|bazaa?r|bench|bou?tique|building|casa|church)$","^(baseball|basketball|football|soccer|softball|tennis(halle)?)\\s?(field|court)?$","^(club|green|out|ware)\\s?house$","^(driveway|el árbol|fountain|golf|government|graveyard)$","^(fixme|n\\s?\\/?\\s?a|name|no\\s?name|none|null|temporary|test|unknown)$","^(hofladen|librairie|magazine?|maison)$","^(mobile home|skate)?\\s?park$","^(obuwie|pond|pool|sale|shops?|sklep|stores?)$","^\\?+$","^private$","^tattoo( studio)?$","^windmill$","^церковная( лавка)?$"];
+ var genericWordsJSON = {
+ genericWords: genericWords
+ };
- function svgOsmose(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- return dispatch.call('change');
- }, 1000);
+ var trees$1 = {brands:{emoji:"🍔",mainTag:"brand:wikidata",sourceTags:["brand","name"],nameTags:{primary:"^(name|name:\\w+)$",alternate:"^(brand|brand:\\w+|operator|operator:\\w+|\\w+_name|\\w+_name:\\w+)$"}},flags:{emoji:"🚩",mainTag:"flag:wikidata",nameTags:{primary:"^(flag:name|flag:name:\\w+)$",alternate:"^(country|country:\\w+|flag|flag:\\w+|subject|subject:\\w+)$"}},operators:{emoji:"💼",mainTag:"operator:wikidata",sourceTags:["operator"],nameTags:{primary:"^(name|name:\\w+|operator|operator:\\w+)$",alternate:"^(brand|brand:\\w+|\\w+_name|\\w+_name:\\w+)$"}},transit:{emoji:"🚇",mainTag:"network:wikidata",sourceTags:["network"],nameTags:{primary:"^network$",alternate:"^(operator|operator:\\w+|network:\\w+|\\w+_name|\\w+_name:\\w+)$"}}};
+ var treesJSON = {
+ trees: trees$1
+ };
- var minZoom = 12;
- var touchLayer = select(null);
- var drawLayer = select(null);
- var layerVisible = false;
+ var matchGroups = matchGroupsJSON.matchGroups;
+ var trees = treesJSON.trees;
+ var Matcher = /*#__PURE__*/function () {
+ //
+ // `constructor`
+ // initialize the genericWords regexes
+ function Matcher() {
+ var _this = this;
- function markerPath(selection, klass) {
- selection.attr('class', klass).attr('transform', 'translate(-10, -28)').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
- } // Loosely-coupled osmose service for fetching issues
+ _classCallCheck$1(this, Matcher);
+ // The `matchIndex` is a specialized structure that allows us to quickly answer
+ // _"Given a [key/value tagpair, name, location], what canonical items (brands etc) can match it?"_
+ //
+ // The index contains all valid combinations of k/v tagpairs and names
+ // matchIndex:
+ // {
+ // 'k/v': {
+ // 'primary': Map (String 'nsimple' -> Set (itemIDs…), // matches for tags like `name`, `name:xx`, etc.
+ // 'alternate': Map (String 'nsimple' -> Set (itemIDs…), // matches for tags like `alt_name`, `brand`, etc.
+ // 'excludeNamed': Map (String 'pattern' -> RegExp),
+ // 'excludeGeneric': Map (String 'pattern' -> RegExp)
+ // },
+ // }
+ //
+ // {
+ // 'amenity/bank': {
+ // 'primary': {
+ // 'firstbank': Set ("firstbank-978cca", "firstbank-9794e6", "firstbank-f17495", …),
+ // …
+ // },
+ // 'alternate': {
+ // '1stbank': Set ("firstbank-f17495"),
+ // …
+ // }
+ // },
+ // 'shop/supermarket': {
+ // 'primary': {
+ // 'coop': Set ("coop-76454b", "coop-ebf2d9", "coop-36e991", …),
+ // 'coopfood': Set ("coopfood-a8278b", …),
+ // …
+ // },
+ // 'alternate': {
+ // 'coop': Set ("coopfood-a8278b", …),
+ // 'federatedcooperatives': Set ("coop-76454b", …),
+ // 'thecooperative': Set ("coopfood-a8278b", …),
+ // …
+ // }
+ // }
+ // }
+ //
+ this.matchIndex = undefined; // The `genericWords` structure matches the contents of genericWords.json to instantiated RegExp objects
+ // Map (String 'pattern' -> RegExp),
- function getService() {
- if (services.osmose && !_qaService) {
- _qaService = services.osmose;
+ this.genericWords = new Map();
+ (genericWordsJSON.genericWords || []).forEach(function (s) {
+ return _this.genericWords.set(s, new RegExp(s, 'i'));
+ }); // The `itemLocation` structure maps itemIDs to locationSetIDs:
+ // {
+ // 'firstbank-f17495': '+[first_bank_western_us.geojson]',
+ // 'firstbank-978cca': '+[first_bank_carolinas.geojson]',
+ // 'coop-76454b': '+[Q16]',
+ // 'coopfood-a8278b': '+[Q23666]',
+ // …
+ // }
- _qaService.on('loaded', throttledRedraw);
- } else if (!services.osmose && _qaService) {
- _qaService = null;
- }
+ this.itemLocation = undefined; // The `locationSets` structure maps locationSetIDs to *resolved* locationSets:
+ // {
+ // '+[first_bank_western_us.geojson]': GeoJSON {…},
+ // '+[first_bank_carolinas.geojson]': GeoJSON {…},
+ // '+[Q16]': GeoJSON {…},
+ // '+[Q23666]': GeoJSON {…},
+ // …
+ // }
- return _qaService;
- } // Show the markers
+ this.locationSets = undefined; // The `locationIndex` is an instance of which-polygon spatial index for the locationSets.
+ this.locationIndex = undefined; // Array of match conflict pairs (currently unused)
- function editOn() {
- if (!layerVisible) {
- layerVisible = true;
- drawLayer.style('display', 'block');
- }
- } // Immediately remove the markers and their touch targets
+ this.warnings = [];
+ } //
+ // `buildMatchIndex()`
+ // Call this to prepare the matcher for use
+ //
+ // `data` needs to be an Object indexed on a 'tree/key/value' path.
+ // (e.g. cache filled by `fileTree.read` or data found in `dist/nsi.json`)
+ // {
+ // 'brands/amenity/bank': { properties: {}, items: [ {}, {}, … ] },
+ // 'brands/amenity/bar': { properties: {}, items: [ {}, {}, … ] },
+ // …
+ // }
+ //
- function editOff() {
- if (layerVisible) {
- layerVisible = false;
- drawLayer.style('display', 'none');
- drawLayer.selectAll('.qaItem.osmose').remove();
- touchLayer.selectAll('.qaItem.osmose').remove();
- }
- } // Enable the layer. This shows the markers and transitions them to visible.
+ _createClass$1(Matcher, [{
+ key: "buildMatchIndex",
+ value: function buildMatchIndex(data) {
+ var that = this;
+ if (that.matchIndex) return; // it was built already
+ that.matchIndex = new Map();
+ var seenTree = new Map(); // warn if the same [k, v, nsimple] appears in multiple trees - #5625
- function layerOn() {
- editOn();
- drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
- return dispatch.call('change');
- });
- } // Disable the layer. This transitions the layer invisible and then hides the markers.
+ Object.keys(data).forEach(function (tkv) {
+ var category = data[tkv];
+ var parts = tkv.split('/', 3); // tkv = "tree/key/value"
+ var t = parts[0];
+ var k = parts[1];
+ var v = parts[2];
+ var thiskv = "".concat(k, "/").concat(v);
+ var tree = trees[t];
+ var branch = that.matchIndex.get(thiskv);
- function layerOff() {
- throttledRedraw.cancel();
- drawLayer.interrupt();
- touchLayer.selectAll('.qaItem.osmose').remove();
- drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
- editOff();
- dispatch.call('change');
- });
- } // Update the issue markers
+ if (!branch) {
+ branch = {
+ primary: new Map(),
+ alternate: new Map(),
+ excludeGeneric: new Map(),
+ excludeNamed: new Map()
+ };
+ that.matchIndex.set(thiskv, branch);
+ } // ADD EXCLUSIONS
- function updateMarkers() {
- if (!layerVisible || !_layerEnabled) return;
- var service = getService();
- var selectedID = context.selectedErrorID();
- var data = service ? service.getItems(projection) : [];
- var getTransform = svgPointTransform(projection); // Draw markers..
+ var properties = category.properties || {};
+ var exclude = properties.exclude || {};
+ (exclude.generic || []).forEach(function (s) {
+ return branch.excludeGeneric.set(s, new RegExp(s, 'i'));
+ });
+ (exclude.named || []).forEach(function (s) {
+ return branch.excludeNamed.set(s, new RegExp(s, 'i'));
+ });
+ var excludeRegexes = [].concat(_toConsumableArray(branch.excludeGeneric.values()), _toConsumableArray(branch.excludeNamed.values())); // ADD ITEMS
- var markers = drawLayer.selectAll('.qaItem.osmose').data(data, function (d) {
- return d.id;
- }); // exit
+ var items = category.items;
+ if (!Array.isArray(items) || !items.length) return; // Primary name patterns, match tags to take first
+ // e.g. `name`, `name:ru`
- markers.exit().remove(); // enter
+ var primaryName = new RegExp(tree.nameTags.primary, 'i'); // Alternate name patterns, match tags to consider after primary
+ // e.g. `alt_name`, `short_name`, `brand`, `brand:ru`, etc..
- var markersEnter = markers.enter().append('g').attr('class', function (d) {
- return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
- });
- markersEnter.append('polygon').call(markerPath, 'shadow');
- markersEnter.append('ellipse').attr('cx', 0).attr('cy', 0).attr('rx', 4.5).attr('ry', 2).attr('class', 'stroke');
- markersEnter.append('polygon').attr('fill', function (d) {
- return service.getColor(d.item);
- }).call(markerPath, 'qaItem-fill');
- markersEnter.append('use').attr('transform', 'translate(-6.5, -23)').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('xlink:href', function (d) {
- var picon = d.icon;
-
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return "#".concat(picon).concat(isMaki ? '-11' : '');
- }
- }); // update
+ var alternateName = new RegExp(tree.nameTags.alternate, 'i'); // There are a few exceptions to the name matching regexes.
+ // Usually a tag suffix contains a language code like `name:en`, `name:ru`
+ // but we want to exclude things like `operator:type`, `name:etymology`, etc..
- markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
- return d.id === selectedID;
- }).attr('transform', getTransform); // Draw targets..
+ var notName = /:(colou?r|type|forward|backward|left|right|etymology|pronunciation|wikipedia)$/i; // For certain categories we do not want to match generic KV pairs like `building/yes` or `amenity/yes`
- if (touchLayer.empty()) return;
- var fillClass = context.getDebug('target') ? 'pink' : 'nocolor';
- var targets = touchLayer.selectAll('.qaItem.osmose').data(data, function (d) {
- return d.id;
- }); // exit
+ var skipGenericKV = skipGenericKVMatches(t, k, v); // We will collect the generic KV pairs anyway (for the purpose of filtering them out of matchTags)
- targets.exit().remove(); // enter/update
+ var genericKV = new Set(["".concat(k, "/yes"), "building/yes"]); // Collect alternate tagpairs for this kv category from matchGroups.
+ // We might also pick up a few more generic KVs (like `shop/yes`)
- targets.enter().append('rect').attr('width', '20px').attr('height', '30px').attr('x', '-10px').attr('y', '-28px').merge(targets).sort(sortY).attr('class', function (d) {
- return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
- }).attr('transform', getTransform);
+ var matchGroupKV = new Set();
+ Object.values(matchGroups).forEach(function (matchGroup) {
+ var inGroup = matchGroup.some(function (otherkv) {
+ return otherkv === thiskv;
+ });
+ if (!inGroup) return;
+ matchGroup.forEach(function (otherkv) {
+ if (otherkv === thiskv) return; // skip self
- function sortY(a, b) {
- return a.id === selectedID ? 1 : b.id === selectedID ? -1 : b.loc[1] - a.loc[1];
- }
- } // Draw the Osmose layer and schedule loading issues and updating markers.
+ matchGroupKV.add(otherkv);
+ var otherk = otherkv.split('/', 2)[0]; // we might pick up a `shop/yes`
+ genericKV.add("".concat(otherk, "/yes"));
+ });
+ }); // For each item, insert all [key, value, name] combinations into the match index
- function drawOsmose(selection) {
- var service = getService();
- var surface = context.surface();
+ items.forEach(function (item) {
+ if (!item.id) return; // Automatically remove redundant `matchTags` - #3417
+ // (i.e. This kv is already covered by matchGroups, so it doesn't need to be in `item.matchTags`)
- if (surface && !surface.empty()) {
- touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
- }
+ if (Array.isArray(item.matchTags) && item.matchTags.length) {
+ item.matchTags = item.matchTags.filter(function (matchTag) {
+ return !matchGroupKV.has(matchTag) && !genericKV.has(matchTag);
+ });
+ if (!item.matchTags.length) delete item.matchTags;
+ } // key/value tagpairs to insert into the match index..
- drawLayer = selection.selectAll('.layer-osmose').data(service ? [0] : []);
- drawLayer.exit().remove();
- drawLayer = drawLayer.enter().append('g').attr('class', 'layer-osmose').style('display', _layerEnabled ? 'block' : 'none').merge(drawLayer);
- if (_layerEnabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- service.loadIssues(projection);
- updateMarkers();
- } else {
- editOff();
- }
- }
- } // Toggles the layer on and off
+ var kvTags = ["".concat(thiskv)].concat(item.matchTags || []);
+ if (!skipGenericKV) {
+ kvTags = kvTags.concat(Array.from(genericKV)); // #3454 - match some generic tags
+ } // Index all the namelike tag values
- drawOsmose.enabled = function (val) {
- if (!arguments.length) return _layerEnabled;
- _layerEnabled = val;
- if (_layerEnabled) {
- // Strings supplied by Osmose fetched before showing layer for first time
- // NOTE: Currently no way to change locale in iD at runtime, would need to re-call this method if that's ever implemented
- // Also, If layer is toggled quickly multiple requests are sent
- getService().loadStrings().then(layerOn)["catch"](function (err) {
- console.log(err); // eslint-disable-line no-console
- });
- } else {
- layerOff();
+ Object.keys(item.tags).forEach(function (osmkey) {
+ if (notName.test(osmkey)) return; // osmkey is not a namelike tag, skip
- if (context.selectedErrorID()) {
- context.enter(modeBrowse(context));
- }
- }
+ var osmvalue = item.tags[osmkey];
+ if (!osmvalue || excludeRegexes.some(function (regex) {
+ return regex.test(osmvalue);
+ })) return; // osmvalue missing or excluded
- dispatch.call('change');
- return this;
- };
+ if (primaryName.test(osmkey)) {
+ kvTags.forEach(function (kv) {
+ return insertName('primary', t, kv, simplify$1(osmvalue), item.id);
+ });
+ } else if (alternateName.test(osmkey)) {
+ kvTags.forEach(function (kv) {
+ return insertName('alternate', t, kv, simplify$1(osmvalue), item.id);
+ });
+ }
+ }); // Index `matchNames` after indexing all other names..
- drawOsmose.supported = function () {
- return !!getService();
- };
+ var keepMatchNames = new Set();
+ (item.matchNames || []).forEach(function (matchName) {
+ // If this matchname isn't already indexed, add it to the alternate index
+ var nsimple = simplify$1(matchName);
+ kvTags.forEach(function (kv) {
+ var branch = that.matchIndex.get(kv);
+ var primaryLeaf = branch && branch.primary.get(nsimple);
+ var alternateLeaf = branch && branch.alternate.get(nsimple);
+ var inPrimary = primaryLeaf && primaryLeaf.has(item.id);
+ var inAlternate = alternateLeaf && alternateLeaf.has(item.id);
- return drawOsmose;
- }
+ if (!inPrimary && !inAlternate) {
+ insertName('alternate', t, kv, nsimple, item.id);
+ keepMatchNames.add(matchName);
+ }
+ });
+ }); // Automatically remove redundant `matchNames` - #3417
+ // (i.e. This name got indexed some other way, so it doesn't need to be in `item.matchNames`)
- function svgStreetside(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ if (keepMatchNames.size) {
+ item.matchNames = Array.from(keepMatchNames);
+ } else {
+ delete item.matchNames;
+ }
+ }); // each item
+ }); // each tkv
+ // Insert this item into the matchIndex
- var minZoom = 14;
- var minMarkerZoom = 16;
- var minViewfieldZoom = 18;
- var layer = select(null);
- var _viewerYaw = 0;
- var _selectedSequence = null;
+ function insertName(which, t, kv, nsimple, itemID) {
+ if (!nsimple) {
+ that.warnings.push("Warning: skipping empty ".concat(which, " name for item ").concat(t, "/").concat(kv, ": ").concat(itemID));
+ return;
+ }
- var _streetside;
- /**
- * init().
- */
+ var branch = that.matchIndex.get(kv);
+ if (!branch) {
+ branch = {
+ primary: new Map(),
+ alternate: new Map(),
+ excludeGeneric: new Map(),
+ excludeNamed: new Map()
+ };
+ that.matchIndex.set(kv, branch);
+ }
- function init() {
- if (svgStreetside.initialized) return; // run once
+ var leaf = branch[which].get(nsimple);
- svgStreetside.enabled = false;
- svgStreetside.initialized = true;
- }
- /**
- * getService().
- */
+ if (!leaf) {
+ leaf = new Set();
+ branch[which].set(nsimple, leaf);
+ }
+ leaf.add(itemID); // insert
+ // check for duplicates - #5625
- function getService() {
- if (services.streetside && !_streetside) {
- _streetside = services.streetside;
+ if (!/yes$/.test(kv)) {
+ // ignore genericKV like amenity/yes, building/yes, etc
+ var kvnsimple = "".concat(kv, "/").concat(nsimple);
+ var existing = seenTree.get(kvnsimple);
- _streetside.event.on('viewerChanged.svgStreetside', viewerChanged).on('loadedImages.svgStreetside', throttledRedraw);
- } else if (!services.streetside && _streetside) {
- _streetside = null;
- }
+ if (existing && existing !== t) {
+ var items = Array.from(leaf);
+ that.warnings.push("Duplicate cache key \"".concat(kvnsimple, "\" in trees \"").concat(t, "\" and \"").concat(existing, "\", check items: ").concat(items));
+ return;
+ }
- return _streetside;
- }
- /**
- * showLayer().
- */
+ seenTree.set(kvnsimple, t);
+ }
+ } // For certain categories we do not want to match generic KV pairs like `building/yes` or `amenity/yes`
- function showLayer() {
- var service = getService();
- if (!service) return;
- editOn();
- layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
- dispatch.call('change');
- });
- }
- /**
- * hideLayer().
- */
+ function skipGenericKVMatches(t, k, v) {
+ return t === 'flags' || t === 'transit' || k === 'landuse' || v === 'atm' || v === 'bicycle_parking' || v === 'car_sharing' || v === 'caravan_site' || v === 'charging_station' || v === 'dog_park' || v === 'parking' || v === 'phone' || v === 'playground' || v === 'post_box' || v === 'public_bookcase' || v === 'recycling' || v === 'vending_machine';
+ }
+ } //
+ // `buildLocationIndex()`
+ // Call this to prepare a which-polygon location index.
+ // This *resolves* all the locationSets into GeoJSON, which takes some time.
+ // You can skip this step if you don't care about matching within a location.
+ //
+ // `data` needs to be an Object indexed on a 'tree/key/value' path.
+ // (e.g. cache filled by `fileTree.read` or data found in `dist/nsi.json`)
+ // {
+ // 'brands/amenity/bank': { properties: {}, items: [ {}, {}, … ] },
+ // 'brands/amenity/bar': { properties: {}, items: [ {}, {}, … ] },
+ // …
+ // }
+ //
+ }, {
+ key: "buildLocationIndex",
+ value: function buildLocationIndex(data, loco) {
+ var that = this;
+ if (that.locationIndex) return; // it was built already
- function hideLayer() {
- throttledRedraw.cancel();
- layer.transition().duration(250).style('opacity', 0).on('end', editOff);
- }
- /**
- * editOn().
- */
+ that.itemLocation = new Map();
+ that.locationSets = new Map();
+ Object.keys(data).forEach(function (tkv) {
+ var items = data[tkv].items;
+ if (!Array.isArray(items) || !items.length) return;
+ items.forEach(function (item) {
+ if (that.itemLocation.has(item.id)) return; // we've seen item id already - shouldn't be possible?
+ var resolved;
- function editOn() {
- layer.style('display', 'block');
- }
- /**
- * editOff().
- */
+ try {
+ resolved = loco.resolveLocationSet(item.locationSet); // resolve a feature for this locationSet
+ } catch (err) {
+ console.warn("buildLocationIndex: ".concat(err.message)); // couldn't resolve
+ }
+ if (!resolved || !resolved.id) return;
+ that.itemLocation.set(item.id, resolved.id); // link it to the item
- function editOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
- }
- /**
- * click() Handles 'bubble' point click event.
- */
+ if (that.locationSets.has(resolved.id)) return; // we've seen this locationSet feature before..
+ // First time seeing this locationSet feature, make a copy and add to locationSet cache..
+ var feature = _cloneDeep(resolved.feature);
- function click(d3_event, d) {
- var service = getService();
- if (!service) return; // try to preserve the viewer rotation when staying on the same sequence
+ feature.id = resolved.id; // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`)
- if (d.sequenceKey !== _selectedSequence) {
- _viewerYaw = 0; // reset
- }
+ feature.properties.id = resolved.id;
- _selectedSequence = d.sequenceKey;
- service.ensureViewerLoaded(context).then(function () {
- service.selectImage(context, d.key).yaw(_viewerYaw).showViewer(context);
- });
- context.map().centerEase(d.loc);
- }
- /**
- * mouseover().
- */
+ if (!feature.geometry.coordinates.length || !feature.properties.area) {
+ console.warn("buildLocationIndex: locationSet ".concat(resolved.id, " for ").concat(item.id, " resolves to an empty feature:"));
+ console.warn(JSON.stringify(feature));
+ return;
+ }
+ that.locationSets.set(resolved.id, feature);
+ });
+ });
+ that.locationIndex = whichPolygon_1({
+ type: 'FeatureCollection',
+ features: _toConsumableArray(that.locationSets.values())
+ });
- function mouseover(d3_event, d) {
- var service = getService();
- if (service) service.setStyles(context, d);
- }
- /**
- * mouseout().
- */
+ function _cloneDeep(obj) {
+ return JSON.parse(JSON.stringify(obj));
+ }
+ } //
+ // `match()`
+ // Pass parts and return an Array of matches.
+ // `k` - key
+ // `v` - value
+ // `n` - namelike
+ // `loc` - optional - [lon,lat] location to search
+ //
+ // 1. If the [k,v,n] tuple matches a canonical item…
+ // Return an Array of match results.
+ // Each result will include the area in km² that the item is valid.
+ //
+ // Order of results:
+ // Primary ordering will be on the "match" column:
+ // "primary" - where the query matches the `name` tag, followed by
+ // "alternate" - where the query matches an alternate name tag (e.g. short_name, brand, operator, etc)
+ // Secondary ordering will be on the "area" column:
+ // "area descending" if no location was provided, (worldwide before local)
+ // "area ascending" if location was provided (local before worldwide)
+ //
+ // [
+ // { match: 'primary', itemID: String, area: Number, kv: String, nsimple: String },
+ // { match: 'primary', itemID: String, area: Number, kv: String, nsimple: String },
+ // { match: 'alternate', itemID: String, area: Number, kv: String, nsimple: String },
+ // { match: 'alternate', itemID: String, area: Number, kv: String, nsimple: String },
+ // …
+ // ]
+ //
+ // -or-
+ //
+ // 2. If the [k,v,n] tuple matches an exclude pattern…
+ // Return an Array with a single exclude result, either
+ //
+ // [ { match: 'excludeGeneric', pattern: String, kv: String } ] // "generic" e.g. "Food Court"
+ // or
+ // [ { match: 'excludeNamed', pattern: String, kv: String } ] // "named", e.g. "Kebabai"
+ //
+ // About results
+ // "generic" - a generic word that is probably not really a name.
+ // For these, iD should warn the user "Hey don't put 'food court' in the name tag".
+ // "named" - a real name like "Kebabai" that is just common, but not a brand.
+ // For these, iD should just let it be. We don't include these in NSI, but we don't want to nag users about it either.
+ //
+ // -or-
+ //
+ // 3. If the [k,v,n] tuple matches nothing of any kind, return `null`
+ //
+ //
+ }, {
+ key: "match",
+ value: function match(k, v, n, loc) {
+ var that = this;
- function mouseout() {
- var service = getService();
- if (service) service.setStyles(context, null);
- }
- /**
- * transform().
- */
+ if (!that.matchIndex) {
+ throw new Error('match: matchIndex not built.');
+ } // If we were supplied a location, and a that.locationIndex has been set up,
+ // get the locationSets that are valid there so we can filter results.
- function transform(d) {
- var t = svgPointTransform(projection)(d);
- var rot = d.ca + _viewerYaw;
+ var matchLocations;
- if (rot) {
- t += ' rotate(' + Math.floor(rot) + ',0,0)';
- }
+ if (Array.isArray(loc) && that.locationIndex) {
+ // which-polygon query returns an array of GeoJSON properties, pass true to return all results
+ matchLocations = that.locationIndex([loc[0], loc[1], loc[0], loc[1]], true);
+ }
- return t;
- }
+ var nsimple = simplify$1(n);
+ var seen = new Set();
+ var results = [];
+ gatherResults('primary');
+ gatherResults('alternate');
+ if (results.length) return results;
+ gatherResults('exclude');
+ return results.length ? results : null;
- function viewerChanged() {
- var service = getService();
- if (!service) return;
- var viewer = service.viewer();
- if (!viewer) return; // update viewfield rotation
+ function gatherResults(which) {
+ // First try an exact match on k/v
+ var kv = "".concat(k, "/").concat(v);
+ var didMatch = tryMatch(which, kv);
+ if (didMatch) return; // If that didn't work, look in match groups for other pairs considered equivalent to k/v..
- _viewerYaw = viewer.getYaw(); // avoid updating if the map is currently transformed
- // e.g. during drags or easing.
+ for (var mg in matchGroups) {
+ var matchGroup = matchGroups[mg];
+ var inGroup = matchGroup.some(function (otherkv) {
+ return otherkv === kv;
+ });
+ if (!inGroup) continue;
- if (context.map().isTransformed()) return;
- layer.selectAll('.viewfield-group.currentView').attr('transform', transform);
- }
+ for (var i = 0; i < matchGroup.length; i++) {
+ var otherkv = matchGroup[i];
+ if (otherkv === kv) continue; // skip self
- function filterBubbles(bubbles) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
- var usernames = context.photos().usernames();
+ didMatch = tryMatch(which, otherkv);
+ if (didMatch) return;
+ }
+ } // If finished 'exclude' pass and still haven't matched anything, try the global `genericWords.json` patterns
- if (fromDate) {
- var fromTimestamp = new Date(fromDate).getTime();
- bubbles = bubbles.filter(function (bubble) {
- return new Date(bubble.captured_at).getTime() >= fromTimestamp;
- });
- }
- if (toDate) {
- var toTimestamp = new Date(toDate).getTime();
- bubbles = bubbles.filter(function (bubble) {
- return new Date(bubble.captured_at).getTime() <= toTimestamp;
- });
- }
+ if (which === 'exclude') {
+ var regex = _toConsumableArray(that.genericWords.values()).find(function (regex) {
+ return regex.test(n);
+ });
- if (usernames) {
- bubbles = bubbles.filter(function (bubble) {
- return usernames.indexOf(bubble.captured_by) !== -1;
- });
- }
+ if (regex) {
+ results.push({
+ match: 'excludeGeneric',
+ pattern: String(regex)
+ }); // note no `branch`, no `kv`
- return bubbles;
- }
+ return;
+ }
+ }
+ }
- function filterSequences(sequences) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
- var usernames = context.photos().usernames();
+ function tryMatch(which, kv) {
+ var branch = that.matchIndex.get(kv);
+ if (!branch) return;
- if (fromDate) {
- var fromTimestamp = new Date(fromDate).getTime();
- sequences = sequences.filter(function (sequences) {
- return new Date(sequences.properties.captured_at).getTime() >= fromTimestamp;
- });
- }
+ if (which === 'exclude') {
+ // Test name `n` against named and generic exclude patterns
+ var regex = _toConsumableArray(branch.excludeNamed.values()).find(function (regex) {
+ return regex.test(n);
+ });
- if (toDate) {
- var toTimestamp = new Date(toDate).getTime();
- sequences = sequences.filter(function (sequences) {
- return new Date(sequences.properties.captured_at).getTime() <= toTimestamp;
- });
- }
+ if (regex) {
+ results.push({
+ match: 'excludeNamed',
+ pattern: String(regex),
+ kv: kv
+ });
+ return;
+ }
- if (usernames) {
- sequences = sequences.filter(function (sequences) {
- return usernames.indexOf(sequences.properties.captured_by) !== -1;
- });
- }
+ regex = _toConsumableArray(branch.excludeGeneric.values()).find(function (regex) {
+ return regex.test(n);
+ });
- return sequences;
- }
- /**
- * update().
- */
+ if (regex) {
+ results.push({
+ match: 'excludeGeneric',
+ pattern: String(regex),
+ kv: kv
+ });
+ return;
+ }
+ return;
+ }
- function update() {
- var viewer = context.container().select('.photoviewer');
- var selected = viewer.empty() ? undefined : viewer.datum();
- var z = ~~context.map().zoom();
- var showMarkers = z >= minMarkerZoom;
- var showViewfields = z >= minViewfieldZoom;
- var service = getService();
- var sequences = [];
- var bubbles = [];
+ var leaf = branch[which].get(nsimple);
+ if (!leaf || !leaf.size) return; // If we get here, we matched something..
+ // Prepare the results, calculate areas (if location index was set up)
- if (context.photos().showsPanoramic()) {
- sequences = service ? service.sequences(projection) : [];
- bubbles = service && showMarkers ? service.bubbles(projection) : [];
- sequences = filterSequences(sequences);
- bubbles = filterBubbles(bubbles);
- }
+ var hits = Array.from(leaf).map(function (itemID) {
+ var area = Infinity;
- var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
- return d.properties.key;
- }); // exit
+ if (that.itemLocation && that.locationSets) {
+ var location = that.locationSets.get(that.itemLocation.get(itemID));
+ area = location && location.properties.area || Infinity;
+ }
- traces.exit().remove(); // enter/update
+ return {
+ match: which,
+ itemID: itemID,
+ area: area,
+ kv: kv,
+ nsimple: nsimple
+ };
+ });
+ var sortFn = byAreaDescending; // Filter the match to include only results valid in the requested `loc`..
- traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
- var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(bubbles, function (d) {
- // force reenter once bubbles are attached to a sequence
- return d.key + (d.sequenceKey ? 'v1' : 'v0');
- }); // exit
+ if (matchLocations) {
+ hits = hits.filter(isValidLocation);
+ sortFn = byAreaAscending;
+ }
- groups.exit().remove(); // enter
+ if (!hits.length) return; // push results
- var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
- groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+ hits.sort(sortFn).forEach(function (hit) {
+ if (seen.has(hit.itemID)) return;
+ seen.add(hit.itemID);
+ results.push(hit);
+ });
+ return true;
- var markers = groups.merge(groupsEnter).sort(function (a, b) {
- return a === selected ? 1 : b === selected ? -1 : b.loc[1] - a.loc[1];
- }).attr('transform', transform).select('.viewfield-scale');
- markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
- var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
- viewfields.exit().remove(); // viewfields may or may not be drawn...
- // but if they are, draw below the circles
+ function isValidLocation(hit) {
+ if (!that.itemLocation) return true;
+ return matchLocations.find(function (props) {
+ return props.id === that.itemLocation.get(hit.itemID);
+ });
+ } // Sort smaller (more local) locations first.
- viewfields.enter().insert('path', 'circle').attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', viewfieldPath);
- function viewfieldPath() {
- var d = this.parentNode.__data__;
+ function byAreaAscending(hitA, hitB) {
+ return hitA.area - hitB.area;
+ } // Sort larger (more worldwide) locations first.
- if (d.pano) {
- return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0';
- } else {
- return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
+
+ function byAreaDescending(hitA, hitB) {
+ return hitB.area - hitA.area;
+ }
}
+ } //
+ // `getWarnings()`
+ // Return any warnings discovered when buiding the index.
+ // (currently this does nothing)
+ //
+
+ }, {
+ key: "getWarnings",
+ value: function getWarnings() {
+ return this.warnings;
}
- }
- /**
- * drawImages()
- * drawImages is the method that is returned (and that runs) every time 'svgStreetside()' is called.
- * 'svgStreetside()' is called from index.js
- */
+ }]);
+ return Matcher;
+ }();
- function drawImages(selection) {
- var enabled = svgStreetside.enabled;
- var service = getService();
- layer = selection.selectAll('.layer-streetside-images').data(service ? [0] : []);
- layer.exit().remove();
- var layerEnter = layer.enter().append('g').attr('class', 'layer-streetside-images').style('display', enabled ? 'block' : 'none');
- layerEnter.append('g').attr('class', 'sequences');
- layerEnter.append('g').attr('class', 'markers');
- layer = layerEnter.merge(layer);
+ /*
+ iD.coreDifference represents the difference between two graphs.
+ It knows how to calculate the set of entities that were
+ created, modified, or deleted, and also contains the logic
+ for recursively extending a difference to the complete set
+ of entities that will require a redraw, taking into account
+ child and parent relationships.
+ */
- if (enabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- service.loadBubbles(projection);
- } else {
- editOff();
- }
- }
- }
- /**
- * drawImages.enabled().
- */
+ function coreDifference(base, head) {
+ var _changes = {};
+ var _didChange = {}; // 'addition', 'deletion', 'geometry', 'properties'
+ var _diff = {};
- drawImages.enabled = function (_) {
- if (!arguments.length) return svgStreetside.enabled;
- svgStreetside.enabled = _;
+ function checkEntityID(id) {
+ var h = head.entities[id];
+ var b = base.entities[id];
+ if (h === b) return;
+ if (_changes[id]) return;
- if (svgStreetside.enabled) {
- showLayer();
- context.photos().on('change.streetside', update);
- } else {
- hideLayer();
- context.photos().on('change.streetside', null);
+ if (!h && b) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.deletion = true;
+ return;
}
- dispatch.call('change');
- return this;
- };
- /**
- * drawImages.supported().
- */
-
-
- drawImages.supported = function () {
- return !!getService();
- };
+ if (h && !b) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.addition = true;
+ return;
+ }
- init();
- return drawImages;
- }
+ if (h && b) {
+ if (h.members && b.members && !fastDeepEqual(h.members, b.members)) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.geometry = true;
+ _didChange.properties = true;
+ return;
+ }
- function svgMapillaryImages(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ if (h.loc && b.loc && !geoVecEqual(h.loc, b.loc)) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.geometry = true;
+ }
- var minZoom = 12;
- var minMarkerZoom = 16;
- var minViewfieldZoom = 18;
- var layer = select(null);
+ if (h.nodes && b.nodes && !fastDeepEqual(h.nodes, b.nodes)) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.geometry = true;
+ }
- var _mapillary;
+ if (h.tags && b.tags && !fastDeepEqual(h.tags, b.tags)) {
+ _changes[id] = {
+ base: b,
+ head: h
+ };
+ _didChange.properties = true;
+ }
+ }
+ }
- function init() {
- if (svgMapillaryImages.initialized) return; // run once
+ function load() {
+ // HOT CODE: there can be many thousands of downloaded entities, so looping
+ // through them all can become a performance bottleneck. Optimize by
+ // resolving duplicates and using a basic `for` loop
+ var ids = utilArrayUniq(Object.keys(head.entities).concat(Object.keys(base.entities)));
- svgMapillaryImages.enabled = false;
- svgMapillaryImages.initialized = true;
+ for (var i = 0; i < ids.length; i++) {
+ checkEntityID(ids[i]);
+ }
}
- function getService() {
- if (services.mapillary && !_mapillary) {
- _mapillary = services.mapillary;
+ load();
- _mapillary.event.on('loadedImages', throttledRedraw);
- } else if (!services.mapillary && _mapillary) {
- _mapillary = null;
- }
+ _diff.length = function length() {
+ return Object.keys(_changes).length;
+ };
- return _mapillary;
- }
+ _diff.changes = function changes() {
+ return _changes;
+ };
- function showLayer() {
- var service = getService();
- if (!service) return;
- editOn();
- layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
- dispatch.call('change');
- });
- }
+ _diff.didChange = _didChange; // pass true to include affected relation members
- function hideLayer() {
- throttledRedraw.cancel();
- layer.transition().duration(250).style('opacity', 0).on('end', editOff);
- }
+ _diff.extantIDs = function extantIDs(includeRelMembers) {
+ var result = new Set();
+ Object.keys(_changes).forEach(function (id) {
+ if (_changes[id].head) {
+ result.add(id);
+ }
- function editOn() {
- layer.style('display', 'block');
- }
+ var h = _changes[id].head;
+ var b = _changes[id].base;
+ var entity = h || b;
- function editOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
- }
+ if (includeRelMembers && entity.type === 'relation') {
+ var mh = h ? h.members.map(function (m) {
+ return m.id;
+ }) : [];
+ var mb = b ? b.members.map(function (m) {
+ return m.id;
+ }) : [];
+ utilArrayUnion(mh, mb).forEach(function (memberID) {
+ if (head.hasEntity(memberID)) {
+ result.add(memberID);
+ }
+ });
+ }
+ });
+ return Array.from(result);
+ };
- function click(d3_event, image) {
- var service = getService();
- if (!service) return;
- service.ensureViewerLoaded(context).then(function () {
- service.selectImage(context, image.id).showViewer(context);
+ _diff.modified = function modified() {
+ var result = [];
+ Object.values(_changes).forEach(function (change) {
+ if (change.base && change.head) {
+ result.push(change.head);
+ }
});
- context.map().centerEase(image.loc);
- }
+ return result;
+ };
- function mouseover(d3_event, image) {
- var service = getService();
- if (service) service.setStyles(context, image);
- }
+ _diff.created = function created() {
+ var result = [];
+ Object.values(_changes).forEach(function (change) {
+ if (!change.base && change.head) {
+ result.push(change.head);
+ }
+ });
+ return result;
+ };
- function mouseout() {
- var service = getService();
- if (service) service.setStyles(context, null);
- }
+ _diff.deleted = function deleted() {
+ var result = [];
+ Object.values(_changes).forEach(function (change) {
+ if (change.base && !change.head) {
+ result.push(change.base);
+ }
+ });
+ return result;
+ };
- function transform(d) {
- var t = svgPointTransform(projection)(d);
+ _diff.summary = function summary() {
+ var relevant = {};
+ var keys = Object.keys(_changes);
- if (d.ca) {
- t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
- }
+ for (var i = 0; i < keys.length; i++) {
+ var change = _changes[keys[i]];
- return t;
- }
+ if (change.head && change.head.geometry(head) !== 'vertex') {
+ addEntity(change.head, head, change.base ? 'modified' : 'created');
+ } else if (change.base && change.base.geometry(base) !== 'vertex') {
+ addEntity(change.base, base, 'deleted');
+ } else if (change.base && change.head) {
+ // modified vertex
+ var moved = !fastDeepEqual(change.base.loc, change.head.loc);
+ var retagged = !fastDeepEqual(change.base.tags, change.head.tags);
- function filterImages(images) {
- var showsPano = context.photos().showsPanoramic();
- var showsFlat = context.photos().showsFlat();
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
+ if (moved) {
+ addParents(change.head);
+ }
- if (!showsPano || !showsFlat) {
- images = images.filter(function (image) {
- if (image.is_pano) return showsPano;
- return showsFlat;
- });
+ if (retagged || moved && change.head.hasInterestingTags()) {
+ addEntity(change.head, head, 'modified');
+ }
+ } else if (change.head && change.head.hasInterestingTags()) {
+ // created vertex
+ addEntity(change.head, head, 'created');
+ } else if (change.base && change.base.hasInterestingTags()) {
+ // deleted vertex
+ addEntity(change.base, base, 'deleted');
+ }
}
- if (fromDate) {
- images = images.filter(function (image) {
- return new Date(image.captured_at).getTime() >= new Date(fromDate).getTime();
- });
- }
+ return Object.values(relevant);
- if (toDate) {
- images = images.filter(function (image) {
- return new Date(image.captured_at).getTime() <= new Date(toDate).getTime();
- });
+ function addEntity(entity, graph, changeType) {
+ relevant[entity.id] = {
+ entity: entity,
+ graph: graph,
+ changeType: changeType
+ };
}
- return images;
- }
+ function addParents(entity) {
+ var parents = head.parentWays(entity);
- function filterSequences(sequences) {
- var showsPano = context.photos().showsPanoramic();
- var showsFlat = context.photos().showsFlat();
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
+ for (var j = parents.length - 1; j >= 0; j--) {
+ var parent = parents[j];
- if (!showsPano || !showsFlat) {
- sequences = sequences.filter(function (sequence) {
- if (sequence.properties.hasOwnProperty('is_pano')) {
- if (sequence.properties.is_pano) return showsPano;
- return showsFlat;
+ if (!(parent.id in relevant)) {
+ addEntity(parent, head, 'modified');
}
-
- return false;
- });
- }
-
- if (fromDate) {
- sequences = sequences.filter(function (sequence) {
- return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime().toString();
- });
+ }
}
+ }; // returns complete set of entities that require a redraw
+ // (optionally within given `extent`)
- if (toDate) {
- sequences = sequences.filter(function (sequence) {
- return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime().toString();
- });
- }
- return sequences;
- }
+ _diff.complete = function complete(extent) {
+ var result = {};
+ var id, change;
- function update() {
- var z = ~~context.map().zoom();
- var showMarkers = z >= minMarkerZoom;
- var showViewfields = z >= minViewfieldZoom;
- var service = getService();
- var sequences = service ? service.sequences(projection) : [];
- var images = service && showMarkers ? service.images(projection) : [];
- images = filterImages(images);
- sequences = filterSequences(sequences);
- service.filterViewer(context);
- var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
- return d.properties.id;
- }); // exit
+ for (id in _changes) {
+ change = _changes[id];
+ var h = change.head;
+ var b = change.base;
+ var entity = h || b;
+ var i;
- traces.exit().remove(); // enter/update
+ if (extent && (!h || !h.intersects(extent, head)) && (!b || !b.intersects(extent, base))) {
+ continue;
+ }
- traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
- var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(images, function (d) {
- return d.id;
- }); // exit
+ result[id] = h;
- groups.exit().remove(); // enter
+ if (entity.type === 'way') {
+ var nh = h ? h.nodes : [];
+ var nb = b ? b.nodes : [];
+ var diff;
+ diff = utilArrayDifference(nh, nb);
- var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
- groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+ for (i = 0; i < diff.length; i++) {
+ result[diff[i]] = head.hasEntity(diff[i]);
+ }
- var markers = groups.merge(groupsEnter).sort(function (a, b) {
- return b.loc[1] - a.loc[1]; // sort Y
- }).attr('transform', transform).select('.viewfield-scale');
- markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
- var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
- viewfields.exit().remove();
- viewfields.enter() // viewfields may or may not be drawn...
- .insert('path', 'circle') // but if they are, draw below the circles
- .attr('class', 'viewfield').classed('pano', function () {
- return this.parentNode.__data__.is_pano;
- }).attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', viewfieldPath);
+ diff = utilArrayDifference(nb, nh);
- function viewfieldPath() {
- if (this.parentNode.__data__.is_pano) {
- return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0';
- } else {
- return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
+ for (i = 0; i < diff.length; i++) {
+ result[diff[i]] = head.hasEntity(diff[i]);
+ }
}
- }
- }
- function drawImages(selection) {
- var enabled = svgMapillaryImages.enabled;
- var service = getService();
- layer = selection.selectAll('.layer-mapillary').data(service ? [0] : []);
- layer.exit().remove();
- var layerEnter = layer.enter().append('g').attr('class', 'layer-mapillary').style('display', enabled ? 'block' : 'none');
- layerEnter.append('g').attr('class', 'sequences');
- layerEnter.append('g').attr('class', 'markers');
- layer = layerEnter.merge(layer);
+ if (entity.type === 'relation' && entity.isMultipolygon()) {
+ var mh = h ? h.members.map(function (m) {
+ return m.id;
+ }) : [];
+ var mb = b ? b.members.map(function (m) {
+ return m.id;
+ }) : [];
+ var ids = utilArrayUnion(mh, mb);
- if (enabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- service.loadImages(projection);
- } else {
- editOff();
- }
- }
- }
+ for (i = 0; i < ids.length; i++) {
+ var member = head.hasEntity(ids[i]);
+ if (!member) continue; // not downloaded
- drawImages.enabled = function (_) {
- if (!arguments.length) return svgMapillaryImages.enabled;
- svgMapillaryImages.enabled = _;
+ if (extent && !member.intersects(extent, head)) continue; // not visible
- if (svgMapillaryImages.enabled) {
- showLayer();
- context.photos().on('change.mapillary_images', update);
- } else {
- hideLayer();
- context.photos().on('change.mapillary_images', null);
+ result[ids[i]] = member;
+ }
+ }
+
+ addParents(head.parentWays(entity), result);
+ addParents(head.parentRelations(entity), result);
}
- dispatch.call('change');
- return this;
- };
+ return result;
- drawImages.supported = function () {
- return !!getService();
+ function addParents(parents, result) {
+ for (var i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+ if (parent.id in result) continue;
+ result[parent.id] = parent;
+ addParents(head.parentRelations(parent), result);
+ }
+ }
};
- init();
- return drawImages;
+ return _diff;
}
- function svgMapillaryPosition(projection, context) {
- var throttledRedraw = throttle(function () {
- update();
- }, 1000);
-
- var minZoom = 12;
- var minViewfieldZoom = 18;
- var layer = select(null);
+ function coreTree(head) {
+ // tree for entities
+ var _rtree = new RBush();
- var _mapillary;
+ var _bboxes = {}; // maintain a separate tree for granular way segments
- var viewerCompassAngle;
+ var _segmentsRTree = new RBush();
- function init() {
- if (svgMapillaryPosition.initialized) return; // run once
+ var _segmentsBBoxes = {};
+ var _segmentsByWayId = {};
+ var tree = {};
- svgMapillaryPosition.initialized = true;
+ function entityBBox(entity) {
+ var bbox = entity.extent(head).bbox();
+ bbox.id = entity.id;
+ _bboxes[entity.id] = bbox;
+ return bbox;
}
- function getService() {
- if (services.mapillary && !_mapillary) {
- _mapillary = services.mapillary;
+ function segmentBBox(segment) {
+ var extent = segment.extent(head); // extent can be null if the node entities aren't in the graph for some reason
- _mapillary.event.on('imageChanged', throttledRedraw);
+ if (!extent) return null;
+ var bbox = extent.bbox();
+ bbox.segment = segment;
+ _segmentsBBoxes[segment.id] = bbox;
+ return bbox;
+ }
- _mapillary.event.on('bearingChanged', function (e) {
- viewerCompassAngle = e.bearing;
- if (context.map().isTransformed()) return;
- layer.selectAll('.viewfield-group.currentView').filter(function (d) {
- return d.is_pano;
- }).attr('transform', transform);
- });
- } else if (!services.mapillary && _mapillary) {
- _mapillary = null;
- }
+ function removeEntity(entity) {
+ _rtree.remove(_bboxes[entity.id]);
- return _mapillary;
- }
+ delete _bboxes[entity.id];
- function editOn() {
- layer.style('display', 'block');
- }
+ if (_segmentsByWayId[entity.id]) {
+ _segmentsByWayId[entity.id].forEach(function (segment) {
+ _segmentsRTree.remove(_segmentsBBoxes[segment.id]);
- function editOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
+ delete _segmentsBBoxes[segment.id];
+ });
+
+ delete _segmentsByWayId[entity.id];
+ }
}
- function transform(d) {
- var t = svgPointTransform(projection)(d);
+ function loadEntities(entities) {
+ _rtree.load(entities.map(entityBBox));
- if (d.is_pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
- t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)';
- } else if (d.ca) {
- t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
- }
+ var segments = [];
+ entities.forEach(function (entity) {
+ if (entity.segments) {
+ var entitySegments = entity.segments(head); // cache these to make them easy to remove later
- return t;
+ _segmentsByWayId[entity.id] = entitySegments;
+ segments = segments.concat(entitySegments);
+ }
+ });
+ if (segments.length) _segmentsRTree.load(segments.map(segmentBBox).filter(Boolean));
}
- function update() {
- var z = ~~context.map().zoom();
- var showViewfields = z >= minViewfieldZoom;
- var service = getService();
- var image = service && service.getActiveImage();
- var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(image ? [image] : [], function (d) {
- return d.id;
- }); // exit
+ function updateParents(entity, insertions, memo) {
+ head.parentWays(entity).forEach(function (way) {
+ if (_bboxes[way.id]) {
+ removeEntity(way);
+ insertions[way.id] = way;
+ }
- groups.exit().remove(); // enter
+ updateParents(way, insertions, memo);
+ });
+ head.parentRelations(entity).forEach(function (relation) {
+ if (memo[entity.id]) return;
+ memo[entity.id] = true;
- var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group currentView highlighted');
- groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+ if (_bboxes[relation.id]) {
+ removeEntity(relation);
+ insertions[relation.id] = relation;
+ }
- var markers = groups.merge(groupsEnter).attr('transform', transform).select('.viewfield-scale');
- markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
- var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
- viewfields.exit().remove();
- viewfields.enter().insert('path', 'circle').attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
+ updateParents(relation, insertions, memo);
+ });
}
- function drawImages(selection) {
- var service = getService();
- layer = selection.selectAll('.layer-mapillary-position').data(service ? [0] : []);
- layer.exit().remove();
- var layerEnter = layer.enter().append('g').attr('class', 'layer-mapillary-position');
- layerEnter.append('g').attr('class', 'markers');
- layer = layerEnter.merge(layer);
+ tree.rebase = function (entities, force) {
+ var insertions = {};
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- } else {
- editOff();
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ if (!entity.visible) continue;
+
+ if (head.entities.hasOwnProperty(entity.id) || _bboxes[entity.id]) {
+ if (!force) {
+ continue;
+ } else if (_bboxes[entity.id]) {
+ removeEntity(entity);
+ }
+ }
+
+ insertions[entity.id] = entity;
+ updateParents(entity, insertions, {});
}
- }
- drawImages.enabled = function () {
- update();
- return this;
+ loadEntities(Object.values(insertions));
+ return tree;
};
- drawImages.supported = function () {
- return !!getService();
- };
+ function updateToGraph(graph) {
+ if (graph === head) return;
+ var diff = coreDifference(head, graph);
+ head = graph;
+ var changed = diff.didChange;
+ if (!changed.addition && !changed.deletion && !changed.geometry) return;
+ var insertions = {};
- init();
- return drawImages;
- }
+ if (changed.deletion) {
+ diff.deleted().forEach(function (entity) {
+ removeEntity(entity);
+ });
+ }
- function svgMapillarySigns(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ if (changed.geometry) {
+ diff.modified().forEach(function (entity) {
+ removeEntity(entity);
+ insertions[entity.id] = entity;
+ updateParents(entity, insertions, {});
+ });
+ }
- var minZoom = 12;
- var layer = select(null);
+ if (changed.addition) {
+ diff.created().forEach(function (entity) {
+ insertions[entity.id] = entity;
+ });
+ }
- var _mapillary;
+ loadEntities(Object.values(insertions));
+ } // returns an array of entities with bounding boxes overlapping `extent` for the given `graph`
- function init() {
- if (svgMapillarySigns.initialized) return; // run once
- svgMapillarySigns.enabled = false;
- svgMapillarySigns.initialized = true;
- }
+ tree.intersects = function (extent, graph) {
+ updateToGraph(graph);
+ return _rtree.search(extent.bbox()).map(function (bbox) {
+ return graph.entity(bbox.id);
+ });
+ }; // returns an array of segment objects with bounding boxes overlapping `extent` for the given `graph`
- function getService() {
- if (services.mapillary && !_mapillary) {
- _mapillary = services.mapillary;
- _mapillary.event.on('loadedSigns', throttledRedraw);
- } else if (!services.mapillary && _mapillary) {
- _mapillary = null;
- }
+ tree.waySegments = function (extent, graph) {
+ updateToGraph(graph);
+ return _segmentsRTree.search(extent.bbox()).map(function (bbox) {
+ return bbox.segment;
+ });
+ };
- return _mapillary;
- }
+ return tree;
+ }
- function showLayer() {
- var service = getService();
- if (!service) return;
- service.loadSignResources(context);
- editOn();
- }
+ function svgIcon(name, svgklass, useklass) {
+ return function drawIcon(selection) {
+ selection.selectAll('svg.icon' + (svgklass ? '.' + svgklass.split(' ')[0] : '')).data([0]).enter().append('svg').attr('class', 'icon ' + (svgklass || '')).append('use').attr('xlink:href', name).attr('class', useklass);
+ };
+ }
- function hideLayer() {
- throttledRedraw.cancel();
- editOff();
- }
+ function uiModal(selection, blocking) {
+ var _this = this;
- function editOn() {
- layer.style('display', 'block');
- }
+ var keybinding = utilKeybinding('modal');
+ var previous = selection.select('div.modal');
+ var animate = previous.empty();
+ previous.transition().duration(200).style('opacity', 0).remove();
+ var shaded = selection.append('div').attr('class', 'shaded').style('opacity', 0);
- function editOff() {
- layer.selectAll('.icon-sign').remove();
- layer.style('display', 'none');
- }
+ shaded.close = function () {
+ shaded.transition().duration(200).style('opacity', 0).remove();
+ modal.transition().duration(200).style('top', '0px');
+ select(document).call(keybinding.unbind);
+ };
- function click(d3_event, d) {
- var service = getService();
- if (!service) return;
- context.map().centerEase(d.loc);
- var selectedImageId = service.getActiveImage() && service.getActiveImage().id;
- service.getDetections(d.id).then(function (detections) {
- if (detections.length) {
- var imageId = detections[0].image.id;
+ var modal = shaded.append('div').attr('class', 'modal fillL');
+ modal.append('input').attr('class', 'keytrap keytrap-first').on('focus.keytrap', moveFocusToLast);
- if (imageId === selectedImageId) {
- service.highlightDetection(detections[0]).selectImage(context, imageId);
- } else {
- service.ensureViewerLoaded(context).then(function () {
- service.highlightDetection(detections[0]).selectImage(context, imageId).showViewer(context);
- });
- }
+ if (!blocking) {
+ shaded.on('click.remove-modal', function (d3_event) {
+ if (d3_event.target === _this) {
+ shaded.close();
}
});
+ modal.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', shaded.close).call(svgIcon('#iD-icon-close'));
+ keybinding.on('⌫', shaded.close).on('⎋', shaded.close);
+ select(document).call(keybinding);
}
- function filterData(detectedFeatures) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
+ modal.append('div').attr('class', 'content');
+ modal.append('input').attr('class', 'keytrap keytrap-last').on('focus.keytrap', moveFocusToFirst);
- if (fromDate) {
- var fromTimestamp = new Date(fromDate).getTime();
- detectedFeatures = detectedFeatures.filter(function (feature) {
- return new Date(feature.last_seen_at).getTime() >= fromTimestamp;
- });
- }
+ if (animate) {
+ shaded.transition().style('opacity', 1);
+ } else {
+ shaded.style('opacity', 1);
+ }
- if (toDate) {
- var toTimestamp = new Date(toDate).getTime();
- detectedFeatures = detectedFeatures.filter(function (feature) {
- return new Date(feature.first_seen_at).getTime() <= toTimestamp;
- });
+ return shaded;
+
+ function moveFocusToFirst() {
+ var node = modal // there are additional rules about what's focusable, but this suits our purposes
+ .select('a, button, input:not(.keytrap), select, textarea').node();
+
+ if (node) {
+ node.focus();
+ } else {
+ select(this).node().blur();
}
+ }
- return detectedFeatures;
+ function moveFocusToLast() {
+ var nodes = modal.selectAll('a, button, input:not(.keytrap), select, textarea').nodes();
+
+ if (nodes.length) {
+ nodes[nodes.length - 1].focus();
+ } else {
+ select(this).node().blur();
+ }
}
+ }
- function update() {
- var service = getService();
- var data = service ? service.signs(projection) : [];
- data = filterData(data);
- var transform = svgPointTransform(projection);
- var signs = layer.selectAll('.icon-sign').data(data, function (d) {
- return d.id;
- }); // exit
+ function uiLoading(context) {
+ var _modalSelection = select(null);
- signs.exit().remove(); // enter
+ var _message = '';
+ var _blocking = false;
- var enter = signs.enter().append('g').attr('class', 'icon-sign icon-detected').on('click', click);
- enter.append('use').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px').attr('xlink:href', function (d) {
- return '#' + d.value;
- });
- enter.append('rect').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px'); // update
+ var loading = function loading(selection) {
+ _modalSelection = uiModal(selection, _blocking);
- signs.merge(enter).attr('transform', transform);
- }
+ var loadertext = _modalSelection.select('.content').classed('loading-modal', true).append('div').attr('class', 'modal-section fillL');
- function drawSigns(selection) {
- var enabled = svgMapillarySigns.enabled;
- var service = getService();
- layer = selection.selectAll('.layer-mapillary-signs').data(service ? [0] : []);
- layer.exit().remove();
- layer = layer.enter().append('g').attr('class', 'layer-mapillary-signs layer-mapillary-detections').style('display', enabled ? 'block' : 'none').merge(layer);
+ loadertext.append('img').attr('class', 'loader').attr('src', context.imagePath('loader-white.gif'));
+ loadertext.append('h3').html(_message);
- if (enabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- service.loadSigns(projection);
- service.showSignDetections(true);
- } else {
- editOff();
- }
- } else if (service) {
- service.showSignDetections(false);
- }
- }
+ _modalSelection.select('button.close').attr('class', 'hide');
- drawSigns.enabled = function (_) {
- if (!arguments.length) return svgMapillarySigns.enabled;
- svgMapillarySigns.enabled = _;
+ return loading;
+ };
- if (svgMapillarySigns.enabled) {
- showLayer();
- context.photos().on('change.mapillary_signs', update);
- } else {
- hideLayer();
- context.photos().on('change.mapillary_signs', null);
- }
+ loading.message = function (val) {
+ if (!arguments.length) return _message;
+ _message = val;
+ return loading;
+ };
- dispatch.call('change');
- return this;
+ loading.blocking = function (val) {
+ if (!arguments.length) return _blocking;
+ _blocking = val;
+ return loading;
};
- drawSigns.supported = function () {
- return !!getService();
+ loading.close = function () {
+ _modalSelection.remove();
};
- init();
- return drawSigns;
+ loading.isShown = function () {
+ return _modalSelection && !_modalSelection.empty() && _modalSelection.node().parentNode;
+ };
+
+ return loading;
}
- function svgMapillaryMapFeatures(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ function coreHistory(context) {
+ var dispatch = dispatch$8('reset', 'change', 'merge', 'restore', 'undone', 'redone', 'storage_error');
- var minZoom = 12;
- var layer = select(null);
+ var _lock = utilSessionMutex('lock'); // restorable if iD not open in another window/tab and a saved history exists in localStorage
- var _mapillary;
- function init() {
- if (svgMapillaryMapFeatures.initialized) return; // run once
+ var _hasUnresolvedRestorableChanges = _lock.lock() && !!corePreferences(getKey('saved_history'));
- svgMapillaryMapFeatures.enabled = false;
- svgMapillaryMapFeatures.initialized = true;
- }
+ var duration = 150;
+ var _imageryUsed = [];
+ var _photoOverlaysUsed = [];
+ var _checkpoints = {};
- function getService() {
- if (services.mapillary && !_mapillary) {
- _mapillary = services.mapillary;
+ var _pausedGraph;
- _mapillary.event.on('loadedMapFeatures', throttledRedraw);
- } else if (!services.mapillary && _mapillary) {
- _mapillary = null;
- }
+ var _stack;
- return _mapillary;
- }
+ var _index;
- function showLayer() {
- var service = getService();
- if (!service) return;
- service.loadObjectResources(context);
- editOn();
- }
+ var _tree; // internal _act, accepts list of actions and eased time
- function hideLayer() {
- throttledRedraw.cancel();
- editOff();
- }
- function editOn() {
- layer.style('display', 'block');
- }
+ function _act(actions, t) {
+ actions = Array.prototype.slice.call(actions);
+ var annotation;
- function editOff() {
- layer.selectAll('.icon-map-feature').remove();
- layer.style('display', 'none');
- }
+ if (typeof actions[actions.length - 1] !== 'function') {
+ annotation = actions.pop();
+ }
- function click(d3_event, d) {
- var service = getService();
- if (!service) return;
- context.map().centerEase(d.loc);
- var selectedImageId = service.getActiveImage() && service.getActiveImage().id;
- service.getDetections(d.id).then(function (detections) {
- if (detections.length) {
- var imageId = detections[0].image.id;
+ var graph = _stack[_index].graph;
- if (imageId === selectedImageId) {
- service.highlightDetection(detections[0]).selectImage(context, imageId);
- } else {
- service.ensureViewerLoaded(context).then(function () {
- service.highlightDetection(detections[0]).selectImage(context, imageId).showViewer(context);
- });
- }
- }
- });
- }
-
- function filterData(detectedFeatures) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
-
- if (fromDate) {
- detectedFeatures = detectedFeatures.filter(function (feature) {
- return new Date(feature.last_seen_at).getTime() >= new Date(fromDate).getTime();
- });
+ for (var i = 0; i < actions.length; i++) {
+ graph = actions[i](graph, t);
}
- if (toDate) {
- detectedFeatures = detectedFeatures.filter(function (feature) {
- return new Date(feature.first_seen_at).getTime() <= new Date(toDate).getTime();
- });
- }
+ return {
+ graph: graph,
+ annotation: annotation,
+ imageryUsed: _imageryUsed,
+ photoOverlaysUsed: _photoOverlaysUsed,
+ transform: context.projection.transform(),
+ selectedIDs: context.selectedIDs()
+ };
+ } // internal _perform with eased time
- return detectedFeatures;
- }
- function update() {
- var service = getService();
- var data = service ? service.mapFeatures(projection) : [];
- data = filterData(data);
- var transform = svgPointTransform(projection);
- var mapFeatures = layer.selectAll('.icon-map-feature').data(data, function (d) {
- return d.id;
- }); // exit
+ function _perform(args, t) {
+ var previous = _stack[_index].graph;
+ _stack = _stack.slice(0, _index + 1);
- mapFeatures.exit().remove(); // enter
+ var actionResult = _act(args, t);
- var enter = mapFeatures.enter().append('g').attr('class', 'icon-map-feature icon-detected').on('click', click);
- enter.append('title').text(function (d) {
- var id = d.value.replace(/--/g, '.').replace(/-/g, '_');
- return _t('mapillary_map_features.' + id);
- });
- enter.append('use').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px').attr('xlink:href', function (d) {
- if (d.value === 'object--billboard') {
- // no billboard icon right now, so use the advertisement icon
- return '#object--sign--advertisement';
- }
+ _stack.push(actionResult);
- return '#' + d.value;
- });
- enter.append('rect').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px'); // update
+ _index++;
+ return change(previous);
+ } // internal _replace with eased time
- mapFeatures.merge(enter).attr('transform', transform);
- }
- function drawMapFeatures(selection) {
- var enabled = svgMapillaryMapFeatures.enabled;
- var service = getService();
- layer = selection.selectAll('.layer-mapillary-map-features').data(service ? [0] : []);
- layer.exit().remove();
- layer = layer.enter().append('g').attr('class', 'layer-mapillary-map-features layer-mapillary-detections').style('display', enabled ? 'block' : 'none').merge(layer);
+ function _replace(args, t) {
+ var previous = _stack[_index].graph; // assert(_index == _stack.length - 1)
- if (enabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- service.loadMapFeatures(projection);
- service.showFeatureDetections(true);
- } else {
- editOff();
- }
- } else if (service) {
- service.showFeatureDetections(false);
- }
- }
+ var actionResult = _act(args, t);
- drawMapFeatures.enabled = function (_) {
- if (!arguments.length) return svgMapillaryMapFeatures.enabled;
- svgMapillaryMapFeatures.enabled = _;
+ _stack[_index] = actionResult;
+ return change(previous);
+ } // internal _overwrite with eased time
- if (svgMapillaryMapFeatures.enabled) {
- showLayer();
- context.photos().on('change.mapillary_map_features', update);
- } else {
- hideLayer();
- context.photos().on('change.mapillary_map_features', null);
- }
- dispatch.call('change');
- return this;
- };
+ function _overwrite(args, t) {
+ var previous = _stack[_index].graph;
- drawMapFeatures.supported = function () {
- return !!getService();
- };
+ if (_index > 0) {
+ _index--;
- init();
- return drawMapFeatures;
- }
+ _stack.pop();
+ }
- function svgOpenstreetcamImages(projection, context, dispatch) {
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ _stack = _stack.slice(0, _index + 1);
- var minZoom = 12;
- var minMarkerZoom = 16;
- var minViewfieldZoom = 18;
- var layer = select(null);
+ var actionResult = _act(args, t);
- var _openstreetcam;
+ _stack.push(actionResult);
- function init() {
- if (svgOpenstreetcamImages.initialized) return; // run once
+ _index++;
+ return change(previous);
+ } // determine difference and dispatch a change event
- svgOpenstreetcamImages.enabled = false;
- svgOpenstreetcamImages.initialized = true;
- }
- function getService() {
- if (services.openstreetcam && !_openstreetcam) {
- _openstreetcam = services.openstreetcam;
+ function change(previous) {
+ var difference = coreDifference(previous, history.graph());
- _openstreetcam.event.on('loadedImages', throttledRedraw);
- } else if (!services.openstreetcam && _openstreetcam) {
- _openstreetcam = null;
+ if (!_pausedGraph) {
+ dispatch.call('change', this, difference);
}
- return _openstreetcam;
- }
-
- function showLayer() {
- var service = getService();
- if (!service) return;
- editOn();
- layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
- dispatch.call('change');
- });
- }
-
- function hideLayer() {
- throttledRedraw.cancel();
- layer.transition().duration(250).style('opacity', 0).on('end', editOff);
- }
-
- function editOn() {
- layer.style('display', 'block');
- }
-
- function editOff() {
- layer.selectAll('.viewfield-group').remove();
- layer.style('display', 'none');
- }
-
- function click(d3_event, d) {
- var service = getService();
- if (!service) return;
- service.ensureViewerLoaded(context).then(function () {
- service.selectImage(context, d.key).showViewer(context);
- });
- context.map().centerEase(d.loc);
- }
+ return difference;
+ } // iD uses namespaced keys so multiple installations do not conflict
- function mouseover(d3_event, d) {
- var service = getService();
- if (service) service.setStyles(context, d);
- }
- function mouseout() {
- var service = getService();
- if (service) service.setStyles(context, null);
+ function getKey(n) {
+ return 'iD_' + window.location.origin + '_' + n;
}
- function transform(d) {
- var t = svgPointTransform(projection)(d);
+ var history = {
+ graph: function graph() {
+ return _stack[_index].graph;
+ },
+ tree: function tree() {
+ return _tree;
+ },
+ base: function base() {
+ return _stack[0].graph;
+ },
+ merge: function merge(entities
+ /*, extent*/
+ ) {
+ var stack = _stack.map(function (state) {
+ return state.graph;
+ });
- if (d.ca) {
- t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
- }
+ _stack[0].graph.rebase(entities, stack, false);
- return t;
- }
+ _tree.rebase(entities, false);
- function filterImages(images) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
- var usernames = context.photos().usernames();
+ dispatch.call('merge', this, entities);
+ },
+ perform: function perform() {
+ // complete any transition already in progress
+ select(document).interrupt('history.perform');
+ var transitionable = false;
+ var action0 = arguments[0];
- if (fromDate) {
- var fromTimestamp = new Date(fromDate).getTime();
- images = images.filter(function (item) {
- return new Date(item.captured_at).getTime() >= fromTimestamp;
- });
- }
+ if (arguments.length === 1 || arguments.length === 2 && typeof arguments[1] !== 'function') {
+ transitionable = !!action0.transitionable;
+ }
- if (toDate) {
- var toTimestamp = new Date(toDate).getTime();
- images = images.filter(function (item) {
- return new Date(item.captured_at).getTime() <= toTimestamp;
- });
- }
+ if (transitionable) {
+ var origArguments = arguments;
+ select(document).transition('history.perform').duration(duration).ease(linear$1).tween('history.tween', function () {
+ return function (t) {
+ if (t < 1) _overwrite([action0], t);
+ };
+ }).on('start', function () {
+ _perform([action0], 0);
+ }).on('end interrupt', function () {
+ _overwrite(origArguments, 1);
+ });
+ } else {
+ return _perform(arguments);
+ }
+ },
+ replace: function replace() {
+ select(document).interrupt('history.perform');
+ return _replace(arguments, 1);
+ },
+ // Same as calling pop and then perform
+ overwrite: function overwrite() {
+ select(document).interrupt('history.perform');
+ return _overwrite(arguments, 1);
+ },
+ pop: function pop(n) {
+ select(document).interrupt('history.perform');
+ var previous = _stack[_index].graph;
- if (usernames) {
- images = images.filter(function (item) {
- return usernames.indexOf(item.captured_by) !== -1;
- });
- }
+ if (isNaN(+n) || +n < 0) {
+ n = 1;
+ }
- return images;
- }
+ while (n-- > 0 && _index > 0) {
+ _index--;
- function filterSequences(sequences) {
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
- var usernames = context.photos().usernames();
+ _stack.pop();
+ }
- if (fromDate) {
- var fromTimestamp = new Date(fromDate).getTime();
- sequences = sequences.filter(function (image) {
- return new Date(image.properties.captured_at).getTime() >= fromTimestamp;
- });
- }
+ return change(previous);
+ },
+ // Back to the previous annotated state or _index = 0.
+ undo: function undo() {
+ select(document).interrupt('history.perform');
+ var previousStack = _stack[_index];
+ var previous = previousStack.graph;
- if (toDate) {
- var toTimestamp = new Date(toDate).getTime();
- sequences = sequences.filter(function (image) {
- return new Date(image.properties.captured_at).getTime() <= toTimestamp;
- });
- }
+ while (_index > 0) {
+ _index--;
+ if (_stack[_index].annotation) break;
+ }
- if (usernames) {
- sequences = sequences.filter(function (image) {
- return usernames.indexOf(image.properties.captured_by) !== -1;
- });
- }
+ dispatch.call('undone', this, _stack[_index], previousStack);
+ return change(previous);
+ },
+ // Forward to the next annotated state.
+ redo: function redo() {
+ select(document).interrupt('history.perform');
+ var previousStack = _stack[_index];
+ var previous = previousStack.graph;
+ var tryIndex = _index;
- return sequences;
- }
+ while (tryIndex < _stack.length - 1) {
+ tryIndex++;
- function update() {
- var viewer = context.container().select('.photoviewer');
- var selected = viewer.empty() ? undefined : viewer.datum();
- var z = ~~context.map().zoom();
- var showMarkers = z >= minMarkerZoom;
- var showViewfields = z >= minViewfieldZoom;
- var service = getService();
- var sequences = [];
- var images = [];
+ if (_stack[tryIndex].annotation) {
+ _index = tryIndex;
+ dispatch.call('redone', this, _stack[_index], previousStack);
+ break;
+ }
+ }
- if (context.photos().showsFlat()) {
- sequences = service ? service.sequences(projection) : [];
- images = service && showMarkers ? service.images(projection) : [];
- sequences = filterSequences(sequences);
- images = filterImages(images);
- }
+ return change(previous);
+ },
+ pauseChangeDispatch: function pauseChangeDispatch() {
+ if (!_pausedGraph) {
+ _pausedGraph = _stack[_index].graph;
+ }
+ },
+ resumeChangeDispatch: function resumeChangeDispatch() {
+ if (_pausedGraph) {
+ var previous = _pausedGraph;
+ _pausedGraph = null;
+ return change(previous);
+ }
+ },
+ undoAnnotation: function undoAnnotation() {
+ var i = _index;
- var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
- return d.properties.key;
- }); // exit
+ while (i >= 0) {
+ if (_stack[i].annotation) return _stack[i].annotation;
+ i--;
+ }
+ },
+ redoAnnotation: function redoAnnotation() {
+ var i = _index + 1;
- traces.exit().remove(); // enter/update
+ while (i <= _stack.length - 1) {
+ if (_stack[i].annotation) return _stack[i].annotation;
+ i++;
+ }
+ },
+ // Returns the entities from the active graph with bounding boxes
+ // overlapping the given `extent`.
+ intersects: function intersects(extent) {
+ return _tree.intersects(extent, _stack[_index].graph);
+ },
+ difference: function difference() {
+ var base = _stack[0].graph;
+ var head = _stack[_index].graph;
+ return coreDifference(base, head);
+ },
+ changes: function changes(action) {
+ var base = _stack[0].graph;
+ var head = _stack[_index].graph;
- traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
- var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(images, function (d) {
- return d.key;
- }); // exit
+ if (action) {
+ head = action(head);
+ }
- groups.exit().remove(); // enter
+ var difference = coreDifference(base, head);
+ return {
+ modified: difference.modified(),
+ created: difference.created(),
+ deleted: difference.deleted()
+ };
+ },
+ hasChanges: function hasChanges() {
+ return this.difference().length() > 0;
+ },
+ imageryUsed: function imageryUsed(sources) {
+ if (sources) {
+ _imageryUsed = sources;
+ return history;
+ } else {
+ var s = new Set();
- var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
- groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+ _stack.slice(1, _index + 1).forEach(function (state) {
+ state.imageryUsed.forEach(function (source) {
+ if (source !== 'Custom') {
+ s.add(source);
+ }
+ });
+ });
- var markers = groups.merge(groupsEnter).sort(function (a, b) {
- return a === selected ? 1 : b === selected ? -1 : b.loc[1] - a.loc[1]; // sort Y
- }).attr('transform', transform).select('.viewfield-scale');
- markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
- var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
- viewfields.exit().remove();
- viewfields.enter() // viewfields may or may not be drawn...
- .insert('path', 'circle') // but if they are, draw below the circles
- .attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
- }
+ return Array.from(s);
+ }
+ },
+ photoOverlaysUsed: function photoOverlaysUsed(sources) {
+ if (sources) {
+ _photoOverlaysUsed = sources;
+ return history;
+ } else {
+ var s = new Set();
- function drawImages(selection) {
- var enabled = svgOpenstreetcamImages.enabled,
- service = getService();
- layer = selection.selectAll('.layer-openstreetcam').data(service ? [0] : []);
- layer.exit().remove();
- var layerEnter = layer.enter().append('g').attr('class', 'layer-openstreetcam').style('display', enabled ? 'block' : 'none');
- layerEnter.append('g').attr('class', 'sequences');
- layerEnter.append('g').attr('class', 'markers');
- layer = layerEnter.merge(layer);
+ _stack.slice(1, _index + 1).forEach(function (state) {
+ if (state.photoOverlaysUsed && Array.isArray(state.photoOverlaysUsed)) {
+ state.photoOverlaysUsed.forEach(function (photoOverlay) {
+ s.add(photoOverlay);
+ });
+ }
+ });
- if (enabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- update();
- service.loadImages(projection);
+ return Array.from(s);
+ }
+ },
+ // save the current history state
+ checkpoint: function checkpoint(key) {
+ _checkpoints[key] = {
+ stack: _stack,
+ index: _index
+ };
+ return history;
+ },
+ // restore history state to a given checkpoint or reset completely
+ reset: function reset(key) {
+ if (key !== undefined && _checkpoints.hasOwnProperty(key)) {
+ _stack = _checkpoints[key].stack;
+ _index = _checkpoints[key].index;
} else {
- editOff();
+ _stack = [{
+ graph: coreGraph()
+ }];
+ _index = 0;
+ _tree = coreTree(_stack[0].graph);
+ _checkpoints = {};
}
- }
- }
- drawImages.enabled = function (_) {
- if (!arguments.length) return svgOpenstreetcamImages.enabled;
- svgOpenstreetcamImages.enabled = _;
+ dispatch.call('reset');
+ dispatch.call('change');
+ return history;
+ },
+ // `toIntroGraph()` is used to export the intro graph used by the walkthrough.
+ //
+ // To use it:
+ // 1. Start the walkthrough.
+ // 2. Get to a "free editing" tutorial step
+ // 3. Make your edits to the walkthrough map
+ // 4. In your browser dev console run:
+ // `id.history().toIntroGraph()`
+ // 5. This outputs stringified JSON to the browser console
+ // 6. Copy it to `data/intro_graph.json` and prettify it in your code editor
+ toIntroGraph: function toIntroGraph() {
+ var nextID = {
+ n: 0,
+ r: 0,
+ w: 0
+ };
+ var permIDs = {};
+ var graph = this.graph();
+ var baseEntities = {}; // clone base entities..
- if (svgOpenstreetcamImages.enabled) {
- showLayer();
- context.photos().on('change.openstreetcam_images', update);
- } else {
- hideLayer();
- context.photos().on('change.openstreetcam_images', null);
- }
+ Object.values(graph.base().entities).forEach(function (entity) {
+ var copy = copyIntroEntity(entity);
+ baseEntities[copy.id] = copy;
+ }); // replace base entities with head entities..
- dispatch.call('change');
- return this;
- };
-
- drawImages.supported = function () {
- return !!getService();
- };
+ Object.keys(graph.entities).forEach(function (id) {
+ var entity = graph.entities[id];
- init();
- return drawImages;
- }
+ if (entity) {
+ var copy = copyIntroEntity(entity);
+ baseEntities[copy.id] = copy;
+ } else {
+ delete baseEntities[id];
+ }
+ }); // swap temporary for permanent ids..
- function svgOsm(projection, context, dispatch) {
- var enabled = true;
+ Object.values(baseEntities).forEach(function (entity) {
+ if (Array.isArray(entity.nodes)) {
+ entity.nodes = entity.nodes.map(function (node) {
+ return permIDs[node] || node;
+ });
+ }
- function drawOsm(selection) {
- selection.selectAll('.layer-osm').data(['covered', 'areas', 'lines', 'points', 'labels']).enter().append('g').attr('class', function (d) {
- return 'layer-osm ' + d;
- });
- selection.selectAll('.layer-osm.points').selectAll('.points-group').data(['points', 'midpoints', 'vertices', 'turns']).enter().append('g').attr('class', function (d) {
- return 'points-group ' + d;
- });
- }
+ if (Array.isArray(entity.members)) {
+ entity.members = entity.members.map(function (member) {
+ member.id = permIDs[member.id] || member.id;
+ return member;
+ });
+ }
+ });
+ return JSON.stringify({
+ dataIntroGraph: baseEntities
+ });
- function showLayer() {
- var layer = context.surface().selectAll('.data-layer.osm');
- layer.interrupt();
- layer.classed('disabled', false).style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
- dispatch.call('change');
- });
- }
+ function copyIntroEntity(source) {
+ var copy = utilObjectOmit(source, ['type', 'user', 'v', 'version', 'visible']); // Note: the copy is no longer an osmEntity, so it might not have `tags`
- function hideLayer() {
- var layer = context.surface().selectAll('.data-layer.osm');
- layer.interrupt();
- layer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
- layer.classed('disabled', true);
- dispatch.call('change');
- });
- }
+ if (copy.tags && !Object.keys(copy.tags)) {
+ delete copy.tags;
+ }
- drawOsm.enabled = function (val) {
- if (!arguments.length) return enabled;
- enabled = val;
+ if (Array.isArray(copy.loc)) {
+ copy.loc[0] = +copy.loc[0].toFixed(6);
+ copy.loc[1] = +copy.loc[1].toFixed(6);
+ }
- if (enabled) {
- showLayer();
- } else {
- hideLayer();
- }
+ var match = source.id.match(/([nrw])-\d*/); // temporary id
- dispatch.call('change');
- return this;
- };
+ if (match !== null) {
+ var nrw = match[1];
+ var permID;
- return drawOsm;
- }
+ do {
+ permID = nrw + ++nextID[nrw];
+ } while (baseEntities.hasOwnProperty(permID));
- var _notesEnabled = false;
+ copy.id = permIDs[source.id] = permID;
+ }
- var _osmService;
+ return copy;
+ }
+ },
+ toJSON: function toJSON() {
+ if (!this.hasChanges()) return;
+ var allEntities = {};
+ var baseEntities = {};
+ var base = _stack[0];
- function svgNotes(projection, context, dispatch) {
- if (!dispatch) {
- dispatch = dispatch$8('change');
- }
+ var s = _stack.map(function (i) {
+ var modified = [];
+ var deleted = [];
+ Object.keys(i.graph.entities).forEach(function (id) {
+ var entity = i.graph.entities[id];
- var throttledRedraw = throttle(function () {
- dispatch.call('change');
- }, 1000);
+ if (entity) {
+ var key = osmEntity.key(entity);
+ allEntities[key] = entity;
+ modified.push(key);
+ } else {
+ deleted.push(id);
+ } // make sure that the originals of changed or deleted entities get merged
+ // into the base of the _stack after restoring the data from JSON.
- var minZoom = 12;
- var touchLayer = select(null);
- var drawLayer = select(null);
- var _notesVisible = false;
- function markerPath(selection, klass) {
- selection.attr('class', klass).attr('transform', 'translate(-8, -22)').attr('d', 'm17.5,0l-15,0c-1.37,0 -2.5,1.12 -2.5,2.5l0,11.25c0,1.37 1.12,2.5 2.5,2.5l3.75,0l0,3.28c0,0.38 0.43,0.6 0.75,0.37l4.87,-3.65l5.62,0c1.37,0 2.5,-1.12 2.5,-2.5l0,-11.25c0,-1.37 -1.12,-2.5 -2.5,-2.5z');
- } // Loosely-coupled osm service for fetching notes.
+ if (id in base.graph.entities) {
+ baseEntities[id] = base.graph.entities[id];
+ }
+ if (entity && entity.nodes) {
+ // get originals of pre-existing child nodes
+ entity.nodes.forEach(function (nodeID) {
+ if (nodeID in base.graph.entities) {
+ baseEntities[nodeID] = base.graph.entities[nodeID];
+ }
+ });
+ } // get originals of parent entities too
- function getService() {
- if (services.osm && !_osmService) {
- _osmService = services.osm;
- _osmService.on('loadedNotes', throttledRedraw);
- } else if (!services.osm && _osmService) {
- _osmService = null;
- }
+ var baseParents = base.graph._parentWays[id];
- return _osmService;
- } // Show the notes
+ if (baseParents) {
+ baseParents.forEach(function (parentID) {
+ if (parentID in base.graph.entities) {
+ baseEntities[parentID] = base.graph.entities[parentID];
+ }
+ });
+ }
+ });
+ var x = {};
+ if (modified.length) x.modified = modified;
+ if (deleted.length) x.deleted = deleted;
+ if (i.imageryUsed) x.imageryUsed = i.imageryUsed;
+ if (i.photoOverlaysUsed) x.photoOverlaysUsed = i.photoOverlaysUsed;
+ if (i.annotation) x.annotation = i.annotation;
+ if (i.transform) x.transform = i.transform;
+ if (i.selectedIDs) x.selectedIDs = i.selectedIDs;
+ return x;
+ });
+ return JSON.stringify({
+ version: 3,
+ entities: Object.values(allEntities),
+ baseEntities: Object.values(baseEntities),
+ stack: s,
+ nextIDs: osmEntity.id.next,
+ index: _index,
+ // note the time the changes were saved
+ timestamp: new Date().getTime()
+ });
+ },
+ fromJSON: function fromJSON(json, loadChildNodes) {
+ var h = JSON.parse(json);
+ var loadComplete = true;
+ osmEntity.id.next = h.nextIDs;
+ _index = h.index;
- function editOn() {
- if (!_notesVisible) {
- _notesVisible = true;
- drawLayer.style('display', 'block');
- }
- } // Immediately remove the notes and their touch targets
+ if (h.version === 2 || h.version === 3) {
+ var allEntities = {};
+ h.entities.forEach(function (entity) {
+ allEntities[osmEntity.key(entity)] = osmEntity(entity);
+ });
+ if (h.version === 3) {
+ // This merges originals for changed entities into the base of
+ // the _stack even if the current _stack doesn't have them (for
+ // example when iD has been restarted in a different region)
+ var baseEntities = h.baseEntities.map(function (d) {
+ return osmEntity(d);
+ });
- function editOff() {
- if (_notesVisible) {
- _notesVisible = false;
- drawLayer.style('display', 'none');
- drawLayer.selectAll('.note').remove();
- touchLayer.selectAll('.note').remove();
- }
- } // Enable the layer. This shows the notes and transitions them to visible.
+ var stack = _stack.map(function (state) {
+ return state.graph;
+ });
+ _stack[0].graph.rebase(baseEntities, stack, true);
- function layerOn() {
- editOn();
- drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
- dispatch.call('change');
- });
- } // Disable the layer. This transitions the layer invisible and then hides the notes.
+ _tree.rebase(baseEntities, true); // When we restore a modified way, we also need to fetch any missing
+ // childnodes that would normally have been downloaded with it.. #2142
- function layerOff() {
- throttledRedraw.cancel();
- drawLayer.interrupt();
- touchLayer.selectAll('.note').remove();
- drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
- editOff();
- dispatch.call('change');
- });
- } // Update the note markers
+ if (loadChildNodes) {
+ var osm = context.connection();
+ var baseWays = baseEntities.filter(function (e) {
+ return e.type === 'way';
+ });
+ var nodeIDs = baseWays.reduce(function (acc, way) {
+ return utilArrayUnion(acc, way.nodes);
+ }, []);
+ var missing = nodeIDs.filter(function (n) {
+ return !_stack[0].graph.hasEntity(n);
+ });
+ if (missing.length && osm) {
+ loadComplete = false;
+ context.map().redrawEnable(false);
+ var loading = uiLoading(context).blocking(true);
+ context.container().call(loading);
- function updateMarkers() {
- if (!_notesVisible || !_notesEnabled) return;
- var service = getService();
- var selectedID = context.selectedNoteID();
- var data = service ? service.notes(projection) : [];
- var getTransform = svgPointTransform(projection); // Draw markers..
+ var childNodesLoaded = function childNodesLoaded(err, result) {
+ if (!err) {
+ var visibleGroups = utilArrayGroupBy(result.data, 'visible');
+ var visibles = visibleGroups["true"] || []; // alive nodes
- var notes = drawLayer.selectAll('.note').data(data, function (d) {
- return d.status + d.id;
- }); // exit
+ var invisibles = visibleGroups["false"] || []; // deleted nodes
- notes.exit().remove(); // enter
+ if (visibles.length) {
+ var visibleIDs = visibles.map(function (entity) {
+ return entity.id;
+ });
- var notesEnter = notes.enter().append('g').attr('class', function (d) {
- return 'note note-' + d.id + ' ' + d.status;
- }).classed('new', function (d) {
- return d.id < 0;
- });
- notesEnter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
- notesEnter.append('path').call(markerPath, 'shadow');
- notesEnter.append('use').attr('class', 'note-fill').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').attr('xlink:href', '#iD-icon-note');
- notesEnter.selectAll('.icon-annotation').data(function (d) {
- return [d];
- }).enter().append('use').attr('class', 'icon-annotation').attr('width', '10px').attr('height', '10px').attr('x', '-3px').attr('y', '-19px').attr('xlink:href', function (d) {
- if (d.id < 0) return '#iD-icon-plus';
- if (d.status === 'open') return '#iD-icon-close';
- return '#iD-icon-apply';
- }); // update
+ var stack = _stack.map(function (state) {
+ return state.graph;
+ });
- notes.merge(notesEnter).sort(sortY).classed('selected', function (d) {
- var mode = context.mode();
- var isMoving = mode && mode.id === 'drag-note'; // no shadows when dragging
+ missing = utilArrayDifference(missing, visibleIDs);
- return !isMoving && d.id === selectedID;
- }).attr('transform', getTransform); // Draw targets..
+ _stack[0].graph.rebase(visibles, stack, true);
- if (touchLayer.empty()) return;
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var targets = touchLayer.selectAll('.note').data(data, function (d) {
- return d.id;
- }); // exit
+ _tree.rebase(visibles, true);
+ } // fetch older versions of nodes that were deleted..
- targets.exit().remove(); // enter/update
- targets.enter().append('rect').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').merge(targets).sort(sortY).attr('class', function (d) {
- var newClass = d.id < 0 ? 'new' : '';
- return 'note target note-' + d.id + ' ' + fillClass + newClass;
- }).attr('transform', getTransform);
+ invisibles.forEach(function (entity) {
+ osm.loadEntityVersion(entity.id, +entity.version - 1, childNodesLoaded);
+ });
+ }
- function sortY(a, b) {
- if (a.id === selectedID) return 1;
- if (b.id === selectedID) return -1;
- return b.loc[1] - a.loc[1];
- }
- } // Draw the notes layer and schedule loading notes and updating markers.
+ if (err || !missing.length) {
+ loading.close();
+ context.map().redrawEnable(true);
+ dispatch.call('change');
+ dispatch.call('restore', this);
+ }
+ };
+ osm.loadMultiple(missing, childNodesLoaded);
+ }
+ }
+ }
- function drawNotes(selection) {
- var service = getService();
- var surface = context.surface();
+ _stack = h.stack.map(function (d) {
+ var entities = {},
+ entity;
- if (surface && !surface.empty()) {
- touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
- }
+ if (d.modified) {
+ d.modified.forEach(function (key) {
+ entity = allEntities[key];
+ entities[entity.id] = entity;
+ });
+ }
- drawLayer = selection.selectAll('.layer-notes').data(service ? [0] : []);
- drawLayer.exit().remove();
- drawLayer = drawLayer.enter().append('g').attr('class', 'layer-notes').style('display', _notesEnabled ? 'block' : 'none').merge(drawLayer);
+ if (d.deleted) {
+ d.deleted.forEach(function (id) {
+ entities[id] = undefined;
+ });
+ }
- if (_notesEnabled) {
- if (service && ~~context.map().zoom() >= minZoom) {
- editOn();
- service.loadNotes(projection);
- updateMarkers();
+ return {
+ graph: coreGraph(_stack[0].graph).load(entities),
+ annotation: d.annotation,
+ imageryUsed: d.imageryUsed,
+ photoOverlaysUsed: d.photoOverlaysUsed,
+ transform: d.transform,
+ selectedIDs: d.selectedIDs
+ };
+ });
} else {
- editOff();
+ // original version
+ _stack = h.stack.map(function (d) {
+ var entities = {};
+
+ for (var i in d.entities) {
+ var entity = d.entities[i];
+ entities[i] = entity === 'undefined' ? undefined : osmEntity(entity);
+ }
+
+ d.graph = coreGraph(_stack[0].graph).load(entities);
+ return d;
+ });
}
- }
- } // Toggles the layer on and off
+ var transform = _stack[_index].transform;
- drawNotes.enabled = function (val) {
- if (!arguments.length) return _notesEnabled;
- _notesEnabled = val;
+ if (transform) {
+ context.map().transformEase(transform, 0); // 0 = immediate, no easing
+ }
- if (_notesEnabled) {
- layerOn();
- } else {
- layerOff();
+ if (loadComplete) {
+ dispatch.call('change');
+ dispatch.call('restore', this);
+ }
- if (context.selectedNoteID()) {
- context.enter(modeBrowse(context));
+ return history;
+ },
+ lock: function lock() {
+ return _lock.lock();
+ },
+ unlock: function unlock() {
+ _lock.unlock();
+ },
+ save: function save() {
+ if (_lock.locked() && // don't overwrite existing, unresolved changes
+ !_hasUnresolvedRestorableChanges) {
+ var success = corePreferences(getKey('saved_history'), history.toJSON() || null);
+ if (!success) dispatch.call('storage_error');
}
- }
- dispatch.call('change');
- return this;
- };
+ return history;
+ },
+ // delete the history version saved in localStorage
+ clearSaved: function clearSaved() {
+ context.debouncedSave.cancel();
- return drawNotes;
- }
+ if (_lock.locked()) {
+ _hasUnresolvedRestorableChanges = false;
+ corePreferences(getKey('saved_history'), null); // clear the changeset metadata associated with the saved history
- function svgTouch() {
- function drawTouch(selection) {
- selection.selectAll('.layer-touch').data(['areas', 'lines', 'points', 'turns', 'markers']).enter().append('g').attr('class', function (d) {
- return 'layer-touch ' + d;
- });
- }
+ corePreferences('comment', null);
+ corePreferences('hashtags', null);
+ corePreferences('source', null);
+ }
- return drawTouch;
+ return history;
+ },
+ savedHistoryJSON: function savedHistoryJSON() {
+ return corePreferences(getKey('saved_history'));
+ },
+ hasRestorableChanges: function hasRestorableChanges() {
+ return _hasUnresolvedRestorableChanges;
+ },
+ // load history from a version stored in localStorage
+ restore: function restore() {
+ if (_lock.locked()) {
+ _hasUnresolvedRestorableChanges = false;
+ var json = this.savedHistoryJSON();
+ if (json) history.fromJSON(json, true);
+ }
+ },
+ _getKey: getKey
+ };
+ history.reset();
+ return utilRebind(history, dispatch, 'on');
}
- function refresh(selection, node) {
- var cr = node.getBoundingClientRect();
- var prop = [cr.width, cr.height];
- selection.property('__dimensions__', prop);
- return prop;
- }
+ /**
+ * Look for roads that can be connected to other roads with a short extension
+ */
- function utilGetDimensions(selection, force) {
- if (!selection || selection.empty()) {
- return [0, 0];
- }
+ function validationAlmostJunction(context) {
+ var type = 'almost_junction';
+ var EXTEND_TH_METERS = 5;
+ var WELD_TH_METERS = 0.75; // Comes from considering bounding case of parallel ways
- var node = selection.node(),
- cached = selection.property('__dimensions__');
- return !cached || force ? refresh(selection, node) : cached;
- }
- function utilSetDimensions(selection, dimensions) {
- if (!selection || selection.empty()) {
- return selection;
- }
+ var CLOSE_NODE_TH = EXTEND_TH_METERS - WELD_TH_METERS; // Comes from considering bounding case of perpendicular ways
- var node = selection.node();
+ var SIG_ANGLE_TH = Math.atan(WELD_TH_METERS / EXTEND_TH_METERS);
- if (dimensions === null) {
- refresh(selection, node);
- return selection;
+ function isHighway(entity) {
+ return entity.type === 'way' && osmRoutableHighwayTagValues[entity.tags.highway];
}
- return selection.property('__dimensions__', [dimensions[0], dimensions[1]]).attr('width', dimensions[0]).attr('height', dimensions[1]);
- }
+ function isTaggedAsNotContinuing(node) {
+ return node.tags.noexit === 'yes' || node.tags.amenity === 'parking_entrance' || node.tags.entrance && node.tags.entrance !== 'no';
+ }
- function svgLayers(projection, context) {
- var dispatch = dispatch$8('change');
- var svg = select(null);
- var _layers = [{
- id: 'osm',
- layer: svgOsm(projection, context, dispatch)
- }, {
- id: 'notes',
- layer: svgNotes(projection, context, dispatch)
- }, {
- id: 'data',
- layer: svgData(projection, context, dispatch)
- }, {
- id: 'keepRight',
- layer: svgKeepRight(projection, context, dispatch)
- }, {
- id: 'improveOSM',
- layer: svgImproveOSM(projection, context, dispatch)
- }, {
- id: 'osmose',
- layer: svgOsmose(projection, context, dispatch)
- }, {
- id: 'streetside',
- layer: svgStreetside(projection, context, dispatch)
- }, {
- id: 'mapillary',
- layer: svgMapillaryImages(projection, context, dispatch)
- }, {
- id: 'mapillary-position',
- layer: svgMapillaryPosition(projection, context)
- }, {
- id: 'mapillary-map-features',
- layer: svgMapillaryMapFeatures(projection, context, dispatch)
- }, {
- id: 'mapillary-signs',
- layer: svgMapillarySigns(projection, context, dispatch)
- }, {
- id: 'openstreetcam',
- layer: svgOpenstreetcamImages(projection, context, dispatch)
- }, {
- id: 'debug',
- layer: svgDebug(projection, context)
- }, {
- id: 'geolocate',
- layer: svgGeolocate(projection)
- }, {
- id: 'touch',
- layer: svgTouch()
- }];
+ var validation = function checkAlmostJunction(entity, graph) {
+ if (!isHighway(entity)) return [];
+ if (entity.isDegenerate()) return [];
+ var tree = context.history().tree();
+ var extendableNodeInfos = findConnectableEndNodesByExtension(entity);
+ var issues = [];
+ extendableNodeInfos.forEach(function (extendableNodeInfo) {
+ issues.push(new validationIssue({
+ type: type,
+ subtype: 'highway-highway',
+ severity: 'warning',
+ message: function message(context) {
+ var entity1 = context.hasEntity(this.entityIds[0]);
- function drawLayers(selection) {
- svg = selection.selectAll('.surface').data([0]);
- svg = svg.enter().append('svg').attr('class', 'surface').merge(svg);
- var defs = svg.selectAll('.surface-defs').data([0]);
- defs.enter().append('defs').attr('class', 'surface-defs');
- var groups = svg.selectAll('.data-layer').data(_layers);
- groups.exit().remove();
- groups.enter().append('g').attr('class', function (d) {
- return 'data-layer ' + d.id;
- }).merge(groups).each(function (d) {
- select(this).call(d.layer);
+ if (this.entityIds[0] === this.entityIds[2]) {
+ return entity1 ? _t.html('issues.almost_junction.self.message', {
+ feature: utilDisplayLabel(entity1, context.graph())
+ }) : '';
+ } else {
+ var entity2 = context.hasEntity(this.entityIds[2]);
+ return entity1 && entity2 ? _t.html('issues.almost_junction.message', {
+ feature: utilDisplayLabel(entity1, context.graph()),
+ feature2: utilDisplayLabel(entity2, context.graph())
+ }) : '';
+ }
+ },
+ reference: showReference,
+ entityIds: [entity.id, extendableNodeInfo.node.id, extendableNodeInfo.wid],
+ loc: extendableNodeInfo.node.loc,
+ hash: JSON.stringify(extendableNodeInfo.node.loc),
+ data: {
+ midId: extendableNodeInfo.mid.id,
+ edge: extendableNodeInfo.edge,
+ cross_loc: extendableNodeInfo.cross_loc
+ },
+ dynamicFixes: makeFixes
+ }));
});
- }
+ return issues;
- drawLayers.all = function () {
- return _layers;
- };
+ function makeFixes(context) {
+ var fixes = [new validationIssueFix({
+ icon: 'iD-icon-abutment',
+ title: _t.html('issues.fix.connect_features.title'),
+ onClick: function onClick(context) {
+ var annotation = _t('issues.fix.connect_almost_junction.annotation');
- drawLayers.layer = function (id) {
- var obj = _layers.find(function (o) {
- return o.id === id;
- });
+ var _this$issue$entityIds = _slicedToArray(this.issue.entityIds, 3),
+ endNodeId = _this$issue$entityIds[1],
+ crossWayId = _this$issue$entityIds[2];
- return obj && obj.layer;
- };
+ var midNode = context.entity(this.issue.data.midId);
+ var endNode = context.entity(endNodeId);
+ var crossWay = context.entity(crossWayId); // When endpoints are close, just join if resulting small change in angle (#7201)
- drawLayers.only = function (what) {
- var arr = [].concat(what);
+ var nearEndNodes = findNearbyEndNodes(endNode, crossWay);
- var all = _layers.map(function (layer) {
- return layer.id;
- });
+ if (nearEndNodes.length > 0) {
+ var collinear = findSmallJoinAngle(midNode, endNode, nearEndNodes);
- return drawLayers.remove(utilArrayDifference(all, arr));
- };
+ if (collinear) {
+ context.perform(actionMergeNodes([collinear.id, endNode.id], collinear.loc), annotation);
+ return;
+ }
+ }
- drawLayers.remove = function (what) {
- var arr = [].concat(what);
- arr.forEach(function (id) {
- _layers = _layers.filter(function (o) {
- return o.id !== id;
- });
- });
- dispatch.call('change');
- return this;
- };
+ var targetEdge = this.issue.data.edge;
+ var crossLoc = this.issue.data.cross_loc;
+ var edgeNodes = [context.entity(targetEdge[0]), context.entity(targetEdge[1])];
+ var closestNodeInfo = geoSphericalClosestNode(edgeNodes, crossLoc); // already a point nearby, just connect to that
- drawLayers.add = function (what) {
- var arr = [].concat(what);
- arr.forEach(function (obj) {
- if ('id' in obj && 'layer' in obj) {
- _layers.push(obj);
- }
- });
- dispatch.call('change');
- return this;
- };
+ if (closestNodeInfo.distance < WELD_TH_METERS) {
+ context.perform(actionMergeNodes([closestNodeInfo.node.id, endNode.id], closestNodeInfo.node.loc), annotation); // else add the end node to the edge way
+ } else {
+ context.perform(actionAddMidpoint({
+ loc: crossLoc,
+ edge: targetEdge
+ }, endNode), annotation);
+ }
+ }
+ })];
+ var node = context.hasEntity(this.entityIds[1]);
- drawLayers.dimensions = function (val) {
- if (!arguments.length) return utilGetDimensions(svg);
- utilSetDimensions(svg, val);
- return this;
- };
+ if (node && !node.hasInterestingTags()) {
+ // node has no descriptive tags, suggest noexit fix
+ fixes.push(new validationIssueFix({
+ icon: 'maki-barrier',
+ title: _t.html('issues.fix.tag_as_disconnected.title'),
+ onClick: function onClick(context) {
+ var nodeID = this.issue.entityIds[1];
+ var tags = Object.assign({}, context.entity(nodeID).tags);
+ tags.noexit = 'yes';
+ context.perform(actionChangeTags(nodeID, tags), _t('issues.fix.tag_as_disconnected.annotation'));
+ }
+ }));
+ }
- return utilRebind(drawLayers, dispatch, 'on');
- }
+ return fixes;
+ }
- function svgLines(projection, context) {
- var detected = utilDetect();
- var highway_stack = {
- motorway: 0,
- motorway_link: 1,
- trunk: 2,
- trunk_link: 3,
- primary: 4,
- primary_link: 5,
- secondary: 6,
- tertiary: 7,
- unclassified: 8,
- residential: 9,
- service: 10,
- footway: 11
- };
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.almost_junction.highway-highway.reference'));
+ }
- function drawTargets(selection, graph, entities, filter) {
- var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
- var getPath = svgPath(projection).geojson;
- var activeID = context.activeID();
- var base = context.history().base(); // The targets and nopes will be MultiLineString sub-segments of the ways
+ function isExtendableCandidate(node, way) {
+ // can not accurately test vertices on tiles not downloaded from osm - #5938
+ var osm = services.osm;
- var data = {
- targets: [],
- nopes: []
- };
- entities.forEach(function (way) {
- var features = svgSegmentWay(way, graph, activeID);
- data.targets.push.apply(data.targets, features.passive);
- data.nopes.push.apply(data.nopes, features.active);
- }); // Targets allow hover and vertex snapping
+ if (osm && !osm.isDataLoaded(node.loc)) {
+ return false;
+ }
- var targetData = data.targets.filter(getPath);
- var targets = selection.selectAll('.line.target-allowed').filter(function (d) {
- return filter(d.properties.entity);
- }).data(targetData, function key(d) {
- return d.id;
- }); // exit
+ if (isTaggedAsNotContinuing(node) || graph.parentWays(node).length !== 1) {
+ return false;
+ }
- targets.exit().remove();
+ var occurrences = 0;
- var segmentWasEdited = function segmentWasEdited(d) {
- var wayID = d.properties.entity.id; // if the whole line was edited, don't draw segment changes
+ for (var index in way.nodes) {
+ if (way.nodes[index] === node.id) {
+ occurrences += 1;
- if (!base.entities[wayID] || !fastDeepEqual(graph.entities[wayID].nodes, base.entities[wayID].nodes)) {
- return false;
+ if (occurrences > 1) {
+ return false;
+ }
+ }
}
- return d.properties.nodes.some(function (n) {
- return !base.entities[n.id] || !fastDeepEqual(graph.entities[n.id].loc, base.entities[n.id].loc);
- });
- }; // enter/update
+ return true;
+ }
+ function findConnectableEndNodesByExtension(way) {
+ var results = [];
+ if (way.isClosed()) return results;
+ var testNodes;
+ var indices = [0, way.nodes.length - 1];
+ indices.forEach(function (nodeIndex) {
+ var nodeID = way.nodes[nodeIndex];
+ var node = graph.entity(nodeID);
+ if (!isExtendableCandidate(node, way)) return;
+ var connectionInfo = canConnectByExtend(way, nodeIndex);
+ if (!connectionInfo) return;
+ testNodes = graph.childNodes(way).slice(); // shallow copy
- targets.enter().append('path').merge(targets).attr('d', getPath).attr('class', function (d) {
- return 'way line target target-allowed ' + targetClass + d.id;
- }).classed('segment-edited', segmentWasEdited); // NOPE
+ testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc); // don't flag issue if connecting the ways would cause self-intersection
- var nopeData = data.nopes.filter(getPath);
- var nopes = selection.selectAll('.line.target-nope').filter(function (d) {
- return filter(d.properties.entity);
- }).data(nopeData, function key(d) {
- return d.id;
- }); // exit
+ if (geoHasSelfIntersections(testNodes, nodeID)) return;
+ results.push(connectionInfo);
+ });
+ return results;
+ }
- nopes.exit().remove(); // enter/update
+ function findNearbyEndNodes(node, way) {
+ return [way.nodes[0], way.nodes[way.nodes.length - 1]].map(function (d) {
+ return graph.entity(d);
+ }).filter(function (d) {
+ // Node cannot be near to itself, but other endnode of same way could be
+ return d.id !== node.id && geoSphericalDistance(node.loc, d.loc) <= CLOSE_NODE_TH;
+ });
+ }
- nopes.enter().append('path').merge(nopes).attr('d', getPath).attr('class', function (d) {
- return 'way line target target-nope ' + nopeClass + d.id;
- }).classed('segment-edited', segmentWasEdited);
- }
+ function findSmallJoinAngle(midNode, tipNode, endNodes) {
+ // Both nodes could be close, so want to join whichever is closest to collinear
+ var joinTo;
+ var minAngle = Infinity; // Checks midNode -> tipNode -> endNode for collinearity
- function drawLines(selection, graph, entities, filter) {
- var base = context.history().base();
+ endNodes.forEach(function (endNode) {
+ var a1 = geoAngle(midNode, tipNode, context.projection) + Math.PI;
+ var a2 = geoAngle(midNode, endNode, context.projection) + Math.PI;
+ var diff = Math.max(a1, a2) - Math.min(a1, a2);
- function waystack(a, b) {
- var selected = context.selectedIDs();
- var scoreA = selected.indexOf(a.id) !== -1 ? 20 : 0;
- var scoreB = selected.indexOf(b.id) !== -1 ? 20 : 0;
+ if (diff < minAngle) {
+ joinTo = endNode;
+ minAngle = diff;
+ }
+ });
+ /* Threshold set by considering right angle triangle
+ based on node joining threshold and extension distance */
- if (a.tags.highway) {
- scoreA -= highway_stack[a.tags.highway];
- }
+ if (minAngle <= SIG_ANGLE_TH) return joinTo;
+ return null;
+ }
- if (b.tags.highway) {
- scoreB -= highway_stack[b.tags.highway];
- }
+ function hasTag(tags, key) {
+ return tags[key] !== undefined && tags[key] !== 'no';
+ }
- return scoreA - scoreB;
+ function canConnectWays(way, way2) {
+ // allow self-connections
+ if (way.id === way2.id) return true; // if one is bridge or tunnel, both must be bridge or tunnel
+
+ if ((hasTag(way.tags, 'bridge') || hasTag(way2.tags, 'bridge')) && !(hasTag(way.tags, 'bridge') && hasTag(way2.tags, 'bridge'))) return false;
+ if ((hasTag(way.tags, 'tunnel') || hasTag(way2.tags, 'tunnel')) && !(hasTag(way.tags, 'tunnel') && hasTag(way2.tags, 'tunnel'))) return false; // must have equivalent layers and levels
+
+ var layer1 = way.tags.layer || '0',
+ layer2 = way2.tags.layer || '0';
+ if (layer1 !== layer2) return false;
+ var level1 = way.tags.level || '0',
+ level2 = way2.tags.level || '0';
+ if (level1 !== level2) return false;
+ return true;
}
- function drawLineGroup(selection, klass, isSelected) {
- // Note: Don't add `.selected` class in draw modes
- var mode = context.mode();
- var isDrawing = mode && /^draw/.test(mode.id);
- var selectedClass = !isDrawing && isSelected ? 'selected ' : '';
- var lines = selection.selectAll('path').filter(filter).data(getPathData(isSelected), osmEntity.key);
- lines.exit().remove(); // Optimization: Call expensive TagClasses only on enter selection. This
- // works because osmEntity.key is defined to include the entity v attribute.
+ function canConnectByExtend(way, endNodeIdx) {
+ var tipNid = way.nodes[endNodeIdx]; // the 'tip' node for extension point
- lines.enter().append('path').attr('class', function (d) {
- var prefix = 'way line'; // if this line isn't styled by its own tags
+ var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2]; // the other node of the edge
- if (!d.hasInterestingTags()) {
- var parentRelations = graph.parentRelations(d);
- var parentMultipolygons = parentRelations.filter(function (relation) {
- return relation.isMultipolygon();
- }); // and if it's a member of at least one multipolygon relation
+ var tipNode = graph.entity(tipNid);
+ var midNode = graph.entity(midNid);
+ var lon = tipNode.loc[0];
+ var lat = tipNode.loc[1];
+ var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2;
+ var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2;
+ var queryExtent = geoExtent([[lon - lon_range, lat - lat_range], [lon + lon_range, lat + lat_range]]); // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location
- if (parentMultipolygons.length > 0 && // and only multipolygon relations
- parentRelations.length === parentMultipolygons.length) {
- // then fudge the classes to style this as an area edge
- prefix = 'relation area';
- }
+ var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc);
+ var t = EXTEND_TH_METERS / edgeLen + 1.0;
+ var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t); // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways
+
+ var segmentInfos = tree.waySegments(queryExtent, graph);
+
+ for (var i = 0; i < segmentInfos.length; i++) {
+ var segmentInfo = segmentInfos[i];
+ var way2 = graph.entity(segmentInfo.wayId);
+ if (!isHighway(way2)) continue;
+ if (!canConnectWays(way, way2)) continue;
+ var nAid = segmentInfo.nodes[0],
+ nBid = segmentInfo.nodes[1];
+ if (nAid === tipNid || nBid === tipNid) continue;
+ var nA = graph.entity(nAid),
+ nB = graph.entity(nBid);
+ var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]);
+
+ if (crossLoc) {
+ return {
+ mid: midNode,
+ node: tipNode,
+ wid: way2.id,
+ edge: [nA.id, nB.id],
+ cross_loc: crossLoc
+ };
}
+ }
- var oldMPClass = oldMultiPolygonOuters[d.id] ? 'old-multipolygon ' : '';
- return prefix + ' ' + klass + ' ' + selectedClass + oldMPClass + d.id;
- }).classed('added', function (d) {
- return !base.entities[d.id];
- }).classed('geometry-edited', function (d) {
- return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].nodes, base.entities[d.id].nodes);
- }).classed('retagged', function (d) {
- return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
- }).call(svgTagClasses()).merge(lines).sort(waystack).attr('d', getPath).call(svgTagClasses().tags(svgRelationMemberTags(graph)));
- return selection;
+ return null;
}
+ };
- function getPathData(isSelected) {
- return function () {
- var layer = this.parentNode.__data__;
- var data = pathdata[layer] || [];
- return data.filter(function (d) {
- if (isSelected) {
- return context.selectedIDs().indexOf(d.id) !== -1;
- } else {
- return context.selectedIDs().indexOf(d.id) === -1;
- }
- });
- };
+ validation.type = type;
+ return validation;
+ }
+
+ function validationCloseNodes(context) {
+ var type = 'close_nodes';
+ var pointThresholdMeters = 0.2;
+
+ var validation = function validation(entity, graph) {
+ if (entity.type === 'node') {
+ return getIssuesForNode(entity);
+ } else if (entity.type === 'way') {
+ return getIssuesForWay(entity);
}
- function addMarkers(layergroup, pathclass, groupclass, groupdata, marker) {
- var markergroup = layergroup.selectAll('g.' + groupclass).data([pathclass]);
- markergroup = markergroup.enter().append('g').attr('class', groupclass).merge(markergroup);
- var markers = markergroup.selectAll('path').filter(filter).data(function data() {
- return groupdata[this.parentNode.__data__] || [];
- }, function key(d) {
- return [d.id, d.index];
- });
- markers.exit().remove();
- markers = markers.enter().append('path').attr('class', pathclass).merge(markers).attr('marker-mid', marker).attr('d', function (d) {
- return d.d;
- });
+ return [];
- if (detected.ie) {
- markers.each(function () {
- this.parentNode.insertBefore(this, this);
- });
+ function getIssuesForNode(node) {
+ var parentWays = graph.parentWays(node);
+
+ if (parentWays.length) {
+ return getIssuesForVertex(node, parentWays);
+ } else {
+ return getIssuesForDetachedPoint(node);
}
}
- var getPath = svgPath(projection, graph);
- var ways = [];
- var onewaydata = {};
- var sideddata = {};
- var oldMultiPolygonOuters = {};
+ function wayTypeFor(way) {
+ if (way.tags.boundary && way.tags.boundary !== 'no') return 'boundary';
+ if (way.tags.indoor && way.tags.indoor !== 'no') return 'indoor';
+ if (way.tags.building && way.tags.building !== 'no' || way.tags['building:part'] && way.tags['building:part'] !== 'no') return 'building';
+ if (osmPathHighwayTagValues[way.tags.highway]) return 'path';
+ var parentRelations = graph.parentRelations(way);
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- var outer = osmOldMultipolygonOuterMember(entity, graph);
+ for (var i in parentRelations) {
+ var relation = parentRelations[i];
+ if (relation.tags.type === 'boundary') return 'boundary';
- if (outer) {
- ways.push(entity.mergeTags(outer.tags));
- oldMultiPolygonOuters[outer.id] = true;
- } else if (entity.geometry(graph) === 'line') {
- ways.push(entity);
+ if (relation.isMultipolygon()) {
+ if (relation.tags.indoor && relation.tags.indoor !== 'no') return 'indoor';
+ if (relation.tags.building && relation.tags.building !== 'no' || relation.tags['building:part'] && relation.tags['building:part'] !== 'no') return 'building';
+ }
}
+
+ return 'other';
}
- ways = ways.filter(getPath);
- var pathdata = utilArrayGroupBy(ways, function (way) {
- return way.layer();
- });
- Object.keys(pathdata).forEach(function (k) {
- var v = pathdata[k];
- var onewayArr = v.filter(function (d) {
- return d.isOneWay();
- });
- var onewaySegments = svgMarkerSegments(projection, graph, 35, function shouldReverse(entity) {
- return entity.tags.oneway === '-1';
- }, function bothDirections(entity) {
- return entity.tags.oneway === 'reversible' || entity.tags.oneway === 'alternating';
- });
- onewaydata[k] = utilArrayFlatten(onewayArr.map(onewaySegments));
- var sidedArr = v.filter(function (d) {
- return d.isSided();
- });
- var sidedSegments = svgMarkerSegments(projection, graph, 30, function shouldReverse() {
- return false;
- }, function bothDirections() {
- return false;
- });
- sideddata[k] = utilArrayFlatten(sidedArr.map(sidedSegments));
- });
- var covered = selection.selectAll('.layer-osm.covered'); // under areas
+ function shouldCheckWay(way) {
+ // don't flag issues where merging would create degenerate ways
+ if (way.nodes.length <= 2 || way.isClosed() && way.nodes.length <= 4) return false;
+ var bbox = way.extent(graph).bbox();
+ var hypotenuseMeters = geoSphericalDistance([bbox.minX, bbox.minY], [bbox.maxX, bbox.maxY]); // don't flag close nodes in very small ways
- var uncovered = selection.selectAll('.layer-osm.lines'); // over areas
+ if (hypotenuseMeters < 1.5) return false;
+ return true;
+ }
- var touchLayer = selection.selectAll('.layer-touch.lines'); // Draw lines..
+ function getIssuesForWay(way) {
+ if (!shouldCheckWay(way)) return [];
+ var issues = [],
+ nodes = graph.childNodes(way);
- [covered, uncovered].forEach(function (selection) {
- var range = selection === covered ? range$1(-10, 0) : range$1(0, 11);
- var layergroup = selection.selectAll('g.layergroup').data(range);
- layergroup = layergroup.enter().append('g').attr('class', function (d) {
- return 'layergroup layer' + String(d);
- }).merge(layergroup);
- layergroup.selectAll('g.linegroup').data(['shadow', 'casing', 'stroke', 'shadow-highlighted', 'casing-highlighted', 'stroke-highlighted']).enter().append('g').attr('class', function (d) {
- return 'linegroup line-' + d;
- });
- layergroup.selectAll('g.line-shadow').call(drawLineGroup, 'shadow', false);
- layergroup.selectAll('g.line-casing').call(drawLineGroup, 'casing', false);
- layergroup.selectAll('g.line-stroke').call(drawLineGroup, 'stroke', false);
- layergroup.selectAll('g.line-shadow-highlighted').call(drawLineGroup, 'shadow', true);
- layergroup.selectAll('g.line-casing-highlighted').call(drawLineGroup, 'casing', true);
- layergroup.selectAll('g.line-stroke-highlighted').call(drawLineGroup, 'stroke', true);
- addMarkers(layergroup, 'oneway', 'onewaygroup', onewaydata, 'url(#ideditor-oneway-marker)');
- addMarkers(layergroup, 'sided', 'sidedgroup', sideddata, function marker(d) {
- var category = graph.entity(d.id).sidednessIdentifier();
- return 'url(#ideditor-sided-marker-' + category + ')';
- });
- }); // Draw touch targets..
+ for (var i = 0; i < nodes.length - 1; i++) {
+ var node1 = nodes[i];
+ var node2 = nodes[i + 1];
+ var issue = getWayIssueIfAny(node1, node2, way);
+ if (issue) issues.push(issue);
+ }
- touchLayer.call(drawTargets, graph, ways, filter);
- }
+ return issues;
+ }
- return drawLines;
- }
+ function getIssuesForVertex(node, parentWays) {
+ var issues = [];
- function svgMidpoints(projection, context) {
- var targetRadius = 8;
+ function checkForCloseness(node1, node2, way) {
+ var issue = getWayIssueIfAny(node1, node2, way);
+ if (issue) issues.push(issue);
+ }
- function drawTargets(selection, graph, entities, filter) {
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var getTransform = svgPointTransform(projection).geojson;
- var data = entities.map(function (midpoint) {
- return {
- type: 'Feature',
- id: midpoint.id,
- properties: {
- target: true,
- entity: midpoint
- },
- geometry: {
- type: 'Point',
- coordinates: midpoint.loc
+ for (var i = 0; i < parentWays.length; i++) {
+ var parentWay = parentWays[i];
+ if (!shouldCheckWay(parentWay)) continue;
+ var lastIndex = parentWay.nodes.length - 1;
+
+ for (var j = 0; j < parentWay.nodes.length; j++) {
+ if (j !== 0) {
+ if (parentWay.nodes[j - 1] === node.id) {
+ checkForCloseness(node, graph.entity(parentWay.nodes[j]), parentWay);
+ }
+ }
+
+ if (j !== lastIndex) {
+ if (parentWay.nodes[j + 1] === node.id) {
+ checkForCloseness(graph.entity(parentWay.nodes[j]), node, parentWay);
+ }
+ }
}
- };
- });
- var targets = selection.selectAll('.midpoint.target').filter(function (d) {
- return filter(d.properties.entity);
- }).data(data, function key(d) {
- return d.id;
- }); // exit
+ }
- targets.exit().remove(); // enter/update
+ return issues;
+ }
- targets.enter().append('circle').attr('r', targetRadius).merge(targets).attr('class', function (d) {
- return 'node midpoint target ' + fillClass + d.id;
- }).attr('transform', getTransform);
- }
+ function thresholdMetersForWay(way) {
+ if (!shouldCheckWay(way)) return 0;
+ var wayType = wayTypeFor(way); // don't flag boundaries since they might be highly detailed and can't be easily verified
- function drawMidpoints(selection, graph, entities, filter, extent) {
- var drawLayer = selection.selectAll('.layer-osm.points .points-group.midpoints');
- var touchLayer = selection.selectAll('.layer-touch.points');
- var mode = context.mode();
+ if (wayType === 'boundary') return 0; // expect some features to be mapped with higher levels of detail
- if (mode && mode.id !== 'select' || !context.map().withinEditableZoom()) {
- drawLayer.selectAll('.midpoint').remove();
- touchLayer.selectAll('.midpoint.target').remove();
- return;
+ if (wayType === 'indoor') return 0.01;
+ if (wayType === 'building') return 0.05;
+ if (wayType === 'path') return 0.1;
+ return 0.2;
}
- var poly = extent.polygon();
- var midpoints = {};
-
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- if (entity.type !== 'way') continue;
- if (!filter(entity)) continue;
- if (context.selectedIDs().indexOf(entity.id) < 0) continue;
- var nodes = graph.childNodes(entity);
+ function getIssuesForDetachedPoint(node) {
+ var issues = [];
+ var lon = node.loc[0];
+ var lat = node.loc[1];
+ var lon_range = geoMetersToLon(pointThresholdMeters, lat) / 2;
+ var lat_range = geoMetersToLat(pointThresholdMeters) / 2;
+ var queryExtent = geoExtent([[lon - lon_range, lat - lat_range], [lon + lon_range, lat + lat_range]]);
+ var intersected = context.history().tree().intersects(queryExtent, graph);
- for (var j = 0; j < nodes.length - 1; j++) {
- var a = nodes[j];
- var b = nodes[j + 1];
- var id = [a.id, b.id].sort().join('-');
+ for (var j = 0; j < intersected.length; j++) {
+ var nearby = intersected[j];
+ if (nearby.id === node.id) continue;
+ if (nearby.type !== 'node' || nearby.geometry(graph) !== 'point') continue;
- if (midpoints[id]) {
- midpoints[id].parents.push(entity);
- } else if (geoVecLength(projection(a.loc), projection(b.loc)) > 40) {
- var point = geoVecInterp(a.loc, b.loc, 0.5);
- var loc = null;
+ if (nearby.loc === node.loc || geoSphericalDistance(node.loc, nearby.loc) < pointThresholdMeters) {
+ // allow very close points if tags indicate the z-axis might vary
+ var zAxisKeys = {
+ layer: true,
+ level: true,
+ 'addr:housenumber': true,
+ 'addr:unit': true
+ };
+ var zAxisDifferentiates = false;
- if (extent.intersects(point)) {
- loc = point;
- } else {
- for (var k = 0; k < 4; k++) {
- point = geoLineIntersection([a.loc, b.loc], [poly[k], poly[k + 1]]);
+ for (var key in zAxisKeys) {
+ var nodeValue = node.tags[key] || '0';
+ var nearbyValue = nearby.tags[key] || '0';
- if (point && geoVecLength(projection(a.loc), projection(point)) > 20 && geoVecLength(projection(b.loc), projection(point)) > 20) {
- loc = point;
- break;
- }
+ if (nodeValue !== nearbyValue) {
+ zAxisDifferentiates = true;
+ break;
}
}
- if (loc) {
- midpoints[id] = {
- type: 'midpoint',
- id: id,
- loc: loc,
- edge: [a.id, b.id],
- parents: [entity]
- };
- }
+ if (zAxisDifferentiates) continue;
+ issues.push(new validationIssue({
+ type: type,
+ subtype: 'detached',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]),
+ entity2 = context.hasEntity(this.entityIds[1]);
+ return entity && entity2 ? _t.html('issues.close_nodes.detached.message', {
+ feature: utilDisplayLabel(entity, context.graph()),
+ feature2: utilDisplayLabel(entity2, context.graph())
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [node.id, nearby.id],
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-operation-disconnect',
+ title: _t.html('issues.fix.move_points_apart.title')
+ }), new validationIssueFix({
+ icon: 'iD-icon-layers',
+ title: _t.html('issues.fix.use_different_layers_or_levels.title')
+ })];
+ }
+ }));
}
}
- }
- function midpointFilter(d) {
- if (midpoints[d.id]) return true;
+ return issues;
- for (var i = 0; i < d.parents.length; i++) {
- if (filter(d.parents[i])) {
- return true;
- }
+ function showReference(selection) {
+ var referenceText = _t('issues.close_nodes.detached.reference');
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(referenceText);
}
-
- return false;
}
- var groups = drawLayer.selectAll('.midpoint').filter(midpointFilter).data(Object.values(midpoints), function (d) {
- return d.id;
- });
- groups.exit().remove();
- var enter = groups.enter().insert('g', ':first-child').attr('class', 'midpoint');
- enter.append('polygon').attr('points', '-6,8 10,0 -6,-8').attr('class', 'shadow');
- enter.append('polygon').attr('points', '-3,4 5,0 -3,-4').attr('class', 'fill');
- groups = groups.merge(enter).attr('transform', function (d) {
- var translate = svgPointTransform(projection);
- var a = graph.entity(d.edge[0]);
- var b = graph.entity(d.edge[1]);
- var angle = geoAngle(a, b, projection) * (180 / Math.PI);
- return translate(d) + ' rotate(' + angle + ')';
- }).call(svgTagClasses().tags(function (d) {
- return d.parents[0].tags;
- })); // Propagate data bindings.
+ function getWayIssueIfAny(node1, node2, way) {
+ if (node1.id === node2.id || node1.hasInterestingTags() && node2.hasInterestingTags()) {
+ return null;
+ }
- groups.select('polygon.shadow');
- groups.select('polygon.fill'); // Draw touch targets..
+ if (node1.loc !== node2.loc) {
+ var parentWays1 = graph.parentWays(node1);
+ var parentWays2 = new Set(graph.parentWays(node2));
+ var sharedWays = parentWays1.filter(function (parentWay) {
+ return parentWays2.has(parentWay);
+ });
+ var thresholds = sharedWays.map(function (parentWay) {
+ return thresholdMetersForWay(parentWay);
+ });
+ var threshold = Math.min.apply(Math, _toConsumableArray(thresholds));
+ var distance = geoSphericalDistance(node1.loc, node2.loc);
+ if (distance > threshold) return null;
+ }
- touchLayer.call(drawTargets, graph, Object.values(midpoints), midpointFilter);
- }
+ return new validationIssue({
+ type: type,
+ subtype: 'vertices',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.close_nodes.message', {
+ way: utilDisplayLabel(entity, context.graph())
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [way.id, node1.id, node2.id],
+ loc: node1.loc,
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-icon-plus',
+ title: _t.html('issues.fix.merge_points.title'),
+ onClick: function onClick(context) {
+ var entityIds = this.issue.entityIds;
+ var action = actionMergeNodes([entityIds[1], entityIds[2]]);
+ context.perform(action, _t('issues.fix.merge_close_vertices.annotation'));
+ }
+ }), new validationIssueFix({
+ icon: 'iD-operation-disconnect',
+ title: _t.html('issues.fix.move_points_apart.title')
+ })];
+ }
+ });
- return drawMidpoints;
+ function showReference(selection) {
+ var referenceText = _t('issues.close_nodes.reference');
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').html(referenceText);
+ }
+ }
+ };
+
+ validation.type = type;
+ return validation;
}
- function svgPoints(projection, context) {
- function markerPath(selection, klass) {
- selection.attr('class', klass).attr('transform', 'translate(-8, -23)').attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
- }
+ function validationCrossingWays(context) {
+ var type = 'crossing_ways'; // returns the way or its parent relation, whichever has a useful feature type
- function sortY(a, b) {
- return b.loc[1] - a.loc[1];
- } // Avoid exit/enter if we're just moving stuff around.
- // The node will get a new version but we only need to run the update selection.
+ function getFeatureWithFeatureTypeTagsForWay(way, graph) {
+ if (getFeatureType(way, graph) === null) {
+ // if the way doesn't match a feature type, check its parent relations
+ var parentRels = graph.parentRelations(way);
+ for (var i = 0; i < parentRels.length; i++) {
+ var rel = parentRels[i];
- function fastEntityKey(d) {
- var mode = context.mode();
- var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
- return isMoving ? d.id : osmEntity.key(d);
- }
+ if (getFeatureType(rel, graph) !== null) {
+ return rel;
+ }
+ }
+ }
- function drawTargets(selection, graph, entities, filter) {
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var getTransform = svgPointTransform(projection).geojson;
- var activeID = context.activeID();
- var data = [];
- entities.forEach(function (node) {
- if (activeID === node.id) return; // draw no target on the activeID
+ return way;
+ }
- data.push({
- type: 'Feature',
- id: node.id,
- properties: {
- target: true,
- entity: node
- },
- geometry: node.asGeoJSON()
- });
- });
- var targets = selection.selectAll('.point.target').filter(function (d) {
- return filter(d.properties.entity);
- }).data(data, function key(d) {
- return d.id;
- }); // exit
+ function hasTag(tags, key) {
+ return tags[key] !== undefined && tags[key] !== 'no';
+ }
- targets.exit().remove(); // enter/update
+ function taggedAsIndoor(tags) {
+ return hasTag(tags, 'indoor') || hasTag(tags, 'level') || tags.highway === 'corridor';
+ }
- targets.enter().append('rect').attr('x', -10).attr('y', -26).attr('width', 20).attr('height', 30).merge(targets).attr('class', function (d) {
- return 'node point target ' + fillClass + d.id;
- }).attr('transform', getTransform);
+ function allowsBridge(featureType) {
+ return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
}
- function drawPoints(selection, graph, entities, filter) {
- var wireframe = context.surface().classed('fill-wireframe');
- var zoom = geoScaleToZoom(projection.scale());
- var base = context.history().base(); // Points with a direction will render as vertices at higher zooms..
+ function allowsTunnel(featureType) {
+ return featureType === 'highway' || featureType === 'railway' || featureType === 'waterway';
+ } // discard
- function renderAsPoint(entity) {
- return entity.geometry(graph) === 'point' && !(zoom >= 18 && entity.directions(graph, projection).length);
- } // All points will render as vertices in wireframe mode too..
+ var ignoredBuildings = {
+ demolished: true,
+ dismantled: true,
+ proposed: true,
+ razed: true
+ };
- var points = wireframe ? [] : entities.filter(renderAsPoint);
- points.sort(sortY);
- var drawLayer = selection.selectAll('.layer-osm.points .points-group.points');
- var touchLayer = selection.selectAll('.layer-touch.points'); // Draw points..
+ function getFeatureType(entity, graph) {
+ var geometry = entity.geometry(graph);
+ if (geometry !== 'line' && geometry !== 'area') return null;
+ var tags = entity.tags;
+ if (hasTag(tags, 'building') && !ignoredBuildings[tags.building]) return 'building';
+ if (hasTag(tags, 'highway') && osmRoutableHighwayTagValues[tags.highway]) return 'highway'; // don't check railway or waterway areas
- var groups = drawLayer.selectAll('g.point').filter(filter).data(points, fastEntityKey);
- groups.exit().remove();
- var enter = groups.enter().append('g').attr('class', function (d) {
- return 'node point ' + d.id;
- }).order();
- enter.append('path').call(markerPath, 'shadow');
- enter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
- enter.append('path').call(markerPath, 'stroke');
- enter.append('use').attr('transform', 'translate(-5, -19)').attr('class', 'icon').attr('width', '11px').attr('height', '11px');
- groups = groups.merge(enter).attr('transform', svgPointTransform(projection)).classed('added', function (d) {
- return !base.entities[d.id]; // if it doesn't exist in the base graph, it's new
- }).classed('moved', function (d) {
- return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].loc, base.entities[d.id].loc);
- }).classed('retagged', function (d) {
- return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
- }).call(svgTagClasses());
- groups.select('.shadow'); // propagate bound data
+ if (geometry !== 'line') return null;
+ if (hasTag(tags, 'railway') && osmRailwayTrackTagValues[tags.railway]) return 'railway';
+ if (hasTag(tags, 'waterway') && osmFlowingWaterwayTagValues[tags.waterway]) return 'waterway';
+ return null;
+ }
- groups.select('.stroke'); // propagate bound data
+ function isLegitCrossing(tags1, featureType1, tags2, featureType2) {
+ // assume 0 by default
+ var level1 = tags1.level || '0';
+ var level2 = tags2.level || '0';
- groups.select('.icon') // propagate bound data
- .attr('xlink:href', function (entity) {
- var preset = _mainPresetIndex.match(entity, graph);
- var picon = preset && preset.icon;
+ if (taggedAsIndoor(tags1) && taggedAsIndoor(tags2) && level1 !== level2) {
+ // assume features don't interact if they're indoor on different levels
+ return true;
+ } // assume 0 by default; don't use way.layer() since we account for structures here
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return '#' + picon + (isMaki ? '-11' : '');
- }
- }); // Draw touch targets..
- touchLayer.call(drawTargets, graph, points, filter);
- }
+ var layer1 = tags1.layer || '0';
+ var layer2 = tags2.layer || '0';
- return drawPoints;
- }
+ if (allowsBridge(featureType1) && allowsBridge(featureType2)) {
+ if (hasTag(tags1, 'bridge') && !hasTag(tags2, 'bridge')) return true;
+ if (!hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge')) return true; // crossing bridges must use different layers
- function svgTurns(projection, context) {
- function icon(turn) {
- var u = turn.u ? '-u' : '';
- if (turn.no) return '#iD-turn-no' + u;
- if (turn.only) return '#iD-turn-only' + u;
- return '#iD-turn-yes' + u;
- }
+ if (hasTag(tags1, 'bridge') && hasTag(tags2, 'bridge') && layer1 !== layer2) return true;
+ } else if (allowsBridge(featureType1) && hasTag(tags1, 'bridge')) return true;else if (allowsBridge(featureType2) && hasTag(tags2, 'bridge')) return true;
- function drawTurns(selection, graph, turns) {
- function turnTransform(d) {
- var pxRadius = 50;
- var toWay = graph.entity(d.to.way);
- var toPoints = graph.childNodes(toWay).map(function (n) {
- return n.loc;
- }).map(projection);
- var toLength = geoPathLength(toPoints);
- var mid = toLength / 2; // midpoint of destination way
+ if (allowsTunnel(featureType1) && allowsTunnel(featureType2)) {
+ if (hasTag(tags1, 'tunnel') && !hasTag(tags2, 'tunnel')) return true;
+ if (!hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel')) return true; // crossing tunnels must use different layers
- var toNode = graph.entity(d.to.node);
- var toVertex = graph.entity(d.to.vertex);
- var a = geoAngle(toVertex, toNode, projection);
- var o = projection(toVertex.loc);
- var r = d.u ? 0 // u-turn: no radius
- : !toWay.__via ? pxRadius // leaf way: put marker at pxRadius
- : Math.min(mid, pxRadius); // via way: prefer pxRadius, fallback to mid for very short ways
+ if (hasTag(tags1, 'tunnel') && hasTag(tags2, 'tunnel') && layer1 !== layer2) return true;
+ } else if (allowsTunnel(featureType1) && hasTag(tags1, 'tunnel')) return true;else if (allowsTunnel(featureType2) && hasTag(tags2, 'tunnel')) return true; // don't flag crossing waterways and pier/highways
- return 'translate(' + (r * Math.cos(a) + o[0]) + ',' + (r * Math.sin(a) + o[1]) + ') ' + 'rotate(' + a * 180 / Math.PI + ')';
+
+ if (featureType1 === 'waterway' && featureType2 === 'highway' && tags2.man_made === 'pier') return true;
+ if (featureType2 === 'waterway' && featureType1 === 'highway' && tags1.man_made === 'pier') return true;
+
+ if (featureType1 === 'building' || featureType2 === 'building') {
+ // for building crossings, different layers are enough
+ if (layer1 !== layer2) return true;
}
- var drawLayer = selection.selectAll('.layer-osm.points .points-group.turns');
- var touchLayer = selection.selectAll('.layer-touch.turns'); // Draw turns..
+ return false;
+ } // highway values for which we shouldn't recommend connecting to waterways
- var groups = drawLayer.selectAll('g.turn').data(turns, function (d) {
- return d.key;
- }); // exit
- groups.exit().remove(); // enter
+ var highwaysDisallowingFords = {
+ motorway: true,
+ motorway_link: true,
+ trunk: true,
+ trunk_link: true,
+ primary: true,
+ primary_link: true,
+ secondary: true,
+ secondary_link: true
+ };
+ var nonCrossingHighways = {
+ track: true
+ };
- var groupsEnter = groups.enter().append('g').attr('class', function (d) {
- return 'turn ' + d.key;
- });
- var turnsEnter = groupsEnter.filter(function (d) {
- return !d.u;
- });
- turnsEnter.append('rect').attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
- turnsEnter.append('use').attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
- var uEnter = groupsEnter.filter(function (d) {
- return d.u;
- });
- uEnter.append('circle').attr('r', '16');
- uEnter.append('use').attr('transform', 'translate(-16, -16)').attr('width', '32').attr('height', '32'); // update
+ function tagsForConnectionNodeIfAllowed(entity1, entity2, graph) {
+ var featureType1 = getFeatureType(entity1, graph);
+ var featureType2 = getFeatureType(entity2, graph);
+ var geometry1 = entity1.geometry(graph);
+ var geometry2 = entity2.geometry(graph);
+ var bothLines = geometry1 === 'line' && geometry2 === 'line';
- groups = groups.merge(groupsEnter).attr('opacity', function (d) {
- return d.direct === false ? '0.7' : null;
- }).attr('transform', turnTransform);
- groups.select('use').attr('xlink:href', icon);
- groups.select('rect'); // propagate bound data
+ if (featureType1 === featureType2) {
+ if (featureType1 === 'highway') {
+ var entity1IsPath = osmPathHighwayTagValues[entity1.tags.highway];
+ var entity2IsPath = osmPathHighwayTagValues[entity2.tags.highway];
- groups.select('circle'); // propagate bound data
- // Draw touch targets..
+ if ((entity1IsPath || entity2IsPath) && entity1IsPath !== entity2IsPath) {
+ // one feature is a path but not both
+ var roadFeature = entity1IsPath ? entity2 : entity1;
- var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- groups = touchLayer.selectAll('g.turn').data(turns, function (d) {
- return d.key;
- }); // exit
+ if (nonCrossingHighways[roadFeature.tags.highway]) {
+ // don't mark path connections with certain roads as crossings
+ return {};
+ }
- groups.exit().remove(); // enter
+ var pathFeature = entity1IsPath ? entity1 : entity2;
- groupsEnter = groups.enter().append('g').attr('class', function (d) {
- return 'turn ' + d.key;
- });
- turnsEnter = groupsEnter.filter(function (d) {
- return !d.u;
- });
- turnsEnter.append('rect').attr('class', 'target ' + fillClass).attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
- uEnter = groupsEnter.filter(function (d) {
- return d.u;
- });
- uEnter.append('circle').attr('class', 'target ' + fillClass).attr('r', '16'); // update
+ if (['marked', 'unmarked'].indexOf(pathFeature.tags.crossing) !== -1) {
+ // if the path is a crossing, match the crossing type
+ return bothLines ? {
+ highway: 'crossing',
+ crossing: pathFeature.tags.crossing
+ } : {};
+ } // don't add a `crossing` subtag to ambiguous crossings
- groups = groups.merge(groupsEnter).attr('transform', turnTransform);
- groups.select('rect'); // propagate bound data
- groups.select('circle'); // propagate bound data
+ return bothLines ? {
+ highway: 'crossing'
+ } : {};
+ }
- return this;
- }
+ return {};
+ }
- return drawTurns;
- }
+ if (featureType1 === 'waterway') return {};
+ if (featureType1 === 'railway') return {};
+ } else {
+ var featureTypes = [featureType1, featureType2];
- function svgVertices(projection, context) {
- var radiuses = {
- // z16-, z17, z18+, w/icon
- shadow: [6, 7.5, 7.5, 12],
- stroke: [2.5, 3.5, 3.5, 8],
- fill: [1, 1.5, 1.5, 1.5]
- };
+ if (featureTypes.indexOf('highway') !== -1) {
+ if (featureTypes.indexOf('railway') !== -1) {
+ if (!bothLines) return {};
+ var isTram = entity1.tags.railway === 'tram' || entity2.tags.railway === 'tram';
- var _currHoverTarget;
+ if (osmPathHighwayTagValues[entity1.tags.highway] || osmPathHighwayTagValues[entity2.tags.highway]) {
+ // path-tram connections use this tag
+ if (isTram) return {
+ railway: 'tram_crossing'
+ }; // other path-rail connections use this tag
- var _currPersistent = {};
- var _currHover = {};
- var _prevHover = {};
- var _currSelected = {};
- var _prevSelected = {};
- var _radii = {};
+ return {
+ railway: 'crossing'
+ };
+ } else {
+ // path-tram connections use this tag
+ if (isTram) return {
+ railway: 'tram_level_crossing'
+ }; // other road-rail connections use this tag
- function sortY(a, b) {
- return b.loc[1] - a.loc[1];
- } // Avoid exit/enter if we're just moving stuff around.
- // The node will get a new version but we only need to run the update selection.
+ return {
+ railway: 'level_crossing'
+ };
+ }
+ }
+ if (featureTypes.indexOf('waterway') !== -1) {
+ // do not allow fords on structures
+ if (hasTag(entity1.tags, 'tunnel') && hasTag(entity2.tags, 'tunnel')) return null;
+ if (hasTag(entity1.tags, 'bridge') && hasTag(entity2.tags, 'bridge')) return null;
- function fastEntityKey(d) {
- var mode = context.mode();
- var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
- return isMoving ? d.id : osmEntity.key(d);
+ if (highwaysDisallowingFords[entity1.tags.highway] || highwaysDisallowingFords[entity2.tags.highway]) {
+ // do not allow fords on major highways
+ return null;
+ }
+
+ return bothLines ? {
+ ford: 'yes'
+ } : {};
+ }
+ }
+ }
+
+ return null;
}
- function draw(selection, graph, vertices, sets, filter) {
- sets = sets || {
- selected: {},
- important: {},
- hovered: {}
- };
- var icons = {};
- var directions = {};
- var wireframe = context.surface().classed('fill-wireframe');
- var zoom = geoScaleToZoom(projection.scale());
- var z = zoom < 17 ? 0 : zoom < 18 ? 1 : 2;
- var activeID = context.activeID();
- var base = context.history().base();
+ function findCrossingsByWay(way1, graph, tree) {
+ var edgeCrossInfos = [];
+ if (way1.type !== 'way') return edgeCrossInfos;
+ var taggedFeature1 = getFeatureWithFeatureTypeTagsForWay(way1, graph);
+ var way1FeatureType = getFeatureType(taggedFeature1, graph);
+ if (way1FeatureType === null) return edgeCrossInfos;
+ var checkedSingleCrossingWays = {}; // declare vars ahead of time to reduce garbage collection
- function getIcon(d) {
- // always check latest entity, as fastEntityKey avoids enter/exit now
- var entity = graph.entity(d.id);
- if (entity.id in icons) return icons[entity.id];
- icons[entity.id] = entity.hasInterestingTags() && _mainPresetIndex.match(entity, graph).icon;
- return icons[entity.id];
- } // memoize directions results, return false for empty arrays (for use in filter)
+ var i, j;
+ var extent;
+ var n1, n2, nA, nB, nAId, nBId;
+ var segment1, segment2;
+ var oneOnly;
+ var segmentInfos, segment2Info, way2, taggedFeature2, way2FeatureType;
+ var way1Nodes = graph.childNodes(way1);
+ var comparedWays = {};
+ for (i = 0; i < way1Nodes.length - 1; i++) {
+ n1 = way1Nodes[i];
+ n2 = way1Nodes[i + 1];
+ extent = 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])]]); // Optimize by only checking overlapping segments, not every segment
+ // of overlapping ways
- function getDirections(entity) {
- if (entity.id in directions) return directions[entity.id];
- var angles = entity.directions(graph, projection);
- directions[entity.id] = angles.length ? angles : false;
- return angles;
- }
+ segmentInfos = tree.waySegments(extent, graph);
- function updateAttributes(selection) {
- ['shadow', 'stroke', 'fill'].forEach(function (klass) {
- var rads = radiuses[klass];
- selection.selectAll('.' + klass).each(function (entity) {
- var i = z && getIcon(entity);
- var r = rads[i ? 3 : z]; // slightly increase the size of unconnected endpoints #3775
+ for (j = 0; j < segmentInfos.length; j++) {
+ segment2Info = segmentInfos[j]; // don't check for self-intersection in this validation
- if (entity.id !== activeID && entity.isEndpoint(graph) && !entity.isConnected(graph)) {
- r += 1.5;
- }
+ if (segment2Info.wayId === way1.id) continue; // skip if this way was already checked and only one issue is needed
- if (klass === 'shadow') {
- // remember this value, so we don't need to
- _radii[entity.id] = r; // recompute it when we draw the touch targets
- }
+ if (checkedSingleCrossingWays[segment2Info.wayId]) continue; // mark this way as checked even if there are no crossings
- select(this).attr('r', r).attr('visibility', i && klass === 'fill' ? 'hidden' : null);
- });
- });
- }
+ comparedWays[segment2Info.wayId] = true;
+ way2 = graph.hasEntity(segment2Info.wayId);
+ if (!way2) continue;
+ taggedFeature2 = getFeatureWithFeatureTypeTagsForWay(way2, graph); // only check crossing highway, waterway, building, and railway
- vertices.sort(sortY);
- var groups = selection.selectAll('g.vertex').filter(filter).data(vertices, fastEntityKey); // exit
+ way2FeatureType = getFeatureType(taggedFeature2, graph);
- groups.exit().remove(); // enter
+ if (way2FeatureType === null || isLegitCrossing(taggedFeature1.tags, way1FeatureType, taggedFeature2.tags, way2FeatureType)) {
+ continue;
+ } // create only one issue for building crossings
- var enter = groups.enter().append('g').attr('class', function (d) {
- return 'node vertex ' + d.id;
- }).order();
- enter.append('circle').attr('class', 'shadow');
- enter.append('circle').attr('class', 'stroke'); // Vertices with tags get a fill.
- enter.filter(function (d) {
- return d.hasInterestingTags();
- }).append('circle').attr('class', 'fill'); // update
+ oneOnly = way1FeatureType === 'building' || way2FeatureType === 'building';
+ nAId = segment2Info.nodes[0];
+ nBId = segment2Info.nodes[1];
- groups = groups.merge(enter).attr('transform', svgPointTransform(projection)).classed('sibling', function (d) {
- return d.id in sets.selected;
- }).classed('shared', function (d) {
- return graph.isShared(d);
- }).classed('endpoint', function (d) {
- return d.isEndpoint(graph);
- }).classed('added', function (d) {
- return !base.entities[d.id]; // if it doesn't exist in the base graph, it's new
- }).classed('moved', function (d) {
- return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].loc, base.entities[d.id].loc);
- }).classed('retagged', function (d) {
- return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
- }).call(updateAttributes); // Vertices with icons get a `use`.
+ if (nAId === n1.id || nAId === n2.id || nBId === n1.id || nBId === n2.id) {
+ // n1 or n2 is a connection node; skip
+ continue;
+ }
- var iconUse = groups.selectAll('.icon').data(function data(d) {
- return zoom >= 17 && getIcon(d) ? [d] : [];
- }, fastEntityKey); // exit
+ nA = graph.hasEntity(nAId);
+ if (!nA) continue;
+ nB = graph.hasEntity(nBId);
+ if (!nB) continue;
+ segment1 = [n1.loc, n2.loc];
+ segment2 = [nA.loc, nB.loc];
+ var point = geoLineIntersection(segment1, segment2);
- iconUse.exit().remove(); // enter
+ if (point) {
+ edgeCrossInfos.push({
+ wayInfos: [{
+ way: way1,
+ featureType: way1FeatureType,
+ edge: [n1.id, n2.id]
+ }, {
+ way: way2,
+ featureType: way2FeatureType,
+ edge: [nA.id, nB.id]
+ }],
+ crossPoint: point
+ });
- iconUse.enter().append('use').attr('class', 'icon').attr('width', '11px').attr('height', '11px').attr('transform', 'translate(-5.5, -5.5)').attr('xlink:href', function (d) {
- var picon = getIcon(d);
- var isMaki = /^maki-/.test(picon);
- return '#' + picon + (isMaki ? '-11' : '');
- }); // Vertices with directions get viewfields
+ if (oneOnly) {
+ checkedSingleCrossingWays[way2.id] = true;
+ break;
+ }
+ }
+ }
+ }
- var dgroups = groups.selectAll('.viewfieldgroup').data(function data(d) {
- return zoom >= 18 && getDirections(d) ? [d] : [];
- }, fastEntityKey); // exit
+ return edgeCrossInfos;
+ }
- dgroups.exit().remove(); // enter/update
+ function waysToCheck(entity, graph) {
+ var featureType = getFeatureType(entity, graph);
+ if (!featureType) return [];
- dgroups = dgroups.enter().insert('g', '.shadow').attr('class', 'viewfieldgroup').merge(dgroups);
- var viewfields = dgroups.selectAll('.viewfield').data(getDirections, function key(d) {
- return osmEntity.key(d);
- }); // exit
+ if (entity.type === 'way') {
+ return [entity];
+ } else if (entity.type === 'relation') {
+ return entity.members.reduce(function (array, member) {
+ if (member.type === 'way' && ( // only look at geometry ways
+ !member.role || member.role === 'outer' || member.role === 'inner')) {
+ var entity = graph.hasEntity(member.id); // don't add duplicates
- viewfields.exit().remove(); // enter/update
+ if (entity && array.indexOf(entity) === -1) {
+ array.push(entity);
+ }
+ }
- viewfields.enter().append('path').attr('class', 'viewfield').attr('d', 'M0,0H0').merge(viewfields).attr('marker-start', 'url(#ideditor-viewfield-marker' + (wireframe ? '-wireframe' : '') + ')').attr('transform', function (d) {
- return 'rotate(' + d + ')';
- });
- }
+ return array;
+ }, []);
+ }
- function drawTargets(selection, graph, entities, filter) {
- var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
- var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
- var getTransform = svgPointTransform(projection).geojson;
- var activeID = context.activeID();
- var data = {
- targets: [],
- nopes: []
- };
- entities.forEach(function (node) {
- if (activeID === node.id) return; // draw no target on the activeID
+ return [];
+ }
- var vertexType = svgPassiveVertex(node, graph, activeID);
+ var validation = function checkCrossingWays(entity, graph) {
+ var tree = context.history().tree();
+ var ways = waysToCheck(entity, graph);
+ var issues = []; // declare these here to reduce garbage collection
- if (vertexType !== 0) {
- // passive or adjacent - allow to connect
- data.targets.push({
- type: 'Feature',
- id: node.id,
- properties: {
- target: true,
- entity: node
- },
- geometry: node.asGeoJSON()
- });
- } else {
- data.nopes.push({
- type: 'Feature',
- id: node.id + '-nope',
- properties: {
- nope: true,
- target: true,
- entity: node
- },
- geometry: node.asGeoJSON()
- });
- }
- }); // Targets allow hover and vertex snapping
+ var wayIndex, crossingIndex, crossings;
- var targets = selection.selectAll('.vertex.target-allowed').filter(function (d) {
- return filter(d.properties.entity);
- }).data(data.targets, function key(d) {
- return d.id;
- }); // exit
+ for (wayIndex in ways) {
+ crossings = findCrossingsByWay(ways[wayIndex], graph, tree);
- targets.exit().remove(); // enter/update
+ for (crossingIndex in crossings) {
+ issues.push(createIssue(crossings[crossingIndex], graph));
+ }
+ }
- targets.enter().append('circle').attr('r', function (d) {
- return _radii[d.id] || radiuses.shadow[3];
- }).merge(targets).attr('class', function (d) {
- return 'node vertex target target-allowed ' + targetClass + d.id;
- }).attr('transform', getTransform); // NOPE
+ return issues;
+ };
- var nopes = selection.selectAll('.vertex.target-nope').filter(function (d) {
- return filter(d.properties.entity);
- }).data(data.nopes, function key(d) {
- return d.id;
- }); // exit
+ function createIssue(crossing, graph) {
+ // use the entities with the tags that define the feature type
+ crossing.wayInfos.sort(function (way1Info, way2Info) {
+ var type1 = way1Info.featureType;
+ var type2 = way2Info.featureType;
- nopes.exit().remove(); // enter/update
+ if (type1 === type2) {
+ return utilDisplayLabel(way1Info.way, graph) > utilDisplayLabel(way2Info.way, graph);
+ } else if (type1 === 'waterway') {
+ return true;
+ } else if (type2 === 'waterway') {
+ return false;
+ }
- nopes.enter().append('circle').attr('r', function (d) {
- return _radii[d.properties.entity.id] || radiuses.shadow[3];
- }).merge(nopes).attr('class', function (d) {
- return 'node vertex target target-nope ' + nopeClass + d.id;
- }).attr('transform', getTransform);
- } // Points can also render as vertices:
- // 1. in wireframe mode or
- // 2. at higher zooms if they have a direction
+ return type1 < type2;
+ });
+ var entities = crossing.wayInfos.map(function (wayInfo) {
+ return getFeatureWithFeatureTypeTagsForWay(wayInfo.way, graph);
+ });
+ var edges = [crossing.wayInfos[0].edge, crossing.wayInfos[1].edge];
+ var featureTypes = [crossing.wayInfos[0].featureType, crossing.wayInfos[1].featureType];
+ var connectionTags = tagsForConnectionNodeIfAllowed(entities[0], entities[1], graph);
+ var featureType1 = crossing.wayInfos[0].featureType;
+ var featureType2 = crossing.wayInfos[1].featureType;
+ var isCrossingIndoors = taggedAsIndoor(entities[0].tags) && taggedAsIndoor(entities[1].tags);
+ var isCrossingTunnels = allowsTunnel(featureType1) && hasTag(entities[0].tags, 'tunnel') && allowsTunnel(featureType2) && hasTag(entities[1].tags, 'tunnel');
+ var isCrossingBridges = allowsBridge(featureType1) && hasTag(entities[0].tags, 'bridge') && allowsBridge(featureType2) && hasTag(entities[1].tags, 'bridge');
+ var subtype = [featureType1, featureType2].sort().join('-');
+ var crossingTypeID = subtype;
+ if (isCrossingIndoors) {
+ crossingTypeID = 'indoor-indoor';
+ } else if (isCrossingTunnels) {
+ crossingTypeID = 'tunnel-tunnel';
+ } else if (isCrossingBridges) {
+ crossingTypeID = 'bridge-bridge';
+ }
- function renderAsVertex(entity, graph, wireframe, zoom) {
- var geometry = entity.geometry(graph);
- return geometry === 'vertex' || geometry === 'point' && (wireframe || zoom >= 18 && entity.directions(graph, projection).length);
- }
+ if (connectionTags && (isCrossingIndoors || isCrossingTunnels || isCrossingBridges)) {
+ crossingTypeID += '_connectable';
+ } // Differentiate based on the loc rounded to 4 digits, since two ways can cross multiple times.
- function isEditedNode(node, base, head) {
- var baseNode = base.entities[node.id];
- var headNode = head.entities[node.id];
- return !headNode || !baseNode || !fastDeepEqual(headNode.tags, baseNode.tags) || !fastDeepEqual(headNode.loc, baseNode.loc);
- }
- function getSiblingAndChildVertices(ids, graph, wireframe, zoom) {
- var results = {};
- var seenIds = {};
+ var uniqueID = crossing.crossPoint[0].toFixed(4) + ',' + crossing.crossPoint[1].toFixed(4);
+ return new validationIssue({
+ type: type,
+ subtype: subtype,
+ severity: 'warning',
+ message: function message(context) {
+ var graph = context.graph();
+ var entity1 = graph.hasEntity(this.entityIds[0]),
+ entity2 = graph.hasEntity(this.entityIds[1]);
+ return entity1 && entity2 ? _t.html('issues.crossing_ways.message', {
+ feature: utilDisplayLabel(entity1, graph),
+ feature2: utilDisplayLabel(entity2, graph)
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: entities.map(function (entity) {
+ return entity.id;
+ }),
+ data: {
+ edges: edges,
+ featureTypes: featureTypes,
+ connectionTags: connectionTags
+ },
+ hash: uniqueID,
+ loc: crossing.crossPoint,
+ dynamicFixes: function dynamicFixes(context) {
+ var mode = context.mode();
+ if (!mode || mode.id !== 'select' || mode.selectedIDs().length !== 1) return [];
+ var selectedIndex = this.entityIds[0] === mode.selectedIDs()[0] ? 0 : 1;
+ var selectedFeatureType = this.data.featureTypes[selectedIndex];
+ var otherFeatureType = this.data.featureTypes[selectedIndex === 0 ? 1 : 0];
+ var fixes = [];
- function addChildVertices(entity) {
- // avoid redundant work and infinite recursion of circular relations
- if (seenIds[entity.id]) return;
- seenIds[entity.id] = true;
- var geometry = entity.geometry(graph);
+ if (connectionTags) {
+ fixes.push(makeConnectWaysFix(this.data.connectionTags));
+ }
- if (!context.features().isHiddenFeature(entity, graph, geometry)) {
- var i;
+ if (isCrossingIndoors) {
+ fixes.push(new validationIssueFix({
+ icon: 'iD-icon-layers',
+ title: _t.html('issues.fix.use_different_levels.title')
+ }));
+ } else if (isCrossingTunnels || isCrossingBridges || featureType1 === 'building' || featureType2 === 'building') {
+ fixes.push(makeChangeLayerFix('higher'));
+ fixes.push(makeChangeLayerFix('lower')); // can only add bridge/tunnel if both features are lines
+ } else if (context.graph().geometry(this.entityIds[0]) === 'line' && context.graph().geometry(this.entityIds[1]) === 'line') {
+ // don't recommend adding bridges to waterways since they're uncommon
+ if (allowsBridge(selectedFeatureType) && selectedFeatureType !== 'waterway') {
+ fixes.push(makeAddBridgeOrTunnelFix('add_a_bridge', 'temaki-bridge', 'bridge'));
+ } // don't recommend adding tunnels under waterways since they're uncommon
- if (entity.type === 'way') {
- for (i = 0; i < entity.nodes.length; i++) {
- var child = graph.hasEntity(entity.nodes[i]);
- if (child) {
- addChildVertices(child);
- }
- }
- } else if (entity.type === 'relation') {
- for (i = 0; i < entity.members.length; i++) {
- var member = graph.hasEntity(entity.members[i].id);
+ var skipTunnelFix = otherFeatureType === 'waterway' && selectedFeatureType !== 'waterway';
- if (member) {
- addChildVertices(member);
- }
+ if (allowsTunnel(selectedFeatureType) && !skipTunnelFix) {
+ fixes.push(makeAddBridgeOrTunnelFix('add_a_tunnel', 'temaki-tunnel', 'tunnel'));
}
- } else if (renderAsVertex(entity, graph, wireframe, zoom)) {
- results[entity.id] = entity;
- }
- }
- }
+ } // repositioning the features is always an option
- ids.forEach(function (id) {
- var entity = graph.hasEntity(id);
- if (!entity) return;
- if (entity.type === 'node') {
- if (renderAsVertex(entity, graph, wireframe, zoom)) {
- results[entity.id] = entity;
- graph.parentWays(entity).forEach(function (entity) {
- addChildVertices(entity);
- });
- }
- } else {
- // way, relation
- addChildVertices(entity);
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-move',
+ title: _t.html('issues.fix.reposition_features.title')
+ }));
+ return fixes;
}
});
- return results;
- }
-
- function drawVertices(selection, graph, entities, filter, extent, fullRedraw) {
- var wireframe = context.surface().classed('fill-wireframe');
- var visualDiff = context.surface().classed('highlight-edited');
- var zoom = geoScaleToZoom(projection.scale());
- var mode = context.mode();
- var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
- var base = context.history().base();
- var drawLayer = selection.selectAll('.layer-osm.points .points-group.vertices');
- var touchLayer = selection.selectAll('.layer-touch.points');
-
- if (fullRedraw) {
- _currPersistent = {};
- _radii = {};
- } // Collect important vertices from the `entities` list..
- // (during a partial redraw, it will not contain everything)
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.crossing_ways.' + crossingTypeID + '.reference'));
+ }
+ }
- for (var i = 0; i < entities.length; i++) {
- var entity = entities[i];
- var geometry = entity.geometry(graph);
- var keep = false; // a point that looks like a vertex..
-
- if (geometry === 'point' && renderAsVertex(entity, graph, wireframe, zoom)) {
- _currPersistent[entity.id] = entity;
- keep = true; // a vertex of some importance..
- } else if (geometry === 'vertex' && (entity.hasInterestingTags() || entity.isEndpoint(graph) || entity.isConnected(graph) || visualDiff && isEditedNode(entity, base, graph))) {
- _currPersistent[entity.id] = entity;
- keep = true;
- } // whatever this is, it's not a persistent vertex..
-
-
- if (!keep && !fullRedraw) {
- delete _currPersistent[entity.id];
- }
- } // 3 sets of vertices to consider:
-
+ function makeAddBridgeOrTunnelFix(fixTitleID, iconName, bridgeOrTunnel) {
+ return new validationIssueFix({
+ icon: iconName,
+ title: _t.html('issues.fix.' + fixTitleID + '.title'),
+ onClick: function onClick(context) {
+ var mode = context.mode();
+ if (!mode || mode.id !== 'select') return;
+ var selectedIDs = mode.selectedIDs();
+ if (selectedIDs.length !== 1) return;
+ var selectedWayID = selectedIDs[0];
+ if (!context.hasEntity(selectedWayID)) return;
+ var resultWayIDs = [selectedWayID];
+ var edge, crossedEdge, crossedWayID;
- var sets = {
- persistent: _currPersistent,
- // persistent = important vertices (render always)
- selected: _currSelected,
- // selected + siblings of selected (render always)
- hovered: _currHover // hovered + siblings of hovered (render only in draw modes)
+ if (this.issue.entityIds[0] === selectedWayID) {
+ edge = this.issue.data.edges[0];
+ crossedEdge = this.issue.data.edges[1];
+ crossedWayID = this.issue.entityIds[1];
+ } else {
+ edge = this.issue.data.edges[1];
+ crossedEdge = this.issue.data.edges[0];
+ crossedWayID = this.issue.entityIds[0];
+ }
- };
- var all = Object.assign({}, isMoving ? _currHover : {}, _currSelected, _currPersistent); // Draw the vertices..
- // The filter function controls the scope of what objects d3 will touch (exit/enter/update)
- // Adjust the filter function to expand the scope beyond whatever entities were passed in.
+ var crossingLoc = this.issue.loc;
+ var projection = context.projection;
- var filterRendered = function filterRendered(d) {
- return d.id in _currPersistent || d.id in _currSelected || d.id in _currHover || filter(d);
- };
+ var action = function actionAddStructure(graph) {
+ var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])];
+ var crossedWay = graph.hasEntity(crossedWayID); // use the explicit width of the crossed feature as the structure length, if available
- drawLayer.call(draw, graph, currentVisible(all), sets, filterRendered); // Draw touch targets..
- // When drawing, render all targets (not just those affected by a partial redraw)
+ var structLengthMeters = crossedWay && crossedWay.tags.width && parseFloat(crossedWay.tags.width);
- var filterTouch = function filterTouch(d) {
- return isMoving ? true : filterRendered(d);
- };
+ if (!structLengthMeters) {
+ // if no explicit width is set, approximate the width based on the tags
+ structLengthMeters = crossedWay && crossedWay.impliedLineWidthMeters();
+ }
- touchLayer.call(drawTargets, graph, currentVisible(all), filterTouch);
+ if (structLengthMeters) {
+ if (getFeatureType(crossedWay, graph) === 'railway') {
+ // bridges over railways are generally much longer than the rail bed itself, compensate
+ structLengthMeters *= 2;
+ }
+ } else {
+ // should ideally never land here since all rail/water/road tags should have an implied width
+ structLengthMeters = 8;
+ }
- function currentVisible(which) {
- return Object.keys(which).map(graph.hasEntity, graph) // the current version of this entity
- .filter(function (entity) {
- return entity && entity.intersects(extent, graph);
- });
- }
- } // partial redraw - only update the selected items..
+ var a1 = geoAngle(edgeNodes[0], edgeNodes[1], projection) + Math.PI;
+ var a2 = geoAngle(graph.entity(crossedEdge[0]), graph.entity(crossedEdge[1]), projection) + Math.PI;
+ var crossingAngle = Math.max(a1, a2) - Math.min(a1, a2);
+ if (crossingAngle > Math.PI) crossingAngle -= Math.PI; // lengthen the structure to account for the angle of the crossing
+ structLengthMeters = structLengthMeters / 2 / Math.sin(crossingAngle) * 2; // add padding since the structure must extend past the edges of the crossed feature
- drawVertices.drawSelected = function (selection, graph, extent) {
- var wireframe = context.surface().classed('fill-wireframe');
- var zoom = geoScaleToZoom(projection.scale());
- _prevSelected = _currSelected || {};
+ structLengthMeters += 4; // clamp the length to a reasonable range
- if (context.map().isInWideSelection()) {
- _currSelected = {};
- context.selectedIDs().forEach(function (id) {
- var entity = graph.hasEntity(id);
- if (!entity) return;
+ structLengthMeters = Math.min(Math.max(structLengthMeters, 4), 50);
- if (entity.type === 'node') {
- if (renderAsVertex(entity, graph, wireframe, zoom)) {
- _currSelected[entity.id] = entity;
+ function geomToProj(geoPoint) {
+ return [geoLonToMeters(geoPoint[0], geoPoint[1]), geoLatToMeters(geoPoint[1])];
}
- }
- });
- } else {
- _currSelected = getSiblingAndChildVertices(context.selectedIDs(), graph, wireframe, zoom);
- } // note that drawVertices will add `_currSelected` automatically if needed..
+ function projToGeom(projPoint) {
+ var lat = geoMetersToLat(projPoint[1]);
+ return [geoMetersToLon(projPoint[0], lat), lat];
+ }
- var filter = function filter(d) {
- return d.id in _prevSelected;
- };
+ var projEdgeNode1 = geomToProj(edgeNodes[0].loc);
+ var projEdgeNode2 = geomToProj(edgeNodes[1].loc);
+ var projectedAngle = geoVecAngle(projEdgeNode1, projEdgeNode2);
+ var projectedCrossingLoc = geomToProj(crossingLoc);
+ var linearToSphericalMetersRatio = geoVecLength(projEdgeNode1, projEdgeNode2) / geoSphericalDistance(edgeNodes[0].loc, edgeNodes[1].loc);
- drawVertices(selection, graph, Object.values(_prevSelected), filter, extent, false);
- }; // partial redraw - only update the hovered items..
+ function locSphericalDistanceFromCrossingLoc(angle, distanceMeters) {
+ var lengthSphericalMeters = distanceMeters * linearToSphericalMetersRatio;
+ return projToGeom([projectedCrossingLoc[0] + Math.cos(angle) * lengthSphericalMeters, projectedCrossingLoc[1] + Math.sin(angle) * lengthSphericalMeters]);
+ }
+ var endpointLocGetter1 = function endpointLocGetter1(lengthMeters) {
+ return locSphericalDistanceFromCrossingLoc(projectedAngle, lengthMeters);
+ };
- drawVertices.drawHover = function (selection, graph, target, extent) {
- if (target === _currHoverTarget) return; // continue only if something changed
+ var endpointLocGetter2 = function endpointLocGetter2(lengthMeters) {
+ return locSphericalDistanceFromCrossingLoc(projectedAngle + Math.PI, lengthMeters);
+ }; // avoid creating very short edges from splitting too close to another node
- var wireframe = context.surface().classed('fill-wireframe');
- var zoom = geoScaleToZoom(projection.scale());
- _prevHover = _currHover || {};
- _currHoverTarget = target;
- var entity = target && target.properties && target.properties.entity;
- if (entity) {
- _currHover = getSiblingAndChildVertices([entity.id], graph, wireframe, zoom);
- } else {
- _currHover = {};
- } // note that drawVertices will add `_currHover` automatically if needed..
+ var minEdgeLengthMeters = 0.55; // decide where to bound the structure along the way, splitting as necessary
+ function determineEndpoint(edge, endNode, locGetter) {
+ var newNode;
+ var idealLengthMeters = structLengthMeters / 2; // distance between the crossing location and the end of the edge,
+ // the maximum length of this side of the structure
- var filter = function filter(d) {
- return d.id in _prevHover;
- };
+ var crossingToEdgeEndDistance = geoSphericalDistance(crossingLoc, endNode.loc);
- drawVertices(selection, graph, Object.values(_prevHover), filter, extent, false);
- };
+ if (crossingToEdgeEndDistance - idealLengthMeters > minEdgeLengthMeters) {
+ // the edge is long enough to insert a new node
+ // the loc that would result in the full expected length
+ var idealNodeLoc = locGetter(idealLengthMeters);
+ newNode = osmNode();
+ graph = actionAddMidpoint({
+ loc: idealNodeLoc,
+ edge: edge
+ }, newNode)(graph);
+ } else {
+ var edgeCount = 0;
+ endNode.parentIntersectionWays(graph).forEach(function (way) {
+ way.nodes.forEach(function (nodeID) {
+ if (nodeID === endNode.id) {
+ if (endNode.id === way.first() && endNode.id !== way.last() || endNode.id === way.last() && endNode.id !== way.first()) {
+ edgeCount += 1;
+ } else {
+ edgeCount += 2;
+ }
+ }
+ });
+ });
- return drawVertices;
- }
+ if (edgeCount >= 3) {
+ // the end node is a junction, try to leave a segment
+ // between it and the structure - #7202
+ var insetLength = crossingToEdgeEndDistance - minEdgeLengthMeters;
- function utilBindOnce(target, type, listener, capture) {
- var typeOnce = type + '.once';
+ if (insetLength > minEdgeLengthMeters) {
+ var insetNodeLoc = locGetter(insetLength);
+ newNode = osmNode();
+ graph = actionAddMidpoint({
+ loc: insetNodeLoc,
+ edge: edge
+ }, newNode)(graph);
+ }
+ }
+ } // if the edge is too short to subdivide as desired, then
+ // just bound the structure at the existing end node
- function one() {
- target.on(typeOnce, null);
- listener.apply(this, arguments);
- }
- target.on(typeOnce, one, capture);
- return this;
- }
+ if (!newNode) newNode = endNode;
+ var splitAction = actionSplit([newNode.id]).limitWays(resultWayIDs); // only split selected or created ways
+ // do the split
- function defaultFilter(d3_event) {
- return !d3_event.ctrlKey && !d3_event.button;
- }
+ graph = splitAction(graph);
- function defaultExtent() {
- var e = this;
+ if (splitAction.getCreatedWayIDs().length) {
+ resultWayIDs.push(splitAction.getCreatedWayIDs()[0]);
+ }
- if (e instanceof SVGElement) {
- e = e.ownerSVGElement || e;
+ return newNode;
+ }
- if (e.hasAttribute('viewBox')) {
- e = e.viewBox.baseVal;
- return [[e.x, e.y], [e.x + e.width, e.y + e.height]];
- }
+ var structEndNode1 = determineEndpoint(edge, edgeNodes[1], endpointLocGetter1);
+ var structEndNode2 = determineEndpoint([edgeNodes[0].id, structEndNode1.id], edgeNodes[0], endpointLocGetter2);
+ var structureWay = resultWayIDs.map(function (id) {
+ return graph.entity(id);
+ }).find(function (way) {
+ return way.nodes.indexOf(structEndNode1.id) !== -1 && way.nodes.indexOf(structEndNode2.id) !== -1;
+ });
+ var tags = Object.assign({}, structureWay.tags); // copy tags
- return [[0, 0], [e.width.baseVal.value, e.height.baseVal.value]];
- }
+ if (bridgeOrTunnel === 'bridge') {
+ tags.bridge = 'yes';
+ tags.layer = '1';
+ } else {
+ var tunnelValue = 'yes';
- return [[0, 0], [e.clientWidth, e.clientHeight]];
- }
+ if (getFeatureType(structureWay, graph) === 'waterway') {
+ // use `tunnel=culvert` for waterways by default
+ tunnelValue = 'culvert';
+ }
- function defaultWheelDelta(d3_event) {
- return -d3_event.deltaY * (d3_event.deltaMode === 1 ? 0.05 : d3_event.deltaMode ? 1 : 0.002);
- }
+ tags.tunnel = tunnelValue;
+ tags.layer = '-1';
+ } // apply the structure tags to the way
- function defaultConstrain(transform, extent, translateExtent) {
- var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
- dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
- dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
- dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
- return transform.translate(dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1), dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1));
- }
- function utilZoomPan() {
- var filter = defaultFilter,
- extent = defaultExtent,
- constrain = defaultConstrain,
- wheelDelta = defaultWheelDelta,
- scaleExtent = [0, Infinity],
- translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]],
- interpolate = interpolateZoom,
- dispatch = dispatch$8('start', 'zoom', 'end'),
- _wheelDelay = 150,
- _transform = identity$2,
- _activeGesture;
+ graph = actionChangeTags(structureWay.id, tags)(graph);
+ return graph;
+ };
- function zoom(selection) {
- selection.on('pointerdown.zoom', pointerdown).on('wheel.zoom', wheeled).style('touch-action', 'none').style('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
- select(window).on('pointermove.zoompan', pointermove).on('pointerup.zoompan pointercancel.zoompan', pointerup);
+ context.perform(action, _t('issues.fix.' + fixTitleID + '.annotation'));
+ context.enter(modeSelect(context, resultWayIDs));
+ }
+ });
}
- zoom.transform = function (collection, transform, point) {
- var selection = collection.selection ? collection.selection() : collection;
+ function makeConnectWaysFix(connectionTags) {
+ var fixTitleID = 'connect_features';
- if (collection !== selection) {
- schedule(collection, transform, point);
- } else {
- selection.interrupt().each(function () {
- gesture(this, arguments).start(null).zoom(null, null, typeof transform === 'function' ? transform.apply(this, arguments) : transform).end(null);
- });
+ if (connectionTags.ford) {
+ fixTitleID = 'connect_using_ford';
}
- };
-
- zoom.scaleBy = function (selection, k, p) {
- zoom.scaleTo(selection, function () {
- var k0 = _transform.k,
- k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
- return k0 * k1;
- }, p);
- };
- zoom.scaleTo = function (selection, k, p) {
- zoom.transform(selection, function () {
- var e = extent.apply(this, arguments),
- t0 = _transform,
- p0 = !p ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p,
- p1 = t0.invert(p0),
- k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
- return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
- }, p);
- };
+ return new validationIssueFix({
+ icon: 'iD-icon-crossing',
+ title: _t.html('issues.fix.' + fixTitleID + '.title'),
+ onClick: function onClick(context) {
+ var loc = this.issue.loc;
+ var connectionTags = this.issue.data.connectionTags;
+ var edges = this.issue.data.edges;
+ context.perform(function actionConnectCrossingWays(graph) {
+ // create the new node for the points
+ var node = osmNode({
+ loc: loc,
+ tags: connectionTags
+ });
+ graph = graph.replace(node);
+ var nodesToMerge = [node.id];
+ var mergeThresholdInMeters = 0.75;
+ edges.forEach(function (edge) {
+ var edgeNodes = [graph.entity(edge[0]), graph.entity(edge[1])];
+ var nearby = geoSphericalClosestNode(edgeNodes, loc); // if there is already a suitable node nearby, use that
+ // use the node if node has no interesting tags or if it is a crossing node #8326
- zoom.translateBy = function (selection, x, y) {
- zoom.transform(selection, function () {
- return constrain(_transform.translate(typeof x === 'function' ? x.apply(this, arguments) : x, typeof y === 'function' ? y.apply(this, arguments) : y), extent.apply(this, arguments), translateExtent);
- });
- };
+ if ((!nearby.node.hasInterestingTags() || nearby.node.isCrossing()) && nearby.distance < mergeThresholdInMeters) {
+ nodesToMerge.push(nearby.node.id); // else add the new node to the way
+ } else {
+ graph = actionAddMidpoint({
+ loc: loc,
+ edge: edge
+ }, node)(graph);
+ }
+ });
- zoom.translateTo = function (selection, x, y, p) {
- zoom.transform(selection, function () {
- var e = extent.apply(this, arguments),
- t = _transform,
- p0 = !p ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p;
- return constrain(identity$2.translate(p0[0], p0[1]).scale(t.k).translate(typeof x === 'function' ? -x.apply(this, arguments) : -x, typeof y === 'function' ? -y.apply(this, arguments) : -y), e, translateExtent);
- }, p);
- };
+ if (nodesToMerge.length > 1) {
+ // if we're using nearby nodes, merge them with the new node
+ graph = actionMergeNodes(nodesToMerge, loc)(graph);
+ }
- function scale(transform, k) {
- k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k));
- return k === transform.k ? transform : new Transform(k, transform.x, transform.y);
+ return graph;
+ }, _t('issues.fix.connect_crossing_features.annotation'));
+ }
+ });
}
- function translate(transform, p0, p1) {
- var x = p0[0] - p1[0] * transform.k,
- y = p0[1] - p1[1] * transform.k;
- return x === transform.x && y === transform.y ? transform : new Transform(transform.k, x, y);
- }
+ function makeChangeLayerFix(higherOrLower) {
+ return new validationIssueFix({
+ icon: 'iD-icon-' + (higherOrLower === 'higher' ? 'up' : 'down'),
+ title: _t.html('issues.fix.tag_this_as_' + higherOrLower + '.title'),
+ onClick: function onClick(context) {
+ var mode = context.mode();
+ if (!mode || mode.id !== 'select') return;
+ var selectedIDs = mode.selectedIDs();
+ if (selectedIDs.length !== 1) return;
+ var selectedID = selectedIDs[0];
+ if (!this.issue.entityIds.some(function (entityId) {
+ return entityId === selectedID;
+ })) return;
+ var entity = context.hasEntity(selectedID);
+ if (!entity) return;
+ var tags = Object.assign({}, entity.tags); // shallow copy
- function centroid(extent) {
- return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2];
- }
+ var layer = tags.layer && Number(tags.layer);
- function schedule(transition, transform, point) {
- transition.on('start.zoom', function () {
- gesture(this, arguments).start(null);
- }).on('interrupt.zoom end.zoom', function () {
- gesture(this, arguments).end(null);
- }).tween('zoom', function () {
- var that = this,
- args = arguments,
- g = gesture(that, args),
- e = extent.apply(that, args),
- p = !point ? centroid(e) : typeof point === 'function' ? point.apply(that, args) : point,
- w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]),
- a = _transform,
- b = typeof transform === 'function' ? transform.apply(that, args) : transform,
- i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
- return function (t) {
- if (t === 1) {
- // Avoid rounding error on end.
- t = b;
+ if (layer && !isNaN(layer)) {
+ if (higherOrLower === 'higher') {
+ layer += 1;
+ } else {
+ layer -= 1;
+ }
} else {
- var l = i(t);
- var k = w / l[2];
- t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k);
+ if (higherOrLower === 'higher') {
+ layer = 1;
+ } else {
+ layer = -1;
+ }
}
- g.zoom(null, null, t);
- };
+ tags.layer = layer.toString();
+ context.perform(actionChangeTags(entity.id, tags), _t('operations.change_tags.annotation'));
+ }
});
}
- function gesture(that, args, clean) {
- return !clean && _activeGesture || new Gesture(that, args);
- }
-
- function Gesture(that, args) {
- this.that = that;
- this.args = args;
- this.active = 0;
- this.extent = extent.apply(that, args);
- }
-
- Gesture.prototype = {
- start: function start(d3_event) {
- if (++this.active === 1) {
- _activeGesture = this;
- dispatch.call('start', this, d3_event);
- }
-
- return this;
- },
- zoom: function zoom(d3_event, key, transform) {
- if (this.mouse && key !== 'mouse') this.mouse[1] = transform.invert(this.mouse[0]);
- if (this.pointer0 && key !== 'touch') this.pointer0[1] = transform.invert(this.pointer0[0]);
- if (this.pointer1 && key !== 'touch') this.pointer1[1] = transform.invert(this.pointer1[0]);
- _transform = transform;
- dispatch.call('zoom', this, d3_event, key, transform);
- return this;
- },
- end: function end(d3_event) {
- if (--this.active === 0) {
- _activeGesture = null;
- dispatch.call('end', this, d3_event);
- }
+ validation.type = type;
+ return validation;
+ }
- return this;
- }
- };
+ function behaviorDrawWay(context, wayID, mode, startGraph) {
+ var keybinding = utilKeybinding('drawWay');
+ var dispatch = dispatch$8('rejectedSelfIntersection');
+ var behavior = behaviorDraw(context); // Must be set by `drawWay.nodeIndex` before each install of this behavior.
- function wheeled(d3_event) {
- if (!filter.apply(this, arguments)) return;
- var g = gesture(this, arguments),
- t = _transform,
- k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))),
- p = utilFastMouse(this)(d3_event); // If the mouse is in the same location as before, reuse it.
- // If there were recent wheel events, reset the wheel idle timeout.
+ var _nodeIndex;
- if (g.wheel) {
- if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) {
- g.mouse[1] = t.invert(g.mouse[0] = p);
- }
+ var _origWay;
- clearTimeout(g.wheel); // Otherwise, capture the mouse point and location at the start.
- } else {
- g.mouse = [p, t.invert(p)];
- interrupt(this);
- g.start(d3_event);
- }
+ var _wayGeometry;
- d3_event.preventDefault();
- d3_event.stopImmediatePropagation();
- g.wheel = setTimeout(wheelidled, _wheelDelay);
- g.zoom(d3_event, 'mouse', constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent));
+ var _headNodeID;
- function wheelidled() {
- g.wheel = null;
- g.end(d3_event);
- }
- }
+ var _annotation;
- var _downPointerIDs = new Set();
+ var _pointerHasMoved = false; // The osmNode to be placed.
+ // This is temporary and just follows the mouse cursor until an "add" event occurs.
- var _pointerLocGetter;
+ var _drawNode;
- function pointerdown(d3_event) {
- _downPointerIDs.add(d3_event.pointerId);
+ var _didResolveTempEdit = false;
- if (!filter.apply(this, arguments)) return;
- var g = gesture(this, arguments, _downPointerIDs.size === 1);
- var started;
- d3_event.stopImmediatePropagation();
- _pointerLocGetter = utilFastMouse(this);
+ function createDrawNode(loc) {
+ // don't make the draw node until we actually need it
+ _drawNode = osmNode({
+ loc: loc
+ });
+ context.pauseChangeDispatch();
+ context.replace(function actionAddDrawNode(graph) {
+ // add the draw node to the graph and insert it into the way
+ var way = graph.entity(wayID);
+ return graph.replace(_drawNode).replace(way.addNode(_drawNode.id, _nodeIndex));
+ }, _annotation);
+ context.resumeChangeDispatch();
+ setActiveElements();
+ }
- var loc = _pointerLocGetter(d3_event);
+ function removeDrawNode() {
+ context.pauseChangeDispatch();
+ context.replace(function actionDeleteDrawNode(graph) {
+ var way = graph.entity(wayID);
+ return graph.replace(way.removeNode(_drawNode.id)).remove(_drawNode);
+ }, _annotation);
+ _drawNode = undefined;
+ context.resumeChangeDispatch();
+ }
- var p = [loc, _transform.invert(loc), d3_event.pointerId];
+ function keydown(d3_event) {
+ if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ if (context.surface().classed('nope')) {
+ context.surface().classed('nope-suppressed', true);
+ }
- if (!g.pointer0) {
- g.pointer0 = p;
- started = true;
- } else if (!g.pointer1 && g.pointer0[2] !== p[2]) {
- g.pointer1 = p;
+ context.surface().classed('nope', false).classed('nope-disabled', true);
}
+ }
- if (started) {
- interrupt(this);
- g.start(d3_event);
+ function keyup(d3_event) {
+ if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
+ if (context.surface().classed('nope-suppressed')) {
+ context.surface().classed('nope', true);
+ }
+
+ context.surface().classed('nope-suppressed', false).classed('nope-disabled', false);
}
}
- function pointermove(d3_event) {
- if (!_downPointerIDs.has(d3_event.pointerId)) return;
- if (!_activeGesture || !_pointerLocGetter) return;
- var g = gesture(this, arguments);
- var isPointer0 = g.pointer0 && g.pointer0[2] === d3_event.pointerId;
- var isPointer1 = !isPointer0 && g.pointer1 && g.pointer1[2] === d3_event.pointerId;
+ 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()`
- if ((isPointer0 || isPointer1) && 'buttons' in d3_event && !d3_event.buttons) {
- // The pointer went up without ending the gesture somehow, e.g.
- // a down mouse was moved off the map and released. End it here.
- if (g.pointer0) _downPointerIDs["delete"](g.pointer0[2]);
- if (g.pointer1) _downPointerIDs["delete"](g.pointer1[2]);
- g.end(d3_event);
- return;
+
+ function move(d3_event, datum) {
+ var loc = context.map().mouseCoordinates();
+ if (!_drawNode) createDrawNode(loc);
+ context.surface().classed('nope-disabled', d3_event.altKey);
+ var targetLoc = datum && datum.properties && datum.properties.entity && allowsVertex(datum.properties.entity) && datum.properties.entity.loc;
+ var targetNodes = datum && datum.properties && datum.properties.nodes;
+
+ if (targetLoc) {
+ // snap to node/vertex - a point target with `.loc`
+ loc = targetLoc;
+ } else if (targetNodes) {
+ // snap to way - a line target with `.nodes`
+ var choice = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, _drawNode.id);
+
+ if (choice) {
+ loc = choice.loc;
+ }
}
- d3_event.preventDefault();
- d3_event.stopImmediatePropagation();
+ context.replace(actionMoveNode(_drawNode.id, loc), _annotation);
+ _drawNode = context.entity(_drawNode.id);
+ checkGeometry(true
+ /* includeDrawNode */
+ );
+ } // Check whether this edit causes the geometry to break.
+ // If so, class the surface with a nope cursor.
+ // `includeDrawNode` - Only check the relevant line segments if finishing drawing
- var loc = _pointerLocGetter(d3_event);
- var t, p, l;
- if (isPointer0) g.pointer0[0] = loc;else if (isPointer1) g.pointer1[0] = loc;
- t = _transform;
+ function checkGeometry(includeDrawNode) {
+ var nopeDisabled = context.surface().classed('nope-disabled');
+ var isInvalid = isInvalidGeometry(includeDrawNode);
- if (g.pointer1) {
- var p0 = g.pointer0[0],
- l0 = g.pointer0[1],
- p1 = g.pointer1[0],
- l1 = g.pointer1[1],
- dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp,
- dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl;
- t = scale(t, Math.sqrt(dp / dl));
- p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
- l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
- } else if (g.pointer0) {
- p = g.pointer0[0];
- l = g.pointer0[1];
+ if (nopeDisabled) {
+ context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
} else {
- return;
+ context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
}
-
- g.zoom(d3_event, 'touch', constrain(translate(t, p, l), g.extent, translateExtent));
}
- function pointerup(d3_event) {
- if (!_downPointerIDs.has(d3_event.pointerId)) return;
-
- _downPointerIDs["delete"](d3_event.pointerId);
-
- if (!_activeGesture) return;
- var g = gesture(this, arguments);
- d3_event.stopImmediatePropagation();
- if (g.pointer0 && g.pointer0[2] === d3_event.pointerId) delete g.pointer0;else if (g.pointer1 && g.pointer1[2] === d3_event.pointerId) delete g.pointer1;
+ function isInvalidGeometry(includeDrawNode) {
+ var testNode = _drawNode; // we only need to test the single way we're drawing
- if (g.pointer1 && !g.pointer0) {
- g.pointer0 = g.pointer1;
- delete g.pointer1;
- }
+ var parentWay = context.graph().entity(wayID);
+ var nodes = context.graph().childNodes(parentWay).slice(); // shallow copy
- if (g.pointer0) {
- g.pointer0[1] = _transform.invert(g.pointer0[0]);
+ if (includeDrawNode) {
+ if (parentWay.isClosed()) {
+ // don't test the last segment for closed ways - #4655
+ // (still test the first segment)
+ nodes.pop();
+ }
} else {
- g.end(d3_event);
+ // discount the draw node
+ if (parentWay.isClosed()) {
+ if (nodes.length < 3) return false;
+ if (_drawNode) nodes.splice(-2, 1);
+ testNode = nodes[nodes.length - 2];
+ } else {
+ // there's nothing we need to test if we ignore the draw node on open ways
+ return false;
+ }
}
+
+ return testNode && geoHasSelfIntersections(nodes, testNode.id);
}
- zoom.wheelDelta = function (_) {
- return arguments.length ? (wheelDelta = utilFunctor(+_), zoom) : wheelDelta;
- };
+ function undone() {
+ // undoing removed the temp edit
+ _didResolveTempEdit = true;
+ context.pauseChangeDispatch();
+ var nextMode;
- zoom.filter = function (_) {
- return arguments.length ? (filter = utilFunctor(!!_), zoom) : filter;
- };
+ if (context.graph() === startGraph) {
+ // We've undone back to the initial state before we started drawing.
+ // Just exit the draw mode without undoing whatever we did before
+ // we entered the draw mode.
+ nextMode = modeSelect(context, [wayID]);
+ } else {
+ // The `undo` only removed the temporary edit, so here we have to
+ // manually undo to actually remove the last node we added. We can't
+ // use the `undo` function since the initial "add" graph doesn't have
+ // an annotation and so cannot be undone to.
+ context.pop(1); // continue drawing
- zoom.extent = function (_) {
- return arguments.length ? (extent = utilFunctor([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
- };
+ nextMode = mode;
+ } // clear the redo stack by adding and removing a blank edit
- zoom.scaleExtent = function (_) {
- return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
- };
- zoom.translateExtent = function (_) {
- return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]];
- };
+ context.perform(actionNoop());
+ context.pop(1);
+ context.resumeChangeDispatch();
+ context.enter(nextMode);
+ }
- zoom.constrain = function (_) {
- return arguments.length ? (constrain = _, zoom) : constrain;
- };
+ function setActiveElements() {
+ if (!_drawNode) return;
+ context.surface().selectAll('.' + _drawNode.id).classed('active', true);
+ }
- zoom.interpolate = function (_) {
- return arguments.length ? (interpolate = _, zoom) : interpolate;
- };
+ function resetToStartGraph() {
+ while (context.graph() !== startGraph) {
+ context.pop();
+ }
+ }
- zoom._transform = function (_) {
- return arguments.length ? (_transform = _, zoom) : _transform;
- };
+ var drawWay = function drawWay(surface) {
+ _drawNode = undefined;
+ _didResolveTempEdit = false;
+ _origWay = context.entity(wayID);
- return utilRebind(zoom, dispatch, 'on');
- }
+ if (typeof _nodeIndex === 'number') {
+ _headNodeID = _origWay.nodes[_nodeIndex];
+ } else if (_origWay.isClosed()) {
+ _headNodeID = _origWay.nodes[_origWay.nodes.length - 2];
+ } else {
+ _headNodeID = _origWay.nodes[_origWay.nodes.length - 1];
+ }
- // if pointer events are supported. Falls back to default `dblclick` event.
+ _wayGeometry = _origWay.geometry(context.graph());
+ _annotation = _t((_origWay.nodes.length === (_origWay.isClosed() ? 2 : 1) ? 'operations.start.annotation.' : 'operations.continue.annotation.') + _wayGeometry);
+ _pointerHasMoved = false; // Push an annotated state for undo to return back to.
+ // We must make sure to replace or remove it later.
- function utilDoubleUp() {
- var dispatch = dispatch$8('doubleUp');
- var _maxTimespan = 500; // milliseconds
+ context.pauseChangeDispatch();
+ context.perform(actionNoop(), _annotation);
+ context.resumeChangeDispatch();
+ behavior.hover().initialNodeID(_headNodeID);
+ behavior.on('move', function () {
+ _pointerHasMoved = true;
+ move.apply(this, arguments);
+ }).on('down', function () {
+ move.apply(this, arguments);
+ }).on('downcancel', function () {
+ if (_drawNode) removeDrawNode();
+ }).on('click', drawWay.add).on('clickWay', drawWay.addWay).on('clickNode', drawWay.addNode).on('undo', context.undo).on('cancel', drawWay.cancel).on('finish', drawWay.finish);
+ select(window).on('keydown.drawWay', keydown).on('keyup.drawWay', keyup);
+ context.map().dblclickZoomEnable(false).on('drawn.draw', setActiveElements);
+ setActiveElements();
+ surface.call(behavior);
+ context.history().on('undone.draw', undone);
+ };
- var _maxDistance = 20; // web pixels; be somewhat generous to account for touch devices
+ drawWay.off = function (surface) {
+ if (!_didResolveTempEdit) {
+ // Drawing was interrupted unexpectedly.
+ // This can happen if the user changes modes,
+ // clicks geolocate button, a hashchange event occurs, etc.
+ context.pauseChangeDispatch();
+ resetToStartGraph();
+ context.resumeChangeDispatch();
+ }
- var _pointer; // object representing the pointer that could trigger double up
+ _drawNode = undefined;
+ _nodeIndex = undefined;
+ context.map().on('drawn.draw', null);
+ surface.call(behavior.off).selectAll('.active').classed('active', false);
+ surface.classed('nope', false).classed('nope-suppressed', false).classed('nope-disabled', false);
+ select(window).on('keydown.drawWay', null).on('keyup.drawWay', null);
+ context.history().on('undone.draw', null);
+ };
+ function attemptAdd(d, loc, doAdd) {
+ if (_drawNode) {
+ // move the node to the final loc in case move wasn't called
+ // consistently (e.g. on touch devices)
+ context.replace(actionMoveNode(_drawNode.id, loc), _annotation);
+ _drawNode = context.entity(_drawNode.id);
+ } else {
+ createDrawNode(loc);
+ }
- function pointerIsValidFor(loc) {
- // second pointerup must occur within a small timeframe after the first pointerdown
- return new Date().getTime() - _pointer.startTime <= _maxTimespan && // all pointer events must occur within a small distance of the first pointerdown
- geoVecLength(_pointer.startLoc, loc) <= _maxDistance;
- }
+ checkGeometry(true
+ /* includeDrawNode */
+ );
- function pointerdown(d3_event) {
- // ignore right-click
- if (d3_event.ctrlKey || d3_event.button === 2) return;
- var loc = [d3_event.clientX, d3_event.clientY]; // Don't rely on pointerId here since it can change between pointerdown
- // events on touch devices
+ if (d && d.properties && d.properties.nope || context.surface().classed('nope')) {
+ if (!_pointerHasMoved) {
+ // prevent the temporary draw node from appearing on touch devices
+ removeDrawNode();
+ }
- if (_pointer && !pointerIsValidFor(loc)) {
- // if this pointer is no longer valid, clear it so another can be started
- _pointer = undefined;
+ dispatch.call('rejectedSelfIntersection', this);
+ return; // can't click here
}
- if (!_pointer) {
- _pointer = {
- startLoc: loc,
- startTime: new Date().getTime(),
- upCount: 0,
- pointerId: d3_event.pointerId
- };
- } else {
- // double down
- _pointer.pointerId = d3_event.pointerId;
- }
- }
+ context.pauseChangeDispatch();
+ doAdd(); // we just replaced the temporary edit with the real one
- function pointerup(d3_event) {
- // ignore right-click
- if (d3_event.ctrlKey || d3_event.button === 2) return;
- if (!_pointer || _pointer.pointerId !== d3_event.pointerId) return;
- _pointer.upCount += 1;
+ _didResolveTempEdit = true;
+ context.resumeChangeDispatch();
+ context.enter(mode);
+ } // Accept the current position of the drawing node
- if (_pointer.upCount === 2) {
- // double up!
- var loc = [d3_event.clientX, d3_event.clientY];
- if (pointerIsValidFor(loc)) {
- var locInThis = utilFastMouse(this)(d3_event);
- dispatch.call('doubleUp', this, d3_event, locInThis);
- } // clear the pointer info in any case
+ drawWay.add = function (loc, d) {
+ attemptAdd(d, loc, function () {// don't need to do anything extra
+ });
+ }; // Connect the way to an existing way
- _pointer = undefined;
- }
- }
+ drawWay.addWay = function (loc, edge, d) {
+ attemptAdd(d, loc, function () {
+ context.replace(actionAddMidpoint({
+ loc: loc,
+ edge: edge
+ }, _drawNode), _annotation);
+ });
+ }; // Connect the way to an existing node
- function doubleUp(selection) {
- if ('PointerEvent' in window) {
- // dblclick isn't well supported on touch devices so manually use
- // pointer events if they're available
- selection.on('pointerdown.doubleUp', pointerdown).on('pointerup.doubleUp', pointerup);
- } else {
- // fallback to dblclick
- selection.on('dblclick.doubleUp', function (d3_event) {
- dispatch.call('doubleUp', this, d3_event, utilFastMouse(this)(d3_event));
- });
+
+ drawWay.addNode = function (node, d) {
+ // finish drawing if the mapper targets the prior node
+ if (node.id === _headNodeID || // or the first node when drawing an area
+ _origWay.isClosed() && node.id === _origWay.first()) {
+ drawWay.finish();
+ return;
}
- }
- doubleUp.off = function (selection) {
- selection.on('pointerdown.doubleUp', null).on('pointerup.doubleUp', null).on('dblclick.doubleUp', null);
+ attemptAdd(d, node.loc, function () {
+ context.replace(function actionReplaceDrawNode(graph) {
+ // remove the temporary draw node and insert the existing node
+ // at the same index
+ graph = graph.replace(graph.entity(wayID).removeNode(_drawNode.id)).remove(_drawNode);
+ return graph.replace(graph.entity(wayID).addNode(node.id, _nodeIndex));
+ }, _annotation);
+ });
};
+ /**
+ * @param {(typeof osmWay)[]} ways
+ * @returns {"line" | "area" | "generic"}
+ */
- return utilRebind(doubleUp, dispatch, 'on');
- }
- var TILESIZE = 256;
- var minZoom = 2;
- var maxZoom = 24;
- var kMin = geoZoomToScale(minZoom, TILESIZE);
- var kMax = geoZoomToScale(maxZoom, TILESIZE);
+ function getFeatureType(ways) {
+ if (ways.every(function (way) {
+ return way.isClosed();
+ })) return 'area';
+ if (ways.every(function (way) {
+ return !way.isClosed();
+ })) return 'line';
+ return 'generic';
+ }
+ /** see PR #8671 */
- function clamp$1(num, min, max) {
- return Math.max(min, Math.min(num, max));
- }
- function rendererMap(context) {
- var dispatch = dispatch$8('move', 'drawn', 'crossEditableZoom', 'hitMinZoom', 'changeHighlighting', 'changeAreaFill');
- var projection = context.projection;
- var curtainProjection = context.curtainProjection;
- var drawLayers;
- var drawPoints;
- var drawVertices;
- var drawLines;
- var drawAreas;
- var drawMidpoints;
- var drawLabels;
+ function followMode() {
+ if (_didResolveTempEdit) return;
- var _selection = select(null);
+ try {
+ // get the last 2 added nodes.
+ // check if they are both part of only oneway (the same one)
+ // check if the ways that they're part of are the same way
+ // find index of the last two nodes, to determine the direction to travel around the existing way
+ // add the next node to the way we are drawing
+ // if we're drawing an area, the first node = last node.
+ var isDrawingArea = _origWay.nodes[0] === _origWay.nodes.slice(-1)[0];
- var supersurface = select(null);
- var wrapper = select(null);
- var surface = select(null);
- var _dimensions = [1, 1];
- var _dblClickZoomEnabled = true;
- var _redrawEnabled = true;
+ var _origWay$nodes$slice = _origWay.nodes.slice(isDrawingArea ? -3 : -2),
+ _origWay$nodes$slice2 = _slicedToArray(_origWay$nodes$slice, 2),
+ secondLastNodeId = _origWay$nodes$slice2[0],
+ lastNodeId = _origWay$nodes$slice2[1]; // Unlike startGraph, the full history graph may contain unsaved vertices to follow.
+ // https://github.com/openstreetmap/iD/issues/8749
- var _gestureTransformStart;
- var _transformStart = projection.transform();
+ var historyGraph = context.history().graph();
- var _transformLast;
+ if (!lastNodeId || !secondLastNodeId || !historyGraph.hasEntity(lastNodeId) || !historyGraph.hasEntity(secondLastNodeId)) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html('operations.follow.error.needs_more_initial_nodes'))();
+ return;
+ } // If the way has looped over itself, follow some other way.
- var _isTransformed = false;
- var _minzoom = 0;
- var _getMouseCoords;
+ var lastNodesParents = historyGraph.parentWays(historyGraph.entity(lastNodeId)).filter(function (w) {
+ return w.id !== wayID;
+ });
+ var secondLastNodesParents = historyGraph.parentWays(historyGraph.entity(secondLastNodeId)).filter(function (w) {
+ return w.id !== wayID;
+ });
+ var featureType = getFeatureType(lastNodesParents);
- var _lastPointerEvent;
+ if (lastNodesParents.length !== 1 || secondLastNodesParents.length === 0) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html("operations.follow.error.intersection_of_multiple_ways.".concat(featureType)))();
+ return;
+ } // Check if the last node's parent is also the parent of the second last node.
+ // The last node must only have one parent, but the second last node can have
+ // multiple parents.
- var _lastWithinEditableZoom; // whether a pointerdown event started the zoom
+ if (!secondLastNodesParents.some(function (n) {
+ return n.id === lastNodesParents[0].id;
+ })) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html("operations.follow.error.intersection_of_different_ways.".concat(featureType)))();
+ return;
+ }
- var _pointerDown = false; // use pointer events on supported platforms; fallback to mouse events
+ var way = lastNodesParents[0];
+ var indexOfLast = way.nodes.indexOf(lastNodeId);
+ var indexOfSecondLast = way.nodes.indexOf(secondLastNodeId); // for a closed way, the first/last node is the same so it appears twice in the array,
+ // but indexOf always finds the first occurrence. This is only an issue when following a way
+ // in descending order
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // use pointer event interaction if supported; fallback to touch/mouse events in d3-zoom
+ var isDescendingPastZero = indexOfLast === way.nodes.length - 2 && indexOfSecondLast === 0;
+ var nextNodeIndex = indexOfLast + (indexOfLast > indexOfSecondLast && !isDescendingPastZero ? 1 : -1); // if we're following a closed way and we pass the first/last node, the next index will be -1
+ if (nextNodeIndex === -1) nextNodeIndex = indexOfSecondLast === 1 ? way.nodes.length - 2 : 1;
+ var nextNode = historyGraph.entity(way.nodes[nextNodeIndex]);
+ drawWay.addNode(nextNode, {
+ geometry: {
+ type: 'Point',
+ coordinates: nextNode.loc
+ },
+ id: nextNode.id,
+ properties: {
+ target: true,
+ entity: nextNode
+ }
+ });
+ } catch (ex) {
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t.html('operations.follow.error.unknown'))();
+ }
+ }
- var _zoomerPannerFunction = 'PointerEvent' in window ? utilZoomPan : d3_zoom;
+ keybinding.on(_t('operations.follow.key'), followMode);
+ select(document).call(keybinding); // Finish the draw operation, removing the temporary edit.
+ // If the way has enough nodes to be valid, it's selected.
+ // Otherwise, delete everything and return to browse mode.
- var _zoomerPanner = _zoomerPannerFunction().scaleExtent([kMin, kMax]).interpolate(interpolate$1).filter(zoomEventFilter).on('zoom.map', zoomPan).on('start.map', function (d3_event) {
- _pointerDown = d3_event && (d3_event.type === 'pointerdown' || d3_event.sourceEvent && d3_event.sourceEvent.type === 'pointerdown');
- }).on('end.map', function () {
- _pointerDown = false;
- });
+ drawWay.finish = function () {
+ checkGeometry(false
+ /* includeDrawNode */
+ );
- var _doubleUpHandler = utilDoubleUp();
+ if (context.surface().classed('nope')) {
+ dispatch.call('rejectedSelfIntersection', this);
+ return; // can't click here
+ }
- var scheduleRedraw = throttle(redraw, 750); // var isRedrawScheduled = false;
- // var pendingRedrawCall;
- // function scheduleRedraw() {
- // // Only schedule the redraw if one has not already been set.
- // if (isRedrawScheduled) return;
- // isRedrawScheduled = true;
- // var that = this;
- // var args = arguments;
- // pendingRedrawCall = window.requestIdleCallback(function () {
- // // Reset the boolean so future redraws can be set.
- // isRedrawScheduled = false;
- // redraw.apply(that, args);
- // }, { timeout: 1400 });
- // }
+ context.pauseChangeDispatch(); // remove the temporary edit
+ context.pop(1);
+ _didResolveTempEdit = true;
+ context.resumeChangeDispatch();
+ var way = context.hasEntity(wayID);
- function cancelPendingRedraw() {
- scheduleRedraw.cancel(); // isRedrawScheduled = false;
- // window.cancelIdleCallback(pendingRedrawCall);
- }
+ if (!way || way.isDegenerate()) {
+ drawWay.cancel();
+ return;
+ }
- function map(selection) {
- _selection = selection;
- context.on('change.map', immediateRedraw);
- var osm = context.connection();
+ window.setTimeout(function () {
+ context.map().dblclickZoomEnable(true);
+ }, 1000);
+ var isNewFeature = !mode.isContinuing;
+ context.enter(modeSelect(context, [wayID]).newFeature(isNewFeature));
+ }; // Cancel the draw operation, delete everything, and return to browse mode.
- if (osm) {
- osm.on('change.map', immediateRedraw);
- }
- function didUndoOrRedo(targetTransform) {
- var mode = context.mode().id;
- if (mode !== 'browse' && mode !== 'select') return;
+ drawWay.cancel = function () {
+ context.pauseChangeDispatch();
+ resetToStartGraph();
+ context.resumeChangeDispatch();
+ window.setTimeout(function () {
+ context.map().dblclickZoomEnable(true);
+ }, 1000);
+ context.surface().classed('nope', false).classed('nope-disabled', false).classed('nope-suppressed', false);
+ context.enter(modeBrowse(context));
+ };
- if (targetTransform) {
- map.transformEase(targetTransform);
- }
- }
+ drawWay.nodeIndex = function (val) {
+ if (!arguments.length) return _nodeIndex;
+ _nodeIndex = val;
+ return drawWay;
+ };
- context.history().on('merge.map', function () {
- scheduleRedraw();
- }).on('change.map', immediateRedraw).on('undone.map', function (stack, fromStack) {
- didUndoOrRedo(fromStack.transform);
- }).on('redone.map', function (stack) {
- didUndoOrRedo(stack.transform);
- });
- context.background().on('change.map', immediateRedraw);
- context.features().on('redraw.map', immediateRedraw);
- drawLayers.on('change.map', function () {
- context.background().updateImagery();
- immediateRedraw();
- });
- selection.on('wheel.map mousewheel.map', function (d3_event) {
- // disable swipe-to-navigate browser pages on trackpad/magic mouse – #5552
- d3_event.preventDefault();
- }).call(_zoomerPanner).call(_zoomerPanner.transform, projection.transform()).on('dblclick.zoom', null); // override d3-zoom dblclick handling
+ drawWay.activeID = function () {
+ if (!arguments.length) return _drawNode && _drawNode.id; // no assign
- map.supersurface = supersurface = selection.append('div').attr('class', 'supersurface').call(utilSetTransform, 0, 0); // Need a wrapper div because Opera can't cope with an absolutely positioned
- // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
+ return drawWay;
+ };
- wrapper = supersurface.append('div').attr('class', 'layer layer-data');
- map.surface = surface = wrapper.call(drawLayers).selectAll('.surface');
- surface.call(drawLabels.observe).call(_doubleUpHandler).on(_pointerPrefix + 'down.zoom', function (d3_event) {
- _lastPointerEvent = d3_event;
+ return utilRebind(drawWay, dispatch, 'on');
+ }
- if (d3_event.button === 2) {
- d3_event.stopPropagation();
- }
- }, true).on(_pointerPrefix + 'up.zoom', function (d3_event) {
- _lastPointerEvent = d3_event;
+ function modeDrawLine(context, wayID, startGraph, button, affix, continuing) {
+ var mode = {
+ button: button,
+ id: 'draw-line'
+ };
+ var behavior = behaviorDrawWay(context, wayID, mode, startGraph).on('rejectedSelfIntersection.modeDrawLine', function () {
+ context.ui().flash.iconName('#iD-icon-no').label(_t.html('self_intersection.error.lines'))();
+ });
+ mode.wayID = wayID;
+ mode.isContinuing = continuing;
- if (resetTransform()) {
- immediateRedraw();
- }
- }).on(_pointerPrefix + 'move.map', function (d3_event) {
- _lastPointerEvent = d3_event;
- }).on(_pointerPrefix + 'over.vertices', function (d3_event) {
- if (map.editableDataEnabled() && !_isTransformed) {
- var hover = d3_event.target.__data__;
- surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
- dispatch.call('drawn', this, {
- full: false
- });
- }
- }).on(_pointerPrefix + 'out.vertices', function (d3_event) {
- if (map.editableDataEnabled() && !_isTransformed) {
- var hover = d3_event.relatedTarget && d3_event.relatedTarget.__data__;
- surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
- dispatch.call('drawn', this, {
- full: false
- });
- }
- });
- var detected = utilDetect(); // only WebKit supports gesture events
+ mode.enter = function () {
+ behavior.nodeIndex(affix === 'prefix' ? 0 : undefined);
+ context.install(behavior);
+ };
- if ('GestureEvent' in window && // Listening for gesture events on iOS 13.4+ breaks double-tapping,
- // but we only need to do this on desktop Safari anyway. – #7694
- !detected.isMobileWebKit) {
- // Desktop Safari sends gesture events for multitouch trackpad pinches.
- // We can listen for these and translate them into map zooms.
- surface.on('gesturestart.surface', function (d3_event) {
- d3_event.preventDefault();
- _gestureTransformStart = projection.transform();
- }).on('gesturechange.surface', gestureChange);
- } // must call after surface init
+ mode.exit = function () {
+ context.uninstall(behavior);
+ };
+ mode.selectedIDs = function () {
+ return [wayID];
+ };
- updateAreaFill();
+ mode.activeID = function () {
+ return behavior && behavior.activeID() || [];
+ };
- _doubleUpHandler.on('doubleUp.map', function (d3_event, p0) {
- if (!_dblClickZoomEnabled) return; // don't zoom if targeting something other than the map itself
+ return mode;
+ }
- if (_typeof(d3_event.target.__data__) === 'object' && // or area fills
- !select(d3_event.target).classed('fill')) return;
- var zoomOut = d3_event.shiftKey;
- var t = projection.transform();
- var p1 = t.invert(p0);
- t = t.scale(zoomOut ? 0.5 : 2);
- t.x = p0[0] - p1[0] * t.k;
- t.y = p0[1] - p1[1] * t.k;
- map.transformEase(t);
- });
+ function validationDisconnectedWay() {
+ var type = 'disconnected_way';
- context.on('enter.map', function () {
- if (!map.editableDataEnabled(true
- /* skip zoom check */
- )) return;
- if (_isTransformed) return; // redraw immediately any objects affected by a change in selectedIDs.
+ function isTaggedAsHighway(entity) {
+ return osmRoutableHighwayTagValues[entity.tags.highway];
+ }
- var graph = context.graph();
- var selectedAndParents = {};
- context.selectedIDs().forEach(function (id) {
- var entity = graph.hasEntity(id);
+ var validation = function checkDisconnectedWay(entity, graph) {
+ var routingIslandWays = routingIslandForEntity(entity);
+ if (!routingIslandWays) return [];
+ return [new validationIssue({
+ type: type,
+ subtype: 'highway',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = this.entityIds.length && context.hasEntity(this.entityIds[0]);
+ var label = entity && utilDisplayLabel(entity, context.graph());
+ return _t.html('issues.disconnected_way.routable.message', {
+ count: this.entityIds.length,
+ highway: label
+ });
+ },
+ reference: showReference,
+ entityIds: Array.from(routingIslandWays).map(function (way) {
+ return way.id;
+ }),
+ dynamicFixes: makeFixes
+ })];
- if (entity) {
- selectedAndParents[entity.id] = entity;
+ function makeFixes(context) {
+ var fixes = [];
+ var singleEntity = this.entityIds.length === 1 && context.hasEntity(this.entityIds[0]);
- if (entity.type === 'node') {
- graph.parentWays(entity).forEach(function (parent) {
- selectedAndParents[parent.id] = parent;
- });
- }
+ if (singleEntity) {
+ if (singleEntity.type === 'way' && !singleEntity.isClosed()) {
+ var textDirection = _mainLocalizer.textDirection();
+ var startFix = makeContinueDrawingFixIfAllowed(textDirection, singleEntity.first(), 'start');
+ if (startFix) fixes.push(startFix);
+ var endFix = makeContinueDrawingFixIfAllowed(textDirection, singleEntity.last(), 'end');
+ if (endFix) fixes.push(endFix);
}
- });
- var data = Object.values(selectedAndParents);
-
- var filter = function filter(d) {
- return d.id in selectedAndParents;
- };
- data = context.features().filter(data, graph);
- surface.call(drawVertices.drawSelected, graph, map.extent()).call(drawLines, graph, data, filter).call(drawAreas, graph, data, filter).call(drawMidpoints, graph, data, filter, map.trimmedExtent());
- dispatch.call('drawn', this, {
- full: false
- }); // redraw everything else later
+ if (!fixes.length) {
+ fixes.push(new validationIssueFix({
+ title: _t.html('issues.fix.connect_feature.title')
+ }));
+ }
- scheduleRedraw();
- });
- map.dimensions(utilGetDimensions(selection));
- }
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.delete_feature.title'),
+ entityIds: [singleEntity.id],
+ onClick: function onClick(context) {
+ var id = this.issue.entityIds[0];
+ var operation = operationDelete(context, [id]);
- function zoomEventFilter(d3_event) {
- // Fix for #2151, (see also d3/d3-zoom#60, d3/d3-brush#18)
- // Intercept `mousedown` and check if there is an orphaned zoom gesture.
- // This can happen if a previous `mousedown` occurred without a `mouseup`.
- // If we detect this, dispatch `mouseup` to complete the orphaned gesture,
- // so that d3-zoom won't stop propagation of new `mousedown` events.
- if (d3_event.type === 'mousedown') {
- var hasOrphan = false;
- var listeners = window.__on;
+ if (!operation.disabled()) {
+ operation();
+ }
+ }
+ }));
+ } else {
+ fixes.push(new validationIssueFix({
+ title: _t.html('issues.fix.connect_features.title')
+ }));
+ }
- for (var i = 0; i < listeners.length; i++) {
- var listener = listeners[i];
+ return fixes;
+ }
- if (listener.name === 'zoom' && listener.type === 'mouseup') {
- hasOrphan = true;
- break;
- }
- }
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.disconnected_way.routable.reference'));
+ }
- if (hasOrphan) {
- var event = window.CustomEvent;
+ function routingIslandForEntity(entity) {
+ var routingIsland = new Set(); // the interconnected routable features
- if (event) {
- event = new event('mouseup');
- } else {
- event = window.document.createEvent('Event');
- event.initEvent('mouseup', false, false);
- } // Event needs to be dispatched with an event.view property.
+ var waysToCheck = []; // the queue of remaining routable ways to traverse
+ function queueParentWays(node) {
+ graph.parentWays(node).forEach(function (parentWay) {
+ if (!routingIsland.has(parentWay) && // only check each feature once
+ isRoutableWay(parentWay, false)) {
+ // only check routable features
+ routingIsland.add(parentWay);
+ waysToCheck.push(parentWay);
+ }
+ });
+ }
- event.view = window;
- window.dispatchEvent(event);
+ if (entity.type === 'way' && isRoutableWay(entity, true)) {
+ routingIsland.add(entity);
+ waysToCheck.push(entity);
+ } else if (entity.type === 'node' && isRoutableNode(entity)) {
+ routingIsland.add(entity);
+ queueParentWays(entity);
+ } else {
+ // this feature isn't routable, cannot be a routing island
+ return null;
}
- }
- return d3_event.button !== 2; // ignore right clicks
- }
+ while (waysToCheck.length) {
+ var wayToCheck = waysToCheck.pop();
+ var childNodes = graph.childNodes(wayToCheck);
- function pxCenter() {
- return [_dimensions[0] / 2, _dimensions[1] / 2];
- }
+ for (var i in childNodes) {
+ var vertex = childNodes[i];
- function drawEditable(difference, extent) {
- var mode = context.mode();
- var graph = context.graph();
- var features = context.features();
- var all = context.history().intersects(map.extent());
- var fullRedraw = false;
- var data;
- var set;
- var filter;
- var applyFeatureLayerFilters = true;
+ if (isConnectedVertex(vertex)) {
+ // found a link to the wider network, not a routing island
+ return null;
+ }
- if (map.isInWideSelection()) {
- data = [];
- utilEntityAndDeepMemberIDs(mode.selectedIDs(), context.graph()).forEach(function (id) {
- var entity = context.hasEntity(id);
- if (entity) data.push(entity);
- });
- fullRedraw = true;
- filter = utilFunctor(true); // selected features should always be visible, so we can skip filtering
+ if (isRoutableNode(vertex)) {
+ routingIsland.add(vertex);
+ }
- applyFeatureLayerFilters = false;
- } else if (difference) {
- var complete = difference.complete(map.extent());
- data = Object.values(complete).filter(Boolean);
- set = new Set(Object.keys(complete));
+ queueParentWays(vertex);
+ }
+ } // no network link found, this is a routing island, return its members
- filter = function filter(d) {
- return set.has(d.id);
- };
- features.clear(data);
- } else {
- // force a full redraw if gatherStats detects that a feature
- // should be auto-hidden (e.g. points or buildings)..
- if (features.gatherStats(all, graph, _dimensions)) {
- extent = undefined;
- }
+ return routingIsland;
+ }
- if (extent) {
- data = context.history().intersects(map.extent().intersection(extent));
- set = new Set(data.map(function (entity) {
- return entity.id;
- }));
+ function isConnectedVertex(vertex) {
+ // assume ways overlapping unloaded tiles are connected to the wider road network - #5938
+ var osm = services.osm;
+ if (osm && !osm.isDataLoaded(vertex.loc)) return true; // entrances are considered connected
- filter = function filter(d) {
- return set.has(d.id);
- };
- } else {
- data = all;
- fullRedraw = true;
- filter = utilFunctor(true);
- }
+ if (vertex.tags.entrance && vertex.tags.entrance !== 'no') return true;
+ if (vertex.tags.amenity === 'parking_entrance') return true;
+ return false;
}
- if (applyFeatureLayerFilters) {
- data = features.filter(data, graph);
- } else {
- context.features().resetStats();
+ function isRoutableNode(node) {
+ // treat elevators as distinct features in the highway network
+ if (node.tags.highway === 'elevator') return true;
+ return false;
}
- if (mode && mode.id === 'select') {
- // update selected vertices - the user might have just double-clicked a way,
- // creating a new vertex, triggering a partial redraw without a mode change
- surface.call(drawVertices.drawSelected, graph, map.extent());
+ function isRoutableWay(way, ignoreInnerWays) {
+ if (isTaggedAsHighway(way) || way.tags.route === 'ferry') return true;
+ return graph.parentRelations(way).some(function (parentRelation) {
+ if (parentRelation.tags.type === 'route' && parentRelation.tags.route === 'ferry') return true;
+ if (parentRelation.isMultipolygon() && isTaggedAsHighway(parentRelation) && (!ignoreInnerWays || parentRelation.memberById(way.id).role !== 'inner')) return true;
+ return false;
+ });
}
- surface.call(drawVertices, graph, data, filter, map.extent(), fullRedraw).call(drawLines, graph, data, filter).call(drawAreas, graph, data, filter).call(drawMidpoints, graph, data, filter, map.trimmedExtent()).call(drawLabels, graph, data, filter, _dimensions, fullRedraw).call(drawPoints, graph, data, filter);
- dispatch.call('drawn', this, {
- full: true
- });
- }
+ function makeContinueDrawingFixIfAllowed(textDirection, vertexID, whichEnd) {
+ var vertex = graph.hasEntity(vertexID);
+ if (!vertex || vertex.tags.noexit === 'yes') return null;
+ var useLeftContinue = whichEnd === 'start' && textDirection === 'ltr' || whichEnd === 'end' && textDirection === 'rtl';
+ return new validationIssueFix({
+ icon: 'iD-operation-continue' + (useLeftContinue ? '-left' : ''),
+ title: _t.html('issues.fix.continue_from_' + whichEnd + '.title'),
+ entityIds: [vertexID],
+ onClick: function onClick(context) {
+ var wayId = this.issue.entityIds[0];
+ var way = context.hasEntity(wayId);
+ var vertexId = this.entityIds[0];
+ var vertex = context.hasEntity(vertexId);
+ if (!way || !vertex) return; // make sure the vertex is actually visible and editable
- map.init = function () {
- drawLayers = svgLayers(projection, context);
- drawPoints = svgPoints(projection, context);
- drawVertices = svgVertices(projection, context);
- drawLines = svgLines(projection, context);
- drawAreas = svgAreas(projection, context);
- drawMidpoints = svgMidpoints(projection, context);
- drawLabels = svgLabels(projection, context);
- };
+ var map = context.map();
- function editOff() {
- context.features().resetStats();
- surface.selectAll('.layer-osm *').remove();
- surface.selectAll('.layer-touch:not(.markers) *').remove();
- var allowed = {
- 'browse': true,
- 'save': true,
- 'select-note': true,
- 'select-data': true,
- 'select-error': true
- };
- var mode = context.mode();
+ if (!context.editable() || !map.trimmedExtent().contains(vertex.loc)) {
+ map.zoomToEase(vertex);
+ }
- if (mode && !allowed[mode.id]) {
- context.enter(modeBrowse(context));
+ context.enter(modeDrawLine(context, wayId, context.graph(), 'line', way.affix(vertexId), true));
+ }
+ });
}
+ };
- dispatch.call('drawn', this, {
- full: true
- });
- }
+ validation.type = type;
+ return validation;
+ }
- function gestureChange(d3_event) {
- // Remap Safari gesture events to wheel events - #5492
- // We want these disabled most places, but enabled for zoom/unzoom on map surface
- // https://developer.mozilla.org/en-US/docs/Web/API/GestureEvent
- var e = d3_event;
- e.preventDefault();
- var props = {
- deltaMode: 0,
- // dummy values to ignore in zoomPan
- deltaY: 1,
- // dummy values to ignore in zoomPan
- clientX: e.clientX,
- clientY: e.clientY,
- screenX: e.screenX,
- screenY: e.screenY,
- x: e.x,
- y: e.y
- };
- var e2 = new WheelEvent('wheel', props);
- e2._scale = e.scale; // preserve the original scale
+ function validationFormatting() {
+ var type = 'invalid_format';
- e2._rotation = e.rotation; // preserve the original rotation
+ var validation = function validation(entity) {
+ var issues = [];
- _selection.node().dispatchEvent(e2);
- }
+ function isValidEmail(email) {
+ // Emails in OSM are going to be official so they should be pretty simple
+ // Using negated lists to better support all possible unicode characters (#6494)
+ var valid_email = /^[^\(\)\\,":;<>@\[\]]+@[^\(\)\\,":;<>@\[\]\.]+(?:\.[a-z0-9-]+)*$/i; // An empty value is also acceptable
- function zoomPan(event, key, transform) {
- var source = event && event.sourceEvent || event;
- var eventTransform = transform || event && event.transform;
- var x = eventTransform.x;
- var y = eventTransform.y;
- var k = eventTransform.k; // Special handling of 'wheel' events:
- // They might be triggered by the user scrolling the mouse wheel,
- // or 2-finger pinch/zoom gestures, the transform may need adjustment.
+ return !email || valid_email.test(email);
+ }
- if (source && source.type === 'wheel') {
- // assume that the gesture is already handled by pointer events
- if (_pointerDown) return;
- var detected = utilDetect();
- var dX = source.deltaX;
- var dY = source.deltaY;
- var x2 = x;
- var y2 = y;
- var k2 = k;
- var t0, p0, p1; // Normalize mousewheel scroll speed (Firefox) - #3029
- // If wheel delta is provided in LINE units, recalculate it in PIXEL units
- // We are essentially redoing the calculations that occur here:
- // https://github.com/d3/d3-zoom/blob/78563a8348aa4133b07cac92e2595c2227ca7cd7/src/zoom.js#L203
- // See this for more info:
- // https://github.com/basilfx/normalize-wheel/blob/master/src/normalizeWheel.js
-
- if (source.deltaMode === 1
- /* LINE */
- ) {
- // Convert from lines to pixels, more if the user is scrolling fast.
- // (I made up the exp function to roughly match Firefox to what Chrome does)
- // These numbers should be floats, because integers are treated as pan gesture below.
- var lines = Math.abs(source.deltaY);
- var sign = source.deltaY > 0 ? 1 : -1;
- dY = sign * clamp$1(Math.exp((lines - 1) * 0.75) * 4.000244140625, 4.000244140625, // min
- 350.000244140625 // max
- ); // On Firefox Windows and Linux we always get +/- the scroll line amount (default 3)
- // There doesn't seem to be any scroll acceleration.
- // This multiplier increases the speed a little bit - #5512
-
- if (detected.os !== 'mac') {
- dY *= 5;
- } // recalculate x2,y2,k2
-
-
- t0 = _isTransformed ? _transformLast : _transformStart;
- p0 = _getMouseCoords(source);
- p1 = t0.invert(p0);
- k2 = t0.k * Math.pow(2, -dY / 500);
- k2 = clamp$1(k2, kMin, kMax);
- x2 = p0[0] - p1[0] * k2;
- y2 = p0[1] - p1[1] * k2; // 2 finger map pinch zooming (Safari) - #5492
- // These are fake `wheel` events we made from Safari `gesturechange` events..
- } else if (source._scale) {
- // recalculate x2,y2,k2
- t0 = _gestureTransformStart;
- p0 = _getMouseCoords(source);
- p1 = t0.invert(p0);
- k2 = t0.k * source._scale;
- k2 = clamp$1(k2, kMin, kMax);
- x2 = p0[0] - p1[0] * k2;
- y2 = p0[1] - p1[1] * k2; // 2 finger map pinch zooming (all browsers except Safari) - #5492
- // Pinch zooming via the `wheel` event will always have:
- // - `ctrlKey = true`
- // - `deltaY` is not round integer pixels (ignore `deltaX`)
- } else if (source.ctrlKey && !isInteger(dY)) {
- dY *= 6; // slightly scale up whatever the browser gave us
- // recalculate x2,y2,k2
-
- t0 = _isTransformed ? _transformLast : _transformStart;
- p0 = _getMouseCoords(source);
- p1 = t0.invert(p0);
- k2 = t0.k * Math.pow(2, -dY / 500);
- k2 = clamp$1(k2, kMin, kMax);
- x2 = p0[0] - p1[0] * k2;
- y2 = p0[1] - p1[1] * k2; // Trackpad scroll zooming with shift or alt/option key down
- } else if ((source.altKey || source.shiftKey) && isInteger(dY)) {
- // recalculate x2,y2,k2
- t0 = _isTransformed ? _transformLast : _transformStart;
- p0 = _getMouseCoords(source);
- p1 = t0.invert(p0);
- k2 = t0.k * Math.pow(2, -dY / 500);
- k2 = clamp$1(k2, kMin, kMax);
- x2 = p0[0] - p1[0] * k2;
- y2 = p0[1] - p1[1] * k2; // 2 finger map panning (Mac only, all browsers except Firefox #8595) - #5492, #5512
- // Panning via the `wheel` event will always have:
- // - `ctrlKey = false`
- // - `deltaX`,`deltaY` are round integer pixels
- } else if (detected.os === 'mac' && detected.browser !== 'Firefox' && !source.ctrlKey && isInteger(dX) && isInteger(dY)) {
- p1 = projection.translate();
- x2 = p1[0] - dX;
- y2 = p1[1] - dY;
- k2 = projection.scale();
- k2 = clamp$1(k2, kMin, kMax);
- } // something changed - replace the event transform
-
-
- if (x2 !== x || y2 !== y || k2 !== k) {
- x = x2;
- y = y2;
- k = k2;
- eventTransform = identity$2.translate(x2, y2).scale(k2);
-
- if (_zoomerPanner._transform) {
- // utilZoomPan interface
- _zoomerPanner._transform(eventTransform);
- } else {
- // d3_zoom interface
- _selection.node().__zoom = eventTransform;
- }
- }
+ function showReferenceEmail(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.invalid_format.email.reference'));
}
-
- if (_transformStart.x === x && _transformStart.y === y && _transformStart.k === k) {
- return; // no change
+ /* see https://github.com/openstreetmap/iD/issues/6831#issuecomment-537121379
+ function isSchemePresent(url) {
+ var valid_scheme = /^https?:\/\//i;
+ return (!url || valid_scheme.test(url));
}
-
- if (geoScaleToZoom(k, TILESIZE) < _minzoom) {
- surface.interrupt();
- dispatch.call('hitMinZoom', this, map);
- setCenterZoom(map.center(), context.minEditableZoom(), 0, true);
- scheduleRedraw();
- dispatch.call('move', this, map);
- return;
+ function showReferenceWebsite(selection) {
+ selection.selectAll('.issue-reference')
+ .data([0])
+ .enter()
+ .append('div')
+ .attr('class', 'issue-reference')
+ .call(t.append('issues.invalid_format.website.reference'));
}
+ if (entity.tags.website) {
+ // Multiple websites are possible
+ // If ever we support ES6, arrow functions make this nicer
+ var websites = entity.tags.website
+ .split(';')
+ .map(function(s) { return s.trim(); })
+ .filter(function(x) { return !isSchemePresent(x); });
+ if (websites.length) {
+ issues.push(new validationIssue({
+ type: type,
+ subtype: 'website',
+ severity: 'warning',
+ message: function(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? t.html('issues.invalid_format.website.message' + this.data,
+ { feature: utilDisplayLabel(entity, context.graph()), site: websites.join(', ') }) : '';
+ },
+ reference: showReferenceWebsite,
+ entityIds: [entity.id],
+ hash: websites.join(),
+ data: (websites.length > 1) ? '_multi' : ''
+ }));
+ }
+ }*/
- projection.transform(eventTransform);
- var withinEditableZoom = map.withinEditableZoom();
- if (_lastWithinEditableZoom !== withinEditableZoom) {
- if (_lastWithinEditableZoom !== undefined) {
- // notify that the map zoomed in or out over the editable zoom threshold
- dispatch.call('crossEditableZoom', this, withinEditableZoom);
- }
+ if (entity.tags.email) {
+ // Multiple emails are possible
+ var emails = entity.tags.email.split(';').map(function (s) {
+ return s.trim();
+ }).filter(function (x) {
+ return !isValidEmail(x);
+ });
- _lastWithinEditableZoom = withinEditableZoom;
+ if (emails.length) {
+ issues.push(new validationIssue({
+ type: type,
+ subtype: 'email',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.invalid_format.email.message' + this.data, {
+ feature: utilDisplayLabel(entity, context.graph()),
+ email: emails.join(', ')
+ }) : '';
+ },
+ reference: showReferenceEmail,
+ entityIds: [entity.id],
+ hash: emails.join(),
+ data: emails.length > 1 ? '_multi' : ''
+ }));
+ }
}
- var scale = k / _transformStart.k;
- var tX = (x / scale - _transformStart.x) * scale;
- var tY = (y / scale - _transformStart.y) * scale;
+ return issues;
+ };
- if (context.inIntro()) {
- curtainProjection.transform({
- x: x - tX,
- y: y - tY,
- k: k
- });
- }
+ validation.type = type;
+ return validation;
+ }
- if (source) {
- _lastPointerEvent = event;
- }
+ function validationHelpRequest(context) {
+ var type = 'help_request';
- _isTransformed = true;
- _transformLast = eventTransform;
- utilSetTransform(supersurface, tX, tY, scale);
- scheduleRedraw();
- dispatch.call('move', this, map);
+ var validation = function checkFixmeTag(entity) {
+ if (!entity.tags.fixme) return []; // don't flag fixmes on features added by the user
- function isInteger(val) {
- return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
- }
- }
+ if (entity.version === undefined) return [];
- function resetTransform() {
- if (!_isTransformed) return false;
- utilSetTransform(supersurface, 0, 0);
- _isTransformed = false;
+ if (entity.v !== undefined) {
+ var baseEntity = context.history().base().hasEntity(entity.id); // don't flag fixmes added by the user on existing features
- if (context.inIntro()) {
- curtainProjection.transform(projection.transform());
+ if (!baseEntity || !baseEntity.tags.fixme) return [];
}
- return true;
- }
-
- function redraw(difference, extent) {
- if (surface.empty() || !_redrawEnabled) return; // If we are in the middle of a zoom/pan, we can't do differenced redraws.
- // It would result in artifacts where differenced entities are redrawn with
- // one transform and unchanged entities with another.
+ return [new validationIssue({
+ type: type,
+ subtype: 'fixme_tag',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.fixme_tag.message', {
+ feature: utilDisplayLabel(entity, context.graph(), true
+ /* verbose */
+ )
+ }) : '';
+ },
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ title: _t.html('issues.fix.address_the_concern.title')
+ })];
+ },
+ reference: showReference,
+ entityIds: [entity.id]
+ })];
- if (resetTransform()) {
- difference = extent = undefined;
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.fixme_tag.reference'));
}
+ };
- var zoom = map.zoom();
- var z = String(~~zoom);
+ validation.type = type;
+ return validation;
+ }
- if (surface.attr('data-zoom') !== z) {
- surface.attr('data-zoom', z);
- } // class surface as `lowzoom` around z17-z18.5 (based on latitude)
+ function validationImpossibleOneway() {
+ var type = 'impossible_oneway';
+ var validation = function checkImpossibleOneway(entity, graph) {
+ if (entity.type !== 'way' || entity.geometry(graph) !== 'line') return [];
+ if (entity.isClosed()) return [];
+ if (!typeForWay(entity)) return [];
+ if (!isOneway(entity)) return [];
+ var firstIssues = issuesForNode(entity, entity.first());
+ var lastIssues = issuesForNode(entity, entity.last());
+ return firstIssues.concat(lastIssues);
- var lat = map.center()[1];
- var lowzoom = linear().domain([-60, 0, 60]).range([17, 18.5, 17]).clamp(true);
- surface.classed('low-zoom', zoom <= lowzoom(lat));
+ function typeForWay(way) {
+ if (way.geometry(graph) !== 'line') return null;
+ if (osmRoutableHighwayTagValues[way.tags.highway]) return 'highway';
+ if (osmFlowingWaterwayTagValues[way.tags.waterway]) return 'waterway';
+ return null;
+ }
- if (!difference) {
- supersurface.call(context.background());
- wrapper.call(drawLayers);
- } // OSM
+ function isOneway(way) {
+ if (way.tags.oneway === 'yes') return true;
+ if (way.tags.oneway) return false;
+ for (var key in way.tags) {
+ if (osmOneWayTags[key] && osmOneWayTags[key][way.tags[key]]) {
+ return true;
+ }
+ }
- if (map.editableDataEnabled() || map.isInWideSelection()) {
- context.loadTiles(projection);
- drawEditable(difference, extent);
- } else {
- editOff();
+ return false;
}
- _transformStart = projection.transform();
- return map;
- }
-
- var immediateRedraw = function immediateRedraw(difference, extent) {
- if (!difference && !extent) cancelPendingRedraw();
- redraw(difference, extent);
- };
+ function nodeOccursMoreThanOnce(way, nodeID) {
+ var occurrences = 0;
- map.lastPointerEvent = function () {
- return _lastPointerEvent;
- };
+ for (var index in way.nodes) {
+ if (way.nodes[index] === nodeID) {
+ occurrences += 1;
+ if (occurrences > 1) return true;
+ }
+ }
- map.mouse = function (d3_event) {
- var event = d3_event || _lastPointerEvent;
+ return false;
+ }
- if (event) {
- var s;
+ function isConnectedViaOtherTypes(way, node) {
+ var wayType = typeForWay(way);
- while (s = event.sourceEvent) {
- event = s;
+ if (wayType === 'highway') {
+ // entrances are considered connected
+ if (node.tags.entrance && node.tags.entrance !== 'no') return true;
+ if (node.tags.amenity === 'parking_entrance') return true;
+ } else if (wayType === 'waterway') {
+ if (node.id === way.first()) {
+ // multiple waterways may start at the same spring
+ if (node.tags.natural === 'spring') return true;
+ } else {
+ // multiple waterways may end at the same drain
+ if (node.tags.manhole === 'drain') return true;
+ }
}
- return _getMouseCoords(event);
- }
+ return graph.parentWays(node).some(function (parentWay) {
+ if (parentWay.id === way.id) return false;
- return null;
- }; // returns Lng/Lat
+ if (wayType === 'highway') {
+ // allow connections to highway areas
+ if (parentWay.geometry(graph) === 'area' && osmRoutableHighwayTagValues[parentWay.tags.highway]) return true; // count connections to ferry routes as connected
+ if (parentWay.tags.route === 'ferry') return true;
+ return graph.parentRelations(parentWay).some(function (parentRelation) {
+ if (parentRelation.tags.type === 'route' && parentRelation.tags.route === 'ferry') return true; // allow connections to highway multipolygons
- map.mouseCoordinates = function () {
- var coord = map.mouse() || pxCenter();
- return projection.invert(coord);
- };
+ return parentRelation.isMultipolygon() && osmRoutableHighwayTagValues[parentRelation.tags.highway];
+ });
+ } else if (wayType === 'waterway') {
+ // multiple waterways may start or end at a water body at the same node
+ if (parentWay.tags.natural === 'water' || parentWay.tags.natural === 'coastline') return true;
+ }
- map.dblclickZoomEnable = function (val) {
- if (!arguments.length) return _dblClickZoomEnabled;
- _dblClickZoomEnabled = val;
- return map;
- };
+ return false;
+ });
+ }
- map.redrawEnable = function (val) {
- if (!arguments.length) return _redrawEnabled;
- _redrawEnabled = val;
- return map;
- };
+ function issuesForNode(way, nodeID) {
+ var isFirst = nodeID === way.first();
+ var wayType = typeForWay(way); // ignore if this way is self-connected at this node
- map.isTransformed = function () {
- return _isTransformed;
- };
+ if (nodeOccursMoreThanOnce(way, nodeID)) return [];
+ var osm = services.osm;
+ if (!osm) return [];
+ var node = graph.hasEntity(nodeID); // ignore if this node or its tile are unloaded
- function setTransform(t2, duration, force) {
- var t = projection.transform();
- if (!force && t2.k === t.k && t2.x === t.x && t2.y === t.y) return false;
+ if (!node || !osm.isDataLoaded(node.loc)) return [];
+ if (isConnectedViaOtherTypes(way, node)) return [];
+ var attachedWaysOfSameType = graph.parentWays(node).filter(function (parentWay) {
+ if (parentWay.id === way.id) return false;
+ return typeForWay(parentWay) === wayType;
+ }); // assume it's okay for waterways to start or end disconnected for now
- if (duration) {
- _selection.transition().duration(duration).on('start', function () {
- map.startEase();
- }).call(_zoomerPanner.transform, identity$2.translate(t2.x, t2.y).scale(t2.k));
- } else {
- projection.transform(t2);
- _transformStart = t2;
+ if (wayType === 'waterway' && attachedWaysOfSameType.length === 0) return [];
+ var attachedOneways = attachedWaysOfSameType.filter(function (attachedWay) {
+ return isOneway(attachedWay);
+ }); // ignore if the way is connected to some non-oneway features
- _selection.call(_zoomerPanner.transform, _transformStart);
- }
+ if (attachedOneways.length < attachedWaysOfSameType.length) return [];
- return true;
- }
+ if (attachedOneways.length) {
+ var connectedEndpointsOkay = attachedOneways.some(function (attachedOneway) {
+ if ((isFirst ? attachedOneway.first() : attachedOneway.last()) !== nodeID) return true;
+ if (nodeOccursMoreThanOnce(attachedOneway, nodeID)) return true;
+ return false;
+ });
+ if (connectedEndpointsOkay) return [];
+ }
- function setCenterZoom(loc2, z2, duration, force) {
- var c = map.center();
- var z = map.zoom();
- if (loc2[0] === c[0] && loc2[1] === c[1] && z2 === z && !force) return false;
- var proj = geoRawMercator().transform(projection.transform()); // copy projection
+ var placement = isFirst ? 'start' : 'end',
+ messageID = wayType + '.',
+ referenceID = wayType + '.';
- var k2 = clamp$1(geoZoomToScale(z2, TILESIZE), kMin, kMax);
- proj.scale(k2);
- var t = proj.translate();
- var point = proj(loc2);
- var center = pxCenter();
- t[0] += center[0] - point[0];
- t[1] += center[1] - point[1];
- return setTransform(identity$2.translate(t[0], t[1]).scale(k2), duration, force);
- }
+ if (wayType === 'waterway') {
+ messageID += 'connected.' + placement;
+ referenceID += 'connected';
+ } else {
+ messageID += placement;
+ referenceID += placement;
+ }
- map.pan = function (delta, duration) {
- var t = projection.translate();
- var k = projection.scale();
- t[0] += delta[0];
- t[1] += delta[1];
+ return [new validationIssue({
+ type: type,
+ subtype: wayType,
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.impossible_oneway.' + messageID + '.message', {
+ feature: utilDisplayLabel(entity, context.graph())
+ }) : '';
+ },
+ reference: getReference(referenceID),
+ entityIds: [way.id, node.id],
+ dynamicFixes: function dynamicFixes() {
+ var fixes = [];
- if (duration) {
- _selection.transition().duration(duration).on('start', function () {
- map.startEase();
- }).call(_zoomerPanner.transform, identity$2.translate(t[0], t[1]).scale(k));
- } else {
- projection.translate(t);
- _transformStart = projection.transform();
+ if (attachedOneways.length) {
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-reverse',
+ title: _t.html('issues.fix.reverse_feature.title'),
+ entityIds: [way.id],
+ onClick: function onClick(context) {
+ var id = this.issue.entityIds[0];
+ context.perform(actionReverse(id), _t('operations.reverse.annotation.line', {
+ n: 1
+ }));
+ }
+ }));
+ }
- _selection.call(_zoomerPanner.transform, _transformStart);
+ if (node.tags.noexit !== 'yes') {
+ var textDirection = _mainLocalizer.textDirection();
+ var useLeftContinue = isFirst && textDirection === 'ltr' || !isFirst && textDirection === 'rtl';
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-continue' + (useLeftContinue ? '-left' : ''),
+ title: _t.html('issues.fix.continue_from_' + (isFirst ? 'start' : 'end') + '.title'),
+ onClick: function onClick(context) {
+ var entityID = this.issue.entityIds[0];
+ var vertexID = this.issue.entityIds[1];
+ var way = context.entity(entityID);
+ var vertex = context.entity(vertexID);
+ continueDrawing(way, vertex, context);
+ }
+ }));
+ }
- dispatch.call('move', this, map);
- immediateRedraw();
- }
+ return fixes;
+ },
+ loc: node.loc
+ })];
- return map;
+ function getReference(referenceID) {
+ return function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.impossible_oneway.' + referenceID + '.reference'));
+ };
+ }
+ }
};
- map.dimensions = function (val) {
- if (!arguments.length) return _dimensions;
- _dimensions = val;
- drawLayers.dimensions(_dimensions);
- context.background().dimensions(_dimensions);
- projection.clipExtent([[0, 0], _dimensions]);
- _getMouseCoords = utilFastMouse(supersurface.node());
- scheduleRedraw();
- return map;
- };
+ function continueDrawing(way, vertex, context) {
+ // make sure the vertex is actually visible and editable
+ var map = context.map();
- function zoomIn(delta) {
- setCenterZoom(map.center(), ~~map.zoom() + delta, 250, true);
- }
+ if (!context.editable() || !map.trimmedExtent().contains(vertex.loc)) {
+ map.zoomToEase(vertex);
+ }
- function zoomOut(delta) {
- setCenterZoom(map.center(), ~~map.zoom() - delta, 250, true);
+ context.enter(modeDrawLine(context, way.id, context.graph(), 'line', way.affix(vertex.id), true));
}
- map.zoomIn = function () {
- zoomIn(1);
- };
+ validation.type = type;
+ return validation;
+ }
- map.zoomInFurther = function () {
- zoomIn(4);
- };
+ function validationIncompatibleSource() {
+ var type = 'incompatible_source';
+ var incompatibleRules = [{
+ id: 'amap',
+ regex: /(^amap$|^amap\.com|autonavi|mapabc|高德)/i
+ }, {
+ id: 'baidu',
+ regex: /(baidu|mapbar|百度)/i
+ }, {
+ id: 'google',
+ regex: /google/i,
+ exceptRegex: /((books|drive)\.google|google\s?(books|drive|plus))|(esri\/Google_Africa_Buildings)/i
+ }];
- map.canZoomIn = function () {
- return map.zoom() < maxZoom;
- };
+ var validation = function checkIncompatibleSource(entity) {
+ var entitySources = entity.tags && entity.tags.source && entity.tags.source.split(';');
+ if (!entitySources) return [];
+ var entityID = entity.id;
+ return entitySources.map(function (source) {
+ var matchRule = incompatibleRules.find(function (rule) {
+ if (!rule.regex.test(source)) return false;
+ if (rule.exceptRegex && rule.exceptRegex.test(source)) return false;
+ return true;
+ });
+ if (!matchRule) return null;
+ return new validationIssue({
+ type: type,
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(entityID);
+ return entity ? _t.html('issues.incompatible_source.feature.message', {
+ feature: utilDisplayLabel(entity, context.graph(), true
+ /* verbose */
+ ),
+ value: source
+ }) : '';
+ },
+ reference: getReference(matchRule.id),
+ entityIds: [entityID],
+ hash: source,
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ title: _t.html('issues.fix.remove_proprietary_data.title')
+ })];
+ }
+ });
+ }).filter(Boolean);
- map.zoomOut = function () {
- zoomOut(1);
+ function getReference(id) {
+ return function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append("issues.incompatible_source.reference.".concat(id)));
+ };
+ }
};
- map.zoomOutFurther = function () {
- zoomOut(4);
- };
+ validation.type = type;
+ return validation;
+ }
- map.canZoomOut = function () {
- return map.zoom() > minZoom;
- };
+ function validationMaprules() {
+ var type = 'maprules';
- map.center = function (loc2) {
- if (!arguments.length) {
- return projection.invert(pxCenter());
- }
+ var validation = function checkMaprules(entity, graph) {
+ if (!services.maprules) return [];
+ var rules = services.maprules.validationRules();
+ var issues = [];
- if (setCenterZoom(loc2, map.zoom())) {
- dispatch.call('move', this, map);
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ rule.findIssues(entity, graph, issues);
}
- scheduleRedraw();
- return map;
+ return issues;
};
- map.unobscuredCenterZoomEase = function (loc, zoom) {
- var offset = map.unobscuredOffsetPx();
- var proj = geoRawMercator().transform(projection.transform()); // copy projection
- // use the target zoom to calculate the offset center
+ validation.type = type;
+ return validation;
+ }
- proj.scale(geoZoomToScale(zoom, TILESIZE));
- var locPx = proj(loc);
- var offsetLocPx = [locPx[0] + offset[0], locPx[1] + offset[1]];
- var offsetLoc = proj.invert(offsetLocPx);
- map.centerZoomEase(offsetLoc, zoom);
- };
+ function validationMismatchedGeometry() {
+ var type = 'mismatched_geometry';
- map.unobscuredOffsetPx = function () {
- var openPane = context.container().select('.map-panes .map-pane.shown');
+ function tagSuggestingLineIsArea(entity) {
+ if (entity.type !== 'way' || entity.isClosed()) return null;
+ var tagSuggestingArea = entity.tagSuggestingArea();
- if (!openPane.empty()) {
- return [openPane.node().offsetWidth / 2, 0];
+ if (!tagSuggestingArea) {
+ return null;
}
- return [0, 0];
- };
+ var asLine = _mainPresetIndex.matchTags(tagSuggestingArea, 'line');
+ var asArea = _mainPresetIndex.matchTags(tagSuggestingArea, 'area');
- map.zoom = function (z2) {
- if (!arguments.length) {
- return Math.max(geoScaleToZoom(projection.scale(), TILESIZE), 0);
+ if (asLine && asArea && asLine === asArea) {
+ // these tags also allow lines and making this an area wouldn't matter
+ return null;
}
- if (z2 < _minzoom) {
- surface.interrupt();
- dispatch.call('hitMinZoom', this, map);
- z2 = context.minEditableZoom();
- }
+ return tagSuggestingArea;
+ }
- if (setCenterZoom(map.center(), z2)) {
- dispatch.call('move', this, map);
- }
+ function makeConnectEndpointsFixOnClick(way, graph) {
+ // must have at least three nodes to close this automatically
+ if (way.nodes.length < 3) return null;
+ var nodes = graph.childNodes(way),
+ testNodes;
+ var firstToLastDistanceMeters = geoSphericalDistance(nodes[0].loc, nodes[nodes.length - 1].loc); // if the distance is very small, attempt to merge the endpoints
- scheduleRedraw();
- return map;
- };
+ if (firstToLastDistanceMeters < 0.75) {
+ testNodes = nodes.slice(); // shallow copy
- map.centerZoom = function (loc2, z2) {
- if (setCenterZoom(loc2, z2)) {
- dispatch.call('move', this, map);
- }
+ testNodes.pop();
+ testNodes.push(testNodes[0]); // make sure this will not create a self-intersection
- scheduleRedraw();
- return map;
- };
+ if (!geoHasSelfIntersections(testNodes, testNodes[0].id)) {
+ return function (context) {
+ var way = context.entity(this.issue.entityIds[0]);
+ context.perform(actionMergeNodes([way.nodes[0], way.nodes[way.nodes.length - 1]], nodes[0].loc), _t('issues.fix.connect_endpoints.annotation'));
+ };
+ }
+ } // if the points were not merged, attempt to close the way
- map.zoomTo = function (entity) {
- var extent = entity.extent(context.graph());
- if (!isFinite(extent.area())) return map;
- var z2 = clamp$1(map.trimmedExtentZoom(extent), 0, 20);
- return map.centerZoom(extent.center(), z2);
- };
- map.centerEase = function (loc2, duration) {
- duration = duration || 250;
- setCenterZoom(loc2, map.zoom(), duration);
- return map;
- };
+ testNodes = nodes.slice(); // shallow copy
- map.zoomEase = function (z2, duration) {
- duration = duration || 250;
- setCenterZoom(map.center(), z2, duration, false);
- return map;
- };
+ testNodes.push(testNodes[0]); // make sure this will not create a self-intersection
- map.centerZoomEase = function (loc2, z2, duration) {
- duration = duration || 250;
- setCenterZoom(loc2, z2, duration, false);
- return map;
- };
+ if (!geoHasSelfIntersections(testNodes, testNodes[0].id)) {
+ return function (context) {
+ var wayId = this.issue.entityIds[0];
+ var way = context.entity(wayId);
+ var nodeId = way.nodes[0];
+ var index = way.nodes.length;
+ context.perform(actionAddVertex(wayId, nodeId, index), _t('issues.fix.connect_endpoints.annotation'));
+ };
+ }
+ }
- map.transformEase = function (t2, duration) {
- duration = duration || 250;
- setTransform(t2, duration, false
- /* don't force */
- );
- return map;
- };
+ function lineTaggedAsAreaIssue(entity) {
+ var tagSuggestingArea = tagSuggestingLineIsArea(entity);
+ if (!tagSuggestingArea) return null;
+ return new validationIssue({
+ type: type,
+ subtype: 'area_as_line',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.tag_suggests_area.message', {
+ feature: utilDisplayLabel(entity, 'area', true
+ /* verbose */
+ ),
+ tag: utilTagText({
+ tags: tagSuggestingArea
+ })
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [entity.id],
+ hash: JSON.stringify(tagSuggestingArea),
+ dynamicFixes: function dynamicFixes(context) {
+ var fixes = [];
+ var entity = context.entity(this.entityIds[0]);
+ var connectEndsOnClick = makeConnectEndpointsFixOnClick(entity, context.graph());
+ fixes.push(new validationIssueFix({
+ title: _t.html('issues.fix.connect_endpoints.title'),
+ onClick: connectEndsOnClick
+ }));
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.remove_tag.title'),
+ onClick: function onClick(context) {
+ var entityId = this.issue.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- map.zoomToEase = function (obj, duration) {
- var extent;
+ for (var key in tagSuggestingArea) {
+ delete tags[key];
+ }
- if (Array.isArray(obj)) {
- obj.forEach(function (entity) {
- var entityExtent = entity.extent(context.graph());
+ context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_tag.annotation'));
+ }
+ }));
+ return fixes;
+ }
+ });
- if (!extent) {
- extent = entityExtent;
- } else {
- extent = extent.extend(entityExtent);
- }
- });
- } else {
- extent = obj.extent(context.graph());
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.tag_suggests_area.reference'));
}
+ }
- if (!isFinite(extent.area())) return map;
- var z2 = clamp$1(map.trimmedExtentZoom(extent), 0, 20);
- return map.centerZoomEase(extent.center(), z2, duration);
- };
-
- map.startEase = function () {
- utilBindOnce(surface, _pointerPrefix + 'down.ease', function () {
- map.cancelEase();
- });
- return map;
- };
+ function vertexPointIssue(entity, graph) {
+ // we only care about nodes
+ if (entity.type !== 'node') return null; // ignore tagless points
- map.cancelEase = function () {
- _selection.interrupt();
+ if (Object.keys(entity.tags).length === 0) return null; // address lines are special so just ignore them
- return map;
- };
+ if (entity.isOnAddressLine(graph)) return null;
+ var geometry = entity.geometry(graph);
+ var allowedGeometries = osmNodeGeometriesForTags(entity.tags);
- map.extent = function (val) {
- if (!arguments.length) {
- return new geoExtent(projection.invert([0, _dimensions[1]]), projection.invert([_dimensions[0], 0]));
- } else {
- var extent = geoExtent(val);
- map.centerZoom(extent.center(), map.extentZoom(extent));
+ if (geometry === 'point' && !allowedGeometries.point && allowedGeometries.vertex) {
+ return new validationIssue({
+ type: type,
+ subtype: 'vertex_as_point',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.vertex_as_point.message', {
+ feature: utilDisplayLabel(entity, 'vertex', true
+ /* verbose */
+ )
+ }) : '';
+ },
+ reference: function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.vertex_as_point.reference'));
+ },
+ entityIds: [entity.id]
+ });
+ } else if (geometry === 'vertex' && !allowedGeometries.vertex && allowedGeometries.point) {
+ return new validationIssue({
+ type: type,
+ subtype: 'point_as_vertex',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.point_as_vertex.message', {
+ feature: utilDisplayLabel(entity, 'point', true
+ /* verbose */
+ )
+ }) : '';
+ },
+ reference: function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.point_as_vertex.reference'));
+ },
+ entityIds: [entity.id],
+ dynamicFixes: extractPointDynamicFixes
+ });
}
- };
- map.trimmedExtent = function (val) {
- if (!arguments.length) {
- var headerY = 71;
- var footerY = 30;
- var pad = 10;
- return new geoExtent(projection.invert([pad, _dimensions[1] - footerY - pad]), projection.invert([_dimensions[0] - pad, headerY + pad]));
- } else {
- var extent = geoExtent(val);
- map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
- }
- };
+ return null;
+ }
- function calcExtentZoom(extent, dim) {
- var tl = projection([extent[0][0], extent[1][1]]);
- var br = projection([extent[1][0], extent[0][1]]); // Calculate maximum zoom that fits extent
+ function otherMismatchIssue(entity, graph) {
+ // ignore boring features
+ if (!entity.hasInterestingTags()) return null;
+ if (entity.type !== 'node' && entity.type !== 'way') return null; // address lines are special so just ignore them
- var hFactor = (br[0] - tl[0]) / dim[0];
- var vFactor = (br[1] - tl[1]) / dim[1];
- var hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2;
- var vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2;
- var newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff);
- return newZoom;
- }
+ if (entity.type === 'node' && entity.isOnAddressLine(graph)) return null;
+ var sourceGeom = entity.geometry(graph);
+ var targetGeoms = entity.type === 'way' ? ['point', 'vertex'] : ['line', 'area'];
+ if (sourceGeom === 'area') targetGeoms.unshift('line');
+ var asSource = _mainPresetIndex.match(entity, graph);
+ var targetGeom = targetGeoms.find(function (nodeGeom) {
+ var asTarget = _mainPresetIndex.matchTags(entity.tags, nodeGeom);
+ if (!asSource || !asTarget || asSource === asTarget || // sometimes there are two presets with the same tags for different geometries
+ fastDeepEqual(asSource.tags, asTarget.tags)) return false;
+ if (asTarget.isFallback()) return false;
+ var primaryKey = Object.keys(asTarget.tags)[0]; // special case: buildings-as-points are discouraged by iD, but common in OSM, so ignore them
- map.extentZoom = function (val) {
- return calcExtentZoom(geoExtent(val), _dimensions);
- };
+ if (primaryKey === 'building') return false;
+ if (asTarget.tags[primaryKey] === '*') return false;
+ return asSource.isFallback() || asSource.tags[primaryKey] === '*';
+ });
+ if (!targetGeom) return null;
+ var subtype = targetGeom + '_as_' + sourceGeom;
+ if (targetGeom === 'vertex') targetGeom = 'point';
+ if (sourceGeom === 'vertex') sourceGeom = 'point';
+ var referenceId = targetGeom + '_as_' + sourceGeom;
+ var dynamicFixes;
- map.trimmedExtentZoom = function (val) {
- var trimY = 120;
- var trimX = 40;
- var trimmed = [_dimensions[0] - trimX, _dimensions[1] - trimY];
- return calcExtentZoom(geoExtent(val), trimmed);
- };
+ if (targetGeom === 'point') {
+ dynamicFixes = extractPointDynamicFixes;
+ } else if (sourceGeom === 'area' && targetGeom === 'line') {
+ dynamicFixes = lineToAreaDynamicFixes;
+ }
- map.withinEditableZoom = function () {
- return map.zoom() >= context.minEditableZoom();
- };
+ return new validationIssue({
+ type: type,
+ subtype: subtype,
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.' + referenceId + '.message', {
+ feature: utilDisplayLabel(entity, targetGeom, true
+ /* verbose */
+ )
+ }) : '';
+ },
+ reference: function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.mismatched_geometry.reference'));
+ },
+ entityIds: [entity.id],
+ dynamicFixes: dynamicFixes
+ });
+ }
- map.isInWideSelection = function () {
- return !map.withinEditableZoom() && context.selectedIDs().length;
- };
+ function lineToAreaDynamicFixes(context) {
+ var convertOnClick;
+ var entityId = this.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- map.editableDataEnabled = function (skipZoomCheck) {
- var layer = context.layers().layer('osm');
- if (!layer || !layer.enabled()) return false;
- return skipZoomCheck || map.withinEditableZoom();
- };
+ delete tags.area;
- map.notesEditable = function () {
- var layer = context.layers().layer('notes');
- if (!layer || !layer.enabled()) return false;
- return map.withinEditableZoom();
- };
+ if (!osmTagSuggestingArea(tags)) {
+ // if removing the area tag would make this a line, offer that as a quick fix
+ convertOnClick = function convertOnClick(context) {
+ var entityId = this.issue.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- map.minzoom = function (val) {
- if (!arguments.length) return _minzoom;
- _minzoom = val;
- return map;
- };
+ if (tags.area) {
+ delete tags.area;
+ }
- map.toggleHighlightEdited = function () {
- surface.classed('highlight-edited', !surface.classed('highlight-edited'));
- map.pan([0, 0]); // trigger a redraw
+ context.perform(actionChangeTags(entityId, tags), _t('issues.fix.convert_to_line.annotation'));
+ };
+ }
- dispatch.call('changeHighlighting', this);
- };
+ return [new validationIssueFix({
+ icon: 'iD-icon-line',
+ title: _t.html('issues.fix.convert_to_line.title'),
+ onClick: convertOnClick
+ })];
+ }
- map.areaFillOptions = ['wireframe', 'partial', 'full'];
+ function extractPointDynamicFixes(context) {
+ var entityId = this.entityIds[0];
+ var extractOnClick = null;
- map.activeAreaFill = function (val) {
- if (!arguments.length) return corePreferences('area-fill') || 'partial';
- corePreferences('area-fill', val);
+ if (!context.hasHiddenConnections(entityId)) {
+ extractOnClick = function extractOnClick(context) {
+ var entityId = this.issue.entityIds[0];
+ var action = actionExtract(entityId, context.projection);
+ context.perform(action, _t('operations.extract.annotation', {
+ n: 1
+ })); // re-enter mode to trigger updates
- if (val !== 'wireframe') {
- corePreferences('area-fill-toggle', val);
+ context.enter(modeSelect(context, [action.getExtractedNodeID()]));
+ };
}
- updateAreaFill();
- map.pan([0, 0]); // trigger a redraw
+ return [new validationIssueFix({
+ icon: 'iD-operation-extract',
+ title: _t.html('issues.fix.extract_point.title'),
+ onClick: extractOnClick
+ })];
+ }
- dispatch.call('changeAreaFill', this);
- return map;
- };
+ function unclosedMultipolygonPartIssues(entity, graph) {
+ if (entity.type !== 'relation' || !entity.isMultipolygon() || entity.isDegenerate() || // cannot determine issues for incompletely-downloaded relations
+ !entity.isComplete(graph)) return [];
+ var sequences = osmJoinWays(entity.members, graph);
+ var issues = [];
- map.toggleWireframe = function () {
- var activeFill = map.activeAreaFill();
+ for (var i in sequences) {
+ var sequence = sequences[i];
+ if (!sequence.nodes) continue;
+ var firstNode = sequence.nodes[0];
+ var lastNode = sequence.nodes[sequence.nodes.length - 1]; // part is closed if the first and last nodes are the same
- if (activeFill === 'wireframe') {
- activeFill = corePreferences('area-fill-toggle') || 'partial';
- } else {
- activeFill = 'wireframe';
+ if (firstNode === lastNode) continue;
+ var issue = new validationIssue({
+ type: type,
+ subtype: 'unclosed_multipolygon_part',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.unclosed_multipolygon_part.message', {
+ feature: utilDisplayLabel(entity, context.graph(), true
+ /* verbose */
+ )
+ }) : '';
+ },
+ reference: showReference,
+ loc: sequence.nodes[0].loc,
+ entityIds: [entity.id],
+ hash: sequence.map(function (way) {
+ return way.id;
+ }).join()
+ });
+ issues.push(issue);
}
- map.activeAreaFill(activeFill);
- };
+ return issues;
- function updateAreaFill() {
- var activeFill = map.activeAreaFill();
- map.areaFillOptions.forEach(function (opt) {
- surface.classed('fill-' + opt, Boolean(opt === activeFill));
- });
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.unclosed_multipolygon_part.reference'));
+ }
}
- map.layers = function () {
- return drawLayers;
- };
-
- map.doubleUpHandler = function () {
- return _doubleUpHandler;
+ var validation = function checkMismatchedGeometry(entity, graph) {
+ var vertexPoint = vertexPointIssue(entity, graph);
+ if (vertexPoint) return [vertexPoint];
+ var lineAsArea = lineTaggedAsAreaIssue(entity);
+ if (lineAsArea) return [lineAsArea];
+ var mismatch = otherMismatchIssue(entity, graph);
+ if (mismatch) return [mismatch];
+ return unclosedMultipolygonPartIssues(entity, graph);
};
- return utilRebind(map, dispatch, 'on');
+ validation.type = type;
+ return validation;
}
- function rendererPhotos(context) {
- var dispatch = dispatch$8('change');
- var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'openstreetcam'];
- var _allPhotoTypes = ['flat', 'panoramic'];
-
- var _shownPhotoTypes = _allPhotoTypes.slice(); // shallow copy
+ function validationMissingRole() {
+ var type = 'missing_role';
+ var validation = function checkMissingRole(entity, graph) {
+ var issues = [];
- var _dateFilters = ['fromDate', 'toDate'];
+ if (entity.type === 'way') {
+ graph.parentRelations(entity).forEach(function (relation) {
+ if (!relation.isMultipolygon()) return;
+ var member = relation.memberById(entity.id);
- var _fromDate;
+ if (member && isMissingRole(member)) {
+ issues.push(makeIssue(entity, relation, member));
+ }
+ });
+ } else if (entity.type === 'relation' && entity.isMultipolygon()) {
+ entity.indexedMembers().forEach(function (member) {
+ var way = graph.hasEntity(member.id);
- var _toDate;
+ if (way && isMissingRole(member)) {
+ issues.push(makeIssue(way, entity, member));
+ }
+ });
+ }
- var _usernames;
+ return issues;
+ };
- function photos() {}
+ function isMissingRole(member) {
+ return !member.role || !member.role.trim().length;
+ }
- function updateStorage() {
- if (window.mocha) return;
- var hash = utilStringQs(window.location.hash);
- var enabled = context.layers().all().filter(function (d) {
- return _layerIDs.indexOf(d.id) !== -1 && d.layer && d.layer.supported() && d.layer.enabled();
- }).map(function (d) {
- return d.id;
+ function makeIssue(way, relation, member) {
+ return new validationIssue({
+ type: type,
+ severity: 'warning',
+ message: function message(context) {
+ var member = context.hasEntity(this.entityIds[1]),
+ relation = context.hasEntity(this.entityIds[0]);
+ return member && relation ? _t.html('issues.missing_role.message', {
+ member: utilDisplayLabel(member, context.graph()),
+ relation: utilDisplayLabel(relation, context.graph())
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [relation.id, way.id],
+ data: {
+ member: member
+ },
+ hash: member.index.toString(),
+ dynamicFixes: function dynamicFixes() {
+ return [makeAddRoleFix('inner'), makeAddRoleFix('outer'), new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.remove_from_relation.title'),
+ onClick: function onClick(context) {
+ context.perform(actionDeleteMember(this.issue.entityIds[0], this.issue.data.member.index), _t('operations.delete_member.annotation', {
+ n: 1
+ }));
+ }
+ })];
+ }
});
- if (enabled.length) {
- hash.photo_overlay = enabled.join(',');
- } else {
- delete hash.photo_overlay;
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.missing_role.multipolygon.reference'));
}
+ }
- window.location.replace('#' + utilQsString(hash, true));
+ function makeAddRoleFix(role) {
+ return new validationIssueFix({
+ title: _t.html('issues.fix.set_as_' + role + '.title'),
+ onClick: function onClick(context) {
+ var oldMember = this.issue.data.member;
+ var member = {
+ id: this.issue.entityIds[1],
+ type: oldMember.type,
+ role: role
+ };
+ context.perform(actionChangeMember(this.issue.entityIds[0], member, oldMember.index), _t('operations.change_role.annotation', {
+ n: 1
+ }));
+ }
+ });
}
- photos.overlayLayerIDs = function () {
- return _layerIDs;
- };
+ validation.type = type;
+ return validation;
+ }
- photos.allPhotoTypes = function () {
- return _allPhotoTypes;
- };
+ function validationMissingTag(context) {
+ var type = 'missing_tag';
- photos.dateFilters = function () {
- return _dateFilters;
- };
+ function hasDescriptiveTags(entity, graph) {
+ var onlyAttributeKeys = ['description', 'name', 'note', 'start_date'];
+ var entityDescriptiveKeys = Object.keys(entity.tags).filter(function (k) {
+ if (k === 'area' || !osmIsInterestingTag(k)) return false;
+ return !onlyAttributeKeys.some(function (attributeKey) {
+ return k === attributeKey || k.indexOf(attributeKey + ':') === 0;
+ });
+ });
- photos.dateFilterValue = function (val) {
- return val === _dateFilters[0] ? _fromDate : _toDate;
- };
+ if (entity.type === 'relation' && entityDescriptiveKeys.length === 1 && entity.tags.type === 'multipolygon') {
+ // this relation's only interesting tag just says its a multipolygon,
+ // which is not descriptive enough
+ // It's okay for a simple multipolygon to have no descriptive tags
+ // if its outer way has them (old model, see `outdated_tags.js`)
+ return osmOldMultipolygonOuterMemberOfRelation(entity, graph);
+ }
- photos.setDateFilter = function (type, val, updateUrl) {
- // validate the date
- var date = val && new Date(val);
+ return entityDescriptiveKeys.length > 0;
+ }
- if (date && !isNaN(date)) {
- val = date.toISOString().substr(0, 10);
- } else {
- val = null;
- }
+ function isUnknownRoad(entity) {
+ return entity.type === 'way' && entity.tags.highway === 'road';
+ }
- if (type === _dateFilters[0]) {
- _fromDate = val;
+ function isUntypedRelation(entity) {
+ return entity.type === 'relation' && !entity.tags.type;
+ }
- if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
- _toDate = _fromDate;
+ var validation = function checkMissingTag(entity, graph) {
+ var subtype;
+ var osm = context.connection();
+ var isUnloadedNode = entity.type === 'node' && osm && !osm.isDataLoaded(entity.loc); // we can't know if the node is a vertex if the tile is undownloaded
+
+ if (!isUnloadedNode && // allow untagged nodes that are part of ways
+ entity.geometry(graph) !== 'vertex' && // allow untagged entities that are part of relations
+ !entity.hasParentRelations(graph)) {
+ if (Object.keys(entity.tags).length === 0) {
+ subtype = 'any';
+ } else if (!hasDescriptiveTags(entity, graph)) {
+ subtype = 'descriptive';
+ } else if (isUntypedRelation(entity)) {
+ subtype = 'relation_type';
}
- }
+ } // flag an unknown road even if it's a member of a relation
- if (type === _dateFilters[1]) {
- _toDate = val;
- if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
- _fromDate = _toDate;
- }
+ if (!subtype && isUnknownRoad(entity)) {
+ subtype = 'highway_classification';
}
- dispatch.call('change', this);
+ if (!subtype) return [];
+ var messageID = subtype === 'highway_classification' ? 'unknown_road' : 'missing_tag.' + subtype;
+ var referenceID = subtype === 'highway_classification' ? 'unknown_road' : 'missing_tag'; // can always delete if the user created it in the first place..
- if (updateUrl) {
- var rangeString;
+ var canDelete = entity.version === undefined || entity.v !== undefined;
+ var severity = canDelete && subtype !== 'highway_classification' ? 'error' : 'warning';
+ return [new validationIssue({
+ type: type,
+ subtype: subtype,
+ severity: severity,
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.' + messageID + '.message', {
+ feature: utilDisplayLabel(entity, context.graph())
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [entity.id],
+ dynamicFixes: function dynamicFixes(context) {
+ var fixes = [];
+ var selectFixType = subtype === 'highway_classification' ? 'select_road_type' : 'select_preset';
+ fixes.push(new validationIssueFix({
+ icon: 'iD-icon-search',
+ title: _t.html('issues.fix.' + selectFixType + '.title'),
+ onClick: function onClick(context) {
+ context.ui().sidebar.showPresetList();
+ }
+ }));
+ var deleteOnClick;
+ var id = this.entityIds[0];
+ var operation = operationDelete(context, [id]);
+ var disabledReasonID = operation.disabled();
- if (_fromDate || _toDate) {
- rangeString = (_fromDate || '') + '_' + (_toDate || '');
+ if (!disabledReasonID) {
+ deleteOnClick = function deleteOnClick(context) {
+ var id = this.issue.entityIds[0];
+ var operation = operationDelete(context, [id]);
+
+ if (!operation.disabled()) {
+ operation();
+ }
+ };
+ }
+
+ fixes.push(new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.delete_feature.title'),
+ disabledReason: disabledReasonID ? _t('operations.delete.' + disabledReasonID + '.single') : undefined,
+ onClick: deleteOnClick
+ }));
+ return fixes;
}
+ })];
- setUrlFilterValue('photo_dates', rangeString);
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.' + referenceID + '.reference'));
}
};
- photos.setUsernameFilter = function (val, updateUrl) {
- if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(',');
+ validation.type = type;
+ return validation;
+ }
- if (val) {
- val = val.map(function (d) {
- return d.trim();
- }).filter(Boolean);
+ function validationOutdatedTags() {
+ var type = 'outdated_tags';
+ var _waitingForDeprecated = true;
- if (!val.length) {
- val = null;
- }
- }
+ var _dataDeprecated; // fetch deprecated tags
- _usernames = val;
- dispatch.call('change', this);
- if (updateUrl) {
- var hashString;
+ _mainFileFetcher.get('deprecated').then(function (d) {
+ return _dataDeprecated = d;
+ })["catch"](function () {
+ /* ignore */
+ })["finally"](function () {
+ return _waitingForDeprecated = false;
+ });
- if (_usernames) {
- hashString = _usernames.join(',');
- }
+ function oldTagIssues(entity, graph) {
+ var oldTags = Object.assign({}, entity.tags); // shallow copy
- setUrlFilterValue('photo_username', hashString);
- }
- };
+ var preset = _mainPresetIndex.match(entity, graph);
+ var subtype = 'deprecated_tags';
+ if (!preset) return [];
+ if (!entity.hasInterestingTags()) return []; // Upgrade preset, if a replacement is available..
- function setUrlFilterValue(property, val) {
- if (!window.mocha) {
- var hash = utilStringQs(window.location.hash);
+ if (preset.replacement) {
+ var newPreset = _mainPresetIndex.item(preset.replacement);
+ graph = actionChangePreset(entity.id, preset, newPreset, true
+ /* skip field defaults */
+ )(graph);
+ entity = graph.entity(entity.id);
+ preset = newPreset;
+ } // Upgrade deprecated tags..
- if (val) {
- if (hash[property] === val) return;
- hash[property] = val;
- } else {
- if (!(property in hash)) return;
- delete hash[property];
- }
- window.location.replace('#' + utilQsString(hash, true));
- }
- }
+ if (_dataDeprecated) {
+ var deprecatedTags = entity.deprecatedTags(_dataDeprecated);
- function showsLayer(id) {
- var layer = context.layers().layer(id);
- return layer && layer.supported() && layer.enabled();
- }
+ if (deprecatedTags.length) {
+ deprecatedTags.forEach(function (tag) {
+ graph = actionUpgradeTags(entity.id, tag.old, tag.replace)(graph);
+ });
+ entity = graph.entity(entity.id);
+ }
+ } // Add missing addTags from the detected preset
- photos.shouldFilterByDate = function () {
- return showsLayer('mapillary') || showsLayer('openstreetcam') || showsLayer('streetside');
- };
- photos.shouldFilterByPhotoType = function () {
- return showsLayer('mapillary') || showsLayer('streetside') && showsLayer('openstreetcam');
- };
+ var newTags = Object.assign({}, entity.tags); // shallow copy
- photos.shouldFilterByUsername = function () {
- return !showsLayer('mapillary') && showsLayer('openstreetcam') && !showsLayer('streetside');
- };
+ if (preset.tags !== preset.addTags) {
+ Object.keys(preset.addTags).forEach(function (k) {
+ if (!newTags[k]) {
+ if (preset.addTags[k] === '*') {
+ newTags[k] = 'yes';
+ } else {
+ newTags[k] = preset.addTags[k];
+ }
+ }
+ });
+ } // Attempt to match a canonical record in the name-suggestion-index.
- photos.showsPhotoType = function (val) {
- if (!photos.shouldFilterByPhotoType()) return true;
- return _shownPhotoTypes.indexOf(val) !== -1;
- };
- photos.showsFlat = function () {
- return photos.showsPhotoType('flat');
- };
+ var nsi = services.nsi;
+ var waitingForNsi = false;
+ var nsiResult;
- photos.showsPanoramic = function () {
- return photos.showsPhotoType('panoramic');
- };
+ if (nsi) {
+ waitingForNsi = nsi.status() === 'loading';
- photos.fromDate = function () {
- return _fromDate;
- };
+ if (!waitingForNsi) {
+ var loc = entity.extent(graph).center();
+ nsiResult = nsi.upgradeTags(newTags, loc);
- photos.toDate = function () {
- return _toDate;
- };
+ if (nsiResult) {
+ newTags = nsiResult.newTags;
+ subtype = 'noncanonical_brand';
+ }
+ }
+ }
- photos.togglePhotoType = function (val) {
- var index = _shownPhotoTypes.indexOf(val);
+ var issues = [];
+ issues.provisional = _waitingForDeprecated || waitingForNsi; // determine diff
- if (index !== -1) {
- _shownPhotoTypes.splice(index, 1);
- } else {
- _shownPhotoTypes.push(val);
- }
+ var tagDiff = utilTagDiff(oldTags, newTags);
+ if (!tagDiff.length) return issues;
+ var isOnlyAddingTags = tagDiff.every(function (d) {
+ return d.type === '+';
+ });
+ var prefix = '';
- dispatch.call('change', this);
- return photos;
- };
+ if (nsiResult) {
+ prefix = 'noncanonical_brand.';
+ } else if (subtype === 'deprecated_tags' && isOnlyAddingTags) {
+ subtype = 'incomplete_tags';
+ prefix = 'incomplete.';
+ } // don't allow autofixing brand tags
- photos.usernames = function () {
- return _usernames;
- };
- photos.init = function () {
- var hash = utilStringQs(window.location.hash);
+ var autoArgs = subtype !== 'noncanonical_brand' ? [doUpgrade, _t('issues.fix.upgrade_tags.annotation')] : null;
+ issues.push(new validationIssue({
+ type: type,
+ subtype: subtype,
+ severity: 'warning',
+ message: showMessage,
+ reference: showReference,
+ entityIds: [entity.id],
+ hash: utilHashcode(JSON.stringify(tagDiff)),
+ dynamicFixes: function dynamicFixes() {
+ var fixes = [new validationIssueFix({
+ autoArgs: autoArgs,
+ title: _t.html('issues.fix.upgrade_tags.title'),
+ onClick: function onClick(context) {
+ context.perform(doUpgrade, _t('issues.fix.upgrade_tags.annotation'));
+ }
+ })];
+ var item = nsiResult && nsiResult.matched;
- if (hash.photo_dates) {
- // expect format like `photo_dates=2019-01-01_2020-12-31`, but allow a couple different separators
- var parts = /^(.*)[–_](.*)$/g.exec(hash.photo_dates.trim());
- this.setDateFilter('fromDate', parts && parts.length >= 2 && parts[1], false);
- this.setDateFilter('toDate', parts && parts.length >= 3 && parts[2], false);
- }
+ if (item) {
+ fixes.push(new validationIssueFix({
+ title: _t.html('issues.fix.tag_as_not.title', {
+ name: item.displayName
+ }),
+ onClick: function onClick(context) {
+ context.perform(addNotTag, _t('issues.fix.tag_as_not.annotation'));
+ }
+ }));
+ }
- if (hash.photo_username) {
- this.setUsernameFilter(hash.photo_username, false);
- }
+ return fixes;
+ }
+ }));
+ return issues;
- if (hash.photo_overlay) {
- // support enabling photo layers by default via a URL parameter, e.g. `photo_overlay=openstreetcam;mapillary;streetside`
- var hashOverlayIDs = hash.photo_overlay.replace(/;/g, ',').split(',');
- hashOverlayIDs.forEach(function (id) {
- var layer = _layerIDs.indexOf(id) !== -1 && context.layers().layer(id);
- if (layer && !layer.enabled()) layer.enabled(true);
+ function doUpgrade(graph) {
+ var currEntity = graph.hasEntity(entity.id);
+ if (!currEntity) return graph;
+ var newTags = Object.assign({}, currEntity.tags); // shallow copy
+
+ tagDiff.forEach(function (diff) {
+ if (diff.type === '-') {
+ delete newTags[diff.key];
+ } else if (diff.type === '+') {
+ newTags[diff.key] = diff.newVal;
+ }
});
+ return actionChangeTags(currEntity.id, newTags)(graph);
}
- if (hash.photo) {
- // support opening a photo via a URL parameter, e.g. `photo=mapillary-fztgSDtLpa08ohPZFZjeRQ`
- var photoIds = hash.photo.replace(/;/g, ',').split(',');
- var photoId = photoIds.length && photoIds[0].trim();
- var results = /(.*)\/(.*)/g.exec(photoId);
+ function addNotTag(graph) {
+ var currEntity = graph.hasEntity(entity.id);
+ if (!currEntity) return graph;
+ var item = nsiResult && nsiResult.matched;
+ if (!item) return graph;
+ var newTags = Object.assign({}, currEntity.tags); // shallow copy
- if (results && results.length >= 3) {
- var serviceId = results[1];
- var photoKey = results[2];
- var service = services[serviceId];
+ var wd = item.mainTag; // e.g. `brand:wikidata`
- if (service && service.ensureViewerLoaded) {
- // if we're showing a photo then make sure its layer is enabled too
- var layer = _layerIDs.indexOf(serviceId) !== -1 && context.layers().layer(serviceId);
- if (layer && !layer.enabled()) layer.enabled(true);
- var baselineTime = Date.now();
- service.on('loadedImages.rendererPhotos', function () {
- // don't open the viewer if too much time has elapsed
- if (Date.now() - baselineTime > 45000) {
- service.on('loadedImages.rendererPhotos', null);
- return;
- }
+ var notwd = "not:".concat(wd); // e.g. `not:brand:wikidata`
- if (!service.cachedImage(photoKey)) return;
- service.on('loadedImages.rendererPhotos', null);
- service.ensureViewerLoaded(context).then(function () {
- service.selectImage(context, photoKey).showViewer(context);
- });
- });
- }
- }
- }
+ var qid = item.tags[wd];
+ newTags[notwd] = qid;
- context.layers().on('change.rendererPhotos', updateStorage);
- };
+ if (newTags[wd] === qid) {
+ // if `brand:wikidata` was set to that qid
+ var wp = item.mainTag.replace('wikidata', 'wikipedia');
+ delete newTags[wd]; // remove `brand:wikidata`
- return utilRebind(photos, dispatch, 'on');
- }
+ delete newTags[wp]; // remove `brand:wikipedia`
+ }
- function uiAccount(context) {
- var osm = context.connection();
+ return actionChangeTags(currEntity.id, newTags)(graph);
+ }
- function update(selection) {
- if (!osm) return;
+ function showMessage(context) {
+ var currEntity = context.hasEntity(entity.id);
+ if (!currEntity) return '';
+ var messageID = "issues.outdated_tags.".concat(prefix, "message");
- if (!osm.authenticated()) {
- selection.selectAll('.userLink, .logoutLink').classed('hide', true);
- return;
- }
+ if (subtype === 'noncanonical_brand' && isOnlyAddingTags) {
+ messageID += '_incomplete';
+ }
- osm.userDetails(function (err, details) {
- var userLink = selection.select('.userLink'),
- logoutLink = selection.select('.logoutLink');
- userLink.html('');
- logoutLink.html('');
- if (err || !details) return;
- selection.selectAll('.userLink, .logoutLink').classed('hide', false); // Link
-
- var userLinkA = userLink.append('a').attr('href', osm.userURL(details.display_name)).attr('target', '_blank'); // Add thumbnail or dont
-
- if (details.image_url) {
- userLinkA.append('img').attr('class', 'icon pre-text user-icon').attr('src', details.image_url);
- } else {
- userLinkA.call(svgIcon('#iD-icon-avatar', 'pre-text light'));
- } // Add user name
-
-
- userLinkA.append('span').attr('class', 'label').html(details.display_name);
- logoutLink.append('a').attr('class', 'logout').attr('href', '#').html(_t.html('logout')).on('click.logout', function (d3_event) {
- d3_event.preventDefault();
- osm.logout();
+ return _t.html(messageID, {
+ feature: utilDisplayLabel(currEntity, context.graph(), true
+ /* verbose */
+ )
});
- });
- }
-
- return function (selection) {
- selection.append('li').attr('class', 'userLink').classed('hide', true);
- selection.append('li').attr('class', 'logoutLink').classed('hide', true);
+ }
- if (osm) {
- osm.on('change.account', function () {
- update(selection);
+ function showReference(selection) {
+ var enter = selection.selectAll('.issue-reference').data([0]).enter();
+ enter.append('div').attr('class', 'issue-reference').call(_t.append("issues.outdated_tags.".concat(prefix, "reference")));
+ enter.append('strong').call(_t.append('issues.suggested'));
+ enter.append('table').attr('class', 'tagDiff-table').selectAll('.tagDiff-row').data(tagDiff).enter().append('tr').attr('class', 'tagDiff-row').append('td').attr('class', function (d) {
+ var klass = d.type === '+' ? 'add' : 'remove';
+ return "tagDiff-cell tagDiff-cell-".concat(klass);
+ }).html(function (d) {
+ return d.display;
});
- update(selection);
}
- };
- }
+ }
- function uiAttribution(context) {
- var _selection = select(null);
+ function oldMultipolygonIssues(entity, graph) {
+ var multipolygon, outerWay;
- function render(selection, data, klass) {
- var div = selection.selectAll(".".concat(klass)).data([0]);
- div = div.enter().append('div').attr('class', klass).merge(div);
- var attributions = div.selectAll('.attribution').data(data, function (d) {
- return d.id;
- });
- attributions.exit().remove();
- attributions = attributions.enter().append('span').attr('class', 'attribution').each(function (d, i, nodes) {
- var attribution = select(nodes[i]);
+ if (entity.type === 'relation') {
+ outerWay = osmOldMultipolygonOuterMemberOfRelation(entity, graph);
+ multipolygon = entity;
+ } else if (entity.type === 'way') {
+ multipolygon = osmIsOldMultipolygonOuterMember(entity, graph);
+ outerWay = entity;
+ } else {
+ return [];
+ }
- if (d.terms_html) {
- attribution.html(d.terms_html);
- return;
+ if (!multipolygon || !outerWay) return [];
+ return [new validationIssue({
+ type: type,
+ subtype: 'old_multipolygon',
+ severity: 'warning',
+ message: showMessage,
+ reference: showReference,
+ entityIds: [outerWay.id, multipolygon.id],
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ autoArgs: [doUpgrade, _t('issues.fix.move_tags.annotation')],
+ title: _t.html('issues.fix.move_tags.title'),
+ onClick: function onClick(context) {
+ context.perform(doUpgrade, _t('issues.fix.move_tags.annotation'));
+ }
+ })];
}
+ })];
- if (d.terms_url) {
- attribution = attribution.append('a').attr('href', d.terms_url).attr('target', '_blank');
- }
+ function doUpgrade(graph) {
+ var currMultipolygon = graph.hasEntity(multipolygon.id);
+ var currOuterWay = graph.hasEntity(outerWay.id);
+ if (!currMultipolygon || !currOuterWay) return graph;
+ currMultipolygon = currMultipolygon.mergeTags(currOuterWay.tags);
+ graph = graph.replace(currMultipolygon);
+ return actionChangeTags(currOuterWay.id, {})(graph);
+ }
- var sourceID = d.id.replace(/\./g, '<TX_DOT>');
- var terms_text = _t("imagery.".concat(sourceID, ".attribution.text"), {
- "default": d.terms_text || d.id || d.name()
+ function showMessage(context) {
+ var currMultipolygon = context.hasEntity(multipolygon.id);
+ if (!currMultipolygon) return '';
+ return _t.html('issues.old_multipolygon.message', {
+ multipolygon: utilDisplayLabel(currMultipolygon, context.graph(), true
+ /* verbose */
+ )
});
+ }
- if (d.icon && !d.overlay) {
- attribution.append('img').attr('class', 'source-image').attr('src', d.icon);
- }
-
- attribution.append('span').attr('class', 'attribution-text').html(terms_text);
- }).merge(attributions);
- var copyright = attributions.selectAll('.copyright-notice').data(function (d) {
- var notice = d.copyrightNotices(context.map().zoom(), context.map().extent());
- return notice ? [notice] : [];
- });
- copyright.exit().remove();
- copyright = copyright.enter().append('span').attr('class', 'copyright-notice').merge(copyright);
- copyright.html(String);
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.old_multipolygon.reference'));
+ }
}
- function update() {
- var baselayer = context.background().baseLayerSource();
+ var validation = function checkOutdatedTags(entity, graph) {
+ var issues = oldMultipolygonIssues(entity, graph);
+ if (!issues.length) issues = oldTagIssues(entity, graph);
+ return issues;
+ };
- _selection.call(render, baselayer ? [baselayer] : [], 'base-layer-attribution');
+ validation.type = type;
+ return validation;
+ }
- var z = context.map().zoom();
- var overlays = context.background().overlayLayerSources() || [];
+ function validationPrivateData() {
+ var type = 'private_data'; // assume that some buildings are private
- _selection.call(render, overlays.filter(function (s) {
- return s.validZoom(z);
- }), 'overlay-layer-attribution');
- }
+ var privateBuildingValues = {
+ detached: true,
+ farm: true,
+ house: true,
+ houseboat: true,
+ residential: true,
+ semidetached_house: true,
+ static_caravan: true
+ }; // but they might be public if they have one of these other tags
- return function (selection) {
- _selection = selection;
- context.background().on('change.attribution', update);
- context.map().on('move.attribution', throttle(update, 400, {
- leading: false
- }));
- update();
+ var publicKeys = {
+ amenity: true,
+ craft: true,
+ historic: true,
+ leisure: true,
+ office: true,
+ shop: true,
+ tourism: true
+ }; // these tags may contain personally identifying info
+
+ var personalTags = {
+ 'contact:email': true,
+ 'contact:fax': true,
+ 'contact:phone': true,
+ email: true,
+ fax: true,
+ phone: true
};
- }
- function uiContributors(context) {
- var osm = context.connection(),
- debouncedUpdate = debounce(function () {
- update();
- }, 1000),
- limit = 4,
- hidden = false,
- wrap = select(null);
+ var validation = function checkPrivateData(entity) {
+ var tags = entity.tags;
+ if (!tags.building || !privateBuildingValues[tags.building]) return [];
+ var keepTags = {};
- function update() {
- if (!osm) return;
- var users = {},
- entities = context.history().intersects(context.map().extent());
- entities.forEach(function (entity) {
- if (entity && entity.user) users[entity.user] = true;
- });
- var u = Object.keys(users),
- subset = u.slice(0, u.length > limit ? limit - 1 : limit);
- wrap.html('').call(svgIcon('#iD-icon-nearby', 'pre-text light'));
- var userList = select(document.createElement('span'));
- userList.selectAll().data(subset).enter().append('a').attr('class', 'user-link').attr('href', function (d) {
- return osm.userURL(d);
- }).attr('target', '_blank').html(String);
+ for (var k in tags) {
+ if (publicKeys[k]) return []; // probably a public feature
- if (u.length > limit) {
- var count = select(document.createElement('span'));
- var othersNum = u.length - limit + 1;
- count.append('a').attr('target', '_blank').attr('href', function () {
- return osm.changesetsURL(context.map().center(), context.map().zoom());
- }).html(othersNum);
- wrap.append('span').html(_t.html('contributors.truncated_list', {
- n: othersNum,
- users: userList.html(),
- count: count.html()
- }));
- } else {
- wrap.append('span').html(_t.html('contributors.list', {
- users: userList.html()
- }));
+ if (!personalTags[k]) {
+ keepTags[k] = tags[k];
+ }
}
- if (!u.length) {
- hidden = true;
- wrap.transition().style('opacity', 0);
- } else if (hidden) {
- wrap.transition().style('opacity', 1);
+ var tagDiff = utilTagDiff(tags, keepTags);
+ if (!tagDiff.length) return [];
+ var fixID = tagDiff.length === 1 ? 'remove_tag' : 'remove_tags';
+ return [new validationIssue({
+ type: type,
+ severity: 'warning',
+ message: showMessage,
+ reference: showReference,
+ entityIds: [entity.id],
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.' + fixID + '.title'),
+ onClick: function onClick(context) {
+ context.perform(doUpgrade, _t('issues.fix.upgrade_tags.annotation'));
+ }
+ })];
+ }
+ })];
+
+ function doUpgrade(graph) {
+ var currEntity = graph.hasEntity(entity.id);
+ if (!currEntity) return graph;
+ var newTags = Object.assign({}, currEntity.tags); // shallow copy
+
+ tagDiff.forEach(function (diff) {
+ if (diff.type === '-') {
+ delete newTags[diff.key];
+ } else if (diff.type === '+') {
+ newTags[diff.key] = diff.newVal;
+ }
+ });
+ return actionChangeTags(currEntity.id, newTags)(graph);
}
- }
- return function (selection) {
- if (!osm) return;
- wrap = selection;
- update();
- osm.on('loaded.contributors', debouncedUpdate);
- context.map().on('move.contributors', debouncedUpdate);
+ function showMessage(context) {
+ var currEntity = context.hasEntity(this.entityIds[0]);
+ if (!currEntity) return '';
+ return _t.html('issues.private_data.contact.message', {
+ feature: utilDisplayLabel(currEntity, context.graph())
+ });
+ }
+
+ function showReference(selection) {
+ var enter = selection.selectAll('.issue-reference').data([0]).enter();
+ enter.append('div').attr('class', 'issue-reference').call(_t.append('issues.private_data.reference'));
+ enter.append('strong').call(_t.append('issues.suggested'));
+ enter.append('table').attr('class', 'tagDiff-table').selectAll('.tagDiff-row').data(tagDiff).enter().append('tr').attr('class', 'tagDiff-row').append('td').attr('class', function (d) {
+ var klass = d.type === '+' ? 'add' : 'remove';
+ return 'tagDiff-cell tagDiff-cell-' + klass;
+ }).html(function (d) {
+ return d.display;
+ });
+ }
};
- }
- var _popoverID = 0;
- function uiPopover(klass) {
- var _id = _popoverID++;
+ validation.type = type;
+ return validation;
+ }
- var _anchorSelection = select(null);
+ function validationSuspiciousName() {
+ var type = 'suspicious_name';
+ var keysToTestForGenericValues = ['aerialway', 'aeroway', 'amenity', 'building', 'craft', 'highway', 'leisure', 'railway', 'man_made', 'office', 'shop', 'tourism', 'waterway'];
+ var _waitingForNsi = false; // Attempt to match a generic record in the name-suggestion-index.
- var popover = function popover(selection) {
- _anchorSelection = selection;
- selection.each(setup);
- };
+ function isGenericMatchInNsi(tags) {
+ var nsi = services.nsi;
- var _animation = utilFunctor(false);
+ if (nsi) {
+ _waitingForNsi = nsi.status() === 'loading';
- var _placement = utilFunctor('top'); // top, bottom, left, right
+ if (!_waitingForNsi) {
+ return nsi.isGenericName(tags);
+ }
+ }
+ return false;
+ } // Test if the name is just the key or tag value (e.g. "park")
- var _alignment = utilFunctor('center'); // leading, center, trailing
+ function nameMatchesRawTag(lowercaseName, tags) {
+ for (var i = 0; i < keysToTestForGenericValues.length; i++) {
+ var key = keysToTestForGenericValues[i];
+ var val = tags[key];
- var _scrollContainer = utilFunctor(select(null));
+ if (val) {
+ val = val.toLowerCase();
- var _content;
+ if (key === lowercaseName || val === lowercaseName || key.replace(/\_/g, ' ') === lowercaseName || val.replace(/\_/g, ' ') === lowercaseName) {
+ return true;
+ }
+ }
+ }
- var _displayType = utilFunctor('');
+ return false;
+ }
- var _hasArrow = utilFunctor(true); // use pointer events on supported platforms; fallback to mouse events
+ function isGenericName(name, tags) {
+ name = name.toLowerCase();
+ return nameMatchesRawTag(name, tags) || isGenericMatchInNsi(tags);
+ }
+ function makeGenericNameIssue(entityId, nameKey, genericName, langCode) {
+ return new validationIssue({
+ type: type,
+ subtype: 'generic_name',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ if (!entity) return '';
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ var langName = langCode && _mainLocalizer.languageName(langCode);
+ return _t.html('issues.generic_name.message' + (langName ? '_language' : ''), {
+ feature: preset.name(),
+ name: genericName,
+ language: langName
+ });
+ },
+ reference: showReference,
+ entityIds: [entityId],
+ hash: "".concat(nameKey, "=").concat(genericName),
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.remove_the_name.title'),
+ onClick: function onClick(context) {
+ var entityId = this.issue.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ delete tags[nameKey];
+ context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_generic_name.annotation'));
+ }
+ })];
+ }
+ });
- popover.displayType = function (val) {
- if (arguments.length) {
- _displayType = utilFunctor(val);
- return popover;
- } else {
- return _displayType;
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.generic_name.reference'));
}
- };
+ }
- popover.hasArrow = function (val) {
- if (arguments.length) {
- _hasArrow = utilFunctor(val);
- return popover;
- } else {
- return _hasArrow;
- }
- };
+ function makeIncorrectNameIssue(entityId, nameKey, incorrectName, langCode) {
+ return new validationIssue({
+ type: type,
+ subtype: 'not_name',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ if (!entity) return '';
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ var langName = langCode && _mainLocalizer.languageName(langCode);
+ return _t.html('issues.incorrect_name.message' + (langName ? '_language' : ''), {
+ feature: preset.name(),
+ name: incorrectName,
+ language: langName
+ });
+ },
+ reference: showReference,
+ entityIds: [entityId],
+ hash: "".concat(nameKey, "=").concat(incorrectName),
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-operation-delete',
+ title: _t.html('issues.fix.remove_the_name.title'),
+ onClick: function onClick(context) {
+ var entityId = this.issue.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- popover.placement = function (val) {
- if (arguments.length) {
- _placement = utilFunctor(val);
- return popover;
- } else {
- return _placement;
- }
- };
+ delete tags[nameKey];
+ context.perform(actionChangeTags(entityId, tags), _t('issues.fix.remove_mistaken_name.annotation'));
+ }
+ })];
+ }
+ });
- popover.alignment = function (val) {
- if (arguments.length) {
- _alignment = utilFunctor(val);
- return popover;
- } else {
- return _alignment;
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.generic_name.reference'));
}
- };
+ }
- popover.scrollContainer = function (val) {
- if (arguments.length) {
- _scrollContainer = utilFunctor(val);
- return popover;
- } else {
- return _scrollContainer;
- }
- };
+ var validation = function checkGenericName(entity) {
+ var tags = entity.tags; // a generic name is allowed if it's a known brand or entity
- popover.content = function (val) {
- if (arguments.length) {
- _content = val;
- return popover;
- } else {
- return _content;
- }
- };
+ var hasWikidata = !!tags.wikidata || !!tags['brand:wikidata'] || !!tags['operator:wikidata'];
+ if (hasWikidata) return [];
+ var issues = [];
+ var notNames = (tags['not:name'] || '').split(';');
- popover.isShown = function () {
- var popoverSelection = _anchorSelection.select('.popover-' + _id);
+ for (var key in tags) {
+ var m = key.match(/^name(?:(?::)([a-zA-Z_-]+))?$/);
+ if (!m) continue;
+ var langCode = m.length >= 2 ? m[1] : null;
+ var value = tags[key];
- return !popoverSelection.empty() && popoverSelection.classed('in');
- };
+ if (notNames.length) {
+ for (var i in notNames) {
+ var notName = notNames[i];
- popover.show = function () {
- _anchorSelection.each(show);
- };
+ if (notName && value === notName) {
+ issues.push(makeIncorrectNameIssue(entity.id, key, value, langCode));
+ continue;
+ }
+ }
+ }
- popover.updateContent = function () {
- _anchorSelection.each(updateContent);
- };
+ if (isGenericName(value, tags)) {
+ issues.provisional = _waitingForNsi; // retry later if we are waiting on NSI to finish loading
- popover.hide = function () {
- _anchorSelection.each(hide);
- };
+ issues.push(makeGenericNameIssue(entity.id, key, value, langCode));
+ }
+ }
- popover.toggle = function () {
- _anchorSelection.each(toggle);
+ return issues;
};
- popover.destroy = function (selection, selector) {
- // by default, just destroy the current popover
- selector = selector || '.popover-' + _id;
- selection.on(_pointerPrefix + 'enter.popover', null).on(_pointerPrefix + 'leave.popover', null).on(_pointerPrefix + 'up.popover', null).on(_pointerPrefix + 'down.popover', null).on('click.popover', null).attr('title', function () {
- return this.getAttribute('data-original-title') || this.getAttribute('title');
- }).attr('data-original-title', null).selectAll(selector).remove();
- };
+ validation.type = type;
+ return validation;
+ }
- popover.destroyAny = function (selection) {
- selection.call(popover.destroy, '.popover');
- };
+ function validationUnsquareWay(context) {
+ var type = 'unsquare_way';
+ var DEFAULT_DEG_THRESHOLD = 5; // see also issues.js
+ // use looser epsilon for detection to reduce warnings of buildings that are essentially square already
- function setup() {
- var anchor = select(this);
+ var epsilon = 0.05;
+ var nodeThreshold = 10;
- var animate = _animation.apply(this, arguments);
+ function isBuilding(entity, graph) {
+ if (entity.type !== 'way' || entity.geometry(graph) !== 'area') return false;
+ return entity.tags.building && entity.tags.building !== 'no';
+ }
- var popoverSelection = anchor.selectAll('.popover-' + _id).data([0]);
- var enter = popoverSelection.enter().append('div').attr('class', 'popover popover-' + _id + ' ' + (klass ? klass : '')).classed('arrowed', _hasArrow.apply(this, arguments));
- enter.append('div').attr('class', 'popover-arrow');
- enter.append('div').attr('class', 'popover-inner');
- popoverSelection = enter.merge(popoverSelection);
-
- if (animate) {
- popoverSelection.classed('fade', true);
- }
-
- var display = _displayType.apply(this, arguments);
+ var validation = function checkUnsquareWay(entity, graph) {
+ if (!isBuilding(entity, graph)) return []; // don't flag ways marked as physically unsquare
- if (display === 'hover') {
- var _lastNonMouseEnterTime;
+ if (entity.tags.nonsquare === 'yes') return [];
+ var isClosed = entity.isClosed();
+ if (!isClosed) return []; // this building has bigger problems
+ // don't flag ways with lots of nodes since they are likely detail-mapped
- anchor.on(_pointerPrefix + 'enter.popover', function (d3_event) {
- if (d3_event.pointerType) {
- if (d3_event.pointerType !== 'mouse') {
- _lastNonMouseEnterTime = d3_event.timeStamp; // only allow hover behavior for mouse input
+ var nodes = graph.childNodes(entity).slice(); // shallow copy
- return;
- } else if (_lastNonMouseEnterTime && d3_event.timeStamp - _lastNonMouseEnterTime < 1500) {
- // HACK: iOS 13.4 sends an erroneous `mouse` type pointerenter
- // event for non-mouse interactions right after sending
- // the correct type pointerenter event. Workaround by discarding
- // any mouse event that occurs immediately after a non-mouse event.
- return;
- }
- } // don't show if buttons are pressed, e.g. during click and drag of map
+ if (nodes.length > nodeThreshold + 1) return []; // +1 because closing node appears twice
+ // ignore if not all nodes are fully downloaded
+ var osm = services.osm;
+ if (!osm || nodes.some(function (node) {
+ return !osm.isDataLoaded(node.loc);
+ })) return []; // don't flag connected ways to avoid unresolvable unsquare loops
- if (d3_event.buttons !== 0) return;
- show.apply(this, arguments);
- }).on(_pointerPrefix + 'leave.popover', function () {
- hide.apply(this, arguments);
- }) // show on focus too for better keyboard navigation support
- .on('focus.popover', function () {
- show.apply(this, arguments);
- }).on('blur.popover', function () {
- hide.apply(this, arguments);
- });
- } else if (display === 'clickFocus') {
- anchor.on(_pointerPrefix + 'down.popover', function (d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- }).on(_pointerPrefix + 'up.popover', function (d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- }).on('click.popover', toggle);
- popoverSelection // This attribute lets the popover take focus
- .attr('tabindex', 0).on('blur.popover', function () {
- anchor.each(function () {
- hide.apply(this, arguments);
+ var hasConnectedSquarableWays = nodes.some(function (node) {
+ return graph.parentWays(node).some(function (way) {
+ if (way.id === entity.id) return false;
+ if (isBuilding(way, graph)) return true;
+ return graph.parentRelations(way).some(function (parentRelation) {
+ return parentRelation.isMultipolygon() && parentRelation.tags.building && parentRelation.tags.building !== 'no';
});
});
- }
- }
+ });
+ if (hasConnectedSquarableWays) return []; // user-configurable square threshold
- function show() {
- var anchor = select(this);
- var popoverSelection = anchor.selectAll('.popover-' + _id);
+ var storedDegreeThreshold = corePreferences('validate-square-degrees');
+ var degreeThreshold = isNaN(storedDegreeThreshold) ? DEFAULT_DEG_THRESHOLD : parseFloat(storedDegreeThreshold);
+ var points = nodes.map(function (node) {
+ return context.projection(node.loc);
+ });
+ if (!geoOrthoCanOrthogonalize(points, isClosed, epsilon, degreeThreshold, true)) return [];
+ var autoArgs; // don't allow autosquaring features linked to wikidata
- if (popoverSelection.empty()) {
- // popover was removed somehow, put it back
- anchor.call(popover.destroy);
- anchor.each(setup);
- popoverSelection = anchor.selectAll('.popover-' + _id);
+ if (!entity.tags.wikidata) {
+ // use same degree threshold as for detection
+ var autoAction = actionOrthogonalize(entity.id, context.projection, undefined, degreeThreshold);
+ autoAction.transitionable = false; // when autofixing, do it instantly
+
+ autoArgs = [autoAction, _t('operations.orthogonalize.annotation.feature', {
+ n: 1
+ })];
}
- popoverSelection.classed('in', true);
+ return [new validationIssue({
+ type: type,
+ subtype: 'building',
+ severity: 'warning',
+ message: function message(context) {
+ var entity = context.hasEntity(this.entityIds[0]);
+ return entity ? _t.html('issues.unsquare_way.message', {
+ feature: utilDisplayLabel(entity, context.graph())
+ }) : '';
+ },
+ reference: showReference,
+ entityIds: [entity.id],
+ hash: degreeThreshold,
+ dynamicFixes: function dynamicFixes() {
+ return [new validationIssueFix({
+ icon: 'iD-operation-orthogonalize',
+ title: _t.html('issues.fix.square_feature.title'),
+ autoArgs: autoArgs,
+ onClick: function onClick(context, completionHandler) {
+ var entityId = this.issue.entityIds[0]; // use same degree threshold as for detection
- var displayType = _displayType.apply(this, arguments);
+ context.perform(actionOrthogonalize(entityId, context.projection, undefined, degreeThreshold), _t('operations.orthogonalize.annotation.feature', {
+ n: 1
+ })); // run after the squaring transition (currently 150ms)
- if (displayType === 'clickFocus') {
- anchor.classed('active', true);
- popoverSelection.node().focus();
+ window.setTimeout(function () {
+ completionHandler();
+ }, 175);
+ }
+ })
+ /*
+ new validationIssueFix({
+ title: t.html('issues.fix.tag_as_unsquare.title'),
+ onClick: function(context) {
+ var entityId = this.issue.entityIds[0];
+ var entity = context.entity(entityId);
+ var tags = Object.assign({}, entity.tags); // shallow copy
+ tags.nonsquare = 'yes';
+ context.perform(
+ actionChangeTags(entityId, tags),
+ t('issues.fix.tag_as_unsquare.annotation')
+ );
+ }
+ })
+ */
+ ];
+ }
+ })];
+
+ function showReference(selection) {
+ selection.selectAll('.issue-reference').data([0]).enter().append('div').attr('class', 'issue-reference').call(_t.append('issues.unsquare_way.buildings.reference'));
}
+ };
- anchor.each(updateContent);
- }
+ validation.type = type;
+ return validation;
+ }
- function updateContent() {
- var anchor = select(this);
+ var Validations = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ validationAlmostJunction: validationAlmostJunction,
+ validationCloseNodes: validationCloseNodes,
+ validationCrossingWays: validationCrossingWays,
+ validationDisconnectedWay: validationDisconnectedWay,
+ validationFormatting: validationFormatting,
+ validationHelpRequest: validationHelpRequest,
+ validationImpossibleOneway: validationImpossibleOneway,
+ validationIncompatibleSource: validationIncompatibleSource,
+ validationMaprules: validationMaprules,
+ validationMismatchedGeometry: validationMismatchedGeometry,
+ validationMissingRole: validationMissingRole,
+ validationMissingTag: validationMissingTag,
+ validationOutdatedTags: validationOutdatedTags,
+ validationPrivateData: validationPrivateData,
+ validationSuspiciousName: validationSuspiciousName,
+ validationUnsquareWay: validationUnsquareWay
+ });
- if (_content) {
- anchor.selectAll('.popover-' + _id + ' > .popover-inner').call(_content.apply(this, arguments));
- }
+ function coreValidator(context) {
+ var _this = this;
- updatePosition.apply(this, arguments); // hack: update multiple times to fix instances where the absolute offset is
- // set before the dynamic popover size is calculated by the browser
+ var dispatch = dispatch$8('validated', 'focusedIssue');
+ var validator = utilRebind({}, dispatch, 'on');
+ var _rules = {};
+ var _disabledRules = {};
- updatePosition.apply(this, arguments);
- updatePosition.apply(this, arguments);
- }
+ var _ignoredIssueIDs = new Set();
- function updatePosition() {
- var anchor = select(this);
- var popoverSelection = anchor.selectAll('.popover-' + _id);
+ var _resolvedIssueIDs = new Set();
- var scrollContainer = _scrollContainer && _scrollContainer.apply(this, arguments);
+ var _baseCache = validationCache('base'); // issues before any user edits
- var scrollNode = scrollContainer && !scrollContainer.empty() && scrollContainer.node();
- var scrollLeft = scrollNode ? scrollNode.scrollLeft : 0;
- var scrollTop = scrollNode ? scrollNode.scrollTop : 0;
- var placement = _placement.apply(this, arguments);
+ var _headCache = validationCache('head'); // issues after all user edits
- popoverSelection.classed('left', false).classed('right', false).classed('top', false).classed('bottom', false).classed(placement, true);
- var alignment = _alignment.apply(this, arguments);
+ var _completeDiff = {}; // complete diff base -> head of what the user changed
- var alignFactor = 0.5;
+ var _headIsCurrent = false;
- if (alignment === 'leading') {
- alignFactor = 0;
- } else if (alignment === 'trailing') {
- alignFactor = 1;
- }
+ var _deferredRIC = new Set(); // Set( RequestIdleCallback handles )
- var anchorFrame = getFrame(anchor.node());
- var popoverFrame = getFrame(popoverSelection.node());
- var position;
- switch (placement) {
- case 'top':
- position = {
- x: anchorFrame.x + (anchorFrame.w - popoverFrame.w) * alignFactor,
- y: anchorFrame.y - popoverFrame.h
- };
- break;
+ var _deferredST = new Set(); // Set( SetTimeout handles )
- case 'bottom':
- position = {
- x: anchorFrame.x + (anchorFrame.w - popoverFrame.w) * alignFactor,
- y: anchorFrame.y + anchorFrame.h
- };
- break;
- case 'left':
- position = {
- x: anchorFrame.x - popoverFrame.w,
- y: anchorFrame.y + (anchorFrame.h - popoverFrame.h) * alignFactor
- };
- break;
+ var _headPromise; // Promise fulfilled when validation is performed up to headGraph snapshot
- case 'right':
- position = {
- x: anchorFrame.x + anchorFrame.w,
- y: anchorFrame.y + (anchorFrame.h - popoverFrame.h) * alignFactor
- };
- break;
- }
- if (position) {
- if (scrollNode && (placement === 'top' || placement === 'bottom')) {
- var initialPosX = position.x;
+ var RETRY = 5000; // wait 5sec before revalidating provisional entities
+ // Allow validation severity to be overridden by url queryparams...
+ // See: https://github.com/openstreetmap/iD/pull/8243
+ //
+ // Each param should contain a urlencoded comma separated list of
+ // `type/subtype` rules. `*` may be used as a wildcard..
+ // Examples:
+ // `validationError=disconnected_way/*`
+ // `validationError=disconnected_way/highway`
+ // `validationError=crossing_ways/bridge*`
+ // `validationError=crossing_ways/bridge*,crossing_ways/tunnel*`
- if (position.x + popoverFrame.w > scrollNode.offsetWidth - 10) {
- position.x = scrollNode.offsetWidth - 10 - popoverFrame.w;
- } else if (position.x < 10) {
- position.x = 10;
- }
+ var _errorOverrides = parseHashParam(context.initialHashParams.validationError);
- var arrow = anchor.selectAll('.popover-' + _id + ' > .popover-arrow'); // keep the arrow centered on the button, or as close as possible
+ var _warningOverrides = parseHashParam(context.initialHashParams.validationWarning);
- var arrowPosX = Math.min(Math.max(popoverFrame.w / 2 - (position.x - initialPosX), 10), popoverFrame.w - 10);
- arrow.style('left', ~~arrowPosX + 'px');
- }
+ var _disableOverrides = parseHashParam(context.initialHashParams.validationDisable); // `parseHashParam()` (private)
+ // Checks hash parameters for severity overrides
+ // Arguments
+ // `param` - a url hash parameter (`validationError`, `validationWarning`, or `validationDisable`)
+ // Returns
+ // Array of Objects like { type: RegExp, subtype: RegExp }
+ //
- popoverSelection.style('left', ~~position.x + 'px').style('top', ~~position.y + 'px');
- } else {
- popoverSelection.style('left', null).style('top', null);
- }
- function getFrame(node) {
- var positionStyle = select(node).style('position');
+ function parseHashParam(param) {
+ var result = [];
+ var rules = (param || '').split(',');
+ rules.forEach(function (rule) {
+ rule = rule.trim();
+ var parts = rule.split('/', 2); // "type/subtype"
- if (positionStyle === 'absolute' || positionStyle === 'static') {
- return {
- x: node.offsetLeft - scrollLeft,
- y: node.offsetTop - scrollTop,
- w: node.offsetWidth,
- h: node.offsetHeight
- };
- } else {
- return {
- x: 0,
- y: 0,
- w: node.offsetWidth,
- h: node.offsetHeight
- };
- }
- }
- }
+ var type = parts[0];
+ var subtype = parts[1] || '*';
+ if (!type || !subtype) return;
+ result.push({
+ type: makeRegExp(type),
+ subtype: makeRegExp(subtype)
+ });
+ });
+ return result;
- function hide() {
- var anchor = select(this);
+ function makeRegExp(str) {
+ var escaped = str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&') // escape all reserved chars except for the '*'
+ .replace(/\*/g, '.*'); // treat a '*' like '.*'
- if (_displayType.apply(this, arguments) === 'clickFocus') {
- anchor.classed('active', false);
+ return new RegExp('^' + escaped + '$');
}
+ } // `init()`
+ // Initialize the validator, called once on iD startup
+ //
- anchor.selectAll('.popover-' + _id).classed('in', false);
- }
-
- function toggle() {
- if (select(this).select('.popover-' + _id).classed('in')) {
- hide.apply(this, arguments);
- } else {
- show.apply(this, arguments);
- }
- }
- return popover;
- }
+ validator.init = function () {
+ Object.values(Validations).forEach(function (validation) {
+ if (typeof validation !== 'function') return;
+ var fn = validation(context);
+ var key = fn.type;
+ _rules[key] = fn;
+ });
+ var disabledRules = corePreferences('validate-disabledRules');
- function uiTooltip(klass) {
- var tooltip = uiPopover((klass || '') + ' tooltip').displayType('hover');
+ if (disabledRules) {
+ disabledRules.split(',').forEach(function (k) {
+ return _disabledRules[k] = true;
+ });
+ }
+ }; // `reset()` (private)
+ // Cancels deferred work and resets all caches
+ //
+ // Arguments
+ // `resetIgnored` - `true` to clear the list of user-ignored issues
+ //
- var _title = function _title() {
- var title = this.getAttribute('data-original-title');
- if (title) {
- return title;
- } else {
- title = this.getAttribute('title');
- this.removeAttribute('title');
- this.setAttribute('data-original-title', title);
- }
+ function reset(resetIgnored) {
+ // cancel deferred work
+ _deferredRIC.forEach(window.cancelIdleCallback);
- return title;
- };
+ _deferredRIC.clear();
- var _heading = utilFunctor(null);
+ _deferredST.forEach(window.clearTimeout);
- var _keys = utilFunctor(null);
+ _deferredST.clear(); // empty queues and resolve any pending promise
- tooltip.title = function (val) {
- if (!arguments.length) return _title;
- _title = utilFunctor(val);
- return tooltip;
- };
- tooltip.heading = function (val) {
- if (!arguments.length) return _heading;
- _heading = utilFunctor(val);
- return tooltip;
- };
+ _baseCache.queue = [];
+ _headCache.queue = [];
+ processQueue(_headCache);
+ processQueue(_baseCache); // clear caches
- tooltip.keys = function (val) {
- if (!arguments.length) return _keys;
- _keys = utilFunctor(val);
- return tooltip;
- };
+ if (resetIgnored) _ignoredIssueIDs.clear();
- tooltip.content(function () {
- var heading = _heading.apply(this, arguments);
+ _resolvedIssueIDs.clear();
- var text = _title.apply(this, arguments);
+ _baseCache = validationCache('base');
+ _headCache = validationCache('head');
+ _completeDiff = {};
+ _headIsCurrent = false;
+ } // `reset()`
+ // clear caches, called whenever iD resets after a save or switches sources
+ // (clears out the _ignoredIssueIDs set also)
+ //
- var keys = _keys.apply(this, arguments);
- return function (selection) {
- var headingSelect = selection.selectAll('.tooltip-heading').data(heading ? [heading] : []);
- headingSelect.exit().remove();
- headingSelect.enter().append('div').attr('class', 'tooltip-heading').merge(headingSelect).html(heading);
- var textSelect = selection.selectAll('.tooltip-text').data(text ? [text] : []);
- textSelect.exit().remove();
- textSelect.enter().append('div').attr('class', 'tooltip-text').merge(textSelect).html(text);
- var keyhintWrap = selection.selectAll('.keyhint-wrap').data(keys && keys.length ? [0] : []);
- keyhintWrap.exit().remove();
- var keyhintWrapEnter = keyhintWrap.enter().append('div').attr('class', 'keyhint-wrap');
- keyhintWrapEnter.append('span').html(_t.html('tooltip_keyhint'));
- keyhintWrap = keyhintWrapEnter.merge(keyhintWrap);
- keyhintWrap.selectAll('kbd.shortcut').data(keys && keys.length ? keys : []).enter().append('kbd').attr('class', 'shortcut').html(function (d) {
- return d;
- });
- };
- });
- return tooltip;
- }
+ validator.reset = function () {
+ reset(true);
+ }; // `resetIgnoredIssues()`
+ // clears out the _ignoredIssueIDs Set
+ //
- function uiEditMenu(context) {
- var dispatch = dispatch$8('toggled');
- var _menu = select(null);
+ validator.resetIgnoredIssues = function () {
+ _ignoredIssueIDs.clear();
- var _operations = []; // the position the menu should be displayed relative to
+ dispatch.call('validated'); // redraw UI
+ }; // `revalidateUnsquare()`
+ // Called whenever the user changes the unsquare threshold
+ // It reruns just the "unsquare_way" validation on all buildings.
+ //
- var _anchorLoc = [0, 0];
- var _anchorLocLonLat = [0, 0]; // a string indicating how the menu was opened
- var _triggerType = '';
- var _vpTopMargin = 85; // viewport top margin
+ validator.revalidateUnsquare = function () {
+ revalidateUnsquare(_headCache);
+ revalidateUnsquare(_baseCache);
+ dispatch.call('validated');
+ };
- var _vpBottomMargin = 45; // viewport bottom margin
+ function revalidateUnsquare(cache) {
+ var checkUnsquareWay = _rules.unsquare_way;
+ if (!cache.graph || typeof checkUnsquareWay !== 'function') return; // uncache existing
- var _vpSideMargin = 35; // viewport side margin
+ cache.uncacheIssuesOfType('unsquare_way');
+ var buildings = context.history().tree().intersects(geoExtent([-180, -90], [180, 90]), cache.graph) // everywhere
+ .filter(function (entity) {
+ return entity.type === 'way' && entity.tags.building && entity.tags.building !== 'no';
+ }); // rerun for all buildings
- var _menuTop = false;
+ buildings.forEach(function (entity) {
+ var detected = checkUnsquareWay(entity, cache.graph);
+ if (!detected.length) return;
+ cache.cacheIssues(detected);
+ });
+ } // `getIssues()`
+ // Gets all issues that match the given options
+ // This is called by many other places
+ //
+ // Arguments
+ // `options` Object like:
+ // {
+ // what: 'all', // 'all' or 'edited'
+ // where: 'all', // 'all' or 'visible'
+ // includeIgnored: false, // true, false, or 'only'
+ // includeDisabledRules: false // true, false, or 'only'
+ // }
+ //
+ // Returns
+ // An Array containing the issues
+ //
- var _menuHeight;
- var _menuWidth; // hardcode these values to make menu positioning easier
+ validator.getIssues = function (options) {
+ var opts = Object.assign({
+ what: 'all',
+ where: 'all',
+ includeIgnored: false,
+ includeDisabledRules: false
+ }, options);
+ var view = context.map().extent();
+ var seen = new Set();
+ var results = []; // collect head issues - present in the user edits
+ if (_headCache.graph && _headCache.graph !== _baseCache.graph) {
+ Object.values(_headCache.issuesByIssueID).forEach(function (issue) {
+ // In the head cache, only count features that the user is responsible for - #8632
+ // For example, a user can undo some work and an issue will still present in the
+ // head graph, but we don't want to credit the user for causing that issue.
+ var userModified = (issue.entityIds || []).some(function (id) {
+ return _completeDiff.hasOwnProperty(id);
+ });
+ if (opts.what === 'edited' && !userModified) return; // present in head but user didn't touch it
- var _verticalPadding = 4; // see also `.edit-menu .tooltip` CSS; include margin
+ if (!filter(issue)) return;
+ seen.add(issue.id);
+ results.push(issue);
+ });
+ } // collect base issues - present before user edits
- var _tooltipWidth = 210; // offset the menu slightly from the target location
- var _menuSideMargin = 10;
- var _tooltips = [];
-
- var editMenu = function editMenu(selection) {
- var isTouchMenu = _triggerType.includes('touch') || _triggerType.includes('pen');
+ if (opts.what === 'all') {
+ Object.values(_baseCache.issuesByIssueID).forEach(function (issue) {
+ if (!filter(issue)) return;
+ seen.add(issue.id);
+ results.push(issue);
+ });
+ }
- var ops = _operations.filter(function (op) {
- return !isTouchMenu || !op.mouseOnly;
- });
+ return results; // Filter the issue set to include only what the calling code wants to see.
+ // Note that we use `context.graph()`/`context.hasEntity()` here, not `cache.graph`,
+ // because that is the graph that the calling code will be using.
- if (!ops.length) return;
- _tooltips = []; // Position the menu above the anchor for stylus and finger input
- // since the mapper's hand likely obscures the screen below the anchor
+ function filter(issue) {
+ if (!issue) return false;
+ if (seen.has(issue.id)) return false;
+ if (_resolvedIssueIDs.has(issue.id)) return false;
+ if (opts.includeDisabledRules === 'only' && !_disabledRules[issue.type]) return false;
+ if (!opts.includeDisabledRules && _disabledRules[issue.type]) return false;
+ if (opts.includeIgnored === 'only' && !_ignoredIssueIDs.has(issue.id)) return false;
+ if (!opts.includeIgnored && _ignoredIssueIDs.has(issue.id)) return false; // This issue may involve an entity that doesn't exist in context.graph()
+ // This can happen because validation is async and rendering the issue lists is async.
- _menuTop = isTouchMenu; // Show labels for touch input since there aren't hover tooltips
+ if ((issue.entityIds || []).some(function (id) {
+ return !context.hasEntity(id);
+ })) return false;
- var showLabels = isTouchMenu;
- var buttonHeight = showLabels ? 32 : 34;
+ if (opts.where === 'visible') {
+ var extent = issue.extent(context.graph());
+ if (!view.intersects(extent)) return false;
+ }
- if (showLabels) {
- // Get a general idea of the width based on the length of the label
- _menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function (op) {
- return op.title.length;
- })));
- } else {
- _menuWidth = 44;
+ return true;
}
-
- _menuHeight = _verticalPadding * 2 + ops.length * buttonHeight;
- _menu = selection.append('div').attr('class', 'edit-menu').classed('touch-menu', isTouchMenu).style('padding', _verticalPadding + 'px 0');
-
- var buttons = _menu.selectAll('.edit-menu-item').data(ops); // enter
+ }; // `getResolvedIssues()`
+ // Gets the issues that have been fixed by the user.
+ //
+ // Resolved issues are tracked in the `_resolvedIssueIDs` Set,
+ // and they should all be issues that exist in the _baseCache.
+ //
+ // Returns
+ // An Array containing the issues
+ //
- var buttonsEnter = buttons.enter().append('button').attr('class', function (d) {
- return 'edit-menu-item edit-menu-item-' + d.id;
- }).style('height', buttonHeight + 'px').on('click', click) // don't listen for `mouseup` because we only care about non-mouse pointer types
- .on('pointerup', pointerup).on('pointerdown mousedown', function pointerdown(d3_event) {
- // don't let button presses also act as map input - #1869
- d3_event.stopPropagation();
- }).on('mouseenter.highlight', function (d3_event, d) {
- if (!d.relatedEntityIds || select(this).classed('disabled')) return;
- utilHighlightEntities(d.relatedEntityIds(), true, context);
- }).on('mouseleave.highlight', function (d3_event, d) {
- if (!d.relatedEntityIds) return;
- utilHighlightEntities(d.relatedEntityIds(), false, context);
- });
- buttonsEnter.each(function (d) {
- var tooltip = uiTooltip().heading(d.title).title(d.tooltip()).keys([d.keys[0]]);
+ validator.getResolvedIssues = function () {
+ return Array.from(_resolvedIssueIDs).map(function (issueID) {
+ return _baseCache.issuesByIssueID[issueID];
+ }).filter(Boolean);
+ }; // `focusIssue()`
+ // Adjusts the map to focus on the given issue.
+ // (requires the issue to have a reasonable extent defined)
+ //
+ // Arguments
+ // `issue` - the issue to focus on
+ //
- _tooltips.push(tooltip);
- select(this).call(tooltip).append('div').attr('class', 'icon-wrap').call(svgIcon('#iD-operation-' + d.id, 'operation'));
- });
+ validator.focusIssue = function (issue) {
+ // Note that we use `context.graph()`/`context.hasEntity()` here, not `cache.graph`,
+ // because that is the graph that the calling code will be using.
+ var graph = context.graph();
+ var selectID;
+ var focusCenter; // Try to focus the map at the center of the issue..
- if (showLabels) {
- buttonsEnter.append('span').attr('class', 'label').html(function (d) {
- return d.title;
- });
- } // update
+ var issueExtent = issue.extent(graph);
+ if (issueExtent) {
+ focusCenter = issueExtent.center();
+ } // Try to select the first entity in the issue..
- buttonsEnter.merge(buttons).classed('disabled', function (d) {
- return d.disabled();
- });
- updatePosition();
- var initialScale = context.projection.scale();
- context.map().on('move.edit-menu', function () {
- if (initialScale !== context.projection.scale()) {
- editMenu.close();
- }
- }).on('drawn.edit-menu', function (info) {
- if (info.full) updatePosition();
- });
- var lastPointerUpType; // `pointerup` is always called before `click`
- function pointerup(d3_event) {
- lastPointerUpType = d3_event.pointerType;
- }
+ if (issue.entityIds && issue.entityIds.length) {
+ selectID = issue.entityIds[0]; // If a relation, focus on one of its members instead.
+ // Otherwise we might be focusing on a part of map where the relation is not visible.
- function click(d3_event, operation) {
- d3_event.stopPropagation();
+ if (selectID && selectID.charAt(0) === 'r') {
+ // relation
+ var ids = utilEntityAndDeepMemberIDs([selectID], graph);
+ var nodeID = ids.find(function (id) {
+ return id.charAt(0) === 'n' && graph.hasEntity(id);
+ });
- if (operation.relatedEntityIds) {
- utilHighlightEntities(operation.relatedEntityIds(), false, context);
- }
+ if (!nodeID) {
+ // relation has no downloaded nodes to focus on
+ var wayID = ids.find(function (id) {
+ return id.charAt(0) === 'w' && graph.hasEntity(id);
+ });
- if (operation.disabled()) {
- if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
- // there are no tooltips for touch interactions so flash feedback instead
- context.ui().flash.duration(4000).iconName('#iD-operation-' + operation.id).iconClass('operation disabled').label(operation.tooltip)();
- }
- } else {
- if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
- context.ui().flash.duration(2000).iconName('#iD-operation-' + operation.id).iconClass('operation').label(operation.annotation() || operation.title)();
+ if (wayID) {
+ nodeID = graph.entity(wayID).first(); // focus on the first node of this way
+ }
}
- operation();
- editMenu.close();
+ if (nodeID) {
+ focusCenter = graph.entity(nodeID).loc;
+ }
}
-
- lastPointerUpType = null;
}
- dispatch.call('toggled', this, true);
- };
-
- function updatePosition() {
- if (!_menu || _menu.empty()) return;
- var anchorLoc = context.projection(_anchorLocLonLat);
- var viewport = context.surfaceRect();
+ if (focusCenter) {
+ // Adjust the view
+ var setZoom = Math.max(context.map().zoom(), 19);
+ context.map().unobscuredCenterZoomEase(focusCenter, setZoom);
+ }
- if (anchorLoc[0] < 0 || anchorLoc[0] > viewport.width || anchorLoc[1] < 0 || anchorLoc[1] > viewport.height) {
- // close the menu if it's gone offscreen
- editMenu.close();
- return;
+ if (selectID) {
+ // Enter select mode
+ window.setTimeout(function () {
+ context.enter(modeSelect(context, [selectID]));
+ dispatch.call('focusedIssue', _this, issue);
+ }, 250); // after ease
}
+ }; // `getIssuesBySeverity()`
+ // Gets the issues then groups them by error/warning
+ // (This just calls getIssues, then puts issues in groups)
+ //
+ // Arguments
+ // `options` - (see `getIssues`)
+ // Returns
+ // Object result like:
+ // {
+ // error: Array of errors,
+ // warning: Array of warnings
+ // }
+ //
- var menuLeft = displayOnLeft(viewport);
- var offset = [0, 0];
- offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin;
- if (_menuTop) {
- if (anchorLoc[1] - _menuHeight < _vpTopMargin) {
- // menu is near top viewport edge, shift downward
- offset[1] = -anchorLoc[1] + _vpTopMargin;
- } else {
- offset[1] = -_menuHeight;
- }
- } else {
- if (anchorLoc[1] + _menuHeight > viewport.height - _vpBottomMargin) {
- // menu is near bottom viewport edge, shift upwards
- offset[1] = -anchorLoc[1] - _menuHeight + viewport.height - _vpBottomMargin;
- } else {
- offset[1] = 0;
- }
- }
+ validator.getIssuesBySeverity = function (options) {
+ var groups = utilArrayGroupBy(validator.getIssues(options), 'severity');
+ groups.error = groups.error || [];
+ groups.warning = groups.warning || [];
+ return groups;
+ }; // `getEntityIssues()`
+ // Gets the issues that the given entity IDs have in common, matching the given options
+ // (This just calls getIssues, then filters for the given entity IDs)
+ // The issues are sorted for relevance
+ //
+ // Arguments
+ // `entityIDs` - Array or Set of entityIDs to get issues for
+ // `options` - (see `getIssues`)
+ // Returns
+ // An Array containing the issues
+ //
- var origin = geoVecAdd(anchorLoc, offset);
- _menu.style('left', origin[0] + 'px').style('top', origin[1] + 'px');
+ validator.getSharedEntityIssues = function (entityIDs, options) {
+ var orderedIssueTypes = [// Show some issue types in a particular order:
+ 'missing_tag', 'missing_role', // - missing data first
+ 'outdated_tags', 'mismatched_geometry', // - identity issues
+ 'crossing_ways', 'almost_junction', // - geometry issues where fixing them might solve connectivity issues
+ 'disconnected_way', 'impossible_oneway' // - finally connectivity issues
+ ];
+ var allIssues = validator.getIssues(options);
+ var forEntityIDs = new Set(entityIDs);
+ return allIssues.filter(function (issue) {
+ return (issue.entityIds || []).some(function (entityID) {
+ return forEntityIDs.has(entityID);
+ });
+ }).sort(function (issue1, issue2) {
+ if (issue1.type === issue2.type) {
+ // issues of the same type, sort deterministically
+ return issue1.id < issue2.id ? -1 : 1;
+ }
- var tooltipSide = tooltipPosition(viewport, menuLeft);
+ var index1 = orderedIssueTypes.indexOf(issue1.type);
+ var index2 = orderedIssueTypes.indexOf(issue2.type);
- _tooltips.forEach(function (tooltip) {
- tooltip.placement(tooltipSide);
+ if (index1 !== -1 && index2 !== -1) {
+ // both issue types have explicit sort orders
+ return index1 - index2;
+ } else if (index1 === -1 && index2 === -1) {
+ // neither issue type has an explicit sort order, sort by type
+ return issue1.type < issue2.type ? -1 : 1;
+ } else {
+ // order explicit types before everything else
+ return index1 !== -1 ? -1 : 1;
+ }
});
-
- function displayOnLeft(viewport) {
- if (_mainLocalizer.textDirection() === 'ltr') {
- if (anchorLoc[0] + _menuSideMargin + _menuWidth > viewport.width - _vpSideMargin) {
- // right menu would be too close to the right viewport edge, go left
- return true;
- } // prefer right menu
+ }; // `getEntityIssues()`
+ // Get an array of detected issues for the given entityID.
+ // (This just calls getSharedEntityIssues for a single entity)
+ //
+ // Arguments
+ // `entityID` - the entity ID to get the issues for
+ // `options` - (see `getIssues`)
+ // Returns
+ // An Array containing the issues
+ //
- return false;
- } else {
- // rtl
- if (anchorLoc[0] - _menuSideMargin - _menuWidth < _vpSideMargin) {
- // left menu would be too close to the left viewport edge, go right
- return false;
- } // prefer left menu
+ validator.getEntityIssues = function (entityID, options) {
+ return validator.getSharedEntityIssues([entityID], options);
+ }; // `getRuleKeys()`
+ //
+ // Returns
+ // An Array containing the rule keys
+ //
- return true;
- }
- }
+ validator.getRuleKeys = function () {
+ return Object.keys(_rules);
+ }; // `isRuleEnabled()`
+ //
+ // Arguments
+ // `key` - the rule to check (e.g. 'crossing_ways')
+ // Returns
+ // `true`/`false`
+ //
- function tooltipPosition(viewport, menuLeft) {
- if (_mainLocalizer.textDirection() === 'ltr') {
- if (menuLeft) {
- // if there's not room for a right-side menu then there definitely
- // isn't room for right-side tooltips
- return 'left';
- }
- if (anchorLoc[0] + _menuSideMargin + _menuWidth + _tooltipWidth > viewport.width - _vpSideMargin) {
- // right tooltips would be too close to the right viewport edge, go left
- return 'left';
- } // prefer right tooltips
+ validator.isRuleEnabled = function (key) {
+ return !_disabledRules[key];
+ }; // `toggleRule()`
+ // Toggles a single validation rule,
+ // then reruns the validation so that the user sees something happen in the UI
+ //
+ // Arguments
+ // `key` - the rule to toggle (e.g. 'crossing_ways')
+ //
- return 'right';
- } else {
- // rtl
- if (!menuLeft) {
- return 'right';
- }
+ validator.toggleRule = function (key) {
+ if (_disabledRules[key]) {
+ delete _disabledRules[key];
+ } else {
+ _disabledRules[key] = true;
+ }
- if (anchorLoc[0] - _menuSideMargin - _menuWidth - _tooltipWidth < _vpSideMargin) {
- // left tooltips would be too close to the left viewport edge, go right
- return 'right';
- } // prefer left tooltips
+ corePreferences('validate-disabledRules', Object.keys(_disabledRules).join(','));
+ validator.validate();
+ }; // `disableRules()`
+ // Disables given validation rules,
+ // then reruns the validation so that the user sees something happen in the UI
+ //
+ // Arguments
+ // `keys` - Array or Set containing rule keys to disable
+ //
- return 'left';
- }
- }
- }
+ validator.disableRules = function (keys) {
+ _disabledRules = {};
+ keys.forEach(function (k) {
+ return _disabledRules[k] = true;
+ });
+ corePreferences('validate-disabledRules', Object.keys(_disabledRules).join(','));
+ validator.validate();
+ }; // `ignoreIssue()`
+ // Don't show the given issue in lists
+ //
+ // Arguments
+ // `issueID` - the issueID
+ //
- editMenu.close = function () {
- context.map().on('move.edit-menu', null).on('drawn.edit-menu', null);
- _menu.remove();
+ validator.ignoreIssue = function (issueID) {
+ _ignoredIssueIDs.add(issueID);
+ }; // `validate()`
+ // Validates anything that has changed in the head graph since the last time it was run.
+ // (head graph contains user's edits)
+ //
+ // Returns
+ // A Promise fulfilled when the validation has completed and then dispatches a `validated` event.
+ // This may take time but happen in the background during browser idle time.
+ //
- _tooltips = [];
- dispatch.call('toggled', this, false);
- };
- editMenu.anchorLoc = function (val) {
- if (!arguments.length) return _anchorLoc;
- _anchorLoc = val;
- _anchorLocLonLat = context.projection.invert(_anchorLoc);
- return editMenu;
- };
+ validator.validate = function () {
+ // Make sure the caches have graphs assigned to them.
+ // (we don't do this in `reset` because context is still resetting things and `history.base()` is unstable then)
+ var baseGraph = context.history().base();
+ if (!_headCache.graph) _headCache.graph = baseGraph;
+ if (!_baseCache.graph) _baseCache.graph = baseGraph;
+ var prevGraph = _headCache.graph;
+ var currGraph = context.graph();
- editMenu.triggerType = function (val) {
- if (!arguments.length) return _triggerType;
- _triggerType = val;
- return editMenu;
- };
+ if (currGraph === prevGraph) {
+ // _headCache.graph is current - we are caught up
+ _headIsCurrent = true;
+ dispatch.call('validated');
+ return Promise.resolve();
+ }
- editMenu.operations = function (val) {
- if (!arguments.length) return _operations;
- _operations = val;
- return editMenu;
- };
+ if (_headPromise) {
+ // Validation already in process, but we aren't caught up to current
+ _headIsCurrent = false; // We will need to catch up after the validation promise fulfills
- return utilRebind(editMenu, dispatch, 'on');
- }
+ return _headPromise;
+ } // If we get here, its time to start validating stuff.
- function uiFeatureInfo(context) {
- function update(selection) {
- var features = context.features();
- var stats = features.stats();
- var count = 0;
- var hiddenList = features.hidden().map(function (k) {
- if (stats[k]) {
- count += stats[k];
- return _t('inspector.title_count', {
- title: _t.html('feature.' + k + '.description'),
- count: stats[k]
- });
- }
- return null;
- }).filter(Boolean);
- selection.html('');
+ _headCache.graph = currGraph; // take snapshot
- if (hiddenList.length) {
- var tooltipBehavior = uiTooltip().placement('top').title(function () {
- return hiddenList.join('<br/>');
- });
- selection.append('a').attr('class', 'chip').attr('href', '#').html(_t.html('feature_info.hidden_warning', {
- count: count
- })).call(tooltipBehavior).on('click', function (d3_event) {
- tooltipBehavior.hide();
- d3_event.preventDefault(); // open the Map Data pane
+ _completeDiff = context.history().difference().complete();
+ var incrementalDiff = coreDifference(prevGraph, currGraph);
+ var entityIDs = Object.keys(incrementalDiff.complete());
+ entityIDs = _headCache.withAllRelatedEntities(entityIDs); // expand set
- context.ui().togglePanes(context.container().select('.map-panes .map-data-pane'));
- });
+ if (!entityIDs.size) {
+ dispatch.call('validated');
+ return Promise.resolve();
}
- selection.classed('hide', !hiddenList.length);
- }
+ _headPromise = validateEntitiesAsync(entityIDs, _headCache).then(function () {
+ return updateResolvedIssues(entityIDs);
+ }).then(function () {
+ return dispatch.call('validated');
+ })["catch"](function () {
+ /* ignore */
+ }).then(function () {
+ _headPromise = null;
- return function (selection) {
- update(selection);
- context.features().on('change.feature_info', function () {
- update(selection);
+ if (!_headIsCurrent) {
+ validator.validate(); // run it again to catch up to current graph
+ }
});
- };
- }
+ return _headPromise;
+ }; // register event handlers:
+ // WHEN TO RUN VALIDATION:
+ // When history changes:
- function uiFlash(context) {
- var _flashTimer;
- var _duration = 2000;
- var _iconName = '#iD-icon-no';
- var _iconClass = 'disabled';
- var _label = '';
+ context.history().on('restore.validator', validator.validate) // on restore saved history
+ .on('undone.validator', validator.validate) // on undo
+ .on('redone.validator', validator.validate) // on redo
+ .on('reset.validator', function () {
+ // on history reset - happens after save, or enter/exit walkthrough
+ reset(false); // cached issues aren't valid any longer if the history has been reset
- function flash() {
- if (_flashTimer) {
- _flashTimer.stop();
- }
+ validator.validate();
+ }); // but not on 'change' (e.g. while drawing)
+ // When user changes editing modes (to catch recent changes e.g. drawing)
- context.container().select('.main-footer-wrap').classed('footer-hide', true).classed('footer-show', false);
- context.container().select('.flash-wrap').classed('footer-hide', false).classed('footer-show', true);
- var content = context.container().select('.flash-wrap').selectAll('.flash-content').data([0]); // Enter
+ context.on('exit.validator', validator.validate); // When merging fetched data, validate base graph:
- var contentEnter = content.enter().append('div').attr('class', 'flash-content');
- var iconEnter = contentEnter.append('svg').attr('class', 'flash-icon icon').append('g').attr('transform', 'translate(10,10)');
- iconEnter.append('circle').attr('r', 9);
- iconEnter.append('use').attr('transform', 'translate(-7,-7)').attr('width', '14').attr('height', '14');
- contentEnter.append('div').attr('class', 'flash-text'); // Update
+ context.history().on('merge.validator', function (entities) {
+ if (!entities) return; // Make sure the caches have graphs assigned to them.
+ // (we don't do this in `reset` because context is still resetting things and `history.base()` is unstable then)
- content = content.merge(contentEnter);
- content.selectAll('.flash-icon').attr('class', 'icon flash-icon ' + (_iconClass || ''));
- content.selectAll('.flash-icon use').attr('xlink:href', _iconName);
- content.selectAll('.flash-text').attr('class', 'flash-text').html(_label);
- _flashTimer = d3_timeout(function () {
- _flashTimer = null;
- context.container().select('.main-footer-wrap').classed('footer-hide', false).classed('footer-show', true);
- context.container().select('.flash-wrap').classed('footer-hide', true).classed('footer-show', false);
- }, _duration);
- return content;
- }
+ var baseGraph = context.history().base();
+ if (!_headCache.graph) _headCache.graph = baseGraph;
+ if (!_baseCache.graph) _baseCache.graph = baseGraph;
+ var entityIDs = entities.map(function (entity) {
+ return entity.id;
+ });
+ entityIDs = _baseCache.withAllRelatedEntities(entityIDs); // expand set
- flash.duration = function (_) {
- if (!arguments.length) return _duration;
- _duration = _;
- return flash;
- };
+ validateEntitiesAsync(entityIDs, _baseCache);
+ }); // `validateEntity()` (private)
+ // Runs all validation rules on a single entity.
+ // Some things to note:
+ // - Graph is passed in from whenever the validation was started. Validators shouldn't use
+ // `context.graph()` because this all happens async, and the graph might have changed
+ // (for example, nodes getting deleted before the validation can run)
+ // - Validator functions may still be waiting on something and return a "provisional" result.
+ // In this situation, we will schedule to revalidate the entity sometime later.
+ //
+ // Arguments
+ // `entity` - The entity
+ // `graph` - graph containing the entity
+ //
+ // Returns
+ // Object result like:
+ // {
+ // issues: Array of detected issues
+ // provisional: `true` if provisional result, `false` if final result
+ // }
+ //
- flash.label = function (_) {
- if (!arguments.length) return _label;
- _label = _;
- return flash;
- };
+ function validateEntity(entity, graph) {
+ var result = {
+ issues: [],
+ provisional: false
+ };
+ Object.keys(_rules).forEach(runValidation); // run all rules
- flash.iconName = function (_) {
- if (!arguments.length) return _iconName;
- _iconName = _;
- return flash;
- };
+ return result; // runs validation and appends resulting issues
- flash.iconClass = function (_) {
- if (!arguments.length) return _iconClass;
- _iconClass = _;
- return flash;
- };
+ function runValidation(key) {
+ var fn = _rules[key];
- return flash;
- }
+ if (typeof fn !== 'function') {
+ console.error('no such validation rule = ' + key); // eslint-disable-line no-console
- function uiFullScreen(context) {
- var element = context.container().node(); // var button = d3_select(null);
+ return;
+ }
- function getFullScreenFn() {
- if (element.requestFullscreen) {
- return element.requestFullscreen;
- } else if (element.msRequestFullscreen) {
- return element.msRequestFullscreen;
- } else if (element.mozRequestFullScreen) {
- return element.mozRequestFullScreen;
- } else if (element.webkitRequestFullscreen) {
- return element.webkitRequestFullscreen;
- }
- }
+ var detected = fn(entity, graph);
- function getExitFullScreenFn() {
- if (document.exitFullscreen) {
- return document.exitFullscreen;
- } else if (document.msExitFullscreen) {
- return document.msExitFullscreen;
- } else if (document.mozCancelFullScreen) {
- return document.mozCancelFullScreen;
- } else if (document.webkitExitFullscreen) {
- return document.webkitExitFullscreen;
- }
- }
+ if (detected.provisional) {
+ // this validation should be run again later
+ result.provisional = true;
+ }
- function isFullScreen() {
- return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
- }
+ detected = detected.filter(applySeverityOverrides);
+ result.issues = result.issues.concat(detected); // If there are any override rules that match the issue type/subtype,
+ // adjust severity (or disable it) and keep/discard as quickly as possible.
- function isSupported() {
- return !!getFullScreenFn();
- }
+ function applySeverityOverrides(issue) {
+ var type = issue.type;
+ var subtype = issue.subtype || '';
+ var i;
- function fullScreen(d3_event) {
- d3_event.preventDefault();
+ for (i = 0; i < _errorOverrides.length; i++) {
+ if (_errorOverrides[i].type.test(type) && _errorOverrides[i].subtype.test(subtype)) {
+ issue.severity = 'error';
+ return true;
+ }
+ }
- if (!isFullScreen()) {
- // button.classed('active', true);
- getFullScreenFn().apply(element);
- } else {
- // button.classed('active', false);
- getExitFullScreenFn().apply(document);
+ for (i = 0; i < _warningOverrides.length; i++) {
+ if (_warningOverrides[i].type.test(type) && _warningOverrides[i].subtype.test(subtype)) {
+ issue.severity = 'warning';
+ return true;
+ }
+ }
+
+ for (i = 0; i < _disableOverrides.length; i++) {
+ if (_disableOverrides[i].type.test(type) && _disableOverrides[i].subtype.test(subtype)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
- }
+ } // `updateResolvedIssues()` (private)
+ // Determine if any issues were resolved for the given entities.
+ // This is called by `validate()` after validation of the head graph
+ //
+ // Give the user credit for fixing an issue if:
+ // - the issue is in the base cache
+ // - the issue is not in the head cache
+ // - the user did something to one of the entities involved in the issue
+ //
+ // Arguments
+ // `entityIDs` - Array or Set containing entity IDs.
+ //
- return function () {
- // selection) {
- if (!isSupported()) return; // button = selection.append('button')
- // .attr('title', t('full_screen'))
- // .on('click', fullScreen)
- // .call(tooltip);
- // button.append('span')
- // .attr('class', 'icon full-screen');
- var detected = utilDetect();
- var keys = detected.os === 'mac' ? [uiCmd('⌃⌘F'), 'f11'] : ['f11'];
- context.keybinding().on(keys, fullScreen);
- };
- }
+ function updateResolvedIssues(entityIDs) {
+ entityIDs.forEach(function (entityID) {
+ var baseIssues = _baseCache.issuesByEntityID[entityID];
+ if (!baseIssues) return;
+ baseIssues.forEach(function (issueID) {
+ // Check if the user did something to one of the entities involved in this issue.
+ // (This issue could involve multiple entities, e.g. disconnected routable features)
+ var issue = _baseCache.issuesByIssueID[issueID];
+ var userModified = (issue.entityIds || []).some(function (id) {
+ return _completeDiff.hasOwnProperty(id);
+ });
- function uiGeolocate(context) {
- var _geolocationOptions = {
- // prioritize speed and power usage over precision
- enableHighAccuracy: false,
- // don't hang indefinitely getting the location
- timeout: 6000 // 6sec
+ if (userModified && !_headCache.issuesByIssueID[issueID]) {
+ // issue seems fixed
+ _resolvedIssueIDs.add(issueID);
+ } else {
+ // issue still not resolved
+ _resolvedIssueIDs["delete"](issueID); // (did undo, or possibly fixed and then re-caused the issue)
- };
+ }
+ });
+ });
+ } // `validateEntitiesAsync()` (private)
+ // Schedule validation for many entities.
+ //
+ // Arguments
+ // `entityIDs` - Array or Set containing entityIDs.
+ // `graph` - the graph to validate that contains those entities
+ // `cache` - the cache to store results in (_headCache or _baseCache)
+ //
+ // Returns
+ // A Promise fulfilled when the validation has completed.
+ // This may take time but happen in the background during browser idle time.
+ //
- var _locating = uiLoading(context).message(_t.html('geolocate.locating')).blocking(true);
- var _layer = context.layers().layer('geolocate');
+ function validateEntitiesAsync(entityIDs, cache) {
+ // Enqueue the work
+ var jobs = Array.from(entityIDs).map(function (entityID) {
+ if (cache.queuedEntityIDs.has(entityID)) return null; // queued already
- var _position;
+ cache.queuedEntityIDs.add(entityID); // Clear caches for existing issues related to this entity
- var _extent;
+ cache.uncacheEntityID(entityID);
+ return function () {
+ cache.queuedEntityIDs["delete"](entityID);
+ var graph = cache.graph;
+ if (!graph) return; // was reset?
- var _timeoutID;
+ var entity = graph.hasEntity(entityID); // Sanity check: don't validate deleted entities
- var _button = select(null);
+ if (!entity) return; // detect new issues and update caches
- function click() {
- if (context.inIntro()) return;
+ var result = validateEntity(entity, graph);
- if (!_layer.enabled() && !_locating.isShown()) {
- // This timeout ensures that we still call finish() even if
- // the user declines to share their location in Firefox
- _timeoutID = setTimeout(error, 10000
- /* 10sec */
- );
- context.container().call(_locating); // get the latest position even if we already have one
+ if (result.provisional) {
+ // provisional result
+ cache.provisionalEntityIDs.add(entityID); // we'll need to revalidate this entity again later
+ }
- navigator.geolocation.getCurrentPosition(success, error, _geolocationOptions);
- } else {
- _locating.close();
+ cache.cacheIssues(result.issues); // update cache
+ };
+ }).filter(Boolean); // Perform the work in chunks.
+ // Because this will happen during idle callbacks, we want to choose a chunk size
+ // that won't make the browser stutter too badly.
- _layer.enabled(null, false);
+ cache.queue = cache.queue.concat(utilArrayChunk(jobs, 100)); // Perform the work
- updateButtonState();
- }
- }
+ if (cache.queuePromise) return cache.queuePromise;
+ cache.queuePromise = processQueue(cache).then(function () {
+ return revalidateProvisionalEntities(cache);
+ })["catch"](function () {
+ /* ignore */
+ })["finally"](function () {
+ return cache.queuePromise = null;
+ });
+ return cache.queuePromise;
+ } // `revalidateProvisionalEntities()` (private)
+ // Sometimes a validator will return a "provisional" result.
+ // In this situation, we'll need to revalidate the entity later.
+ // This function waits a delay, then places them back into the validation queue.
+ //
+ // Arguments
+ // `cache` - The cache (_headCache or _baseCache)
+ //
- function zoomTo() {
- context.enter(modeBrowse(context));
- var map = context.map();
- _layer.enabled(_position, true);
+ function revalidateProvisionalEntities(cache) {
+ if (!cache.provisionalEntityIDs.size) return; // nothing to do
- updateButtonState();
- map.centerZoomEase(_extent.center(), Math.min(20, map.extentZoom(_extent)));
- }
+ var handle = window.setTimeout(function () {
+ _deferredST["delete"](handle);
- function success(geolocation) {
- _position = geolocation;
- var coords = _position.coords;
- _extent = geoExtent([coords.longitude, coords.latitude]).padByMeters(coords.accuracy);
- zoomTo();
- finish();
- }
+ if (!cache.provisionalEntityIDs.size) return; // nothing to do
- function error() {
- if (_position) {
- // use the position from a previous call if we have one
- zoomTo();
- } else {
- context.ui().flash.label(_t.html('geolocate.location_unavailable')).iconName('#iD-icon-geolocate')();
- }
+ validateEntitiesAsync(Array.from(cache.provisionalEntityIDs), cache);
+ }, RETRY);
- finish();
- }
+ _deferredST.add(handle);
+ } // `processQueue(queue)` (private)
+ // Process the next chunk of deferred validation work
+ //
+ // Arguments
+ // `cache` - The cache (_headCache or _baseCache)
+ //
+ // Returns
+ // A Promise fulfilled when the validation has completed.
+ // This may take time but happen in the background during browser idle time.
+ //
- function finish() {
- _locating.close(); // unblock ui
+ function processQueue(cache) {
+ // console.log(`${cache.which} queue length ${cache.queue.length}`);
+ if (!cache.queue.length) return Promise.resolve(); // we're done
- if (_timeoutID) {
- clearTimeout(_timeoutID);
- }
+ var chunk = cache.queue.pop();
+ return new Promise(function (resolvePromise) {
+ var handle = window.requestIdleCallback(function () {
+ _deferredRIC["delete"](handle); // const t0 = performance.now();
- _timeoutID = undefined;
- }
- function updateButtonState() {
- _button.classed('active', _layer.enabled());
- }
+ chunk.forEach(function (job) {
+ return job();
+ }); // const t1 = performance.now();
+ // console.log('chunk processed in ' + (t1 - t0) + ' ms');
- return function (selection) {
- if (!navigator.geolocation || !navigator.geolocation.getCurrentPosition) return;
- _button = selection.append('button').on('click', click).call(svgIcon('#iD-icon-geolocate', 'light')).call(uiTooltip().placement(_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left').title(_t.html('geolocate.title')).keys([_t('geolocate.key')]));
- context.keybinding().on(_t('geolocate.key'), click);
- };
- }
+ resolvePromise();
+ });
- function uiPanelBackground(context) {
- var background = context.background();
- var _currSourceName = null;
- var _metadata = {};
- var _metadataKeys = ['zoom', 'vintage', 'source', 'description', 'resolution', 'accuracy'];
+ _deferredRIC.add(handle);
+ }).then(function () {
+ // dispatch an event sometimes to redraw various UI things
+ if (cache.queue.length % 25 === 0) dispatch.call('validated');
+ }).then(function () {
+ return processQueue(cache);
+ });
+ }
- var debouncedRedraw = debounce(redraw, 250);
+ return validator;
+ } // `validationCache()` (private)
+ // Creates a cache to store validation state
+ // We create 2 of these:
+ // `_baseCache` for validation on the base graph (unedited)
+ // `_headCache` for validation on the head graph (user edits applied)
+ //
+ // Arguments
+ // `which` - just a String 'base' or 'head' to keep track of it
+ //
- function redraw(selection) {
- var source = background.baseLayerSource();
- if (!source) return;
- var isDG = source.id.match(/^DigitalGlobe/i) !== null;
- var sourceLabel = source.label();
+ function validationCache(which) {
+ var cache = {
+ which: which,
+ graph: null,
+ queue: [],
+ queuePromise: null,
+ queuedEntityIDs: new Set(),
+ provisionalEntityIDs: new Set(),
+ issuesByIssueID: {},
+ // issue.id -> issue
+ issuesByEntityID: {} // entity.id -> Set(issue.id)
- if (_currSourceName !== sourceLabel) {
- _currSourceName = sourceLabel;
- _metadata = {};
- }
+ };
- selection.html('');
- var list = selection.append('ul').attr('class', 'background-info');
- list.append('li').html(_currSourceName);
+ cache.cacheIssue = function (issue) {
+ (issue.entityIds || []).forEach(function (entityID) {
+ if (!cache.issuesByEntityID[entityID]) {
+ cache.issuesByEntityID[entityID] = new Set();
+ }
- _metadataKeys.forEach(function (k) {
- // DigitalGlobe vintage is available in raster layers for now.
- if (isDG && k === 'vintage') return;
- list.append('li').attr('class', 'background-info-list-' + k).classed('hide', !_metadata[k]).html(_t.html('info_panels.background.' + k) + ':').append('span').attr('class', 'background-info-span-' + k).html(_metadata[k]);
+ cache.issuesByEntityID[entityID].add(issue.id);
});
+ cache.issuesByIssueID[issue.id] = issue;
+ };
- debouncedGetMetadata(selection);
- var toggleTiles = context.getDebug('tile') ? 'hide_tiles' : 'show_tiles';
- selection.append('a').html(_t.html('info_panels.background.' + toggleTiles)).attr('href', '#').attr('class', 'button button-toggle-tiles').on('click', function (d3_event) {
- d3_event.preventDefault();
- context.setDebug('tile', !context.getDebug('tile'));
- selection.call(redraw);
+ cache.uncacheIssue = function (issue) {
+ (issue.entityIds || []).forEach(function (entityID) {
+ if (cache.issuesByEntityID[entityID]) {
+ cache.issuesByEntityID[entityID]["delete"](issue.id);
+ }
});
+ delete cache.issuesByIssueID[issue.id];
+ };
- if (isDG) {
- var key = source.id + '-vintage';
- var sourceVintage = context.background().findSource(key);
- var showsVintage = context.background().showsLayer(sourceVintage);
- var toggleVintage = showsVintage ? 'hide_vintage' : 'show_vintage';
- selection.append('a').html(_t.html('info_panels.background.' + toggleVintage)).attr('href', '#').attr('class', 'button button-toggle-vintage').on('click', function (d3_event) {
- d3_event.preventDefault();
- context.background().toggleOverlayLayer(sourceVintage);
- selection.call(redraw);
- });
- } // disable if necessary
-
+ cache.cacheIssues = function (issues) {
+ issues.forEach(cache.cacheIssue);
+ };
- ['DigitalGlobe-Premium', 'DigitalGlobe-Standard'].forEach(function (layerId) {
- if (source.id !== layerId) {
- var key = layerId + '-vintage';
- var sourceVintage = context.background().findSource(key);
+ cache.uncacheIssues = function (issues) {
+ issues.forEach(cache.uncacheIssue);
+ };
- if (context.background().showsLayer(sourceVintage)) {
- context.background().toggleOverlayLayer(sourceVintage);
- }
- }
+ cache.uncacheIssuesOfType = function (type) {
+ var issuesOfType = Object.values(cache.issuesByIssueID).filter(function (issue) {
+ return issue.type === type;
});
- }
+ cache.uncacheIssues(issuesOfType);
+ }; // Remove a single entity and all its related issues from the caches
- var debouncedGetMetadata = debounce(getMetadata, 250);
- function getMetadata(selection) {
- var tile = context.container().select('.layer-background img.tile-center'); // tile near viewport center
+ cache.uncacheEntityID = function (entityID) {
+ var entityIssueIDs = cache.issuesByEntityID[entityID];
- if (tile.empty()) return;
- var sourceName = _currSourceName;
- var d = tile.datum();
- var zoom = d && d.length >= 3 && d[2] || Math.floor(context.map().zoom());
- var center = context.map().center(); // update zoom
+ if (entityIssueIDs) {
+ entityIssueIDs.forEach(function (issueID) {
+ var issue = cache.issuesByIssueID[issueID];
- _metadata.zoom = String(zoom);
- selection.selectAll('.background-info-list-zoom').classed('hide', false).selectAll('.background-info-span-zoom').html(_metadata.zoom);
- if (!d || !d.length >= 3) return;
- background.baseLayerSource().getMetadata(center, d, function (err, result) {
- if (err || _currSourceName !== sourceName) return; // update vintage
+ if (issue) {
+ cache.uncacheIssue(issue);
+ } else {
+ // shouldn't happen, clean up
+ delete cache.issuesByIssueID[issueID];
+ }
+ });
+ }
- var vintage = result.vintage;
- _metadata.vintage = vintage && vintage.range || _t('info_panels.background.unknown');
- selection.selectAll('.background-info-list-vintage').classed('hide', false).selectAll('.background-info-span-vintage').html(_metadata.vintage); // update other _metadata
+ delete cache.issuesByEntityID[entityID];
+ cache.provisionalEntityIDs["delete"](entityID);
+ }; // Return the expandeded set of entityIDs related to issues for the given entityIDs
+ //
+ // Arguments
+ // `entityIDs` - Array or Set containing entityIDs.
+ //
- _metadataKeys.forEach(function (k) {
- if (k === 'zoom' || k === 'vintage') return; // done already
- var val = result[k];
- _metadata[k] = val;
- selection.selectAll('.background-info-list-' + k).classed('hide', !val).selectAll('.background-info-span-' + k).html(val);
- });
- });
- }
+ cache.withAllRelatedEntities = function (entityIDs) {
+ var result = new Set();
+ (entityIDs || []).forEach(function (entityID) {
+ result.add(entityID); // include self
- var panel = function panel(selection) {
- selection.call(redraw);
- context.map().on('drawn.info-background', function () {
- selection.call(debouncedRedraw);
- }).on('move.info-background', function () {
- selection.call(debouncedGetMetadata);
+ var entityIssueIDs = cache.issuesByEntityID[entityID];
+
+ if (entityIssueIDs) {
+ entityIssueIDs.forEach(function (issueID) {
+ var issue = cache.issuesByIssueID[issueID];
+
+ if (issue) {
+ (issue.entityIds || []).forEach(function (relatedID) {
+ return result.add(relatedID);
+ });
+ } else {
+ // shouldn't happen, clean up
+ delete cache.issuesByIssueID[issueID];
+ }
+ });
+ }
});
+ return result;
};
- panel.off = function () {
- context.map().on('drawn.info-background', null).on('move.info-background', null);
- };
+ return cache;
+ }
- panel.id = 'background';
- panel.label = _t.html('info_panels.background.title');
- panel.key = _t('info_panels.background.key');
- return panel;
- }
+ function coreUploader(context) {
+ var dispatch = dispatch$8( // Start and end events are dispatched exactly once each per legitimate outside call to `save`
+ 'saveStarted', // dispatched as soon as a call to `save` has been deemed legitimate
+ 'saveEnded', // dispatched after the result event has been dispatched
+ 'willAttemptUpload', // dispatched before the actual upload call occurs, if it will
+ 'progressChanged', // Each save results in one of these outcomes:
+ 'resultNoChanges', // upload wasn't attempted since there were no edits
+ 'resultErrors', // upload failed due to errors
+ 'resultConflicts', // upload failed due to data conflicts
+ 'resultSuccess' // upload completed without errors
+ );
+ var _isSaving = false;
+ var _conflicts = [];
+ var _errors = [];
- function uiPanelHistory(context) {
- var osm;
+ var _origChanges;
- function displayTimestamp(timestamp) {
- if (!timestamp) return _t('info_panels.history.unknown');
- var options = {
- day: 'numeric',
- month: 'short',
- year: 'numeric',
- hour: 'numeric',
- minute: 'numeric',
- second: 'numeric'
- };
- var d = new Date(timestamp);
- if (isNaN(d.getTime())) return _t('info_panels.history.unknown');
- return d.toLocaleString(_mainLocalizer.localeCode(), options);
- }
+ var _discardTags = {};
+ _mainFileFetcher.get('discarded').then(function (d) {
+ _discardTags = d;
+ })["catch"](function () {
+ /* ignore */
+ });
+ var uploader = utilRebind({}, dispatch, 'on');
- function displayUser(selection, userName) {
- if (!userName) {
- selection.append('span').html(_t.html('info_panels.history.unknown'));
+ uploader.isSaving = function () {
+ return _isSaving;
+ };
+
+ uploader.save = function (changeset, tryAgain, checkConflicts) {
+ // Guard against accidentally entering save code twice - #4641
+ if (_isSaving && !tryAgain) {
return;
}
- selection.append('span').attr('class', 'user-name').html(userName);
- var links = selection.append('div').attr('class', 'links');
+ var osm = context.connection();
+ if (!osm) return; // If user somehow got logged out mid-save, try to reauthenticate..
+ // This can happen if they were logged in from before, but the tokens are no longer valid.
- if (osm) {
- links.append('a').attr('class', 'user-osm-link').attr('href', osm.userURL(userName)).attr('target', '_blank').html('OSM');
+ if (!osm.authenticated()) {
+ osm.authenticate(function (err) {
+ if (!err) {
+ uploader.save(changeset, tryAgain, checkConflicts); // continue where we left off..
+ }
+ });
+ return;
}
- links.append('a').attr('class', 'user-hdyc-link').attr('href', 'https://hdyc.neis-one.org/?' + userName).attr('target', '_blank').attr('tabindex', -1).html('HDYC');
- }
-
- function displayChangeset(selection, changeset) {
- if (!changeset) {
- selection.append('span').html(_t.html('info_panels.history.unknown'));
- return;
+ if (!_isSaving) {
+ _isSaving = true;
+ dispatch.call('saveStarted', this);
}
- selection.append('span').attr('class', 'changeset-id').html(changeset);
- var links = selection.append('div').attr('class', 'links');
+ var history = context.history();
+ _conflicts = [];
+ _errors = []; // Store original changes, in case user wants to download them as an .osc file
- if (osm) {
- links.append('a').attr('class', 'changeset-osm-link').attr('href', osm.changesetURL(changeset)).attr('target', '_blank').html('OSM');
- }
+ _origChanges = history.changes(actionDiscardTags(history.difference(), _discardTags)); // First time, `history.perform` a no-op action.
+ // Any conflict resolutions will be done as `history.replace`
+ // Remember to pop this later if needed
- links.append('a').attr('class', 'changeset-osmcha-link').attr('href', 'https://osmcha.org/changesets/' + changeset).attr('target', '_blank').html('OSMCha');
- links.append('a').attr('class', 'changeset-achavi-link').attr('href', 'https://overpass-api.de/achavi/?changeset=' + changeset).attr('target', '_blank').html('Achavi');
- }
+ if (!tryAgain) {
+ history.perform(actionNoop());
+ } // Attempt a fast upload.. If there are conflicts, re-enter with `checkConflicts = true`
- function redraw(selection) {
- var selectedNoteID = context.selectedNoteID();
- osm = context.connection();
- var selected, note, entity;
- if (selectedNoteID && osm) {
- // selected 1 note
- selected = [_t('note.note') + ' ' + selectedNoteID];
- note = osm.getNote(selectedNoteID);
+ if (!checkConflicts) {
+ upload(changeset); // Do the full (slow) conflict check..
} else {
- // selected 1..n entities
- selected = context.selectedIDs().filter(function (e) {
- return context.hasEntity(e);
- });
-
- if (selected.length) {
- entity = context.entity(selected[0]);
- }
+ performFullConflictCheck(changeset);
}
+ };
- var singular = selected.length === 1 ? selected[0] : null;
- selection.html('');
- selection.append('h4').attr('class', 'history-heading').html(singular || _t.html('info_panels.selected', {
- n: selected.length
- }));
- if (!singular) return;
+ function performFullConflictCheck(changeset) {
+ var osm = context.connection();
+ if (!osm) return;
+ var history = context.history();
+ var localGraph = context.graph();
+ var remoteGraph = coreGraph(history.base(), true);
+ var summary = history.difference().summary();
+ var _toCheck = [];
- if (entity) {
- selection.call(redrawEntity, entity);
- } else if (note) {
- selection.call(redrawNote, note);
- }
- }
+ for (var i = 0; i < summary.length; i++) {
+ var item = summary[i];
- function redrawNote(selection, note) {
- if (!note || note.isNew()) {
- selection.append('div').html(_t.html('info_panels.history.note_no_history'));
- return;
+ if (item.changeType === 'modified') {
+ _toCheck.push(item.entity.id);
+ }
}
- var list = selection.append('ul');
- list.append('li').html(_t.html('info_panels.history.note_comments') + ':').append('span').html(note.comments.length);
-
- if (note.comments.length) {
- list.append('li').html(_t.html('info_panels.history.note_created_date') + ':').append('span').html(displayTimestamp(note.comments[0].date));
- list.append('li').html(_t.html('info_panels.history.note_created_user') + ':').call(displayUser, note.comments[0].user);
- }
+ var _toLoad = withChildNodes(_toCheck, localGraph);
- if (osm) {
- selection.append('a').attr('class', 'view-history-on-osm').attr('target', '_blank').attr('href', osm.noteURL(note)).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').html(_t.html('info_panels.history.note_link_text'));
- }
- }
+ var _loaded = {};
+ var _toLoadCount = 0;
+ var _toLoadTotal = _toLoad.length;
- function redrawEntity(selection, entity) {
- if (!entity || entity.isNew()) {
- selection.append('div').html(_t.html('info_panels.history.no_history'));
- return;
- }
+ if (_toCheck.length) {
+ dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
- var links = selection.append('div').attr('class', 'links');
+ _toLoad.forEach(function (id) {
+ _loaded[id] = false;
+ });
- if (osm) {
- links.append('a').attr('class', 'view-history-on-osm').attr('href', osm.historyURL(entity)).attr('target', '_blank').attr('title', _t('info_panels.history.link_text')).html('OSM');
+ osm.loadMultiple(_toLoad, loaded);
+ } else {
+ upload(changeset);
}
- links.append('a').attr('class', 'pewu-history-viewer-link').attr('href', 'https://pewu.github.io/osm-history/#/' + entity.type + '/' + entity.osmId()).attr('target', '_blank').attr('tabindex', -1).html('PeWu');
- var list = selection.append('ul');
- list.append('li').html(_t.html('info_panels.history.version') + ':').append('span').html(entity.version);
- list.append('li').html(_t.html('info_panels.history.last_edit') + ':').append('span').html(displayTimestamp(entity.timestamp));
- list.append('li').html(_t.html('info_panels.history.edited_by') + ':').call(displayUser, entity.user);
- list.append('li').html(_t.html('info_panels.history.changeset') + ':').call(displayChangeset, entity.changeset);
- }
-
- var panel = function panel(selection) {
- selection.call(redraw);
- context.map().on('drawn.info-history', function () {
- selection.call(redraw);
- });
- context.on('enter.info-history', function () {
- selection.call(redraw);
- });
- };
+ return;
- panel.off = function () {
- context.map().on('drawn.info-history', null);
- context.on('enter.info-history', null);
- };
+ function withChildNodes(ids, graph) {
+ var s = new Set(ids);
+ ids.forEach(function (id) {
+ var entity = graph.entity(id);
+ if (entity.type !== 'way') return;
+ graph.childNodes(entity).forEach(function (child) {
+ if (child.version !== undefined) {
+ s.add(child.id);
+ }
+ });
+ });
+ return Array.from(s);
+ } // Reload modified entities into an alternate graph and check for conflicts..
- panel.id = 'history';
- panel.label = _t.html('info_panels.history.title');
- panel.key = _t('info_panels.history.key');
- return panel;
- }
- var OSM_PRECISION = 7;
- /**
- * Returns a localized representation of the given length measurement.
- *
- * @param {Number} m area in meters
- * @param {Boolean} isImperial true for U.S. customary units; false for metric
- */
+ function loaded(err, result) {
+ if (_errors.length) return;
- function displayLength(m, isImperial) {
- var d = m * (isImperial ? 3.28084 : 1);
- var unit;
+ if (err) {
+ _errors.push({
+ msg: err.message || err.responseText,
+ details: [_t('save.status_code', {
+ code: err.status
+ })]
+ });
- if (isImperial) {
- if (d >= 5280) {
- d /= 5280;
- unit = 'miles';
- } else {
- unit = 'feet';
- }
- } else {
- if (d >= 1000) {
- d /= 1000;
- unit = 'kilometers';
- } else {
- unit = 'meters';
- }
- }
+ didResultInErrors();
+ } else {
+ var loadMore = [];
+ result.data.forEach(function (entity) {
+ remoteGraph.replace(entity);
+ _loaded[entity.id] = true;
+ _toLoad = _toLoad.filter(function (val) {
+ return val !== entity.id;
+ });
+ if (!entity.visible) return; // Because loadMultiple doesn't download /full like loadEntity,
+ // need to also load children that aren't already being checked..
- return _t('units.' + unit, {
- quantity: d.toLocaleString(_mainLocalizer.localeCode(), {
- maximumSignificantDigits: 4
- })
- });
- }
- /**
- * Returns a localized representation of the given area measurement.
- *
- * @param {Number} m2 area in square meters
- * @param {Boolean} isImperial true for U.S. customary units; false for metric
- */
+ var i, id;
- function displayArea(m2, isImperial) {
- var locale = _mainLocalizer.localeCode();
- var d = m2 * (isImperial ? 10.7639111056 : 1);
- var d1, d2, area;
- var unit1 = '';
- var unit2 = '';
+ if (entity.type === 'way') {
+ for (i = 0; i < entity.nodes.length; i++) {
+ id = entity.nodes[i];
- if (isImperial) {
- if (d >= 6969600) {
- // > 0.25mi² show mi²
- d1 = d / 27878400;
- unit1 = 'square_miles';
- } else {
- d1 = d;
- unit1 = 'square_feet';
- }
+ if (_loaded[id] === undefined) {
+ _loaded[id] = false;
+ loadMore.push(id);
+ }
+ }
+ } else if (entity.type === 'relation' && entity.isMultipolygon()) {
+ for (i = 0; i < entity.members.length; i++) {
+ id = entity.members[i].id;
- if (d > 4356 && d < 43560000) {
- // 0.1 - 1000 acres
- d2 = d / 43560;
- unit2 = 'acres';
- }
- } else {
- if (d >= 250000) {
- // > 0.25km² show km²
- d1 = d / 1000000;
- unit1 = 'square_kilometers';
- } else {
- d1 = d;
- unit1 = 'square_meters';
- }
+ if (_loaded[id] === undefined) {
+ _loaded[id] = false;
+ loadMore.push(id);
+ }
+ }
+ }
+ });
+ _toLoadCount += result.data.length;
+ _toLoadTotal += loadMore.length;
+ dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
- if (d > 1000 && d < 10000000) {
- // 0.1 - 1000 hectares
- d2 = d / 10000;
- unit2 = 'hectares';
- }
- }
+ if (loadMore.length) {
+ _toLoad.push.apply(_toLoad, loadMore);
- area = _t('units.' + unit1, {
- quantity: d1.toLocaleString(locale, {
- maximumSignificantDigits: 4
- })
- });
+ osm.loadMultiple(loadMore, loaded);
+ }
- if (d2) {
- return _t('units.area_pair', {
- area1: area,
- area2: _t('units.' + unit2, {
- quantity: d2.toLocaleString(locale, {
- maximumSignificantDigits: 2
- })
- })
- });
- } else {
- return area;
- }
- }
+ if (!_toLoad.length) {
+ detectConflicts();
+ upload(changeset);
+ }
+ }
+ }
- function wrap(x, min, max) {
- var d = max - min;
- return ((x - min) % d + d) % d + min;
- }
+ function detectConflicts() {
+ function choice(id, text, _action) {
+ return {
+ id: id,
+ text: text,
+ action: function action() {
+ history.replace(_action);
+ }
+ };
+ }
- function clamp(x, min, max) {
- return Math.max(min, Math.min(x, max));
- }
+ function formatUser(d) {
+ return '<a href="' + osm.userURL(d) + '" target="_blank">' + escape$4(d) + '</a>';
+ }
- function displayCoordinate(deg, pos, neg) {
- var locale = _mainLocalizer.localeCode();
- var min = (Math.abs(deg) - Math.floor(Math.abs(deg))) * 60;
- var sec = (min - Math.floor(min)) * 60;
- var displayDegrees = _t('units.arcdegrees', {
- quantity: Math.floor(Math.abs(deg)).toLocaleString(locale)
- });
- var displayCoordinate;
+ function entityName(entity) {
+ return utilDisplayName(entity) || utilDisplayType(entity.id) + ' ' + entity.id;
+ }
- if (Math.floor(sec) > 0) {
- displayCoordinate = displayDegrees + _t('units.arcminutes', {
- quantity: Math.floor(min).toLocaleString(locale)
- }) + _t('units.arcseconds', {
- quantity: Math.round(sec).toLocaleString(locale)
- });
- } else if (Math.floor(min) > 0) {
- displayCoordinate = displayDegrees + _t('units.arcminutes', {
- quantity: Math.round(min).toLocaleString(locale)
- });
- } else {
- displayCoordinate = _t('units.arcdegrees', {
- quantity: Math.round(Math.abs(deg)).toLocaleString(locale)
- });
- }
+ function sameVersions(local, remote) {
+ if (local.version !== remote.version) return false;
- if (deg === 0) {
- return displayCoordinate;
- } else {
- return _t('units.coordinate', {
- coordinate: displayCoordinate,
- direction: _t('units.' + (deg > 0 ? pos : neg))
- });
- }
- }
- /**
- * Returns given coordinate pair in degree-minute-second format.
- *
- * @param {Array<Number>} coord longitude and latitude
- */
+ if (local.type === 'way') {
+ var children = utilArrayUnion(local.nodes, remote.nodes);
+ for (var i = 0; i < children.length; i++) {
+ var a = localGraph.hasEntity(children[i]);
+ var b = remoteGraph.hasEntity(children[i]);
+ if (a && b && a.version !== b.version) return false;
+ }
+ }
- function dmsCoordinatePair(coord) {
- return _t('units.coordinate_pair', {
- latitude: displayCoordinate(clamp(coord[1], -90, 90), 'north', 'south'),
- longitude: displayCoordinate(wrap(coord[0], -180, 180), 'east', 'west')
- });
- }
- /**
- * Returns the given coordinate pair in decimal format.
- * note: unlocalized to avoid comma ambiguity - see #4765
- *
- * @param {Array<Number>} coord longitude and latitude
- */
+ return true;
+ }
- function decimalCoordinatePair(coord) {
- return _t('units.coordinate_pair', {
- latitude: clamp(coord[1], -90, 90).toFixed(OSM_PRECISION),
- longitude: wrap(coord[0], -180, 180).toFixed(OSM_PRECISION)
- });
- }
+ _toCheck.forEach(function (id) {
+ var local = localGraph.entity(id);
+ var remote = remoteGraph.entity(id);
+ if (sameVersions(local, remote)) return;
+ var merge = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags, formatUser);
+ history.replace(merge);
+ var mergeConflicts = merge.conflicts();
+ if (!mergeConflicts.length) return; // merged safely
- function uiPanelLocation(context) {
- var currLocation = '';
+ var forceLocal = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_local');
+ var forceRemote = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_remote');
+ var keepMine = _t('save.conflict.' + (remote.visible ? 'keep_local' : 'restore'));
+ var keepTheirs = _t('save.conflict.' + (remote.visible ? 'keep_remote' : 'delete'));
- function redraw(selection) {
- selection.html('');
- var list = selection.append('ul'); // Mouse coordinates
+ _conflicts.push({
+ id: id,
+ name: entityName(local),
+ details: mergeConflicts,
+ chosen: 1,
+ choices: [choice(id, keepMine, forceLocal), choice(id, keepTheirs, forceRemote)]
+ });
+ });
+ }
+ }
- var coord = context.map().mouseCoordinates();
+ function upload(changeset) {
+ var osm = context.connection();
- if (coord.some(isNaN)) {
- coord = context.map().center();
+ if (!osm) {
+ _errors.push({
+ msg: 'No OSM Service'
+ });
}
- list.append('li').html(dmsCoordinatePair(coord)).append('li').html(decimalCoordinatePair(coord)); // Location Info
+ if (_conflicts.length) {
+ didResultInConflicts(changeset);
+ } else if (_errors.length) {
+ didResultInErrors();
+ } else {
+ var history = context.history();
+ var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
- selection.append('div').attr('class', 'location-info').html(currLocation || ' ');
- debouncedGetLocation(selection, coord);
+ if (changes.modified.length || changes.created.length || changes.deleted.length) {
+ dispatch.call('willAttemptUpload', this);
+ osm.putChangeset(changeset, changes, uploadCallback);
+ } else {
+ // changes were insignificant or reverted by user
+ didResultInNoChanges();
+ }
+ }
}
- var debouncedGetLocation = debounce(getLocation, 250);
+ function uploadCallback(err, changeset) {
+ if (err) {
+ if (err.status === 409) {
+ // 409 Conflict
+ uploader.save(changeset, true, true); // tryAgain = true, checkConflicts = true
+ } else {
+ _errors.push({
+ msg: err.message || err.responseText,
+ details: [_t('save.status_code', {
+ code: err.status
+ })]
+ });
- function getLocation(selection, coord) {
- if (!services.geocoder) {
- currLocation = _t('info_panels.location.unknown_location');
- selection.selectAll('.location-info').html(currLocation);
+ didResultInErrors();
+ }
} else {
- services.geocoder.reverse(coord, function (err, result) {
- currLocation = result ? result.display_name : _t('info_panels.location.unknown_location');
- selection.selectAll('.location-info').html(currLocation);
- });
+ didResultInSuccess(changeset);
}
}
- var panel = function panel(selection) {
- selection.call(redraw);
- context.surface().on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'move.info-location', function () {
- selection.call(redraw);
- });
- };
+ function didResultInNoChanges() {
+ dispatch.call('resultNoChanges', this);
+ endSave();
+ context.flush(); // reset iD
+ }
- panel.off = function () {
- context.surface().on('.info-location', null);
- };
+ function didResultInErrors() {
+ context.history().pop();
+ dispatch.call('resultErrors', this, _errors);
+ endSave();
+ }
- panel.id = 'location';
- panel.label = _t.html('info_panels.location.title');
- panel.key = _t('info_panels.location.key');
- return panel;
- }
-
- function uiPanelMeasurement(context) {
- function radiansToMeters(r) {
- // using WGS84 authalic radius (6371007.1809 m)
- return r * 6371007.1809;
- }
+ function didResultInConflicts(changeset) {
+ _conflicts.sort(function (a, b) {
+ return b.id.localeCompare(a.id);
+ });
- function steradiansToSqmeters(r) {
- // http://gis.stackexchange.com/a/124857/40446
- return r / (4 * Math.PI) * 510065621724000;
+ dispatch.call('resultConflicts', this, changeset, _conflicts, _origChanges);
+ endSave();
}
- function toLineString(feature) {
- if (feature.type === 'LineString') return feature;
- var result = {
- type: 'LineString',
- coordinates: []
- };
+ function didResultInSuccess(changeset) {
+ // delete the edit stack cached to local storage
+ context.history().clearSaved();
+ dispatch.call('resultSuccess', this, changeset); // Add delay to allow for postgres replication #1646 #2678
- if (feature.type === 'Polygon') {
- result.coordinates = feature.coordinates[0];
- } else if (feature.type === 'MultiPolygon') {
- result.coordinates = feature.coordinates[0][0];
- }
+ window.setTimeout(function () {
+ endSave();
+ context.flush(); // reset iD
+ }, 2500);
+ }
- return result;
+ function endSave() {
+ _isSaving = false;
+ dispatch.call('saveEnded', this);
}
- var _isImperial = !_mainLocalizer.usesMetric();
+ uploader.cancelConflictResolution = function () {
+ context.history().pop();
+ };
- function redraw(selection) {
- var graph = context.graph();
- var selectedNoteID = context.selectedNoteID();
- var osm = services.osm;
- var localeCode = _mainLocalizer.localeCode();
- var heading;
- var center, location, centroid;
- var closed, geometry;
- var totalNodeCount,
- length = 0,
- area = 0,
- distance;
+ uploader.processResolvedConflicts = function (changeset) {
+ var history = context.history();
- if (selectedNoteID && osm) {
- // selected 1 note
- var note = osm.getNote(selectedNoteID);
- heading = _t('note.note') + ' ' + selectedNoteID;
- location = note.loc;
- geometry = 'note';
- } else {
- // selected 1..n entities
- var selectedIDs = context.selectedIDs().filter(function (id) {
- return context.hasEntity(id);
- });
- var selected = selectedIDs.map(function (id) {
- return context.entity(id);
- });
- heading = selected.length === 1 ? selected[0].id : _t('info_panels.selected', {
- n: selected.length
- });
+ for (var i = 0; i < _conflicts.length; i++) {
+ if (_conflicts[i].chosen === 1) {
+ // user chose "use theirs"
+ var entity = context.hasEntity(_conflicts[i].id);
- if (selected.length) {
- var extent = geoExtent();
+ if (entity && entity.type === 'way') {
+ var children = utilArrayUniq(entity.nodes);
- for (var i in selected) {
- var entity = selected[i];
+ for (var j = 0; j < children.length; j++) {
+ history.replace(actionRevert(children[j]));
+ }
+ }
- extent._extend(entity.extent(graph));
+ history.replace(actionRevert(_conflicts[i].id));
+ }
+ }
- geometry = entity.geometry(graph);
+ uploader.save(changeset, true, false); // tryAgain = true, checkConflicts = false
+ };
- if (geometry === 'line' || geometry === 'area') {
- closed = entity.type === 'relation' || entity.isClosed() && !entity.isDegenerate();
- var feature = entity.asGeoJSON(graph);
- length += radiansToMeters(d3_geoLength(toLineString(feature)));
- centroid = d3_geoPath(context.projection).centroid(entity.asGeoJSON(graph));
- centroid = centroid && context.projection.invert(centroid);
+ uploader.reset = function () {};
- if (!centroid || !isFinite(centroid[0]) || !isFinite(centroid[1])) {
- centroid = entity.extent(graph).center();
- }
+ return uploader;
+ }
- if (closed) {
- area += steradiansToSqmeters(entity.area(graph));
- }
- }
- }
+ var $$2 = _export;
+ var fails = fails$S;
+ var expm1 = mathExpm1;
- if (selected.length > 1) {
- geometry = null;
- closed = null;
- centroid = null;
- }
+ var abs = Math.abs;
+ var exp = Math.exp;
+ var E = Math.E;
- if (selected.length === 2 && selected[0].type === 'node' && selected[1].type === 'node') {
- distance = geoSphericalDistance(selected[0].loc, selected[1].loc);
- }
+ var FORCED = fails(function () {
+ // eslint-disable-next-line es/no-math-sinh -- required for testing
+ return Math.sinh(-2e-17) != -2e-17;
+ });
- if (selected.length === 1 && selected[0].type === 'node') {
- location = selected[0].loc;
- } else {
- totalNodeCount = utilGetAllNodes(selectedIDs, context.graph()).length;
- }
+ // `Math.sinh` method
+ // https://tc39.es/ecma262/#sec-math.sinh
+ // V8 near Chromium 38 has a problem with very small numbers
+ $$2({ target: 'Math', stat: true, forced: FORCED }, {
+ sinh: function sinh(x) {
+ return abs(x = +x) < 1 ? (expm1(x) - expm1(-x)) / 2 : (exp(x - 1) - exp(-x - 1)) * (E / 2);
+ }
+ });
- if (!location && !centroid) {
- center = extent.center();
- }
- }
- }
+ var isRetina = window.devicePixelRatio && window.devicePixelRatio >= 2; // listen for DPI change, e.g. when dragging a browser window from a retina to non-retina screen
- selection.html('');
+ window.matchMedia("\n (-webkit-min-device-pixel-ratio: 2), /* Safari */\n (min-resolution: 2dppx), /* standard */\n (min-resolution: 192dpi) /* fallback */\n ").addListener(function () {
+ isRetina = window.devicePixelRatio && window.devicePixelRatio >= 2;
+ });
- if (heading) {
- selection.append('h4').attr('class', 'measurement-heading').html(heading);
- }
+ function localeDateString(s) {
+ if (!s) return null;
+ var options = {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric'
+ };
+ var d = new Date(s);
+ if (isNaN(d.getTime())) return null;
+ return d.toLocaleDateString(_mainLocalizer.localeCode(), options);
+ }
- var list = selection.append('ul');
- var coordItem;
+ function vintageRange(vintage) {
+ var s;
- if (geometry) {
- list.append('li').html(_t.html('info_panels.measurement.geometry') + ':').append('span').html(closed ? _t('info_panels.measurement.closed_' + geometry) : _t('geometry.' + geometry));
- }
+ if (vintage.start || vintage.end) {
+ s = vintage.start || '?';
- if (totalNodeCount) {
- list.append('li').html(_t.html('info_panels.measurement.node_count') + ':').append('span').html(totalNodeCount.toLocaleString(localeCode));
+ if (vintage.start !== vintage.end) {
+ s += ' - ' + (vintage.end || '?');
}
+ }
- if (area) {
- list.append('li').html(_t.html('info_panels.measurement.area') + ':').append('span').html(displayArea(area, _isImperial));
- }
+ return s;
+ }
- if (length) {
- list.append('li').html(_t.html('info_panels.measurement.' + (closed ? 'perimeter' : 'length')) + ':').append('span').html(displayLength(length, _isImperial));
- }
+ function rendererBackgroundSource(data) {
+ var source = Object.assign({}, data); // shallow copy
- if (typeof distance === 'number') {
- list.append('li').html(_t.html('info_panels.measurement.distance') + ':').append('span').html(displayLength(distance, _isImperial));
- }
+ var _offset = [0, 0];
+ var _name = source.name;
+ var _description = source.description;
- if (location) {
- coordItem = list.append('li').html(_t.html('info_panels.measurement.location') + ':');
- coordItem.append('span').html(dmsCoordinatePair(location));
- coordItem.append('span').html(decimalCoordinatePair(location));
- }
+ var _best = !!source.best;
- if (centroid) {
- coordItem = list.append('li').html(_t.html('info_panels.measurement.centroid') + ':');
- coordItem.append('span').html(dmsCoordinatePair(centroid));
- coordItem.append('span').html(decimalCoordinatePair(centroid));
- }
+ var _template = source.encrypted ? utilAesDecrypt(source.template) : source.template;
- if (center) {
- coordItem = list.append('li').html(_t.html('info_panels.measurement.center') + ':');
- coordItem.append('span').html(dmsCoordinatePair(center));
- coordItem.append('span').html(decimalCoordinatePair(center));
- }
+ source.tileSize = data.tileSize || 256;
+ source.zoomExtent = data.zoomExtent || [0, 22];
+ source.overzoom = data.overzoom !== false;
- if (length || area || typeof distance === 'number') {
- var toggle = _isImperial ? 'imperial' : 'metric';
- selection.append('a').html(_t.html('info_panels.measurement.' + toggle)).attr('href', '#').attr('class', 'button button-toggle-units').on('click', function (d3_event) {
- d3_event.preventDefault();
- _isImperial = !_isImperial;
- selection.call(redraw);
- });
- }
- }
+ source.offset = function (val) {
+ if (!arguments.length) return _offset;
+ _offset = val;
+ return source;
+ };
- var panel = function panel(selection) {
- selection.call(redraw);
- context.map().on('drawn.info-measurement', function () {
- selection.call(redraw);
+ source.nudge = function (val, zoomlevel) {
+ _offset[0] += val[0] / Math.pow(2, zoomlevel);
+ _offset[1] += val[1] / Math.pow(2, zoomlevel);
+ return source;
+ };
+
+ source.name = function () {
+ var id_safe = source.id.replace(/\./g, '<TX_DOT>');
+ return _t('imagery.' + id_safe + '.name', {
+ "default": lodash.exports.escape(_name)
});
- context.on('enter.info-measurement', function () {
- selection.call(redraw);
+ };
+
+ source.label = function () {
+ var id_safe = source.id.replace(/\./g, '<TX_DOT>');
+ return _t.html('imagery.' + id_safe + '.name', {
+ "default": lodash.exports.escape(_name)
});
};
- panel.off = function () {
- context.map().on('drawn.info-measurement', null);
- context.on('enter.info-measurement', null);
+ source.description = function () {
+ var id_safe = source.id.replace(/\./g, '<TX_DOT>');
+ return _t.html('imagery.' + id_safe + '.description', {
+ "default": lodash.exports.escape(_description)
+ });
};
- panel.id = 'measurement';
- panel.label = _t.html('info_panels.measurement.title');
- panel.key = _t('info_panels.measurement.key');
- return panel;
- }
+ source.best = function () {
+ return _best;
+ };
- var uiInfoPanels = {
- background: uiPanelBackground,
- history: uiPanelHistory,
- location: uiPanelLocation,
- measurement: uiPanelMeasurement
- };
+ source.area = function () {
+ if (!data.polygon) return Number.MAX_VALUE; // worldwide
- function uiInfo(context) {
- var ids = Object.keys(uiInfoPanels);
- var wasActive = ['measurement'];
- var panels = {};
- var active = {}; // create panels
+ var area = d3_geoArea({
+ type: 'MultiPolygon',
+ coordinates: [data.polygon]
+ });
+ return isNaN(area) ? 0 : area;
+ };
- ids.forEach(function (k) {
- if (!panels[k]) {
- panels[k] = uiInfoPanels[k](context);
- active[k] = false;
+ source.imageryUsed = function () {
+ return _name || source.id;
+ };
+
+ source.template = function (val) {
+ if (!arguments.length) return _template;
+
+ if (source.id === 'custom' || source.id === 'Bing') {
+ _template = val;
}
- });
- function info(selection) {
- function redraw() {
- var activeids = ids.filter(function (k) {
- return active[k];
- }).sort();
- var containers = infoPanels.selectAll('.panel-container').data(activeids, function (k) {
- return k;
- });
- containers.exit().style('opacity', 1).transition().duration(200).style('opacity', 0).on('end', function (d) {
- select(this).call(panels[d].off).remove();
- });
- var enter = containers.enter().append('div').attr('class', function (d) {
- return 'fillD2 panel-container panel-container-' + d;
- });
- enter.style('opacity', 0).transition().duration(200).style('opacity', 1);
- var title = enter.append('div').attr('class', 'panel-title fillD2');
- title.append('h3').html(function (d) {
- return panels[d].label;
- });
- title.append('button').attr('class', 'close').on('click', function (d3_event, d) {
- d3_event.stopImmediatePropagation();
- d3_event.preventDefault();
- info.toggle(d);
- }).call(svgIcon('#iD-icon-close'));
- enter.append('div').attr('class', function (d) {
- return 'panel-content panel-content-' + d;
- }); // redraw the panels
+ return source;
+ };
- infoPanels.selectAll('.panel-content').each(function (d) {
- select(this).call(panels[d]);
- });
+ source.url = function (coord) {
+ var result = _template;
+ if (result === '') return result; // source 'none'
+ // Guess a type based on the tokens present in the template
+ // (This is for 'custom' source, where we don't know)
+
+ if (!source.type || source.id === 'custom') {
+ if (/SERVICE=WMS|\{(proj|wkid|bbox)\}/.test(_template)) {
+ source.type = 'wms';
+ source.projection = 'EPSG:3857'; // guess
+ } else if (/\{(x|y)\}/.test(_template)) {
+ source.type = 'tms';
+ } else if (/\{u\}/.test(_template)) {
+ source.type = 'bing';
+ }
}
- info.toggle = function (which) {
- var activeids = ids.filter(function (k) {
- return active[k];
- });
+ if (source.type === 'wms') {
+ var tileToProjectedCoords = function tileToProjectedCoords(x, y, z) {
+ //polyfill for IE11, PhantomJS
+ var sinh = Math.sinh || function (x) {
+ var y = Math.exp(x);
+ return (y - 1 / y) / 2;
+ };
- if (which) {
- // toggle one
- active[which] = !active[which];
+ var zoomSize = Math.pow(2, z);
+ var lon = x / zoomSize * Math.PI * 2 - Math.PI;
+ var lat = Math.atan(sinh(Math.PI * (1 - 2 * y / zoomSize)));
- if (activeids.length === 1 && activeids[0] === which) {
- // none active anymore
- wasActive = [which];
- }
+ switch (source.projection) {
+ case 'EPSG:4326':
+ return {
+ x: lon * 180 / Math.PI,
+ y: lat * 180 / Math.PI
+ };
- context.container().select('.' + which + '-panel-toggle-item').classed('active', active[which]).select('input').property('checked', active[which]);
- } else {
- // toggle all
- if (activeids.length) {
- wasActive = activeids;
- activeids.forEach(function (k) {
- active[k] = false;
- });
- } else {
- wasActive.forEach(function (k) {
- active[k] = true;
- });
+ default:
+ // EPSG:3857 and synonyms
+ var mercCoords = mercatorRaw(lon, lat);
+ return {
+ x: 20037508.34 / Math.PI * mercCoords[0],
+ y: 20037508.34 / Math.PI * mercCoords[1]
+ };
}
- }
+ };
- redraw();
- };
+ var tileSize = source.tileSize;
+ var projection = source.projection;
+ var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]);
+ var maxXminY = tileToProjectedCoords(coord[0] + 1, coord[1] + 1, coord[2]);
+ result = result.replace(/\{(\w+)\}/g, function (token, key) {
+ switch (key) {
+ case 'width':
+ case 'height':
+ return tileSize;
- var infoPanels = selection.selectAll('.info-panels').data([0]);
- infoPanels = infoPanels.enter().append('div').attr('class', 'info-panels').merge(infoPanels);
- redraw();
- context.keybinding().on(uiCmd('⌘' + _t('info_panels.key')), function (d3_event) {
- d3_event.stopImmediatePropagation();
- d3_event.preventDefault();
- info.toggle();
- });
- ids.forEach(function (k) {
- var key = _t('info_panels.' + k + '.key', {
- "default": null
+ case 'proj':
+ return projection;
+
+ case 'wkid':
+ return projection.replace(/^EPSG:/, '');
+
+ case 'bbox':
+ // WMS 1.3 flips x/y for some coordinate systems including EPSG:4326 - #7557
+ if (projection === 'EPSG:4326' && // The CRS parameter implies version 1.3 (prior versions use SRS)
+ /VERSION=1.3|CRS={proj}/.test(source.template().toUpperCase())) {
+ return maxXminY.y + ',' + minXmaxY.x + ',' + minXmaxY.y + ',' + maxXminY.x;
+ } else {
+ return minXmaxY.x + ',' + maxXminY.y + ',' + maxXminY.x + ',' + minXmaxY.y;
+ }
+
+ case 'w':
+ return minXmaxY.x;
+
+ case 's':
+ return maxXminY.y;
+
+ case 'n':
+ return maxXminY.x;
+
+ case 'e':
+ return minXmaxY.y;
+
+ default:
+ return token;
+ }
});
- if (!key) return;
- context.keybinding().on(uiCmd('⌘⇧' + key), function (d3_event) {
- d3_event.stopImmediatePropagation();
- d3_event.preventDefault();
- info.toggle(k);
+ } else if (source.type === 'tms') {
+ result = result.replace('{x}', coord[0]).replace('{y}', coord[1]) // TMS-flipped y coordinate
+ .replace(/\{[t-]y\}/, Math.pow(2, coord[2]) - coord[1] - 1).replace(/\{z(oom)?\}/, coord[2]) // only fetch retina tiles for retina screens
+ .replace(/\{@2x\}|\{r\}/, isRetina ? '@2x' : '');
+ } else if (source.type === 'bing') {
+ result = result.replace('{u}', function () {
+ var u = '';
+
+ for (var zoom = coord[2]; zoom > 0; zoom--) {
+ var b = 0;
+ var mask = 1 << zoom - 1;
+ if ((coord[0] & mask) !== 0) b++;
+ if ((coord[1] & mask) !== 0) b += 2;
+ u += b.toString();
+ }
+
+ return u;
});
+ } // these apply to any type..
+
+
+ result = result.replace(/\{switch:([^}]+)\}/, function (s, r) {
+ var subdomains = r.split(',');
+ return subdomains[(coord[0] + coord[1]) % subdomains.length];
});
- }
+ return result;
+ };
- return info;
- }
+ source.validZoom = function (z) {
+ return source.zoomExtent[0] <= z && (source.overzoom || source.zoomExtent[1] > z);
+ };
- function pointBox(loc, context) {
- var rect = context.surfaceRect();
- var point = context.curtainProjection(loc);
- return {
- left: point[0] + rect.left - 40,
- top: point[1] + rect.top - 60,
- width: 80,
- height: 90
+ source.isLocatorOverlay = function () {
+ return source.id === 'mapbox_locator_overlay';
};
- }
- function pad(locOrBox, padding, context) {
- var box;
+ /* hides a source from the list, but leaves it available for use */
- if (locOrBox instanceof Array) {
- var rect = context.surfaceRect();
- var point = context.curtainProjection(locOrBox);
- box = {
- left: point[0] + rect.left,
- top: point[1] + rect.top
- };
- } else {
- box = locOrBox;
- }
- return {
- left: box.left - padding,
- top: box.top - padding,
- width: (box.width || 0) + 2 * padding,
- height: (box.width || 0) + 2 * padding
+ source.isHidden = function () {
+ return source.id === 'DigitalGlobe-Premium-vintage' || source.id === 'DigitalGlobe-Standard-vintage';
};
- }
- function icon(name, svgklass, useklass) {
- return '<svg class="icon ' + (svgklass || '') + '">' + '<use xlink:href="' + name + '"' + (useklass ? ' class="' + useklass + '"' : '') + '></use></svg>';
- }
- var helpStringReplacements; // Returns the localized HTML element for `id` with a standardized set of icon, key, and
- // label replacements suitable for tutorials and documentation. Optionally supplemented
- // with custom `replacements`
- function helpHtml(id, replacements) {
- // only load these the first time
- if (!helpStringReplacements) {
- helpStringReplacements = {
- // insert icons corresponding to various UI elements
- point_icon: icon('#iD-icon-point', 'inline'),
- line_icon: icon('#iD-icon-line', 'inline'),
- area_icon: icon('#iD-icon-area', 'inline'),
- note_icon: icon('#iD-icon-note', 'inline add-note'),
- plus: icon('#iD-icon-plus', 'inline'),
- minus: icon('#iD-icon-minus', 'inline'),
- layers_icon: icon('#iD-icon-layers', 'inline'),
- data_icon: icon('#iD-icon-data', 'inline'),
- inspect: icon('#iD-icon-inspect', 'inline'),
- help_icon: icon('#iD-icon-help', 'inline'),
- undo_icon: icon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-redo' : '#iD-icon-undo', 'inline'),
- redo_icon: icon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-undo' : '#iD-icon-redo', 'inline'),
- save_icon: icon('#iD-icon-save', 'inline'),
- // operation icons
- circularize_icon: icon('#iD-operation-circularize', 'inline operation'),
- continue_icon: icon('#iD-operation-continue', 'inline operation'),
- copy_icon: icon('#iD-operation-copy', 'inline operation'),
- delete_icon: icon('#iD-operation-delete', 'inline operation'),
- disconnect_icon: icon('#iD-operation-disconnect', 'inline operation'),
- downgrade_icon: icon('#iD-operation-downgrade', 'inline operation'),
- extract_icon: icon('#iD-operation-extract', 'inline operation'),
- merge_icon: icon('#iD-operation-merge', 'inline operation'),
- move_icon: icon('#iD-operation-move', 'inline operation'),
- orthogonalize_icon: icon('#iD-operation-orthogonalize', 'inline operation'),
- paste_icon: icon('#iD-operation-paste', 'inline operation'),
- reflect_long_icon: icon('#iD-operation-reflect-long', 'inline operation'),
- reflect_short_icon: icon('#iD-operation-reflect-short', 'inline operation'),
- reverse_icon: icon('#iD-operation-reverse', 'inline operation'),
- rotate_icon: icon('#iD-operation-rotate', 'inline operation'),
- split_icon: icon('#iD-operation-split', 'inline operation'),
- straighten_icon: icon('#iD-operation-straighten', 'inline operation'),
- // interaction icons
- leftclick: icon('#iD-walkthrough-mouse-left', 'inline operation'),
- rightclick: icon('#iD-walkthrough-mouse-right', 'inline operation'),
- mousewheel_icon: icon('#iD-walkthrough-mousewheel', 'inline operation'),
- tap_icon: icon('#iD-walkthrough-tap', 'inline operation'),
- doubletap_icon: icon('#iD-walkthrough-doubletap', 'inline operation'),
- longpress_icon: icon('#iD-walkthrough-longpress', 'inline operation'),
- touchdrag_icon: icon('#iD-walkthrough-touchdrag', 'inline operation'),
- pinch_icon: icon('#iD-walkthrough-pinch-apart', 'inline operation'),
- // insert keys; may be localized and platform-dependent
- shift: uiCmd.display('⇧'),
- alt: uiCmd.display('⌥'),
- "return": uiCmd.display('↵'),
- esc: _t.html('shortcuts.key.esc'),
- space: _t.html('shortcuts.key.space'),
- add_note_key: _t.html('modes.add_note.key'),
- help_key: _t.html('help.key'),
- shortcuts_key: _t.html('shortcuts.toggle.key'),
- // reference localized UI labels directly so that they'll always match
- save: _t.html('save.title'),
- undo: _t.html('undo.title'),
- redo: _t.html('redo.title'),
- upload: _t.html('commit.save'),
- point: _t.html('modes.add_point.title'),
- line: _t.html('modes.add_line.title'),
- area: _t.html('modes.add_area.title'),
- note: _t.html('modes.add_note.label'),
- circularize: _t.html('operations.circularize.title'),
- "continue": _t.html('operations.continue.title'),
- copy: _t.html('operations.copy.title'),
- "delete": _t.html('operations.delete.title'),
- disconnect: _t.html('operations.disconnect.title'),
- downgrade: _t.html('operations.downgrade.title'),
- extract: _t.html('operations.extract.title'),
- merge: _t.html('operations.merge.title'),
- move: _t.html('operations.move.title'),
- orthogonalize: _t.html('operations.orthogonalize.title'),
- paste: _t.html('operations.paste.title'),
- reflect_long: _t.html('operations.reflect.title.long'),
- reflect_short: _t.html('operations.reflect.title.short'),
- reverse: _t.html('operations.reverse.title'),
- rotate: _t.html('operations.rotate.title'),
- split: _t.html('operations.split.title'),
- straighten: _t.html('operations.straighten.title'),
- map_data: _t.html('map_data.title'),
- osm_notes: _t.html('map_data.layers.notes.title'),
- fields: _t.html('inspector.fields'),
- tags: _t.html('inspector.tags'),
- relations: _t.html('inspector.relations'),
- new_relation: _t.html('inspector.new_relation'),
- turn_restrictions: _t.html('_tagging.presets.fields.restrictions.label'),
- background_settings: _t.html('background.description'),
- imagery_offset: _t.html('background.fix_misalignment'),
- start_the_walkthrough: _t.html('splash.walkthrough'),
- help: _t.html('help.title'),
- ok: _t.html('intro.ok')
- };
- }
-
- var reps;
+ source.copyrightNotices = function () {};
- if (replacements) {
- reps = Object.assign(replacements, helpStringReplacements);
- } else {
- reps = helpStringReplacements;
- }
+ source.getMetadata = function (center, tileCoord, callback) {
+ var vintage = {
+ start: localeDateString(source.startDate),
+ end: localeDateString(source.endDate)
+ };
+ vintage.range = vintageRange(vintage);
+ var metadata = {
+ vintage: vintage
+ };
+ callback(null, metadata);
+ };
- return _t.html(id, reps) // use keyboard key styling for shortcuts
- .replace(/\`(.*?)\`/g, '<kbd>$1</kbd>');
+ return source;
}
- function slugify(text) {
- return text.toString().toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
- .replace(/[^\w\-]+/g, '') // Remove all non-word chars
- .replace(/\-\-+/g, '-') // Replace multiple - with single -
- .replace(/^-+/, '') // Trim - from start of text
- .replace(/-+$/, ''); // Trim - from end of text
- } // console warning for missing walkthrough names
+ rendererBackgroundSource.Bing = function (data, dispatch) {
+ // https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata
+ // https://docs.microsoft.com/en-us/bingmaps/rest-services/directly-accessing-the-bing-maps-tiles
+ //fallback url template
+ data.template = 'https://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&n=z';
+ var bing = rendererBackgroundSource(data); //var key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU'; // P2, JOSM, etc
+ var key = 'Ak5oTE46TUbjRp08OFVcGpkARErDobfpuyNKa-W2mQ8wbt1K1KL8p1bIRwWwcF-Q'; // iD
- var missingStrings = {};
+ /*
+ missing tile image strictness param (n=)
+ • n=f -> (Fail) returns a 404
+ • n=z -> (Empty) returns a 200 with 0 bytes (no content)
+ • n=t -> (Transparent) returns a 200 with a transparent (png) tile
+ */
- function checkKey(key, text) {
- if (_t(key, {
- "default": undefined
- }) === undefined) {
- if (missingStrings.hasOwnProperty(key)) return; // warn once
+ var strictParam = 'n';
+ var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&uriScheme=https&key=' + key;
+ var cache = {};
+ var inflight = {};
+ var providers = [];
+ d3_json(url).then(function (json) {
+ var imageryResource = json.resourceSets[0].resources[0]; //retrieve and prepare up to date imagery template
- missingStrings[key] = text;
- var missing = key + ': ' + text;
- if (typeof console !== 'undefined') console.log(missing); // eslint-disable-line
- }
- }
+ var template = imageryResource.imageUrl; //https://ecn.{subdomain}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=10339
- function localize(obj) {
- var key; // Assign name if entity has one..
+ var subDomains = imageryResource.imageUrlSubdomains; //["t0, t1, t2, t3"]
- var name = obj.tags && obj.tags.name;
+ var subDomainNumbers = subDomains.map(function (subDomain) {
+ return subDomain.substring(1);
+ }).join(',');
+ template = template.replace('{subdomain}', "t{switch:".concat(subDomainNumbers, "}")).replace('{quadkey}', '{u}');
- if (name) {
- key = 'intro.graph.name.' + slugify(name);
- obj.tags.name = _t(key, {
- "default": name
+ if (!new URLSearchParams(template).has(strictParam)) {
+ template += "&".concat(strictParam, "=z");
+ }
+
+ bing.template(template);
+ providers = imageryResource.imageryProviders.map(function (provider) {
+ return {
+ attribution: provider.attribution,
+ areas: provider.coverageAreas.map(function (area) {
+ return {
+ zoom: [area.zoomMin, area.zoomMax],
+ extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]])
+ };
+ })
+ };
});
- checkKey(key, name);
- } // Assign street name if entity has one..
+ dispatch.call('change');
+ })["catch"](function () {
+ /* ignore */
+ });
+ bing.copyrightNotices = function (zoom, extent) {
+ zoom = Math.min(zoom, 21);
+ return providers.filter(function (provider) {
+ return provider.areas.some(function (area) {
+ return extent.intersects(area.extent) && area.zoom[0] <= zoom && area.zoom[1] >= zoom;
+ });
+ }).map(function (provider) {
+ return provider.attribution;
+ }).join(', ');
+ };
- var street = obj.tags && obj.tags['addr:street'];
+ bing.getMetadata = function (center, tileCoord, callback) {
+ var tileID = tileCoord.slice(0, 3).join('/');
+ var zoom = Math.min(tileCoord[2], 21);
+ var centerPoint = center[1] + ',' + center[0]; // lat,lng
- if (street) {
- key = 'intro.graph.name.' + slugify(street);
- obj.tags['addr:street'] = _t(key, {
- "default": street
- });
- checkKey(key, street); // Add address details common across walkthrough..
+ var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + '?zl=' + zoom + '&key=' + key;
+ if (inflight[tileID]) return;
- var addrTags = ['block_number', 'city', 'county', 'district', 'hamlet', 'neighbourhood', 'postcode', 'province', 'quarter', 'state', 'subdistrict', 'suburb'];
- addrTags.forEach(function (k) {
- var key = 'intro.graph.' + k;
- var tag = 'addr:' + k;
- var val = obj.tags && obj.tags[tag];
- var str = _t(key, {
- "default": val
- });
+ if (!cache[tileID]) {
+ cache[tileID] = {};
+ }
- if (str) {
- if (str.match(/^<.*>$/) !== null) {
- delete obj.tags[tag];
- } else {
- obj.tags[tag] = str;
- }
+ if (cache[tileID] && cache[tileID].metadata) {
+ return callback(null, cache[tileID].metadata);
+ }
+
+ inflight[tileID] = true;
+ d3_json(url).then(function (result) {
+ delete inflight[tileID];
+
+ if (!result) {
+ throw new Error('Unknown Error');
}
+
+ var vintage = {
+ start: localeDateString(result.resourceSets[0].resources[0].vintageStart),
+ end: localeDateString(result.resourceSets[0].resources[0].vintageEnd)
+ };
+ vintage.range = vintageRange(vintage);
+ var metadata = {
+ vintage: vintage
+ };
+ cache[tileID].metadata = metadata;
+ if (callback) callback(null, metadata);
+ })["catch"](function (err) {
+ delete inflight[tileID];
+ if (callback) callback(err.message);
});
+ };
+
+ bing.terms_url = 'https://blog.openstreetmap.org/2010/11/30/microsoft-imagery-details';
+ return bing;
+ };
+
+ rendererBackgroundSource.Esri = function (data) {
+ // in addition to using the tilemap at zoom level 20, overzoom real tiles - #4327 (deprecated technique, but it works)
+ if (data.template.match(/blankTile/) === null) {
+ data.template = data.template + '?blankTile=false';
}
- return obj;
- } // Used to detect squareness.. some duplicataion of code from actionOrthogonalize.
+ var esri = rendererBackgroundSource(data);
+ var cache = {};
+ var inflight = {};
- function isMostlySquare(points) {
- // note: uses 15 here instead of the 12 from actionOrthogonalize because
- // actionOrthogonalize can actually straighten some larger angles as it iterates
- var threshold = 15; // degrees within right or straight
+ var _prevCenter; // use a tilemap service to set maximum zoom for esri tiles dynamically
+ // https://developers.arcgis.com/documentation/tiled-elevation-service/
- var lowerBound = Math.cos((90 - threshold) * Math.PI / 180); // near right
- var upperBound = Math.cos(threshold * Math.PI / 180); // near straight
+ esri.fetchTilemap = function (center) {
+ // skip if we have already fetched a tilemap within 5km
+ if (_prevCenter && geoSphericalDistance(center, _prevCenter) < 5000) return;
+ _prevCenter = center; // tiles are available globally to zoom level 19, afterward they may or may not be present
- for (var i = 0; i < points.length; i++) {
- var a = points[(i - 1 + points.length) % points.length];
- var origin = points[i];
- var b = points[(i + 1) % points.length];
- var dotp = geoVecNormalizedDot(a, b, origin);
- var mag = Math.abs(dotp);
+ var z = 20; // first generate a random url using the template
- if (mag > lowerBound && mag < upperBound) {
- return false;
- }
- }
+ var dummyUrl = esri.url([1, 2, 3]); // calculate url z/y/x from the lat/long of the center of the map
- return true;
- }
- function selectMenuItem(context, operation) {
- return context.container().select('.edit-menu .edit-menu-item-' + operation);
- }
- function transitionTime(point1, point2) {
- var distance = geoSphericalDistance(point1, point2);
+ var x = Math.floor((center[0] + 180) / 360 * Math.pow(2, z));
+ var y = Math.floor((1 - Math.log(Math.tan(center[1] * Math.PI / 180) + 1 / Math.cos(center[1] * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, z)); // fetch an 8x8 grid to leverage cache
- if (distance === 0) {
- return 0;
- } else if (distance < 80) {
- return 500;
- } else {
- return 1000;
- }
- }
+ var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+\?blankTile=false/, 'tilemap') + '/' + z + '/' + y + '/' + x + '/8/8'; // make the request and introspect the response from the tilemap server
- // hide class, which sets display=none, and a d3 transition for opacity.
- // this will cause blinking when called repeatedly, so check that the
- // value actually changes between calls.
+ d3_json(tilemapUrl).then(function (tilemap) {
+ if (!tilemap) {
+ throw new Error('Unknown Error');
+ }
- function uiToggle(show, callback) {
- return function (selection) {
- selection.style('opacity', show ? 0 : 1).classed('hide', false).transition().style('opacity', show ? 1 : 0).on('end', function () {
- select(this).classed('hide', !show).style('opacity', null);
- if (callback) callback.apply(this);
- });
- };
- }
+ var hasTiles = true;
- function uiCurtain(containerNode) {
- var surface = select(null),
- tooltip = select(null),
- darkness = select(null);
+ for (var i = 0; i < tilemap.data.length; i++) {
+ // 0 means an individual tile in the grid doesn't exist
+ if (!tilemap.data[i]) {
+ hasTiles = false;
+ break;
+ }
+ } // if any tiles are missing at level 20 we restrict maxZoom to 19
- function curtain(selection) {
- surface = selection.append('svg').attr('class', 'curtain').style('top', 0).style('left', 0);
- darkness = surface.append('path').attr('x', 0).attr('y', 0).attr('class', 'curtain-darkness');
- select(window).on('resize.curtain', resize);
- tooltip = selection.append('div').attr('class', 'tooltip');
- tooltip.append('div').attr('class', 'popover-arrow');
- tooltip.append('div').attr('class', 'popover-inner');
- resize();
- function resize() {
- surface.attr('width', containerNode.clientWidth).attr('height', containerNode.clientHeight);
- curtain.cut(darkness.datum());
+ esri.zoomExtent[1] = hasTiles ? 22 : 19;
+ })["catch"](function () {
+ /* ignore */
+ });
+ };
+
+ esri.getMetadata = function (center, tileCoord, callback) {
+ if (esri.id !== 'EsriWorldImagery') {
+ // rest endpoint is not available for ESRI's "clarity" imagery
+ return callback(null, {});
}
- }
- /**
- * Reveal cuts the curtain to highlight the given box,
- * and shows a tooltip with instructions next to the box.
- *
- * @param {String|ClientRect} [box] box used to cut the curtain
- * @param {String} [text] text for a tooltip
- * @param {Object} [options]
- * @param {string} [options.tooltipClass] optional class to add to the tooltip
- * @param {integer} [options.duration] transition time in milliseconds
- * @param {string} [options.buttonText] if set, create a button with this text label
- * @param {function} [options.buttonCallback] if set, the callback for the button
- * @param {function} [options.padding] extra margin in px to put around bbox
- * @param {String|ClientRect} [options.tooltipBox] box for tooltip position, if different from box for the curtain
- */
+ var tileID = tileCoord.slice(0, 3).join('/');
+ var zoom = Math.min(tileCoord[2], esri.zoomExtent[1]);
+ var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be)
- curtain.reveal = function (box, html, options) {
- options = options || {};
+ var unknown = _t('info_panels.background.unknown');
+ var vintage = {};
+ var metadata = {};
+ if (inflight[tileID]) return; // build up query using the layer appropriate to the current zoom
- if (typeof box === 'string') {
- box = select(box).node();
- }
+ var url = 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/4/query';
+ url += '?returnGeometry=false&geometry=' + centerPoint + '&inSR=4326&geometryType=esriGeometryPoint&outFields=*&f=json';
- if (box && box.getBoundingClientRect) {
- box = copyBox(box.getBoundingClientRect());
- var containerRect = containerNode.getBoundingClientRect();
- box.top -= containerRect.top;
- box.left -= containerRect.left;
+ if (!cache[tileID]) {
+ cache[tileID] = {};
}
- if (box && options.padding) {
- box.top -= options.padding;
- box.left -= options.padding;
- box.bottom += options.padding;
- box.right += options.padding;
- box.height += options.padding * 2;
- box.width += options.padding * 2;
+ if (cache[tileID] && cache[tileID].metadata) {
+ return callback(null, cache[tileID].metadata);
}
- var tooltipBox;
+ inflight[tileID] = true;
+ d3_json(url).then(function (result) {
+ delete inflight[tileID];
+ result = result.features.map(function (f) {
+ return f.attributes;
+ }).filter(function (a) {
+ return a.MinMapLevel <= zoom && a.MaxMapLevel >= zoom;
+ })[0];
- if (options.tooltipBox) {
- tooltipBox = options.tooltipBox;
+ if (!result) {
+ throw new Error('Unknown Error');
+ } else if (result.features && result.features.length < 1) {
+ throw new Error('No Results');
+ } else if (result.error && result.error.message) {
+ throw new Error(result.error.message);
+ } // pass through the discrete capture date from metadata
- if (typeof tooltipBox === 'string') {
- tooltipBox = select(tooltipBox).node();
+
+ var captureDate = localeDateString(result.SRC_DATE2);
+ vintage = {
+ start: captureDate,
+ end: captureDate,
+ range: captureDate
+ };
+ metadata = {
+ vintage: vintage,
+ source: clean(result.NICE_NAME),
+ description: clean(result.NICE_DESC),
+ resolution: clean(+parseFloat(result.SRC_RES).toFixed(4)),
+ accuracy: clean(+parseFloat(result.SRC_ACC).toFixed(4))
+ }; // append units - meters
+
+ if (isFinite(metadata.resolution)) {
+ metadata.resolution += ' m';
}
- if (tooltipBox && tooltipBox.getBoundingClientRect) {
- tooltipBox = copyBox(tooltipBox.getBoundingClientRect());
+ if (isFinite(metadata.accuracy)) {
+ metadata.accuracy += ' m';
}
- } else {
- tooltipBox = box;
+
+ cache[tileID].metadata = metadata;
+ if (callback) callback(null, metadata);
+ })["catch"](function (err) {
+ delete inflight[tileID];
+ if (callback) callback(err.message);
+ });
+
+ function clean(val) {
+ return String(val).trim() || unknown;
}
+ };
- if (tooltipBox && html) {
- if (html.indexOf('**') !== -1) {
- if (html.indexOf('<span') === 0) {
- html = html.replace(/^(<span.*?>)(.+?)(\*\*)/, '$1<span>$2</span>$3');
- } else {
- html = html.replace(/^(.+?)(\*\*)/, '<span>$1</span>$2');
- } // pseudo markdown bold text for the instruction section..
+ return esri;
+ };
+ rendererBackgroundSource.None = function () {
+ var source = rendererBackgroundSource({
+ id: 'none',
+ template: ''
+ });
- html = html.replace(/\*\*(.*?)\*\*/g, '<span class="instruction">$1</span>');
- }
+ source.name = function () {
+ return _t('background.none');
+ };
- html = html.replace(/\*(.*?)\*/g, '<em>$1</em>'); // emphasis
+ source.label = function () {
+ return _t.html('background.none');
+ };
- html = html.replace(/\{br\}/g, '<br/><br/>'); // linebreak
+ source.imageryUsed = function () {
+ return null;
+ };
- if (options.buttonText && options.buttonCallback) {
- html += '<div class="button-section">' + '<button href="#" class="button action">' + options.buttonText + '</button></div>';
- }
+ source.area = function () {
+ return -1; // sources in background pane are sorted by area
+ };
- var classes = 'curtain-tooltip popover tooltip arrowed in ' + (options.tooltipClass || '');
- tooltip.classed(classes, true).selectAll('.popover-inner').html(html);
+ return source;
+ };
- if (options.buttonText && options.buttonCallback) {
- var button = tooltip.selectAll('.button-section .button.action');
- button.on('click', function (d3_event) {
- d3_event.preventDefault();
- options.buttonCallback();
- });
- }
+ rendererBackgroundSource.Custom = function (template) {
+ var source = rendererBackgroundSource({
+ id: 'custom',
+ template: template
+ });
- var tip = copyBox(tooltip.node().getBoundingClientRect()),
- w = containerNode.clientWidth,
- h = containerNode.clientHeight,
- tooltipWidth = 200,
- tooltipArrow = 5,
- side,
- pos; // hack: this will have bottom placement,
- // so need to reserve extra space for the tooltip illustration.
+ source.name = function () {
+ return _t('background.custom');
+ };
- if (options.tooltipClass === 'intro-mouse') {
- tip.height += 80;
- } // trim box dimensions to just the portion that fits in the container..
+ source.label = function () {
+ return _t.html('background.custom');
+ };
+ source.imageryUsed = function () {
+ // sanitize personal connection tokens - #6801
+ var cleaned = source.template(); // from query string parameters
- if (tooltipBox.top + tooltipBox.height > h) {
- tooltipBox.height -= tooltipBox.top + tooltipBox.height - h;
- }
+ if (cleaned.indexOf('?') !== -1) {
+ var parts = cleaned.split('?', 2);
+ var qs = utilStringQs(parts[1]);
+ ['access_token', 'connectId', 'token'].forEach(function (param) {
+ if (qs[param]) {
+ qs[param] = '{apikey}';
+ }
+ });
+ cleaned = parts[0] + '?' + utilQsString(qs, true); // true = soft encode
+ } // from wms/wmts api path parameters
- if (tooltipBox.left + tooltipBox.width > w) {
- tooltipBox.width -= tooltipBox.left + tooltipBox.width - w;
- } // determine tooltip placement..
+ cleaned = cleaned.replace(/token\/(\w+)/, 'token/{apikey}');
+ return 'Custom (' + cleaned + ' )';
+ };
- if (tooltipBox.top + tooltipBox.height < 100) {
- // tooltip below box..
- side = 'bottom';
- pos = [tooltipBox.left + tooltipBox.width / 2 - tip.width / 2, tooltipBox.top + tooltipBox.height];
- } else if (tooltipBox.top > h - 140) {
- // tooltip above box..
- side = 'top';
- pos = [tooltipBox.left + tooltipBox.width / 2 - tip.width / 2, tooltipBox.top - tip.height];
- } else {
- // tooltip to the side of the tooltipBox..
- var tipY = tooltipBox.top + tooltipBox.height / 2 - tip.height / 2;
-
- if (_mainLocalizer.textDirection() === 'rtl') {
- if (tooltipBox.left - tooltipWidth - tooltipArrow < 70) {
- side = 'right';
- pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
- } else {
- side = 'left';
- pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
- }
- } else {
- if (tooltipBox.left + tooltipBox.width + tooltipArrow + tooltipWidth > w - 70) {
- side = 'left';
- pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
- } else {
- side = 'right';
- pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
- }
- }
- }
-
- if (options.duration !== 0 || !tooltip.classed(side)) {
- tooltip.call(uiToggle(true));
- }
-
- tooltip.style('top', pos[1] + 'px').style('left', pos[0] + 'px').attr('class', classes + ' ' + side); // shift popover-inner if it is very close to the top or bottom edge
- // (doesn't affect the placement of the popover-arrow)
-
- var shiftY = 0;
+ source.area = function () {
+ return -2; // sources in background pane are sorted by area
+ };
- if (side === 'left' || side === 'right') {
- if (pos[1] < 60) {
- shiftY = 60 - pos[1];
- } else if (pos[1] + tip.height > h - 100) {
- shiftY = h - pos[1] - tip.height - 100;
- }
- }
+ return source;
+ };
- tooltip.selectAll('.popover-inner').style('top', shiftY + 'px');
- } else {
- tooltip.classed('in', false).call(uiToggle(false));
- }
+ function rendererTileLayer(context) {
+ var transformProp = utilPrefixCSSProperty('Transform');
+ var tiler = utilTiler();
+ var _tileSize = 256;
- curtain.cut(box, options.duration);
- return tooltip;
- };
+ var _projection;
- curtain.cut = function (datum, duration) {
- darkness.datum(datum).interrupt();
- var selection;
+ var _cache = {};
- if (duration === 0) {
- selection = darkness;
- } else {
- selection = darkness.transition().duration(duration || 600).ease(linear$1);
- }
+ var _tileOrigin;
- selection.attr('d', function (d) {
- var containerWidth = containerNode.clientWidth;
- var containerHeight = containerNode.clientHeight;
- var string = 'M 0,0 L 0,' + containerHeight + ' L ' + containerWidth + ',' + containerHeight + 'L' + containerWidth + ',0 Z';
- if (!d) return string;
- return string + 'M' + d.left + ',' + d.top + 'L' + d.left + ',' + (d.top + d.height) + 'L' + (d.left + d.width) + ',' + (d.top + d.height) + 'L' + (d.left + d.width) + ',' + d.top + 'Z';
- });
- };
+ var _zoom;
- curtain.remove = function () {
- surface.remove();
- tooltip.remove();
- select(window).on('resize.curtain', null);
- }; // ClientRects are immutable, so copy them to an object,
- // in case we need to trim the height/width.
+ var _source;
+ function tileSizeAtZoom(d, z) {
+ var EPSILON = 0.002; // close seams
- function copyBox(src) {
- return {
- top: src.top,
- right: src.right,
- bottom: src.bottom,
- left: src.left,
- width: src.width,
- height: src.height
- };
+ return _tileSize * Math.pow(2, z - d[2]) / _tileSize + EPSILON;
}
- return curtain;
- }
+ function atZoom(t, distance) {
+ var power = Math.pow(2, distance);
+ return [Math.floor(t[0] * power), Math.floor(t[1] * power), t[2] + distance];
+ }
- function uiIntroWelcome(context, reveal) {
- var dispatch = dispatch$8('done');
- var chapter = {
- title: 'intro.welcome.title'
- };
+ function lookUp(d) {
+ for (var up = -1; up > -d[2]; up--) {
+ var tile = atZoom(d, up);
- function welcome() {
- context.map().centerZoom([-85.63591, 41.94285], 19);
- reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.welcome'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: practice
- });
+ if (_cache[_source.url(tile)] !== false) {
+ return tile;
+ }
+ }
}
- function practice() {
- reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.practice'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: words
- });
- }
+ function uniqueBy(a, n) {
+ var o = [];
+ var seen = {};
- function words() {
- reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.words'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: chapters
- });
- }
+ for (var i = 0; i < a.length; i++) {
+ if (seen[a[i][n]] === undefined) {
+ o.push(a[i]);
+ seen[a[i][n]] = true;
+ }
+ }
- function chapters() {
- dispatch.call('done');
- reveal('.intro-nav-wrap .chapter-navigation', helpHtml('intro.welcome.chapters', {
- next: _t('intro.navigation.title')
- }));
+ return o;
}
- chapter.enter = function () {
- welcome();
- };
+ function addSource(d) {
+ d.push(_source.url(d));
+ return d;
+ } // Update tiles based on current state of `projection`.
- chapter.exit = function () {
- context.container().select('.curtain-tooltip.intro-mouse').selectAll('.counter').remove();
- };
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
- };
+ function background(selection) {
+ _zoom = geoScaleToZoom(_projection.scale(), _tileSize);
+ var pixelOffset;
- return utilRebind(chapter, dispatch, 'on');
- }
+ if (_source) {
+ pixelOffset = [_source.offset()[0] * Math.pow(2, _zoom), _source.offset()[1] * Math.pow(2, _zoom)];
+ } else {
+ pixelOffset = [0, 0];
+ }
- function uiIntroNavigation(context, reveal) {
- var dispatch = dispatch$8('done');
- var timeouts = [];
- var hallId = 'n2061';
- var townHall = [-85.63591, 41.94285];
- var springStreetId = 'w397';
- var springStreetEndId = 'n1834';
- var springStreet = [-85.63582, 41.94255];
- var onewayField = _mainPresetIndex.field('oneway');
- var maxspeedField = _mainPresetIndex.field('maxspeed');
- var chapter = {
- title: 'intro.navigation.title'
- };
+ var translate = [_projection.translate()[0] + pixelOffset[0], _projection.translate()[1] + pixelOffset[1]];
+ tiler.scale(_projection.scale() * 2 * Math.PI).translate(translate);
+ _tileOrigin = [_projection.scale() * Math.PI - translate[0], _projection.scale() * Math.PI - translate[1]];
+ render(selection);
+ } // Derive the tiles onscreen, remove those offscreen and position them.
+ // Important that this part not depend on `_projection` because it's
+ // rentered when tiles load/error (see #644).
- function timeout(f, t) {
- timeouts.push(window.setTimeout(f, t));
- }
- function eventCancel(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- }
+ function render(selection) {
+ if (!_source) return;
+ var requests = [];
+ var showDebug = context.getDebug('tile') && !_source.overlay;
- function isTownHallSelected() {
- var ids = context.selectedIDs();
- return ids.length === 1 && ids[0] === hallId;
- }
+ if (_source.validZoom(_zoom)) {
+ tiler.skipNullIsland(!!_source.overlay);
+ tiler().forEach(function (d) {
+ addSource(d);
+ if (d[3] === '') return;
+ if (typeof d[3] !== 'string') return; // Workaround for #2295
- function dragMap() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- var msec = transitionTime(townHall, context.map().center());
+ requests.push(d);
- if (msec) {
- reveal(null, null, {
- duration: 0
+ if (_cache[d[3]] === false && lookUp(d)) {
+ requests.push(addSource(lookUp(d)));
+ }
});
- }
-
- context.map().centerZoomEase(townHall, 19, msec);
- timeout(function () {
- var centerStart = context.map().center();
- var textId = context.lastPointerType() === 'mouse' ? 'drag' : 'drag_touch';
- var dragString = helpHtml('intro.navigation.map_info') + '{br}' + helpHtml('intro.navigation.' + textId);
- reveal('.surface', dragString);
- context.map().on('drawn.intro', function () {
- reveal('.surface', dragString, {
- duration: 0
- });
+ requests = uniqueBy(requests, 3).filter(function (r) {
+ // don't re-request tiles which have failed in the past
+ return _cache[r[3]] !== false;
});
- context.map().on('move.intro', function () {
- var centerNow = context.map().center();
+ }
- if (centerStart[0] !== centerNow[0] || centerStart[1] !== centerNow[1]) {
- context.map().on('move.intro', null);
- timeout(function () {
- continueTo(zoomMap);
- }, 3000);
- }
- });
- }, msec + 100);
+ function load(d3_event, d) {
+ _cache[d[3]] = true;
+ select(this).on('error', null).on('load', null).classed('tile-loaded', true);
+ render(selection);
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
+ function error(d3_event, d) {
+ _cache[d[3]] = false;
+ select(this).on('error', null).on('load', null).remove();
+ render(selection);
}
- }
- function zoomMap() {
- var zoomStart = context.map().zoom();
- var textId = context.lastPointerType() === 'mouse' ? 'zoom' : 'zoom_touch';
- var zoomString = helpHtml('intro.navigation.' + textId);
- reveal('.surface', zoomString);
- context.map().on('drawn.intro', function () {
- reveal('.surface', zoomString, {
- duration: 0
- });
- });
- context.map().on('move.intro', function () {
- if (context.map().zoom() !== zoomStart) {
- context.map().on('move.intro', null);
- timeout(function () {
- continueTo(features);
- }, 3000);
- }
- });
+ function imageTransform(d) {
+ var ts = _tileSize * Math.pow(2, _zoom - d[2]);
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
+ var scale = tileSizeAtZoom(d, _zoom);
+ return 'translate(' + (d[0] * ts - _tileOrigin[0]) + 'px,' + (d[1] * ts - _tileOrigin[1]) + 'px) ' + 'scale(' + scale + ',' + scale + ')';
}
- }
- function features() {
- var onClick = function onClick() {
- continueTo(pointsLinesAreas);
- };
-
- reveal('.surface', helpHtml('intro.navigation.features'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.map().on('drawn.intro', function () {
- reveal('.surface', helpHtml('intro.navigation.features'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- });
+ function tileCenter(d) {
+ var ts = _tileSize * Math.pow(2, _zoom - d[2]);
- function continueTo(nextStep) {
- context.map().on('drawn.intro', null);
- nextStep();
+ return [d[0] * ts - _tileOrigin[0] + ts / 2, d[1] * ts - _tileOrigin[1] + ts / 2];
}
- }
-
- function pointsLinesAreas() {
- var onClick = function onClick() {
- continueTo(nodesWays);
- };
- reveal('.surface', helpHtml('intro.navigation.points_lines_areas'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.map().on('drawn.intro', function () {
- reveal('.surface', helpHtml('intro.navigation.points_lines_areas'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- });
+ function debugTransform(d) {
+ var coord = tileCenter(d);
+ return 'translate(' + coord[0] + 'px,' + coord[1] + 'px)';
+ } // Pick a representative tile near the center of the viewport
+ // (This is useful for sampling the imagery vintage)
- function continueTo(nextStep) {
- context.map().on('drawn.intro', null);
- nextStep();
- }
- }
- function nodesWays() {
- var onClick = function onClick() {
- continueTo(clickTownHall);
- };
+ var dims = tiler.size();
+ var mapCenter = [dims[0] / 2, dims[1] / 2];
+ var minDist = Math.max(dims[0], dims[1]);
+ var nearCenter;
+ requests.forEach(function (d) {
+ var c = tileCenter(d);
+ var dist = geoVecLength(c, mapCenter);
- reveal('.surface', helpHtml('intro.navigation.nodes_ways'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
+ if (dist < minDist) {
+ minDist = dist;
+ nearCenter = d;
+ }
});
- context.map().on('drawn.intro', function () {
- reveal('.surface', helpHtml('intro.navigation.nodes_ways'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
+ var image = selection.selectAll('img').data(requests, function (d) {
+ return d[3];
+ });
+ image.exit().style(transformProp, imageTransform).classed('tile-removing', true).classed('tile-center', false).each(function () {
+ var tile = select(this);
+ window.setTimeout(function () {
+ if (tile.classed('tile-removing')) {
+ tile.remove();
+ }
+ }, 300);
+ });
+ image.enter().append('img').attr('class', 'tile').attr('alt', '').attr('draggable', 'false').style('width', _tileSize + 'px').style('height', _tileSize + 'px').attr('src', function (d) {
+ return d[3];
+ }).on('error', error).on('load', load).merge(image).style(transformProp, imageTransform).classed('tile-debug', showDebug).classed('tile-removing', false).classed('tile-center', function (d) {
+ return d === nearCenter;
+ });
+ var debug = selection.selectAll('.tile-label-debug').data(showDebug ? requests : [], function (d) {
+ return d[3];
});
+ debug.exit().remove();
- function continueTo(nextStep) {
- context.map().on('drawn.intro', null);
- nextStep();
- }
- }
+ if (showDebug) {
+ var debugEnter = debug.enter().append('div').attr('class', 'tile-label-debug');
+ debugEnter.append('div').attr('class', 'tile-label-debug-coord');
+ debugEnter.append('div').attr('class', 'tile-label-debug-vintage');
+ debug = debug.merge(debugEnter);
+ debug.style(transformProp, debugTransform);
+ debug.selectAll('.tile-label-debug-coord').text(function (d) {
+ return d[2] + ' / ' + d[0] + ' / ' + d[1];
+ });
+ debug.selectAll('.tile-label-debug-vintage').each(function (d) {
+ var span = select(this);
+ var center = context.projection.invert(tileCenter(d));
- function clickTownHall() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- var entity = context.hasEntity(hallId);
- if (!entity) return;
- reveal(null, null, {
- duration: 0
- });
- context.map().centerZoomEase(entity.loc, 19, 500);
- timeout(function () {
- var entity = context.hasEntity(hallId);
- if (!entity) return;
- var box = pointBox(entity.loc, context);
- var textId = context.lastPointerType() === 'mouse' ? 'click_townhall' : 'tap_townhall';
- reveal(box, helpHtml('intro.navigation.' + textId));
- context.map().on('move.intro drawn.intro', function () {
- var entity = context.hasEntity(hallId);
- if (!entity) return;
- var box = pointBox(entity.loc, context);
- reveal(box, helpHtml('intro.navigation.' + textId), {
- duration: 0
+ _source.getMetadata(center, d, function (err, result) {
+ if (result && result.vintage && result.vintage.range) {
+ span.text(result.vintage.range);
+ } else {
+ span.html(_t.html('info_panels.background.vintage') + ': ' + _t.html('info_panels.background.unknown'));
+ }
});
});
- context.on('enter.intro', function () {
- if (isTownHallSelected()) continueTo(selectedTownHall);
- });
- }, 550); // after centerZoomEase
-
- context.history().on('change.intro', function () {
- if (!context.hasEntity(hallId)) {
- continueTo(clickTownHall);
- }
- });
-
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
}
}
- function selectedTownHall() {
- if (!isTownHallSelected()) return clickTownHall();
- var entity = context.hasEntity(hallId);
- if (!entity) return clickTownHall();
- var box = pointBox(entity.loc, context);
+ background.projection = function (val) {
+ if (!arguments.length) return _projection;
+ _projection = val;
+ return background;
+ };
- var onClick = function onClick() {
- continueTo(editorTownHall);
- };
+ background.dimensions = function (val) {
+ if (!arguments.length) return tiler.size();
+ tiler.size(val);
+ return background;
+ };
- reveal(box, helpHtml('intro.navigation.selected_townhall'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.map().on('move.intro drawn.intro', function () {
- var entity = context.hasEntity(hallId);
- if (!entity) return;
- var box = pointBox(entity.loc, context);
- reveal(box, helpHtml('intro.navigation.selected_townhall'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- });
- context.history().on('change.intro', function () {
- if (!context.hasEntity(hallId)) {
- continueTo(clickTownHall);
- }
- });
+ background.source = function (val) {
+ if (!arguments.length) return _source;
+ _source = val;
+ _tileSize = _source.tileSize;
+ _cache = {};
+ tiler.tileSize(_source.tileSize).zoomExtent(_source.zoomExtent);
+ return background;
+ };
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ return background;
+ }
- function editorTownHall() {
- if (!isTownHallSelected()) return clickTownHall(); // disallow scrolling
+ var _imageryIndex = null;
+ function rendererBackground(context) {
+ var dispatch = dispatch$8('change');
+ var detected = utilDetect();
+ var baseLayer = rendererTileLayer(context).projection(context.projection);
+ var _checkedBlocklists = [];
+ var _isValid = true;
+ var _overlayLayers = [];
+ var _brightness = 1;
+ var _contrast = 1;
+ var _saturation = 1;
+ var _sharpness = 1;
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ function ensureImageryIndex() {
+ return _mainFileFetcher.get('imagery').then(function (sources) {
+ if (_imageryIndex) return _imageryIndex;
+ _imageryIndex = {
+ imagery: sources,
+ features: {}
+ }; // use which-polygon to support efficient index and querying for imagery
- var onClick = function onClick() {
- continueTo(presetTownHall);
- };
-
- reveal('.entity-editor-pane', helpHtml('intro.navigation.editor_townhall'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.on('exit.intro', function () {
- continueTo(clickTownHall);
- });
- context.history().on('change.intro', function () {
- if (!context.hasEntity(hallId)) {
- continueTo(clickTownHall);
- }
- });
+ var features = sources.map(function (source) {
+ if (!source.polygon) return null; // workaround for editor-layer-index weirdness..
+ // Add an extra array nest to each element in `source.polygon`
+ // so the rings are not treated as a bunch of holes:
+ // what we have: [ [[outer],[hole],[hole]] ]
+ // what we want: [ [[outer]],[[outer]],[[outer]] ]
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- nextStep();
- }
- }
+ var rings = source.polygon.map(function (ring) {
+ return [ring];
+ });
+ var feature = {
+ type: 'Feature',
+ properties: {
+ id: source.id
+ },
+ geometry: {
+ type: 'MultiPolygon',
+ coordinates: rings
+ }
+ };
+ _imageryIndex.features[source.id] = feature;
+ return feature;
+ }).filter(Boolean);
+ _imageryIndex.query = whichPolygon_1({
+ type: 'FeatureCollection',
+ features: features
+ }); // Instantiate `rendererBackgroundSource` objects for each source
- function presetTownHall() {
- if (!isTownHallSelected()) return clickTownHall(); // reset pane, in case user happened to change it..
+ _imageryIndex.backgrounds = sources.map(function (source) {
+ if (source.type === 'bing') {
+ return rendererBackgroundSource.Bing(source, dispatch);
+ } else if (/^EsriWorldImagery/.test(source.id)) {
+ return rendererBackgroundSource.Esri(source);
+ } else {
+ return rendererBackgroundSource(source);
+ }
+ }); // Add 'None'
- context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // disallow scrolling
+ _imageryIndex.backgrounds.unshift(rendererBackgroundSource.None()); // Add 'Custom'
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel); // preset match, in case the user happened to change it.
- var entity = context.entity(context.selectedIDs()[0]);
- var preset = _mainPresetIndex.match(entity, context.graph());
+ var template = corePreferences('background-custom-template') || '';
+ var custom = rendererBackgroundSource.Custom(template);
- var onClick = function onClick() {
- continueTo(fieldsTownHall);
- };
+ _imageryIndex.backgrounds.unshift(custom);
- reveal('.entity-editor-pane .section-feature-type', helpHtml('intro.navigation.preset_townhall', {
- preset: preset.name()
- }), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.on('exit.intro', function () {
- continueTo(clickTownHall);
+ return _imageryIndex;
});
- context.history().on('change.intro', function () {
- if (!context.hasEntity(hallId)) {
- continueTo(clickTownHall);
+ }
+
+ function background(selection) {
+ var currSource = baseLayer.source(); // If we are displaying an Esri basemap at high zoom,
+ // check its tilemap to see how high the zoom can go
+
+ if (context.map().zoom() > 18) {
+ if (currSource && /^EsriWorldImagery/.test(currSource.id)) {
+ var center = context.map().center();
+ currSource.fetchTilemap(center);
}
- });
+ } // Is the imagery valid here? - #4827
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- nextStep();
+
+ var sources = background.sources(context.map().extent());
+ var wasValid = _isValid;
+ _isValid = !!sources.filter(function (d) {
+ return d === currSource;
+ }).length;
+
+ if (wasValid !== _isValid) {
+ // change in valid status
+ background.updateImagery();
}
- }
- function fieldsTownHall() {
- if (!isTownHallSelected()) return clickTownHall(); // reset pane, in case user happened to change it..
+ var baseFilter = '';
- context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // disallow scrolling
+ if (detected.cssfilters) {
+ if (_brightness !== 1) {
+ baseFilter += " brightness(".concat(_brightness, ")");
+ }
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ if (_contrast !== 1) {
+ baseFilter += " contrast(".concat(_contrast, ")");
+ }
- var onClick = function onClick() {
- continueTo(closeTownHall);
- };
+ if (_saturation !== 1) {
+ baseFilter += " saturate(".concat(_saturation, ")");
+ }
- reveal('.entity-editor-pane .section-preset-fields', helpHtml('intro.navigation.fields_townhall'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.on('exit.intro', function () {
- continueTo(clickTownHall);
- });
- context.history().on('change.intro', function () {
- if (!context.hasEntity(hallId)) {
- continueTo(clickTownHall);
+ if (_sharpness < 1) {
+ // gaussian blur
+ var blur = d3_interpolateNumber(0.5, 5)(1 - _sharpness);
+ baseFilter += " blur(".concat(blur, "px)");
}
- });
+ }
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- nextStep();
+ var base = selection.selectAll('.layer-background').data([0]);
+ base = base.enter().insert('div', '.layer-data').attr('class', 'layer layer-background').merge(base);
+
+ if (detected.cssfilters) {
+ base.style('filter', baseFilter || null);
+ } else {
+ base.style('opacity', _brightness);
}
- }
- function closeTownHall() {
- if (!isTownHallSelected()) return clickTownHall();
- var selector = '.entity-editor-pane button.close svg use';
- var href = select(selector).attr('href') || '#iD-icon-close';
- reveal('.entity-editor-pane', helpHtml('intro.navigation.close_townhall', {
- button: icon(href, 'inline')
- }));
- context.on('exit.intro', function () {
- continueTo(searchStreet);
- });
- context.history().on('change.intro', function () {
- // update the close icon in the tooltip if the user edits something.
- var selector = '.entity-editor-pane button.close svg use';
- var href = select(selector).attr('href') || '#iD-icon-close';
- reveal('.entity-editor-pane', helpHtml('intro.navigation.close_townhall', {
- button: icon(href, 'inline')
- }), {
- duration: 0
- });
- });
+ var imagery = base.selectAll('.layer-imagery').data([0]);
+ imagery.enter().append('div').attr('class', 'layer layer-imagery').merge(imagery).call(baseLayer);
+ var maskFilter = '';
+ var mixBlendMode = '';
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- nextStep();
+ if (detected.cssfilters && _sharpness > 1) {
+ // apply unsharp mask
+ mixBlendMode = 'overlay';
+ maskFilter = 'saturate(0) blur(3px) invert(1)';
+ var contrast = _sharpness - 1;
+ maskFilter += " contrast(".concat(contrast, ")");
+ var brightness = d3_interpolateNumber(1, 0.85)(_sharpness - 1);
+ maskFilter += " brightness(".concat(brightness, ")");
}
+
+ var mask = base.selectAll('.layer-unsharp-mask').data(detected.cssfilters && _sharpness > 1 ? [0] : []);
+ mask.exit().remove();
+ mask.enter().append('div').attr('class', 'layer layer-mask layer-unsharp-mask').merge(mask).call(baseLayer).style('filter', maskFilter || null).style('mix-blend-mode', mixBlendMode || null);
+ var overlays = selection.selectAll('.layer-overlay').data(_overlayLayers, function (d) {
+ return d.source().name();
+ });
+ overlays.exit().remove();
+ overlays.enter().insert('div', '.layer-data').attr('class', 'layer layer-overlay').merge(overlays).each(function (layer, i, nodes) {
+ return select(nodes[i]).call(layer);
+ });
}
- function searchStreet() {
- context.enter(modeBrowse(context));
- context.history().reset('initial'); // ensure spring street exists
+ background.updateImagery = function () {
+ var currSource = baseLayer.source();
+ if (context.inIntro() || !currSource) return;
- var msec = transitionTime(springStreet, context.map().center());
+ var o = _overlayLayers.filter(function (d) {
+ return !d.source().isLocatorOverlay() && !d.source().isHidden();
+ }).map(function (d) {
+ return d.source().id;
+ }).join(',');
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ var meters = geoOffsetToMeters(currSource.offset());
+ var EPSILON = 0.01;
+ var x = +meters[0].toFixed(2);
+ var y = +meters[1].toFixed(2);
+ var hash = utilStringQs(window.location.hash);
+ var id = currSource.id;
- context.map().centerZoomEase(springStreet, 19, msec); // ..and user can see it
+ if (id === 'custom') {
+ id = "custom:".concat(currSource.template());
+ }
- timeout(function () {
- reveal('.search-header input', helpHtml('intro.navigation.search_street', {
- name: _t('intro.graph.name.spring-street')
- }));
- context.container().select('.search-header input').on('keyup.intro', checkSearchResult);
- }, msec + 100);
- }
+ if (id) {
+ hash.background = id;
+ } else {
+ delete hash.background;
+ }
- function checkSearchResult() {
- var first = context.container().select('.feature-list-item:nth-child(0n+2)'); // skip "No Results" item
+ if (o) {
+ hash.overlays = o;
+ } else {
+ delete hash.overlays;
+ }
- var firstName = first.select('.entity-name');
- var name = _t('intro.graph.name.spring-street');
+ if (Math.abs(x) > EPSILON || Math.abs(y) > EPSILON) {
+ hash.offset = "".concat(x, ",").concat(y);
+ } else {
+ delete hash.offset;
+ }
- if (!firstName.empty() && firstName.html() === name) {
- reveal(first.node(), helpHtml('intro.navigation.choose_street', {
- name: name
- }), {
- duration: 300
- });
- context.on('exit.intro', function () {
- continueTo(selectedStreet);
- });
- context.container().select('.search-header input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
+ if (!window.mocha) {
+ window.location.replace('#' + utilQsString(hash, true));
}
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.container().select('.search-header input').on('keydown.intro', null).on('keyup.intro', null);
- nextStep();
+ var imageryUsed = [];
+ var photoOverlaysUsed = [];
+ var currUsed = currSource.imageryUsed();
+
+ if (currUsed && _isValid) {
+ imageryUsed.push(currUsed);
}
- }
- function selectedStreet() {
- if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
- return searchStreet();
+ _overlayLayers.filter(function (d) {
+ return !d.source().isLocatorOverlay() && !d.source().isHidden();
+ }).forEach(function (d) {
+ return imageryUsed.push(d.source().imageryUsed());
+ });
+
+ var dataLayer = context.layers().layer('data');
+
+ if (dataLayer && dataLayer.enabled() && dataLayer.hasData()) {
+ imageryUsed.push(dataLayer.getSrc());
}
- var onClick = function onClick() {
- continueTo(editorStreet);
+ var photoOverlayLayers = {
+ streetside: 'Bing Streetside',
+ mapillary: 'Mapillary Images',
+ 'mapillary-map-features': 'Mapillary Map Features',
+ 'mapillary-signs': 'Mapillary Signs',
+ kartaview: 'KartaView Images'
};
- var entity = context.entity(springStreetEndId);
- var box = pointBox(entity.loc, context);
- box.height = 500;
- reveal(box, helpHtml('intro.navigation.selected_street', {
- name: _t('intro.graph.name.spring-street')
- }), {
- duration: 600,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- timeout(function () {
- context.map().on('move.intro drawn.intro', function () {
- var entity = context.hasEntity(springStreetEndId);
- if (!entity) return;
- var box = pointBox(entity.loc, context);
- box.height = 500;
- reveal(box, helpHtml('intro.navigation.selected_street', {
- name: _t('intro.graph.name.spring-street')
- }), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- });
- }, 600); // after reveal.
+ for (var layerID in photoOverlayLayers) {
+ var layer = context.layers().layer(layerID);
- context.on('enter.intro', function (mode) {
- if (!context.hasEntity(springStreetId)) {
- return continueTo(searchStreet);
+ if (layer && layer.enabled()) {
+ photoOverlaysUsed.push(layerID);
+ imageryUsed.push(photoOverlayLayers[layerID]);
}
+ }
- var ids = context.selectedIDs();
+ context.history().imageryUsed(imageryUsed);
+ context.history().photoOverlaysUsed(photoOverlaysUsed);
+ };
- if (mode.id !== 'select' || !ids.length || ids[0] !== springStreetId) {
- // keep Spring Street selected..
- context.enter(modeSelect(context, [springStreetId]));
- }
- });
- context.history().on('change.intro', function () {
- if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
- timeout(function () {
- continueTo(searchStreet);
- }, 300); // after any transition (e.g. if user deleted intersection)
- }
- });
+ background.sources = function (extent, zoom, includeCurrent) {
+ if (!_imageryIndex) return []; // called before init()?
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ var visible = {};
+ (_imageryIndex.query.bbox(extent.rectangle(), true) || []).forEach(function (d) {
+ return visible[d.id] = true;
+ });
+ var currSource = baseLayer.source(); // Recheck blocked sources only if we detect new blocklists pulled from the OSM API.
- function editorStreet() {
- var selector = '.entity-editor-pane button.close svg use';
- var href = select(selector).attr('href') || '#iD-icon-close';
- reveal('.entity-editor-pane', helpHtml('intro.navigation.street_different_fields') + '{br}' + helpHtml('intro.navigation.editor_street', {
- button: icon(href, 'inline'),
- field1: onewayField.label(),
- field2: maxspeedField.label()
- }));
- context.on('exit.intro', function () {
- continueTo(play);
+ var osm = context.connection();
+ var blocklists = osm && osm.imageryBlocklists() || [];
+ var blocklistChanged = blocklists.length !== _checkedBlocklists.length || blocklists.some(function (regex, index) {
+ return String(regex) !== _checkedBlocklists[index];
});
- context.history().on('change.intro', function () {
- // update the close icon in the tooltip if the user edits something.
- var selector = '.entity-editor-pane button.close svg use';
- var href = select(selector).attr('href') || '#iD-icon-close';
- reveal('.entity-editor-pane', helpHtml('intro.navigation.street_different_fields') + '{br}' + helpHtml('intro.navigation.editor_street', {
- button: icon(href, 'inline'),
- field1: onewayField.label(),
- field2: maxspeedField.label()
- }), {
- duration: 0
+
+ if (blocklistChanged) {
+ _imageryIndex.backgrounds.forEach(function (source) {
+ source.isBlocked = blocklists.some(function (regex) {
+ return regex.test(source.template());
+ });
});
- });
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- nextStep();
+ _checkedBlocklists = blocklists.map(function (regex) {
+ return String(regex);
+ });
}
- }
- function play() {
- dispatch.call('done');
- reveal('.ideditor', helpHtml('intro.navigation.play', {
- next: _t('intro.points.title')
- }), {
- tooltipBox: '.intro-nav-wrap .chapter-point',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- reveal('.ideditor');
- }
- });
- }
+ return _imageryIndex.backgrounds.filter(function (source) {
+ if (includeCurrent && currSource === source) return true; // optionally always include the current imagery
- chapter.enter = function () {
- dragMap();
- };
+ if (source.isBlocked) return false; // even bundled sources may be blocked - #7905
- chapter.exit = function () {
- timeouts.forEach(window.clearTimeout);
- context.on('enter.intro exit.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.search-header input').on('keydown.intro keyup.intro', null);
- };
+ if (!source.polygon) return true; // always include imagery with worldwide coverage
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
+ if (zoom && zoom < 6) return false; // optionally exclude local imagery at low zooms
+
+ return visible[source.id]; // include imagery visible in given extent
+ });
};
- return utilRebind(chapter, dispatch, 'on');
- }
+ background.dimensions = function (val) {
+ if (!val) return;
+ baseLayer.dimensions(val);
- function uiIntroPoint(context, reveal) {
- var dispatch = dispatch$8('done');
- var timeouts = [];
- var intersection = [-85.63279, 41.94394];
- var building = [-85.632422, 41.944045];
- var cafePreset = _mainPresetIndex.item('amenity/cafe');
- var _pointID = null;
- var chapter = {
- title: 'intro.points.title'
+ _overlayLayers.forEach(function (layer) {
+ return layer.dimensions(val);
+ });
};
- function timeout(f, t) {
- timeouts.push(window.setTimeout(f, t));
- }
-
- function eventCancel(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- }
+ background.baseLayerSource = function (d) {
+ if (!arguments.length) return baseLayer.source(); // test source against OSM imagery blocklists..
- function addPoint() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- var msec = transitionTime(intersection, context.map().center());
+ var osm = context.connection();
+ if (!osm) return background;
+ var blocklists = osm.imageryBlocklists();
+ var template = d.template();
+ var fail = false;
+ var tested = 0;
+ var regex;
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ for (var i = 0; i < blocklists.length; i++) {
+ regex = blocklists[i];
+ fail = regex.test(template);
+ tested++;
+ if (fail) break;
+ } // ensure at least one test was run.
- context.map().centerZoomEase(intersection, 19, msec);
- timeout(function () {
- var tooltip = reveal('button.add-point', helpHtml('intro.points.points_info') + '{br}' + helpHtml('intro.points.add_point'));
- _pointID = null;
- tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-points');
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'add-point') return;
- continueTo(placePoint);
- });
- }, msec + 100);
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
+ if (!tested) {
+ regex = /.*\.google(apis)?\..*\/(vt|kh)[\?\/].*([xyz]=.*){3}.*/;
+ fail = regex.test(template);
}
- }
- function placePoint() {
- if (context.mode().id !== 'add-point') {
- return chapter.restart();
- }
+ baseLayer.source(!fail ? d : background.findSource('none'));
+ dispatch.call('change');
+ background.updateImagery();
+ return background;
+ };
- var pointBox = pad(building, 150, context);
- var textId = context.lastPointerType() === 'mouse' ? 'place_point' : 'place_point_touch';
- reveal(pointBox, helpHtml('intro.points.' + textId));
- context.map().on('move.intro drawn.intro', function () {
- pointBox = pad(building, 150, context);
- reveal(pointBox, helpHtml('intro.points.' + textId), {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return chapter.restart();
- _pointID = context.mode().selectedIDs()[0];
- continueTo(searchPreset);
+ background.findSource = function (id) {
+ if (!id || !_imageryIndex) return null; // called before init()?
+
+ return _imageryIndex.backgrounds.find(function (d) {
+ return d.id && d.id === id;
});
+ };
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
-
- function searchPreset() {
- if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
- return addPoint();
- } // disallow scrolling
-
+ background.bing = function () {
+ background.baseLayerSource(background.findSource('Bing'));
+ };
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.points.search_cafe', {
- preset: cafePreset.name()
- }));
- context.on('enter.intro', function (mode) {
- if (!_pointID || !context.hasEntity(_pointID)) {
- return continueTo(addPoint);
- }
+ background.showsLayer = function (d) {
+ var currSource = baseLayer.source();
+ if (!d || !currSource) return false;
+ return d.id === currSource.id || _overlayLayers.some(function (layer) {
+ return d.id === layer.source().id;
+ });
+ };
- var ids = context.selectedIDs();
+ background.overlayLayerSources = function () {
+ return _overlayLayers.map(function (layer) {
+ return layer.source();
+ });
+ };
- if (mode.id !== 'select' || !ids.length || ids[0] !== _pointID) {
- // keep the user's point selected..
- context.enter(modeSelect(context, [_pointID])); // disallow scrolling
+ background.toggleOverlayLayer = function (d) {
+ var layer;
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.points.search_cafe', {
- preset: cafePreset.name()
- }));
- context.history().on('change.intro', null);
- }
- });
+ for (var i = 0; i < _overlayLayers.length; i++) {
+ layer = _overlayLayers[i];
- function checkPresetSearch() {
- var first = context.container().select('.preset-list-item:first-child');
+ if (layer.source() === d) {
+ _overlayLayers.splice(i, 1);
- if (first.classed('preset-amenity-cafe')) {
- context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
- reveal(first.select('.preset-list-button').node(), helpHtml('intro.points.choose_cafe', {
- preset: cafePreset.name()
- }), {
- duration: 300
- });
- context.history().on('change.intro', function () {
- continueTo(aboutFeatureEditor);
- });
+ dispatch.call('change');
+ background.updateImagery();
+ return;
}
}
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- nextStep();
- }
- }
+ layer = rendererTileLayer(context).source(d).projection(context.projection).dimensions(baseLayer.dimensions());
- function aboutFeatureEditor() {
- if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
- return addPoint();
- }
+ _overlayLayers.push(layer);
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.points.feature_editor'), {
- tooltipClass: 'intro-points-describe',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(addName);
- }
- });
- }, 400);
- context.on('exit.intro', function () {
- // if user leaves select mode here, just continue with the tutorial.
- continueTo(reselectPoint);
- });
+ dispatch.call('change');
+ background.updateImagery();
+ };
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ background.nudge = function (d, zoom) {
+ var currSource = baseLayer.source();
- function addName() {
- if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
- return addPoint();
- } // reset pane, in case user happened to change it..
+ if (currSource) {
+ currSource.nudge(d, zoom);
+ dispatch.call('change');
+ background.updateImagery();
+ }
+ return background;
+ };
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
- var addNameString = helpHtml('intro.points.fields_info') + '{br}' + helpHtml('intro.points.add_name');
- timeout(function () {
- // It's possible for the user to add a name in a previous step..
- // If so, don't tell them to add the name in this step.
- // Give them an OK button instead.
- var entity = context.entity(_pointID);
+ background.offset = function (d) {
+ var currSource = baseLayer.source();
- if (entity.tags.name) {
- var tooltip = reveal('.entity-editor-pane', addNameString, {
- tooltipClass: 'intro-points-describe',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(addCloseEditor);
- }
- });
- tooltip.select('.instruction').style('display', 'none');
- } else {
- reveal('.entity-editor-pane', addNameString, {
- tooltipClass: 'intro-points-describe'
- });
- }
- }, 400);
- context.history().on('change.intro', function () {
- continueTo(addCloseEditor);
- });
- context.on('exit.intro', function () {
- // if user leaves select mode here, just continue with the tutorial.
- continueTo(reselectPoint);
- });
+ if (!arguments.length) {
+ return currSource && currSource.offset() || [0, 0];
+ }
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- nextStep();
+ if (currSource) {
+ currSource.offset(d);
+ dispatch.call('change');
+ background.updateImagery();
}
- }
- function addCloseEditor() {
- // reset pane, in case user happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
- var selector = '.entity-editor-pane button.close svg use';
- var href = select(selector).attr('href') || '#iD-icon-close';
- context.on('exit.intro', function () {
- continueTo(reselectPoint);
- });
- reveal('.entity-editor-pane', helpHtml('intro.points.add_close', {
- button: icon(href, 'inline')
- }));
+ return background;
+ };
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ background.brightness = function (d) {
+ if (!arguments.length) return _brightness;
+ _brightness = d;
+ if (context.mode()) dispatch.call('change');
+ return background;
+ };
- function reselectPoint() {
- if (!_pointID) return chapter.restart();
- var entity = context.hasEntity(_pointID);
- if (!entity) return chapter.restart(); // make sure it's still a cafe, in case user somehow changed it..
+ background.contrast = function (d) {
+ if (!arguments.length) return _contrast;
+ _contrast = d;
+ if (context.mode()) dispatch.call('change');
+ return background;
+ };
- var oldPreset = _mainPresetIndex.match(entity, context.graph());
- context.replace(actionChangePreset(_pointID, oldPreset, cafePreset));
- context.enter(modeBrowse(context));
- var msec = transitionTime(entity.loc, context.map().center());
+ background.saturation = function (d) {
+ if (!arguments.length) return _saturation;
+ _saturation = d;
+ if (context.mode()) dispatch.call('change');
+ return background;
+ };
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ background.sharpness = function (d) {
+ if (!arguments.length) return _sharpness;
+ _sharpness = d;
+ if (context.mode()) dispatch.call('change');
+ return background;
+ };
- context.map().centerEase(entity.loc, msec);
- timeout(function () {
- var box = pointBox(entity.loc, context);
- reveal(box, helpHtml('intro.points.reselect'), {
- duration: 600
- });
- timeout(function () {
- context.map().on('move.intro drawn.intro', function () {
- var entity = context.hasEntity(_pointID);
- if (!entity) return chapter.restart();
- var box = pointBox(entity.loc, context);
- reveal(box, helpHtml('intro.points.reselect'), {
- duration: 0
- });
- });
- }, 600); // after reveal..
+ var _loadPromise;
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return;
- continueTo(updatePoint);
- });
- }, msec + 100);
+ background.ensureLoaded = function () {
+ if (_loadPromise) return _loadPromise;
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ function parseMapParams(qmap) {
+ if (!qmap) return false;
+ var params = qmap.split('/').map(Number);
+ if (params.length < 3 || params.some(isNaN)) return false;
+ return geoExtent([params[2], params[1]]); // lon,lat
}
- }
- function updatePoint() {
- if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
- return continueTo(reselectPoint);
- } // reset pane, in case user happened to untag the point..
+ var hash = utilStringQs(window.location.hash);
+ var requested = hash.background || hash.layer;
+ var extent = parseMapParams(hash.map);
+ return _loadPromise = ensureImageryIndex().then(function (imageryIndex) {
+ var first = imageryIndex.backgrounds.length && imageryIndex.backgrounds[0];
+ var best;
+ if (!requested && extent) {
+ best = background.sources(extent).find(function (s) {
+ return s.best();
+ });
+ } // Decide which background layer to display
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
- context.on('exit.intro', function () {
- continueTo(reselectPoint);
- });
- context.history().on('change.intro', function () {
- continueTo(updateCloseEditor);
- });
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.points.update'), {
- tooltipClass: 'intro-points-describe'
+
+ if (requested && requested.indexOf('custom:') === 0) {
+ var template = requested.replace(/^custom:/, '');
+ var custom = background.findSource('custom');
+ background.baseLayerSource(custom.template(template));
+ corePreferences('background-custom-template', template);
+ } else {
+ background.baseLayerSource(background.findSource(requested) || best || background.findSource(corePreferences('background-last-used')) || background.findSource('Bing') || first || background.findSource('none'));
+ }
+
+ var locator = imageryIndex.backgrounds.find(function (d) {
+ return d.overlay && d["default"];
});
- }, 400);
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ if (locator) {
+ background.toggleOverlayLayer(locator);
+ }
- function updateCloseEditor() {
- if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
- return continueTo(reselectPoint);
- } // reset pane, in case user happened to change it..
+ var overlays = (hash.overlays || '').split(',');
+ overlays.forEach(function (overlay) {
+ overlay = background.findSource(overlay);
+ if (overlay) {
+ background.toggleOverlayLayer(overlay);
+ }
+ });
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
- context.on('exit.intro', function () {
- continueTo(rightClickPoint);
- });
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.points.update_close', {
- button: icon('#iD-icon-close', 'inline')
- }));
- }, 500);
+ if (hash.gpx) {
+ var gpx = context.layers().layer('data');
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ if (gpx) {
+ gpx.url(hash.gpx, '.gpx');
+ }
+ }
- function rightClickPoint() {
- if (!_pointID) return chapter.restart();
- var entity = context.hasEntity(_pointID);
- if (!entity) return chapter.restart();
- context.enter(modeBrowse(context));
- var box = pointBox(entity.loc, context);
- var textId = context.lastPointerType() === 'mouse' ? 'rightclick' : 'edit_menu_touch';
- reveal(box, helpHtml('intro.points.' + textId), {
- duration: 600
- });
- timeout(function () {
- context.map().on('move.intro', function () {
- var entity = context.hasEntity(_pointID);
- if (!entity) return chapter.restart();
- var box = pointBox(entity.loc, context);
- reveal(box, helpHtml('intro.points.' + textId), {
- duration: 0
+ if (hash.offset) {
+ var offset = hash.offset.replace(/;/g, ',').split(',').map(function (n) {
+ return !isNaN(n) && n;
});
- });
- }, 600); // after reveal
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return;
- var ids = context.selectedIDs();
- if (ids.length !== 1 || ids[0] !== _pointID) return;
- timeout(function () {
- var node = selectMenuItem(context, 'delete').node();
- if (!node) return;
- continueTo(enterDelete);
- }, 50); // after menu visible
+ if (offset.length === 2) {
+ background.offset(geoMetersToOffset(offset));
+ }
+ }
+ })["catch"](function () {
+ /* ignore */
});
+ };
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro', null);
- nextStep();
- }
- }
+ return utilRebind(background, dispatch, 'on');
+ }
- function enterDelete() {
- if (!_pointID) return chapter.restart();
- var entity = context.hasEntity(_pointID);
- if (!entity) return chapter.restart();
- var node = selectMenuItem(context, 'delete').node();
+ function rendererFeatures(context) {
+ var dispatch = dispatch$8('change', 'redraw');
+ var features = utilRebind({}, dispatch, 'on');
- if (!node) {
- return continueTo(rightClickPoint);
- }
+ var _deferred = new Set();
- reveal('.edit-menu', helpHtml('intro.points.delete'), {
- padding: 50
- });
- timeout(function () {
- context.map().on('move.intro', function () {
- reveal('.edit-menu', helpHtml('intro.points.delete'), {
- duration: 0,
- padding: 50
- });
- });
- }, 300); // after menu visible
+ var traffic_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
+ };
+ var service_roads = {
+ 'service': true,
+ 'road': true,
+ 'track': true
+ };
+ var paths = {
+ 'path': true,
+ 'footway': true,
+ 'cycleway': true,
+ 'bridleway': true,
+ 'steps': true,
+ 'pedestrian': true
+ };
+ var past_futures = {
+ 'proposed': true,
+ 'construction': true,
+ 'abandoned': true,
+ 'dismantled': true,
+ 'disused': true,
+ 'razed': true,
+ 'demolished': true,
+ 'obliterated': true
+ };
+ var _cullFactor = 1;
+ var _cache = {};
+ var _rules = {};
+ var _stats = {};
+ var _keys = [];
+ var _hidden = [];
+ var _forceVisible = {};
- context.on('exit.intro', function () {
- if (!_pointID) return chapter.restart();
- var entity = context.hasEntity(_pointID);
- if (entity) return continueTo(rightClickPoint); // point still exists
- });
- context.history().on('change.intro', function (changed) {
- if (changed.deleted().length) {
- continueTo(undo);
+ function update() {
+ if (!window.mocha) {
+ var hash = utilStringQs(window.location.hash);
+ var disabled = features.disabled();
+
+ if (disabled.length) {
+ hash.disable_features = disabled.join(',');
+ } else {
+ delete hash.disable_features;
}
- });
- function continueTo(nextStep) {
- context.map().on('move.intro', null);
- context.history().on('change.intro', null);
- context.on('exit.intro', null);
- nextStep();
+ window.location.replace('#' + utilQsString(hash, true));
+ corePreferences('disabled-features', disabled.join(','));
}
+
+ _hidden = features.hidden();
+ dispatch.call('change');
+ dispatch.call('redraw');
}
- function undo() {
- context.history().on('change.intro', function () {
- continueTo(play);
- });
- reveal('.top-toolbar button.undo-button', helpHtml('intro.points.undo'));
+ function defineRule(k, filter, max) {
+ var isEnabled = true;
- function continueTo(nextStep) {
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ _keys.push(k);
- function play() {
- dispatch.call('done');
- reveal('.ideditor', helpHtml('intro.points.play', {
- next: _t('intro.areas.title')
- }), {
- tooltipBox: '.intro-nav-wrap .chapter-area',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- reveal('.ideditor');
+ _rules[k] = {
+ filter: filter,
+ enabled: isEnabled,
+ // whether the user wants it enabled..
+ count: 0,
+ currentMax: max || Infinity,
+ defaultMax: max || Infinity,
+ enable: function enable() {
+ this.enabled = true;
+ this.currentMax = this.defaultMax;
+ },
+ disable: function disable() {
+ this.enabled = false;
+ this.currentMax = 0;
+ },
+ hidden: function hidden() {
+ return this.count === 0 && !this.enabled || this.count > this.currentMax * _cullFactor;
+ },
+ autoHidden: function autoHidden() {
+ return this.hidden() && this.currentMax > 0;
}
- });
+ };
}
- chapter.enter = function () {
- addPoint();
- };
-
- chapter.exit = function () {
- timeouts.forEach(window.clearTimeout);
- context.on('enter.intro exit.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- };
-
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
- };
+ defineRule('points', function isPoint(tags, geometry) {
+ return geometry === 'point';
+ }, 200);
+ defineRule('traffic_roads', function isTrafficRoad(tags) {
+ return traffic_roads[tags.highway];
+ });
+ defineRule('service_roads', function isServiceRoad(tags) {
+ return service_roads[tags.highway];
+ });
+ defineRule('paths', function isPath(tags) {
+ return paths[tags.highway];
+ });
+ defineRule('buildings', function isBuilding(tags) {
+ return !!tags.building && tags.building !== 'no' || tags.parking === 'multi-storey' || tags.parking === 'sheds' || tags.parking === 'carports' || tags.parking === 'garage_boxes';
+ }, 250);
+ defineRule('building_parts', function isBuildingPart(tags) {
+ return tags['building:part'];
+ });
+ defineRule('indoor', function isIndoor(tags) {
+ return tags.indoor;
+ });
+ defineRule('landuse', function isLanduse(tags, geometry) {
+ return geometry === 'area' && !_rules.buildings.filter(tags) && !_rules.building_parts.filter(tags) && !_rules.indoor.filter(tags) && !_rules.water.filter(tags) && !_rules.pistes.filter(tags);
+ });
+ defineRule('boundaries', function isBoundary(tags) {
+ return !!tags.boundary && !(traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway] || tags.waterway || tags.railway || tags.landuse || tags.natural || tags.building || tags.power);
+ });
+ defineRule('water', function isWater(tags) {
+ return !!tags.waterway || tags.natural === 'water' || tags.natural === 'coastline' || tags.natural === 'bay' || tags.landuse === 'pond' || tags.landuse === 'basin' || tags.landuse === 'reservoir' || tags.landuse === 'salt_pond';
+ });
+ defineRule('rail', function isRail(tags) {
+ return (!!tags.railway || tags.landuse === 'railway') && !(traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway]);
+ });
+ defineRule('pistes', function isPiste(tags) {
+ return tags['piste:type'];
+ });
+ defineRule('aerialways', function isPiste(tags) {
+ return tags.aerialway && tags.aerialway !== 'yes' && tags.aerialway !== 'station';
+ });
+ defineRule('power', function isPower(tags) {
+ return !!tags.power;
+ }); // contains a past/future tag, but not in active use as a road/path/cycleway/etc..
- return utilRebind(chapter, dispatch, 'on');
- }
+ defineRule('past_future', function isPastFuture(tags) {
+ if (traffic_roads[tags.highway] || service_roads[tags.highway] || paths[tags.highway]) {
+ return false;
+ }
- function uiIntroArea(context, reveal) {
- var dispatch = dispatch$8('done');
- var playground = [-85.63552, 41.94159];
- var playgroundPreset = _mainPresetIndex.item('leisure/playground');
- var nameField = _mainPresetIndex.field('name');
- var descriptionField = _mainPresetIndex.field('description');
- var timeouts = [];
+ var strings = Object.keys(tags);
- var _areaID;
+ for (var i = 0; i < strings.length; i++) {
+ var s = strings[i];
- var chapter = {
- title: 'intro.areas.title'
- };
+ if (past_futures[s] || past_futures[tags[s]]) {
+ return true;
+ }
+ }
- function timeout(f, t) {
- timeouts.push(window.setTimeout(f, t));
- }
+ return false;
+ }); // Lines or areas that don't match another feature filter.
+ // IMPORTANT: The 'others' feature must be the last one defined,
+ // so that code in getMatches can skip this test if `hasMatch = true`
- function eventCancel(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- }
+ defineRule('others', function isOther(tags, geometry) {
+ return geometry === 'line' || geometry === 'area';
+ });
- function revealPlayground(center, text, options) {
- var padding = 180 * Math.pow(2, context.map().zoom() - 19.5);
- var box = pad(center, padding, context);
- reveal(box, text, options);
- }
+ features.features = function () {
+ return _rules;
+ };
- function addArea() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- _areaID = null;
- var msec = transitionTime(playground, context.map().center());
+ features.keys = function () {
+ return _keys;
+ };
- if (msec) {
- reveal(null, null, {
- duration: 0
+ features.enabled = function (k) {
+ if (!arguments.length) {
+ return _keys.filter(function (k) {
+ return _rules[k].enabled;
});
}
- context.map().centerZoomEase(playground, 19, msec);
- timeout(function () {
- var tooltip = reveal('button.add-area', helpHtml('intro.areas.add_playground'));
- tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-areas');
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'add-area') return;
- continueTo(startPlayground);
- });
- }, msec + 100);
+ return _rules[k] && _rules[k].enabled;
+ };
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
+ features.disabled = function (k) {
+ if (!arguments.length) {
+ return _keys.filter(function (k) {
+ return !_rules[k].enabled;
+ });
}
- }
- function startPlayground() {
- if (context.mode().id !== 'add-area') {
- return chapter.restart();
- }
+ return _rules[k] && !_rules[k].enabled;
+ };
- _areaID = null;
- context.map().zoomEase(19.5, 500);
- timeout(function () {
- var textId = context.lastPointerType() === 'mouse' ? 'starting_node_click' : 'starting_node_tap';
- var startDrawString = helpHtml('intro.areas.start_playground') + helpHtml('intro.areas.' + textId);
- revealPlayground(playground, startDrawString, {
- duration: 250
+ features.hidden = function (k) {
+ if (!arguments.length) {
+ return _keys.filter(function (k) {
+ return _rules[k].hidden();
});
- timeout(function () {
- context.map().on('move.intro drawn.intro', function () {
- revealPlayground(playground, startDrawString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'draw-area') return chapter.restart();
- continueTo(continuePlayground);
- });
- }, 250); // after reveal
- }, 550); // after easing
-
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
}
- }
- function continuePlayground() {
- if (context.mode().id !== 'draw-area') {
- return chapter.restart();
- }
+ return _rules[k] && _rules[k].hidden();
+ };
- _areaID = null;
- revealPlayground(playground, helpHtml('intro.areas.continue_playground'), {
- duration: 250
- });
- timeout(function () {
- context.map().on('move.intro drawn.intro', function () {
- revealPlayground(playground, helpHtml('intro.areas.continue_playground'), {
- duration: 0
- });
+ features.autoHidden = function (k) {
+ if (!arguments.length) {
+ return _keys.filter(function (k) {
+ return _rules[k].autoHidden();
});
- }, 250); // after reveal
+ }
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-area') {
- var entity = context.hasEntity(context.selectedIDs()[0]);
+ return _rules[k] && _rules[k].autoHidden();
+ };
- if (entity && entity.nodes.length >= 6) {
- return continueTo(finishPlayground);
- } else {
- return;
- }
- } else if (mode.id === 'select') {
- _areaID = context.selectedIDs()[0];
- return continueTo(searchPresets);
- } else {
- return chapter.restart();
- }
- });
+ features.enable = function (k) {
+ if (_rules[k] && !_rules[k].enabled) {
+ _rules[k].enable();
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ update();
}
- }
+ };
- function finishPlayground() {
- if (context.mode().id !== 'draw-area') {
- return chapter.restart();
- }
+ features.enableAll = function () {
+ var didEnable = false;
- _areaID = null;
- var finishString = helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.areas.finish_playground');
- revealPlayground(playground, finishString, {
- duration: 250
- });
- timeout(function () {
- context.map().on('move.intro drawn.intro', function () {
- revealPlayground(playground, finishString, {
- duration: 0
- });
- });
- }, 250); // after reveal
+ for (var k in _rules) {
+ if (!_rules[k].enabled) {
+ didEnable = true;
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-area') {
- return;
- } else if (mode.id === 'select') {
- _areaID = context.selectedIDs()[0];
- return continueTo(searchPresets);
- } else {
- return chapter.restart();
+ _rules[k].enable();
}
- });
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ if (didEnable) update();
+ };
+
+ features.disable = function (k) {
+ if (_rules[k] && _rules[k].enabled) {
+ _rules[k].disable();
+
+ update();
}
- }
+ };
- function searchPresets() {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return addArea();
+ features.disableAll = function () {
+ var didDisable = false;
+
+ for (var k in _rules) {
+ if (_rules[k].enabled) {
+ didDisable = true;
+
+ _rules[k].disable();
+ }
}
- var ids = context.selectedIDs();
+ if (didDisable) update();
+ };
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
- context.enter(modeSelect(context, [_areaID]));
- } // disallow scrolling
+ features.toggle = function (k) {
+ if (_rules[k]) {
+ (function (f) {
+ return f.enabled ? f.disable() : f.enable();
+ })(_rules[k]);
+ update();
+ }
+ };
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.areas.search_playground', {
- preset: playgroundPreset.name()
- }));
- }, 400); // after preset list pane visible..
+ features.resetStats = function () {
+ for (var i = 0; i < _keys.length; i++) {
+ _rules[_keys[i]].count = 0;
+ }
- context.on('enter.intro', function (mode) {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return continueTo(addArea);
- }
+ dispatch.call('change');
+ };
- var ids = context.selectedIDs();
+ features.gatherStats = function (d, resolver, dimensions) {
+ var needsRedraw = false;
+ var types = utilArrayGroupBy(d, 'type');
+ var entities = [].concat(types.relation || [], types.way || [], types.node || []);
+ var currHidden, geometry, matches, i, j;
- if (mode.id !== 'select' || !ids.length || ids[0] !== _areaID) {
- // keep the user's area selected..
- context.enter(modeSelect(context, [_areaID])); // reset pane, in case user somehow happened to change it..
+ for (i = 0; i < _keys.length; i++) {
+ _rules[_keys[i]].count = 0;
+ } // adjust the threshold for point/building culling based on viewport size..
+ // a _cullFactor of 1 corresponds to a 1000x1000px viewport..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%'); // disallow scrolling
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.areas.search_playground', {
- preset: playgroundPreset.name()
- }));
- context.history().on('change.intro', null);
- }
- });
+ _cullFactor = dimensions[0] * dimensions[1] / 1000000;
- function checkPresetSearch() {
- var first = context.container().select('.preset-list-item:first-child');
+ for (i = 0; i < entities.length; i++) {
+ geometry = entities[i].geometry(resolver);
+ matches = Object.keys(features.getMatches(entities[i], resolver, geometry));
- if (first.classed('preset-leisure-playground')) {
- reveal(first.select('.preset-list-button').node(), helpHtml('intro.areas.choose_playground', {
- preset: playgroundPreset.name()
- }), {
- duration: 300
- });
- context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
- context.history().on('change.intro', function () {
- continueTo(clickAddField);
- });
+ for (j = 0; j < matches.length; j++) {
+ _rules[matches[j]].count++;
}
}
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- nextStep();
+ currHidden = features.hidden();
+
+ if (currHidden !== _hidden) {
+ _hidden = currHidden;
+ needsRedraw = true;
+ dispatch.call('change');
}
- }
- function clickAddField() {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return addArea();
+ return needsRedraw;
+ };
+
+ features.stats = function () {
+ for (var i = 0; i < _keys.length; i++) {
+ _stats[_keys[i]] = _rules[_keys[i]].count;
}
- var ids = context.selectedIDs();
+ return _stats;
+ };
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
- return searchPresets();
+ features.clear = function (d) {
+ for (var i = 0; i < d.length; i++) {
+ features.clearEntity(d[i]);
}
+ };
- if (!context.container().select('.form-field-description').empty()) {
- return continueTo(describePlayground);
- } // disallow scrolling
+ features.clearEntity = function (entity) {
+ delete _cache[osmEntity.key(entity)];
+ };
+ features.reset = function () {
+ Array.from(_deferred).forEach(function (handle) {
+ window.cancelIdleCallback(handle);
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // It's possible for the user to add a description in a previous step..
- // If they did this already, just continue to next step.
+ _deferred["delete"](handle);
+ });
+ _cache = {};
+ }; // only certain relations are worth checking
- var entity = context.entity(_areaID);
- if (entity.tags.description) {
- return continueTo(play);
- } // scroll "Add field" into view
+ function relationShouldBeChecked(relation) {
+ // multipolygon features have `area` geometry and aren't checked here
+ return relation.tags.type === 'boundary';
+ }
+ features.getMatches = function (entity, resolver, geometry) {
+ if (geometry === 'vertex' || geometry === 'relation' && !relationShouldBeChecked(entity)) return {};
+ var ent = osmEntity.key(entity);
- var box = context.container().select('.more-fields').node().getBoundingClientRect();
+ if (!_cache[ent]) {
+ _cache[ent] = {};
+ }
- if (box.top > 300) {
- var pane = context.container().select('.entity-editor-pane .inspector-body');
- var start = pane.node().scrollTop;
- var end = start + (box.top - 300);
- pane.transition().duration(250).tween('scroll.inspector', function () {
- var node = this;
- var i = d3_interpolateNumber(start, end);
- return function (t) {
- node.scrollTop = i(t);
- };
- });
- }
+ if (!_cache[ent].matches) {
+ var matches = {};
+ var hasMatch = false;
- timeout(function () {
- reveal('.more-fields .combobox-input', helpHtml('intro.areas.add_field', {
- name: nameField.label(),
- description: descriptionField.label()
- }), {
- duration: 300
- });
- context.container().select('.more-fields .combobox-input').on('click.intro', function () {
- // Watch for the combobox to appear...
- var watcher;
- watcher = window.setInterval(function () {
- if (!context.container().select('div.combobox').empty()) {
- window.clearInterval(watcher);
- continueTo(chooseDescriptionField);
- }
- }, 300);
- });
- }, 300); // after "Add Field" visible
- }, 400); // after editor pane visible
+ for (var i = 0; i < _keys.length; i++) {
+ if (_keys[i] === 'others') {
+ if (hasMatch) continue; // If an entity...
+ // 1. is a way that hasn't matched other 'interesting' feature rules,
- context.on('exit.intro', function () {
- return continueTo(searchPresets);
- });
+ if (entity.type === 'way') {
+ var parents = features.getParents(entity, resolver, geometry); // 2a. belongs only to a single multipolygon relation
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.more-fields .combobox-input').on('click.intro', null);
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ if (parents.length === 1 && parents[0].isMultipolygon() || // 2b. or belongs only to boundary relations
+ parents.length > 0 && parents.every(function (parent) {
+ return parent.tags.type === 'boundary';
+ })) {
+ // ...then match whatever feature rules the parent relation has matched.
+ // see #2548, #2887
+ //
+ // IMPORTANT:
+ // For this to work, getMatches must be called on relations before ways.
+ //
+ var pkey = osmEntity.key(parents[0]);
- function chooseDescriptionField() {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return addArea();
- }
+ if (_cache[pkey] && _cache[pkey].matches) {
+ matches = Object.assign({}, _cache[pkey].matches); // shallow copy
- var ids = context.selectedIDs();
+ continue;
+ }
+ }
+ }
+ }
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
- return searchPresets();
- }
+ if (_rules[_keys[i]].filter(entity.tags, geometry)) {
+ matches[_keys[i]] = hasMatch = true;
+ }
+ }
- if (!context.container().select('.form-field-description').empty()) {
- return continueTo(describePlayground);
- } // Make sure combobox is ready..
+ _cache[ent].matches = matches;
+ }
+ return _cache[ent].matches;
+ };
- if (context.container().select('div.combobox').empty()) {
- return continueTo(clickAddField);
- } // Watch for the combobox to go away..
+ features.getParents = function (entity, resolver, geometry) {
+ if (geometry === 'point') return [];
+ var ent = osmEntity.key(entity);
+ if (!_cache[ent]) {
+ _cache[ent] = {};
+ }
- var watcher;
- watcher = window.setInterval(function () {
- if (context.container().select('div.combobox').empty()) {
- window.clearInterval(watcher);
- timeout(function () {
- if (context.container().select('.form-field-description').empty()) {
- continueTo(retryChooseDescription);
- } else {
- continueTo(describePlayground);
- }
- }, 300); // after description field added.
- }
- }, 300);
- reveal('div.combobox', helpHtml('intro.areas.choose_field', {
- field: descriptionField.label()
- }), {
- duration: 300
- });
- context.on('exit.intro', function () {
- return continueTo(searchPresets);
- });
+ if (!_cache[ent].parents) {
+ var parents = [];
- function continueTo(nextStep) {
- if (watcher) window.clearInterval(watcher);
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ if (geometry === 'vertex') {
+ parents = resolver.parentWays(entity);
+ } else {
+ // 'line', 'area', 'relation'
+ parents = resolver.parentRelations(entity);
+ }
- function describePlayground() {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return addArea();
+ _cache[ent].parents = parents;
}
- var ids = context.selectedIDs();
-
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
- return searchPresets();
- } // reset pane, in case user happened to change it..
+ return _cache[ent].parents;
+ };
+ features.isHiddenPreset = function (preset, geometry) {
+ if (!_hidden.length) return false;
+ if (!preset.tags) return false;
+ var test = preset.setTags({}, geometry);
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ for (var key in _rules) {
+ if (_rules[key].filter(test, geometry)) {
+ if (_hidden.indexOf(key) !== -1) {
+ return key;
+ }
- if (context.container().select('.form-field-description').empty()) {
- return continueTo(retryChooseDescription);
+ return false;
+ }
}
- context.on('exit.intro', function () {
- continueTo(play);
- });
- reveal('.entity-editor-pane', helpHtml('intro.areas.describe_playground', {
- button: icon('#iD-icon-close', 'inline')
- }), {
- duration: 300
+ return false;
+ };
+
+ features.isHiddenFeature = function (entity, resolver, geometry) {
+ if (!_hidden.length) return false;
+ if (!entity.version) return false;
+ if (_forceVisible[entity.id]) return false;
+ var matches = Object.keys(features.getMatches(entity, resolver, geometry));
+ return matches.length && matches.every(function (k) {
+ return features.hidden(k);
});
+ };
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ features.isHiddenChild = function (entity, resolver, geometry) {
+ if (!_hidden.length) return false;
+ if (!entity.version || geometry === 'point') return false;
+ if (_forceVisible[entity.id]) return false;
+ var parents = features.getParents(entity, resolver, geometry);
+ if (!parents.length) return false;
- function retryChooseDescription() {
- if (!_areaID || !context.hasEntity(_areaID)) {
- return addArea();
+ for (var i = 0; i < parents.length; i++) {
+ if (!features.isHidden(parents[i], resolver, parents[i].geometry(resolver))) {
+ return false;
+ }
}
- var ids = context.selectedIDs();
-
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
- return searchPresets();
- } // reset pane, in case user happened to change it..
+ return true;
+ };
+ features.hasHiddenConnections = function (entity, resolver) {
+ if (!_hidden.length) return false;
+ var childNodes, connections;
- context.container().select('.inspector-wrap .panewrap').style('right', '0%');
- reveal('.entity-editor-pane', helpHtml('intro.areas.retry_add_field', {
- field: descriptionField.label()
- }), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(clickAddField);
- }
- });
- context.on('exit.intro', function () {
- return continueTo(searchPresets);
- });
+ if (entity.type === 'midpoint') {
+ childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
+ connections = [];
+ } else {
+ childNodes = entity.nodes ? resolver.childNodes(entity) : [];
+ connections = features.getParents(entity, resolver, entity.geometry(resolver));
+ } // gather ways connected to child nodes..
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
- function play() {
- dispatch.call('done');
- reveal('.ideditor', helpHtml('intro.areas.play', {
- next: _t('intro.lines.title')
- }), {
- tooltipBox: '.intro-nav-wrap .chapter-line',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- reveal('.ideditor');
- }
+ connections = childNodes.reduce(function (result, e) {
+ return resolver.isShared(e) ? utilArrayUnion(result, resolver.parentWays(e)) : result;
+ }, connections);
+ return connections.some(function (e) {
+ return features.isHidden(e, resolver, e.geometry(resolver));
});
- }
-
- chapter.enter = function () {
- addArea();
};
- chapter.exit = function () {
- timeouts.forEach(window.clearTimeout);
- context.on('enter.intro exit.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- context.container().select('.more-fields .combobox-input').on('click.intro', null);
+ features.isHidden = function (entity, resolver, geometry) {
+ if (!_hidden.length) return false;
+ if (!entity.version) return false;
+ var fn = geometry === 'vertex' ? features.isHiddenChild : features.isHiddenFeature;
+ return fn(entity, resolver, geometry);
};
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
- };
+ features.filter = function (d, resolver) {
+ if (!_hidden.length) return d;
+ var result = [];
- return utilRebind(chapter, dispatch, 'on');
- }
+ for (var i = 0; i < d.length; i++) {
+ var entity = d[i];
- function uiIntroLine(context, reveal) {
- var dispatch = dispatch$8('done');
- var timeouts = [];
- var _tulipRoadID = null;
- var flowerRoadID = 'w646';
- var tulipRoadStart = [-85.6297754121684, 41.95805253325314];
- var tulipRoadMidpoint = [-85.62975395449628, 41.95787501510204];
- var tulipRoadIntersection = [-85.62974496187628, 41.95742515554585];
- var roadCategory = _mainPresetIndex.item('category-road_minor');
- var residentialPreset = _mainPresetIndex.item('highway/residential');
- var woodRoadID = 'w525';
- var woodRoadEndID = 'n2862';
- var woodRoadAddNode = [-85.62390110349587, 41.95397111462291];
- var woodRoadDragEndpoint = [-85.623867390213, 41.95466987786487];
- var woodRoadDragMidpoint = [-85.62386254803509, 41.95430395953872];
- var washingtonStreetID = 'w522';
- var twelfthAvenueID = 'w1';
- var eleventhAvenueEndID = 'n3550';
- var twelfthAvenueEndID = 'n5';
- var _washingtonSegmentID = null;
- var eleventhAvenueEnd = context.entity(eleventhAvenueEndID).loc;
- var twelfthAvenueEnd = context.entity(twelfthAvenueEndID).loc;
- var deleteLinesLoc = [-85.6219395542764, 41.95228033922477];
- var twelfthAvenue = [-85.62219310052491, 41.952505413152956];
- var chapter = {
- title: 'intro.lines.title'
- };
+ if (!features.isHidden(entity, resolver, entity.geometry(resolver))) {
+ result.push(entity);
+ }
+ }
- function timeout(f, t) {
- timeouts.push(window.setTimeout(f, t));
- }
+ return result;
+ };
- function eventCancel(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- }
+ features.forceVisible = function (entityIDs) {
+ if (!arguments.length) return Object.keys(_forceVisible);
+ _forceVisible = {};
- function addLine() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- var msec = transitionTime(tulipRoadStart, context.map().center());
+ for (var i = 0; i < entityIDs.length; i++) {
+ _forceVisible[entityIDs[i]] = true;
+ var entity = context.hasEntity(entityIDs[i]);
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
+ if (entity && entity.type === 'relation') {
+ // also show relation members (one level deep)
+ for (var j in entity.members) {
+ _forceVisible[entity.members[j].id] = true;
+ }
+ }
}
- context.map().centerZoomEase(tulipRoadStart, 18.5, msec);
- timeout(function () {
- var tooltip = reveal('button.add-line', helpHtml('intro.lines.add_line'));
- tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-lines');
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'add-line') return;
- continueTo(startLine);
- });
- }, msec + 100);
+ return features;
+ };
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
+ features.init = function () {
+ var storage = corePreferences('disabled-features');
+
+ if (storage) {
+ var storageDisabled = storage.replace(/;/g, ',').split(',');
+ storageDisabled.forEach(features.disable);
}
- }
- function startLine() {
- if (context.mode().id !== 'add-line') return chapter.restart();
- _tulipRoadID = null;
- var padding = 70 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(tulipRoadStart, padding, context);
- box.height = box.height + 100;
- var textId = context.lastPointerType() === 'mouse' ? 'start_line' : 'start_line_tap';
- var startLineString = helpHtml('intro.lines.missing_road') + '{br}' + helpHtml('intro.lines.line_draw_info') + helpHtml('intro.lines.' + textId);
- reveal(box, startLineString);
- context.map().on('move.intro drawn.intro', function () {
- padding = 70 * Math.pow(2, context.map().zoom() - 18);
- box = pad(tulipRoadStart, padding, context);
- box.height = box.height + 100;
- reveal(box, startLineString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'draw-line') return chapter.restart();
- continueTo(drawLine);
- });
+ var hash = utilStringQs(window.location.hash);
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ if (hash.disable_features) {
+ var hashDisabled = hash.disable_features.replace(/;/g, ',').split(',');
+ hashDisabled.forEach(features.disable);
}
- }
+ }; // warm up the feature matching cache upon merging fetched data
- function drawLine() {
- if (context.mode().id !== 'draw-line') return chapter.restart();
- _tulipRoadID = context.mode().selectedIDs()[0];
- context.map().centerEase(tulipRoadMidpoint, 500);
- timeout(function () {
- var padding = 200 * Math.pow(2, context.map().zoom() - 18.5);
- var box = pad(tulipRoadMidpoint, padding, context);
- box.height = box.height * 2;
- reveal(box, helpHtml('intro.lines.intersect', {
- name: _t('intro.graph.name.flower-street')
- }));
- context.map().on('move.intro drawn.intro', function () {
- padding = 200 * Math.pow(2, context.map().zoom() - 18.5);
- box = pad(tulipRoadMidpoint, padding, context);
- box.height = box.height * 2;
- reveal(box, helpHtml('intro.lines.intersect', {
- name: _t('intro.graph.name.flower-street')
- }), {
- duration: 0
- });
- });
- }, 550); // after easing..
- context.history().on('change.intro', function () {
- if (isLineConnected()) {
- continueTo(continueLine);
- }
- });
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-line') {
- return;
- } else if (mode.id === 'select') {
- continueTo(retryIntersect);
- return;
- } else {
- return chapter.restart();
+ context.history().on('merge.features', function (newEntities) {
+ if (!newEntities) return;
+ var handle = window.requestIdleCallback(function () {
+ var graph = context.graph();
+ var types = utilArrayGroupBy(newEntities, 'type'); // ensure that getMatches is called on relations before ways
+
+ var entities = [].concat(types.relation || [], types.way || [], types.node || []);
+
+ for (var i = 0; i < entities.length; i++) {
+ var geometry = entities[i].geometry(graph);
+ features.getMatches(entities[i], graph, geometry);
}
});
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
+ _deferred.add(handle);
+ });
+ return features;
+ }
- function isLineConnected() {
- var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
+ //
+ // - the activeID - nope
+ // - 1 away (adjacent) to the activeID - yes (vertices will be merged)
+ // - 2 away from the activeID - nope (would create a self intersecting segment)
+ // - all others on a linear way - yes
+ // - all others on a closed way - nope (would create a self intersecting polygon)
+ //
+ // returns
+ // 0 = active vertex - no touch/connect
+ // 1 = passive vertex - yes touch/connect
+ // 2 = adjacent vertex - yes but pay attention segmenting a line here
+ //
- if (!entity) return false;
- var drawNodes = context.graph().childNodes(entity);
- return drawNodes.some(function (node) {
- return context.graph().parentWays(node).some(function (parent) {
- return parent.id === flowerRoadID;
- });
- });
- }
+ function svgPassiveVertex(node, graph, activeID) {
+ if (!activeID) return 1;
+ if (activeID === node.id) return 0;
+ var parents = graph.parentWays(node);
+ var i, j, nodes, isClosed, ix1, ix2, ix3, ix4, max;
- function retryIntersect() {
- select(window).on('pointerdown.intro mousedown.intro', eventCancel, true);
- var box = pad(tulipRoadIntersection, 80, context);
- reveal(box, helpHtml('intro.lines.retry_intersect', {
- name: _t('intro.graph.name.flower-street')
- }));
- timeout(chapter.restart, 3000);
- }
+ for (i = 0; i < parents.length; i++) {
+ nodes = parents[i].nodes;
+ isClosed = parents[i].isClosed();
- function continueLine() {
- if (context.mode().id !== 'draw-line') return chapter.restart();
+ for (j = 0; j < nodes.length; j++) {
+ // find this vertex, look nearby
+ if (nodes[j] === node.id) {
+ ix1 = j - 2;
+ ix2 = j - 1;
+ ix3 = j + 1;
+ ix4 = j + 2;
- var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
+ if (isClosed) {
+ // wraparound if needed
+ max = nodes.length - 1;
+ if (ix1 < 0) ix1 = max + ix1;
+ if (ix2 < 0) ix2 = max + ix2;
+ if (ix3 > max) ix3 = ix3 - max;
+ if (ix4 > max) ix4 = ix4 - max;
+ }
- if (!entity) return chapter.restart();
- context.map().centerEase(tulipRoadIntersection, 500);
- var continueLineText = helpHtml('intro.lines.continue_line') + '{br}' + helpHtml('intro.lines.finish_line_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.lines.finish_road');
- reveal('.surface', continueLineText);
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-line') {
- return;
- } else if (mode.id === 'select') {
- return continueTo(chooseCategoryRoad);
- } else {
- return chapter.restart();
+ if (nodes[ix1] === activeID) return 0; // no - prevent self intersect
+ else if (nodes[ix2] === activeID) return 2; // ok - adjacent
+ else if (nodes[ix3] === activeID) return 2; // ok - adjacent
+ else if (nodes[ix4] === activeID) return 0; // no - prevent self intersect
+ else if (isClosed && nodes.indexOf(activeID) !== -1) return 0; // no - prevent self intersect
}
- });
-
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
}
}
- function chooseCategoryRoad() {
- if (context.mode().id !== 'select') return chapter.restart();
- context.on('exit.intro', function () {
- return chapter.restart();
+ return 1; // ok
+ }
+ function svgMarkerSegments(projection, graph, dt, shouldReverse, bothDirections) {
+ return function (entity) {
+ var i = 0;
+ var offset = dt;
+ var segments = [];
+ var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
+ var coordinates = graph.childNodes(entity).map(function (n) {
+ return n.loc;
});
- var button = context.container().select('.preset-category-road_minor .preset-list-button');
- if (button.empty()) return chapter.restart(); // disallow scrolling
-
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
- reveal(button.node(), helpHtml('intro.lines.choose_category_road', {
- category: roadCategory.name()
- }));
- button.on('click.intro', function () {
- continueTo(choosePresetResidential);
- });
- }, 400); // after editor pane visible
+ var a, b;
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-list-button').on('click.intro', null);
- context.on('exit.intro', null);
- nextStep();
+ if (shouldReverse(entity)) {
+ coordinates.reverse();
}
- }
-
- function choosePresetResidential() {
- if (context.mode().id !== 'select') return chapter.restart();
- context.on('exit.intro', function () {
- return chapter.restart();
- });
- var subgrid = context.container().select('.preset-category-road_minor .subgrid');
- if (subgrid.empty()) return chapter.restart();
- subgrid.selectAll(':not(.preset-highway-residential) .preset-list-button').on('click.intro', function () {
- continueTo(retryPresetResidential);
- });
- subgrid.selectAll('.preset-highway-residential .preset-list-button').on('click.intro', function () {
- continueTo(nameRoad);
- });
- timeout(function () {
- reveal(subgrid.node(), helpHtml('intro.lines.choose_preset_residential', {
- preset: residentialPreset.name()
- }), {
- tooltipBox: '.preset-highway-residential .preset-list-button',
- duration: 300
- });
- }, 300);
- function continueTo(nextStep) {
- context.container().select('.preset-list-button').on('click.intro', null);
- context.on('exit.intro', null);
- nextStep();
- }
- } // selected wrong road type
+ d3_geoStream({
+ type: 'LineString',
+ coordinates: coordinates
+ }, projection.stream(clip({
+ lineStart: function lineStart() {},
+ lineEnd: function lineEnd() {
+ a = null;
+ },
+ point: function point(x, y) {
+ b = [x, y];
+ if (a) {
+ var span = geoVecLength(a, b) - offset;
- function retryPresetResidential() {
- if (context.mode().id !== 'select') return chapter.restart();
- context.on('exit.intro', function () {
- return chapter.restart();
- }); // disallow scrolling
+ if (span >= 0) {
+ var heading = geoVecAngle(a, b);
+ var dx = dt * Math.cos(heading);
+ var dy = dt * Math.sin(heading);
+ var p = [a[0] + offset * Math.cos(heading), a[1] + offset * Math.sin(heading)]; // gather coordinates
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- var button = context.container().select('.entity-editor-pane .preset-list-button');
- reveal(button.node(), helpHtml('intro.lines.retry_preset_residential', {
- preset: residentialPreset.name()
- }));
- button.on('click.intro', function () {
- continueTo(chooseCategoryRoad);
- });
- }, 500);
+ var coord = [a, p];
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-list-button').on('click.intro', null);
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ for (span -= dt; span >= 0; span -= dt) {
+ p = geoVecAdd(p, [dx, dy]);
+ coord.push(p);
+ }
- function nameRoad() {
- context.on('exit.intro', function () {
- continueTo(didNameRoad);
- });
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.lines.name_road', {
- button: icon('#iD-icon-close', 'inline')
- }), {
- tooltipClass: 'intro-lines-name_road'
- });
- }, 500);
+ coord.push(b); // generate svg paths
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ var segment = '';
+ var j;
- function didNameRoad() {
- context.history().checkpoint('doneAddLine');
- timeout(function () {
- reveal('.surface', helpHtml('intro.lines.did_name_road'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(updateLine);
+ for (j = 0; j < coord.length; j++) {
+ segment += (j === 0 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
+ }
+
+ segments.push({
+ id: entity.id,
+ index: i++,
+ d: segment
+ });
+
+ if (bothDirections(entity)) {
+ segment = '';
+
+ for (j = coord.length - 1; j >= 0; j--) {
+ segment += (j === coord.length - 1 ? 'M' : 'L') + coord[j][0] + ',' + coord[j][1];
+ }
+
+ segments.push({
+ id: entity.id,
+ index: i++,
+ d: segment
+ });
+ }
+ }
+
+ offset = -span;
}
- });
- }, 500);
- function continueTo(nextStep) {
- nextStep();
+ a = b;
+ }
+ })));
+ return segments;
+ };
+ }
+ function svgPath(projection, graph, isArea) {
+ // Explanation of magic numbers:
+ // "padding" here allows space for strokes to extend beyond the viewport,
+ // so that the stroke isn't drawn along the edge of the viewport when
+ // the shape is clipped.
+ //
+ // When drawing lines, pad viewport by 5px.
+ // When drawing areas, pad viewport by 65px in each direction to allow
+ // for 60px area fill stroke (see ".fill-partial path.fill" css rule)
+ var cache = {};
+ var padding = isArea ? 65 : 5;
+ var viewport = projection.clipExtent();
+ var paddedExtent = [[viewport[0][0] - padding, viewport[0][1] - padding], [viewport[1][0] + padding, viewport[1][1] + padding]];
+ var clip = d3_geoIdentity().clipExtent(paddedExtent).stream;
+ var project = projection.stream;
+ var path = d3_geoPath().projection({
+ stream: function stream(output) {
+ return project(clip(output));
}
- }
+ });
- function updateLine() {
- context.history().reset('doneAddLine');
+ var svgpath = function svgpath(entity) {
+ if (entity.id in cache) {
+ return cache[entity.id];
+ } else {
+ return cache[entity.id] = path(entity.asGeoJSON(graph));
+ }
+ };
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return chapter.restart();
+ svgpath.geojson = function (d) {
+ if (d.__featurehash__ !== undefined) {
+ if (d.__featurehash__ in cache) {
+ return cache[d.__featurehash__];
+ } else {
+ return cache[d.__featurehash__] = path(d);
+ }
+ } else {
+ return path(d);
}
+ };
- var msec = transitionTime(woodRoadDragMidpoint, context.map().center());
+ return svgpath;
+ }
+ function svgPointTransform(projection) {
+ var svgpoint = function svgpoint(entity) {
+ // http://jsperf.com/short-array-join
+ var pt = projection(entity.loc);
+ return 'translate(' + pt[0] + ',' + pt[1] + ')';
+ };
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ svgpoint.geojson = function (d) {
+ return svgpoint(d.properties.entity);
+ };
- context.map().centerZoomEase(woodRoadDragMidpoint, 19, msec);
- timeout(function () {
- var padding = 250 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragMidpoint, padding, context);
+ return svgpoint;
+ }
+ function svgRelationMemberTags(graph) {
+ return function (entity) {
+ var tags = entity.tags;
+ var shouldCopyMultipolygonTags = !entity.hasInterestingTags();
+ graph.parentRelations(entity).forEach(function (relation) {
+ var type = relation.tags.type;
- var advance = function advance() {
- continueTo(addNode);
+ if (type === 'multipolygon' && shouldCopyMultipolygonTags || type === 'boundary') {
+ tags = Object.assign({}, relation.tags, tags);
+ }
+ });
+ return tags;
+ };
+ }
+ function svgSegmentWay(way, graph, activeID) {
+ // When there is no activeID, we can memoize this expensive computation
+ if (activeID === undefined) {
+ return graph["transient"](way, 'waySegments', getWaySegments);
+ } else {
+ return getWaySegments();
+ }
+
+ function getWaySegments() {
+ var isActiveWay = way.nodes.indexOf(activeID) !== -1;
+ var features = {
+ passive: [],
+ active: []
+ };
+ var start = {};
+ var end = {};
+ var node, type;
+
+ for (var i = 0; i < way.nodes.length; i++) {
+ node = graph.entity(way.nodes[i]);
+ type = svgPassiveVertex(node, graph, activeID);
+ end = {
+ node: node,
+ type: type
};
- reveal(box, helpHtml('intro.lines.update_line'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- context.map().on('move.intro drawn.intro', function () {
- var padding = 250 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragMidpoint, padding, context);
- reveal(box, helpHtml('intro.lines.update_line'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- });
- }, msec + 100);
+ if (start.type !== undefined) {
+ if (start.node.id === activeID || end.node.id === activeID) ; else if (isActiveWay && (start.type === 2 || end.type === 2)) {
+ // one adjacent vertex
+ pushActive(start, end, i);
+ } else if (start.type === 0 && end.type === 0) {
+ // both active vertices
+ pushActive(start, end, i);
+ } else {
+ pushPassive(start, end, i);
+ }
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
+ start = end;
}
- }
- function addNode() {
- context.history().reset('doneAddLine');
+ return features;
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return chapter.restart();
+ function pushActive(start, end, index) {
+ features.active.push({
+ type: 'Feature',
+ id: way.id + '-' + index + '-nope',
+ properties: {
+ nope: true,
+ target: true,
+ entity: way,
+ nodes: [start.node, end.node],
+ index: index
+ },
+ geometry: {
+ type: 'LineString',
+ coordinates: [start.node.loc, end.node.loc]
+ }
+ });
}
- var padding = 40 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadAddNode, padding, context);
- var addNodeString = helpHtml('intro.lines.add_node' + (context.lastPointerType() === 'mouse' ? '' : '_touch'));
- reveal(box, addNodeString);
- context.map().on('move.intro drawn.intro', function () {
- var padding = 40 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadAddNode, padding, context);
- reveal(box, addNodeString, {
- duration: 0
+ function pushPassive(start, end, index) {
+ features.passive.push({
+ type: 'Feature',
+ id: way.id + '-' + index,
+ properties: {
+ target: true,
+ entity: way,
+ nodes: [start.node, end.node],
+ index: index
+ },
+ geometry: {
+ type: 'LineString',
+ coordinates: [start.node.loc, end.node.loc]
+ }
});
- });
- context.history().on('change.intro', function (changed) {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
-
- if (changed.created().length === 1) {
- timeout(function () {
- continueTo(startDragEndpoint);
- }, 500);
- }
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') {
- continueTo(updateLine);
- }
- });
-
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.on('enter.intro', null);
- nextStep();
}
}
+ }
- function startDragEndpoint() {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
+ function svgTagClasses() {
+ var primaries = ['building', 'highway', 'railway', 'waterway', 'aeroway', 'aerialway', 'piste:type', 'boundary', 'power', 'amenity', 'natural', 'landuse', 'leisure', 'military', 'place', 'man_made', 'route', 'attraction', 'building:part', 'indoor'];
+ var statuses = [// nonexistent, might be built
+ 'proposed', 'planned', // under maintentance or between groundbreaking and opening
+ 'construction', // existent but not functional
+ 'disused', // dilapidated to nonexistent
+ 'abandoned', // nonexistent, still may appear in imagery
+ 'dismantled', 'razed', 'demolished', 'obliterated', // existent occasionally, e.g. stormwater drainage basin
+ 'intermittent'];
+ var secondaries = ['oneway', 'bridge', 'tunnel', 'embankment', 'cutting', 'barrier', 'surface', 'tracktype', 'footway', 'crossing', 'service', 'sport', 'public_transport', 'location', 'parking', 'golf', 'type', 'leisure', 'man_made', 'indoor', 'construction', 'proposed'];
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- var startDragString = helpHtml('intro.lines.start_drag_endpoint' + (context.lastPointerType() === 'mouse' ? '' : '_touch')) + helpHtml('intro.lines.drag_to_intersection');
- reveal(box, startDragString);
- context.map().on('move.intro drawn.intro', function () {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
+ var _tags = function _tags(entity) {
+ return entity.tags;
+ };
+
+ var tagClasses = function tagClasses(selection) {
+ selection.each(function tagClassesEach(entity) {
+ var value = this.className;
+
+ if (value.baseVal !== undefined) {
+ value = value.baseVal;
}
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- reveal(box, startDragString, {
- duration: 0
- });
- var entity = context.entity(woodRoadEndID);
+ var t = _tags(entity);
- if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) <= 4) {
- continueTo(finishDragEndpoint);
+ var computed = tagClasses.getClassesString(t, value);
+
+ if (computed !== value) {
+ select(this).attr('class', computed);
}
});
+ };
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
- }
- }
+ tagClasses.getClassesString = function (t, value) {
+ var primary, status;
+ var i, j, k, v; // in some situations we want to render perimeter strokes a certain way
- function finishDragEndpoint() {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
+ var overrideGeometry;
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- var finishDragString = helpHtml('intro.lines.spot_looks_good') + helpHtml('intro.lines.finish_drag_endpoint' + (context.lastPointerType() === 'mouse' ? '' : '_touch'));
- reveal(box, finishDragString);
- context.map().on('move.intro drawn.intro', function () {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
+ if (/\bstroke\b/.test(value)) {
+ if (!!t.barrier && t.barrier !== 'no') {
+ overrideGeometry = 'line';
}
+ } // preserve base classes (nothing with `tag-`)
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- reveal(box, finishDragString, {
- duration: 0
- });
- var entity = context.entity(woodRoadEndID);
- if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) > 4) {
- continueTo(startDragEndpoint);
+ var classes = value.trim().split(/\s+/).filter(function (klass) {
+ return klass.length && !/^tag-/.test(klass);
+ }).map(function (klass) {
+ // special overrides for some perimeter strokes
+ return klass === 'line' || klass === 'area' ? overrideGeometry || klass : klass;
+ }); // pick at most one primary classification tag..
+
+ for (i = 0; i < primaries.length; i++) {
+ k = primaries[i];
+ v = t[k];
+ if (!v || v === 'no') continue;
+
+ if (k === 'piste:type') {
+ // avoid a ':' in the class name
+ k = 'piste';
+ } else if (k === 'building:part') {
+ // avoid a ':' in the class name
+ k = 'building_part';
}
- });
- context.on('enter.intro', function () {
- continueTo(startDragMidpoint);
- });
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
+ primary = k;
- function startDragMidpoint() {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
+ if (statuses.indexOf(v) !== -1) {
+ // e.g. `railway=abandoned`
+ status = v;
+ classes.push('tag-' + k);
+ } else {
+ classes.push('tag-' + k);
+ classes.push('tag-' + k + '-' + v);
+ }
- if (context.selectedIDs().indexOf(woodRoadID) === -1) {
- context.enter(modeSelect(context, [woodRoadID]));
+ break;
}
- var padding = 80 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragMidpoint, padding, context);
- reveal(box, helpHtml('intro.lines.start_drag_midpoint'));
- context.map().on('move.intro drawn.intro', function () {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
+ if (!primary) {
+ for (i = 0; i < statuses.length; i++) {
+ for (j = 0; j < primaries.length; j++) {
+ k = statuses[i] + ':' + primaries[j]; // e.g. `demolished:building=yes`
- var padding = 80 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragMidpoint, padding, context);
- reveal(box, helpHtml('intro.lines.start_drag_midpoint'), {
- duration: 0
- });
- });
- context.history().on('change.intro', function (changed) {
- if (changed.created().length === 1) {
- continueTo(continueDragMidpoint);
- }
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') {
- // keep Wood Road selected so midpoint triangles are drawn..
- context.enter(modeSelect(context, [woodRoadID]));
+ v = t[k];
+ if (!v || v === 'no') continue;
+ status = statuses[i];
+ break;
+ }
}
- });
+ } // add at most one status tag, only if relates to primary tag..
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
- function continueDragMidpoint() {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
- }
+ if (!status) {
+ for (i = 0; i < statuses.length; i++) {
+ k = statuses[i];
+ v = t[k];
+ if (!v || v === 'no') continue;
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- box.height += 400;
+ if (v === 'yes') {
+ // e.g. `railway=rail + abandoned=yes`
+ status = k;
+ } else if (primary && primary === v) {
+ // e.g. `railway=rail + abandoned=railway`
+ status = k;
+ } else if (!primary && primaries.indexOf(v) !== -1) {
+ // e.g. `abandoned=railway`
+ status = k;
+ primary = v;
+ classes.push('tag-' + v);
+ } // else ignore e.g. `highway=path + abandoned=railway`
- var advance = function advance() {
- context.history().checkpoint('doneUpdateLine');
- continueTo(deleteLines);
- };
- reveal(box, helpHtml('intro.lines.continue_drag_midpoint'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- context.map().on('move.intro drawn.intro', function () {
- if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
- return continueTo(updateLine);
+ if (status) break;
}
+ }
- var padding = 100 * Math.pow(2, context.map().zoom() - 19);
- var box = pad(woodRoadDragEndpoint, padding, context);
- box.height += 400;
- reveal(box, helpHtml('intro.lines.continue_drag_midpoint'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- });
+ if (status) {
+ classes.push('tag-status');
+ classes.push('tag-status-' + status);
+ } // add any secondary tags
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
- }
- }
- function deleteLines() {
- context.history().reset('doneUpdateLine');
- context.enter(modeBrowse(context));
+ for (i = 0; i < secondaries.length; i++) {
+ k = secondaries[i];
+ v = t[k];
+ if (!v || v === 'no' || k === primary) continue;
+ classes.push('tag-' + k);
+ classes.push('tag-' + k + '-' + v);
+ } // For highways, look for surface tagging..
- if (!context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return chapter.restart();
- }
- var msec = transitionTime(deleteLinesLoc, context.map().center());
+ if (primary === 'highway' && !osmPathHighwayTagValues[t.highway] || primary === 'aeroway') {
+ var surface = t.highway === 'track' ? 'unpaved' : 'paved';
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ for (k in t) {
+ v = t[k];
- context.map().centerZoomEase(deleteLinesLoc, 18, msec);
- timeout(function () {
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(deleteLinesLoc, padding, context);
- box.top -= 200;
- box.height += 400;
-
- var advance = function advance() {
- continueTo(rightClickIntersection);
- };
-
- reveal(box, helpHtml('intro.lines.delete_lines', {
- street: _t('intro.graph.name.12th-avenue')
- }), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- context.map().on('move.intro drawn.intro', function () {
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(deleteLinesLoc, padding, context);
- box.top -= 200;
- box.height += 400;
- reveal(box, helpHtml('intro.lines.delete_lines', {
- street: _t('intro.graph.name.12th-avenue')
- }), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- });
- context.history().on('change.intro', function () {
- timeout(function () {
- continueTo(deleteLines);
- }, 500); // after any transition (e.g. if user deleted intersection)
- });
- }, msec + 100);
-
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ if (k in osmPavedTags) {
+ surface = osmPavedTags[k][v] ? 'paved' : 'unpaved';
+ }
- function rightClickIntersection() {
- context.history().reset('doneUpdateLine');
- context.enter(modeBrowse(context));
- context.map().centerZoomEase(eleventhAvenueEnd, 18, 500);
- var rightClickString = helpHtml('intro.lines.split_street', {
- street1: _t('intro.graph.name.11th-avenue'),
- street2: _t('intro.graph.name.washington-street')
- }) + helpHtml('intro.lines.' + (context.lastPointerType() === 'mouse' ? 'rightclick_intersection' : 'edit_menu_intersection_touch'));
- timeout(function () {
- var padding = 60 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(eleventhAvenueEnd, padding, context);
- reveal(box, rightClickString);
- context.map().on('move.intro drawn.intro', function () {
- var padding = 60 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(eleventhAvenueEnd, padding, context);
- reveal(box, rightClickString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return;
- var ids = context.selectedIDs();
- if (ids.length !== 1 || ids[0] !== eleventhAvenueEndID) return;
- timeout(function () {
- var node = selectMenuItem(context, 'split').node();
- if (!node) return;
- continueTo(splitIntersection);
- }, 50); // after menu visible
- });
- context.history().on('change.intro', function () {
- timeout(function () {
- continueTo(deleteLines);
- }, 300); // after any transition (e.g. if user deleted intersection)
- });
- }, 600);
+ if (k in osmSemipavedTags && !!osmSemipavedTags[k][v]) {
+ surface = 'semipaved';
+ }
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ classes.push('tag-' + surface);
+ } // If this is a wikidata-tagged item, add a class for that..
- function splitIntersection() {
- if (!context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(deleteLines);
- }
- var node = selectMenuItem(context, 'split').node();
+ var qid = t.wikidata || t['flag:wikidata'] || t['brand:wikidata'] || t['network:wikidata'] || t['operator:wikidata'];
- if (!node) {
- return continueTo(rightClickIntersection);
+ if (qid) {
+ classes.push('tag-wikidata');
}
- var wasChanged = false;
- _washingtonSegmentID = null;
- reveal('.edit-menu', helpHtml('intro.lines.split_intersection', {
- street: _t('intro.graph.name.washington-street')
- }), {
- padding: 50
- });
- context.map().on('move.intro drawn.intro', function () {
- var node = selectMenuItem(context, 'split').node();
+ return classes.join(' ').trim();
+ };
- if (!wasChanged && !node) {
- return continueTo(rightClickIntersection);
- }
+ tagClasses.tags = function (val) {
+ if (!arguments.length) return _tags;
+ _tags = val;
+ return tagClasses;
+ };
- reveal('.edit-menu', helpHtml('intro.lines.split_intersection', {
- street: _t('intro.graph.name.washington-street')
- }), {
- duration: 0,
- padding: 50
- });
- });
- context.history().on('change.intro', function (changed) {
- wasChanged = true;
- timeout(function () {
- if (context.history().undoAnnotation() === _t('operations.split.annotation.line', {
- n: 1
- })) {
- _washingtonSegmentID = changed.created()[0].id;
- continueTo(didSplit);
- } else {
- _washingtonSegmentID = null;
- continueTo(retrySplit);
- }
- }, 300); // after any transition (e.g. if user deleted intersection)
- });
+ return tagClasses;
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
+ // Patterns only work in Firefox when set directly on element.
+ // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632)
+ var patterns = {
+ // tag - pattern name
+ // -or-
+ // tag - value - pattern name
+ // -or-
+ // tag - value - rules (optional tag-values, pattern name)
+ // (matches earlier rules first, so fallback should be last entry)
+ amenity: {
+ grave_yard: 'cemetery',
+ fountain: 'water_standing'
+ },
+ landuse: {
+ cemetery: [{
+ religion: 'christian',
+ pattern: 'cemetery_christian'
+ }, {
+ religion: 'buddhist',
+ pattern: 'cemetery_buddhist'
+ }, {
+ religion: 'muslim',
+ pattern: 'cemetery_muslim'
+ }, {
+ religion: 'jewish',
+ pattern: 'cemetery_jewish'
+ }, {
+ pattern: 'cemetery'
+ }],
+ construction: 'construction',
+ farmland: 'farmland',
+ farmyard: 'farmyard',
+ forest: [{
+ leaf_type: 'broadleaved',
+ pattern: 'forest_broadleaved'
+ }, {
+ leaf_type: 'needleleaved',
+ pattern: 'forest_needleleaved'
+ }, {
+ leaf_type: 'leafless',
+ pattern: 'forest_leafless'
+ }, {
+ pattern: 'forest'
+ } // same as 'leaf_type:mixed'
+ ],
+ grave_yard: 'cemetery',
+ grass: [{
+ golf: 'green',
+ pattern: 'golf_green'
+ }, {
+ pattern: 'grass'
+ }],
+ landfill: 'landfill',
+ meadow: 'meadow',
+ military: 'construction',
+ orchard: 'orchard',
+ quarry: 'quarry',
+ vineyard: 'vineyard'
+ },
+ natural: {
+ beach: 'beach',
+ grassland: 'grass',
+ sand: 'beach',
+ scrub: 'scrub',
+ water: [{
+ water: 'pond',
+ pattern: 'pond'
+ }, {
+ water: 'reservoir',
+ pattern: 'water_standing'
+ }, {
+ pattern: 'waves'
+ }],
+ wetland: [{
+ wetland: 'marsh',
+ pattern: 'wetland_marsh'
+ }, {
+ wetland: 'swamp',
+ pattern: 'wetland_swamp'
+ }, {
+ wetland: 'bog',
+ pattern: 'wetland_bog'
+ }, {
+ wetland: 'reedbed',
+ pattern: 'wetland_reedbed'
+ }, {
+ pattern: 'wetland'
+ }],
+ wood: [{
+ leaf_type: 'broadleaved',
+ pattern: 'forest_broadleaved'
+ }, {
+ leaf_type: 'needleleaved',
+ pattern: 'forest_needleleaved'
+ }, {
+ leaf_type: 'leafless',
+ pattern: 'forest_leafless'
+ }, {
+ pattern: 'forest'
+ } // same as 'leaf_type:mixed'
+ ]
+ },
+ traffic_calming: {
+ island: [{
+ surface: 'grass',
+ pattern: 'grass'
+ }],
+ chicane: [{
+ surface: 'grass',
+ pattern: 'grass'
+ }],
+ choker: [{
+ surface: 'grass',
+ pattern: 'grass'
+ }]
+ }
+ };
+ function svgTagPattern(tags) {
+ // Skip pattern filling if this is a building (buildings don't get patterns applied)
+ if (tags.building && tags.building !== 'no') {
+ return null;
}
- function retrySplit() {
- context.enter(modeBrowse(context));
- context.map().centerZoomEase(eleventhAvenueEnd, 18, 500);
+ for (var tag in patterns) {
+ var entityValue = tags[tag];
+ if (!entityValue) continue;
- var advance = function advance() {
- continueTo(rightClickIntersection);
- };
+ if (typeof patterns[tag] === 'string') {
+ // extra short syntax (just tag) - pattern name
+ return 'pattern-' + patterns[tag];
+ } else {
+ var values = patterns[tag];
- var padding = 60 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(eleventhAvenueEnd, padding, context);
- reveal(box, helpHtml('intro.lines.retry_split'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- context.map().on('move.intro drawn.intro', function () {
- var padding = 60 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(eleventhAvenueEnd, padding, context);
- reveal(box, helpHtml('intro.lines.retry_split'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: advance
- });
- });
+ for (var value in values) {
+ if (entityValue !== value) continue;
+ var rules = values[value];
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
- }
- }
+ if (typeof rules === 'string') {
+ // short syntax - pattern name
+ return 'pattern-' + rules;
+ } // long syntax - rule array
- function didSplit() {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
- var ids = context.selectedIDs();
- var string = 'intro.lines.did_split_' + (ids.length > 1 ? 'multi' : 'single');
- var street = _t('intro.graph.name.washington-street');
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(twelfthAvenue, padding, context);
- box.width = box.width / 2;
- reveal(box, helpHtml(string, {
- street1: street,
- street2: street
- }), {
- duration: 500
- });
- timeout(function () {
- context.map().centerZoomEase(twelfthAvenue, 18, 500);
- context.map().on('move.intro drawn.intro', function () {
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(twelfthAvenue, padding, context);
- box.width = box.width / 2;
- reveal(box, helpHtml(string, {
- street1: street,
- street2: street
- }), {
- duration: 0
- });
- });
- }, 600); // after initial reveal and curtain cut
+ for (var ruleKey in rules) {
+ var rule = rules[ruleKey];
+ var pass = true;
- context.on('enter.intro', function () {
- var ids = context.selectedIDs();
+ for (var criterion in rule) {
+ if (criterion !== 'pattern') {
+ // reserved for pattern name
+ // The only rule is a required tag-value pair
+ var v = tags[criterion];
- if (ids.length === 1 && ids[0] === _washingtonSegmentID) {
- continueTo(multiSelect);
- }
- });
- context.history().on('change.intro', function () {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
- });
+ if (!v || v !== rule[criterion]) {
+ pass = false;
+ break;
+ }
+ }
+ }
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- nextStep();
+ if (pass) {
+ return 'pattern-' + rule.pattern;
+ }
+ }
+ }
}
}
- function multiSelect() {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
+ return null;
+ }
- var ids = context.selectedIDs();
- var hasWashington = ids.indexOf(_washingtonSegmentID) !== -1;
- var hasTwelfth = ids.indexOf(twelfthAvenueID) !== -1;
+ function svgAreas(projection, context) {
+ function getPatternStyle(tags) {
+ var imageID = svgTagPattern(tags);
- if (hasWashington && hasTwelfth) {
- return continueTo(multiRightClick);
- } else if (!hasWashington && !hasTwelfth) {
- return continueTo(didSplit);
+ if (imageID) {
+ return 'url("#ideditor-' + imageID + '")';
}
- context.map().centerZoomEase(twelfthAvenue, 18, 500);
- timeout(function () {
- var selected, other, padding, box;
+ return '';
+ }
- if (hasWashington) {
- selected = _t('intro.graph.name.washington-street');
- other = _t('intro.graph.name.12th-avenue');
- padding = 60 * Math.pow(2, context.map().zoom() - 18);
- box = pad(twelfthAvenueEnd, padding, context);
- box.width *= 3;
- } else {
- selected = _t('intro.graph.name.12th-avenue');
- other = _t('intro.graph.name.washington-street');
- padding = 200 * Math.pow(2, context.map().zoom() - 18);
- box = pad(twelfthAvenue, padding, context);
- box.width /= 2;
- }
+ function drawTargets(selection, graph, entities, filter) {
+ var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
+ var getPath = svgPath(projection).geojson;
+ var activeID = context.activeID();
+ var base = context.history().base(); // The targets and nopes will be MultiLineString sub-segments of the ways
- reveal(box, helpHtml('intro.lines.multi_select', {
- selected: selected,
- other1: other
- }) + ' ' + helpHtml('intro.lines.add_to_selection_' + (context.lastPointerType() === 'mouse' ? 'click' : 'touch'), {
- selected: selected,
- other2: other
- }));
- context.map().on('move.intro drawn.intro', function () {
- if (hasWashington) {
- selected = _t('intro.graph.name.washington-street');
- other = _t('intro.graph.name.12th-avenue');
- padding = 60 * Math.pow(2, context.map().zoom() - 18);
- box = pad(twelfthAvenueEnd, padding, context);
- box.width *= 3;
- } else {
- selected = _t('intro.graph.name.12th-avenue');
- other = _t('intro.graph.name.washington-street');
- padding = 200 * Math.pow(2, context.map().zoom() - 18);
- box = pad(twelfthAvenue, padding, context);
- box.width /= 2;
- }
+ var data = {
+ targets: [],
+ nopes: []
+ };
+ entities.forEach(function (way) {
+ var features = svgSegmentWay(way, graph, activeID);
+ data.targets.push.apply(data.targets, features.passive);
+ data.nopes.push.apply(data.nopes, features.active);
+ }); // Targets allow hover and vertex snapping
- reveal(box, helpHtml('intro.lines.multi_select', {
- selected: selected,
- other1: other
- }) + ' ' + helpHtml('intro.lines.add_to_selection_' + (context.lastPointerType() === 'mouse' ? 'click' : 'touch'), {
- selected: selected,
- other2: other
- }), {
- duration: 0
- });
- });
- context.on('enter.intro', function () {
- continueTo(multiSelect);
- });
- context.history().on('change.intro', function () {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
- });
- }, 600);
+ var targetData = data.targets.filter(getPath);
+ var targets = selection.selectAll('.area.target-allowed').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(targetData, function key(d) {
+ return d.id;
+ }); // exit
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ targets.exit().remove();
- function multiRightClick() {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
+ var segmentWasEdited = function segmentWasEdited(d) {
+ var wayID = d.properties.entity.id; // if the whole line was edited, don't draw segment changes
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(twelfthAvenue, padding, context);
- var rightClickString = helpHtml('intro.lines.multi_select_success') + helpHtml('intro.lines.multi_' + (context.lastPointerType() === 'mouse' ? 'rightclick' : 'edit_menu_touch'));
- reveal(box, rightClickString);
- context.map().on('move.intro drawn.intro', function () {
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(twelfthAvenue, padding, context);
- reveal(box, rightClickString, {
- duration: 0
+ if (!base.entities[wayID] || !fastDeepEqual(graph.entities[wayID].nodes, base.entities[wayID].nodes)) {
+ return false;
+ }
+
+ return d.properties.nodes.some(function (n) {
+ return !base.entities[n.id] || !fastDeepEqual(graph.entities[n.id].loc, base.entities[n.id].loc);
});
- });
- context.ui().editMenu().on('toggled.intro', function (open) {
- if (!open) return;
- timeout(function () {
- var ids = context.selectedIDs();
+ }; // enter/update
- if (ids.length === 2 && ids.indexOf(twelfthAvenueID) !== -1 && ids.indexOf(_washingtonSegmentID) !== -1) {
- var node = selectMenuItem(context, 'delete').node();
- if (!node) return;
- continueTo(multiDelete);
- } else if (ids.length === 1 && ids.indexOf(_washingtonSegmentID) !== -1) {
- return continueTo(multiSelect);
- } else {
- return continueTo(didSplit);
- }
- }, 300); // after edit menu visible
- });
- context.history().on('change.intro', function () {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
- }
- });
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.ui().editMenu().on('toggled.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
+ targets.enter().append('path').merge(targets).attr('d', getPath).attr('class', function (d) {
+ return 'way area target target-allowed ' + targetClass + d.id;
+ }).classed('segment-edited', segmentWasEdited); // NOPE
+
+ var nopeData = data.nopes.filter(getPath);
+ var nopes = selection.selectAll('.area.target-nope').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(nopeData, function key(d) {
+ return d.id;
+ }); // exit
+
+ nopes.exit().remove(); // enter/update
+
+ nopes.enter().append('path').merge(nopes).attr('d', getPath).attr('class', function (d) {
+ return 'way area target target-nope ' + nopeClass + d.id;
+ }).classed('segment-edited', segmentWasEdited);
}
- function multiDelete() {
- if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
- return continueTo(rightClickIntersection);
+ function drawAreas(selection, graph, entities, filter) {
+ var path = svgPath(projection, graph, true);
+ var areas = {};
+ var multipolygon;
+ var base = context.history().base();
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ if (entity.geometry(graph) !== 'area') continue;
+ multipolygon = osmIsOldMultipolygonOuterMember(entity, graph);
+
+ if (multipolygon) {
+ areas[multipolygon.id] = {
+ entity: multipolygon.mergeTags(entity.tags),
+ area: Math.abs(entity.area(graph))
+ };
+ } else if (!areas[entity.id]) {
+ areas[entity.id] = {
+ entity: entity,
+ area: Math.abs(entity.area(graph))
+ };
+ }
}
- var node = selectMenuItem(context, 'delete').node();
- if (!node) return continueTo(multiRightClick);
- reveal('.edit-menu', helpHtml('intro.lines.multi_delete'), {
- padding: 50
+ var fills = Object.values(areas).filter(function hasPath(a) {
+ return path(a.entity);
});
- context.map().on('move.intro drawn.intro', function () {
- reveal('.edit-menu', helpHtml('intro.lines.multi_delete'), {
- duration: 0,
- padding: 50
- });
+ fills.sort(function areaSort(a, b) {
+ return b.area - a.area;
});
- context.on('exit.intro', function () {
- if (context.hasEntity(_washingtonSegmentID) || context.hasEntity(twelfthAvenueID)) {
- return continueTo(multiSelect); // left select mode but roads still exist
- }
+ fills = fills.map(function (a) {
+ return a.entity;
});
- context.history().on('change.intro', function () {
- if (context.hasEntity(_washingtonSegmentID) || context.hasEntity(twelfthAvenueID)) {
- continueTo(retryDelete); // changed something but roads still exist
- } else {
- continueTo(play);
- }
+ var strokes = fills.filter(function (area) {
+ return area.type === 'way';
});
-
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('exit.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
-
- function retryDelete() {
- context.enter(modeBrowse(context));
- var padding = 200 * Math.pow(2, context.map().zoom() - 18);
- var box = pad(twelfthAvenue, padding, context);
- reveal(box, helpHtml('intro.lines.retry_delete'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(multiSelect);
- }
+ var data = {
+ clip: fills,
+ shadow: strokes,
+ stroke: strokes,
+ fill: fills
+ };
+ var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath-osm').filter(filter).data(data.clip, osmEntity.key);
+ clipPaths.exit().remove();
+ var clipPathsEnter = clipPaths.enter().append('clipPath').attr('class', 'clipPath-osm').attr('id', function (entity) {
+ return 'ideditor-' + entity.id + '-clippath';
});
+ clipPathsEnter.append('path');
+ clipPaths.merge(clipPathsEnter).selectAll('path').attr('d', path);
+ var drawLayer = selection.selectAll('.layer-osm.areas');
+ var touchLayer = selection.selectAll('.layer-touch.areas'); // Draw areas..
- function continueTo(nextStep) {
- nextStep();
- }
- }
+ var areagroup = drawLayer.selectAll('g.areagroup').data(['fill', 'shadow', 'stroke']);
+ areagroup = areagroup.enter().append('g').attr('class', function (d) {
+ return 'areagroup area-' + d;
+ }).merge(areagroup);
+ var paths = areagroup.selectAll('path').filter(filter).data(function (layer) {
+ return data[layer];
+ }, osmEntity.key);
+ paths.exit().remove();
+ var fillpaths = selection.selectAll('.area-fill path.area').nodes();
+ var bisect = d3_bisector(function (node) {
+ return -node.__data__.area(graph);
+ }).left;
- function play() {
- dispatch.call('done');
- reveal('.ideditor', helpHtml('intro.lines.play', {
- next: _t('intro.buildings.title')
- }), {
- tooltipBox: '.intro-nav-wrap .chapter-building',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- reveal('.ideditor');
+ function sortedByArea(entity) {
+ if (this._parent.__data__ === 'fill') {
+ return fillpaths[bisect(fillpaths, -entity.area(graph))];
}
- });
- }
+ }
- chapter.enter = function () {
- addLine();
- };
+ paths = paths.enter().insert('path', sortedByArea).merge(paths).each(function (entity) {
+ var layer = this.parentNode.__data__;
+ this.setAttribute('class', entity.type + ' area ' + layer + ' ' + entity.id);
- chapter.exit = function () {
- timeouts.forEach(window.clearTimeout);
- select(window).on('pointerdown.intro mousedown.intro', null, true);
- context.on('enter.intro exit.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-list-button').on('click.intro', null);
- };
+ if (layer === 'fill') {
+ this.setAttribute('clip-path', 'url(#ideditor-' + entity.id + '-clippath)');
+ this.style.fill = this.style.stroke = getPatternStyle(entity.tags);
+ }
+ }).classed('added', function (d) {
+ return !base.entities[d.id];
+ }).classed('geometry-edited', function (d) {
+ return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].nodes, base.entities[d.id].nodes);
+ }).classed('retagged', function (d) {
+ return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
+ }).call(svgTagClasses()).attr('d', path); // Draw touch targets..
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
- };
+ touchLayer.call(drawTargets, graph, data.stroke, filter);
+ }
- return utilRebind(chapter, dispatch, 'on');
+ return drawAreas;
}
- function uiIntroBuilding(context, reveal) {
- var dispatch = dispatch$8('done');
- var house = [-85.62815, 41.95638];
- var tank = [-85.62732, 41.95347];
- var buildingCatetory = _mainPresetIndex.item('category-building');
- var housePreset = _mainPresetIndex.item('building/house');
- var tankPreset = _mainPresetIndex.item('man_made/storage_tank');
- var timeouts = [];
- var _houseID = null;
- var _tankID = null;
- var chapter = {
- title: 'intro.buildings.title'
+ var fastJsonStableStringify = function fastJsonStableStringify(data, opts) {
+ if (!opts) opts = {};
+ if (typeof opts === 'function') opts = {
+ cmp: opts
};
+ var cycles = typeof opts.cycles === 'boolean' ? opts.cycles : false;
- function timeout(f, t) {
- timeouts.push(window.setTimeout(f, t));
- }
+ var cmp = opts.cmp && function (f) {
+ return function (node) {
+ return function (a, b) {
+ var aobj = {
+ key: a,
+ value: node[a]
+ };
+ var bobj = {
+ key: b,
+ value: node[b]
+ };
+ return f(aobj, bobj);
+ };
+ };
+ }(opts.cmp);
- function eventCancel(d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- }
+ var seen = [];
+ return function stringify(node) {
+ if (node && node.toJSON && typeof node.toJSON === 'function') {
+ node = node.toJSON();
+ }
- function revealHouse(center, text, options) {
- var padding = 160 * Math.pow(2, context.map().zoom() - 20);
- var box = pad(center, padding, context);
- reveal(box, text, options);
- }
+ if (node === undefined) return;
+ if (typeof node == 'number') return isFinite(node) ? '' + node : 'null';
+ if (_typeof(node) !== 'object') return JSON.stringify(node);
+ var i, out;
- function revealTank(center, text, options) {
- var padding = 190 * Math.pow(2, context.map().zoom() - 19.5);
- var box = pad(center, padding, context);
- reveal(box, text, options);
- }
+ if (Array.isArray(node)) {
+ out = '[';
- function addHouse() {
- context.enter(modeBrowse(context));
- context.history().reset('initial');
- _houseID = null;
- var msec = transitionTime(house, context.map().center());
+ for (i = 0; i < node.length; i++) {
+ if (i) out += ',';
+ out += stringify(node[i]) || 'null';
+ }
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
+ return out + ']';
}
- context.map().centerZoomEase(house, 19, msec);
- timeout(function () {
- var tooltip = reveal('button.add-area', helpHtml('intro.buildings.add_building'));
- tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-buildings');
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'add-area') return;
- continueTo(startHouse);
- });
- }, msec + 100);
-
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
- }
- }
+ if (node === null) return 'null';
- function startHouse() {
- if (context.mode().id !== 'add-area') {
- return continueTo(addHouse);
+ if (seen.indexOf(node) !== -1) {
+ if (cycles) return JSON.stringify('__cycle__');
+ throw new TypeError('Converting circular structure to JSON');
}
- _houseID = null;
- context.map().zoomEase(20, 500);
- timeout(function () {
- var startString = helpHtml('intro.buildings.start_building') + helpHtml('intro.buildings.building_corner_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap'));
- revealHouse(house, startString);
- context.map().on('move.intro drawn.intro', function () {
- revealHouse(house, startString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'draw-area') return chapter.restart();
- continueTo(continueHouse);
- });
- }, 550); // after easing
-
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
+ var seenIndex = seen.push(node) - 1;
+ var keys = Object.keys(node).sort(cmp && cmp(node));
+ out = '';
- function continueHouse() {
- if (context.mode().id !== 'draw-area') {
- return continueTo(addHouse);
+ for (i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = stringify(node[key]);
+ if (!value) continue;
+ if (out) out += ',';
+ out += JSON.stringify(key) + ':' + value;
}
- _houseID = null;
- var continueString = helpHtml('intro.buildings.continue_building') + '{br}' + helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.buildings.finish_building');
- revealHouse(house, continueString);
- context.map().on('move.intro drawn.intro', function () {
- revealHouse(house, continueString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-area') {
- return;
- } else if (mode.id === 'select') {
- var graph = context.graph();
- var way = context.entity(context.selectedIDs()[0]);
- var nodes = graph.childNodes(way);
- var points = utilArrayUniq(nodes).map(function (n) {
- return context.projection(n.loc);
- });
+ seen.splice(seenIndex, 1);
+ return '{' + out + '}';
+ }(data);
+ };
- if (isMostlySquare(points)) {
- _houseID = way.id;
- return continueTo(chooseCategoryBuilding);
- } else {
- return continueTo(retryHouse);
- }
- } else {
- return chapter.restart();
- }
- });
+ var $$1 = _export;
+ var $entries = objectToArray.entries;
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
+ // `Object.entries` method
+ // https://tc39.es/ecma262/#sec-object.entries
+ $$1({ target: 'Object', stat: true }, {
+ entries: function entries(O) {
+ return $entries(O);
}
+ });
- function retryHouse() {
- var onClick = function onClick() {
- continueTo(addHouse);
- };
-
- revealHouse(house, helpHtml('intro.buildings.retry_building'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- context.map().on('move.intro drawn.intro', function () {
- revealHouse(house, helpHtml('intro.buildings.retry_building'), {
- duration: 0,
- buttonText: _t.html('intro.ok'),
- buttonCallback: onClick
- });
- });
+ var _marked = /*#__PURE__*/regeneratorRuntime.mark(gpxGen),
+ _marked3 = /*#__PURE__*/regeneratorRuntime.mark(kmlGen);
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- nextStep();
- }
+ // cast array x into numbers
+ // get the content of a text node, if any
+ function nodeVal(x) {
+ if (x && x.normalize) {
+ x.normalize();
}
- function chooseCategoryBuilding() {
- if (!_houseID || !context.hasEntity(_houseID)) {
- return addHouse();
- }
-
- var ids = context.selectedIDs();
-
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
- context.enter(modeSelect(context, [_houseID]));
- } // disallow scrolling
+ return x && x.textContent || "";
+ } // one Y child of X, if any, otherwise null
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
- var button = context.container().select('.preset-category-building .preset-list-button');
- reveal(button.node(), helpHtml('intro.buildings.choose_category_building', {
- category: buildingCatetory.name()
- }));
- button.on('click.intro', function () {
- button.on('click.intro', null);
- continueTo(choosePresetHouse);
- });
- }, 400); // after preset list pane visible..
+ function get1(x, y) {
+ var n = x.getElementsByTagName(y);
+ return n.length ? n[0] : null;
+ }
- context.on('enter.intro', function (mode) {
- if (!_houseID || !context.hasEntity(_houseID)) {
- return continueTo(addHouse);
- }
+ function getLineStyle(extensions) {
+ var style = {};
- var ids = context.selectedIDs();
+ if (extensions) {
+ var lineStyle = get1(extensions, "line");
- if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
- return continueTo(chooseCategoryBuilding);
- }
- });
+ if (lineStyle) {
+ var color = nodeVal(get1(lineStyle, "color")),
+ opacity = parseFloat(nodeVal(get1(lineStyle, "opacity"))),
+ width = parseFloat(nodeVal(get1(lineStyle, "width")));
+ if (color) style.stroke = color;
+ if (!isNaN(opacity)) style["stroke-opacity"] = opacity; // GPX width is in mm, convert to px with 96 px per inch
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-list-button').on('click.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ if (!isNaN(width)) style["stroke-width"] = width * 96 / 25.4;
}
}
- function choosePresetHouse() {
- if (!_houseID || !context.hasEntity(_houseID)) {
- return addHouse();
- }
+ return style;
+ } // get the contents of multiple text nodes, if present
- var ids = context.selectedIDs();
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
- context.enter(modeSelect(context, [_houseID]));
- } // disallow scrolling
+ function getMulti(x, ys) {
+ var o = {};
+ var n;
+ var k;
+ for (k = 0; k < ys.length; k++) {
+ n = get1(x, ys[k]);
+ if (n) o[ys[k]] = nodeVal(n);
+ }
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
- var button = context.container().select('.preset-building-house .preset-list-button');
- reveal(button.node(), helpHtml('intro.buildings.choose_preset_house', {
- preset: housePreset.name()
- }), {
- duration: 300
- });
- button.on('click.intro', function () {
- button.on('click.intro', null);
- continueTo(closeEditorHouse);
- });
- }, 400); // after preset list pane visible..
+ return o;
+ }
- context.on('enter.intro', function (mode) {
- if (!_houseID || !context.hasEntity(_houseID)) {
- return continueTo(addHouse);
- }
+ function getProperties$1(node) {
+ var prop = getMulti(node, ["name", "cmt", "desc", "type", "time", "keywords"]); // Parse additional data from our Garmin extension(s)
- var ids = context.selectedIDs();
+ var extensions = node.getElementsByTagNameNS("http://www.garmin.com/xmlschemas/GpxExtensions/v3", "*");
- if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
- return continueTo(chooseCategoryBuilding);
- }
- });
+ for (var i = 0; i < extensions.length; i++) {
+ var extension = extensions[i]; // Ignore nested extensions, like those on routepoints or trackpoints
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-list-button').on('click.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ if (extension.parentNode.parentNode === node) {
+ prop[extension.tagName.replace(":", "_")] = nodeVal(extension);
}
}
- function closeEditorHouse() {
- if (!_houseID || !context.hasEntity(_houseID)) {
- return addHouse();
- }
+ var links = node.getElementsByTagName("link");
+ if (links.length) prop.links = [];
- var ids = context.selectedIDs();
+ for (var _i = 0; _i < links.length; _i++) {
+ prop.links.push(Object.assign({
+ href: links[_i].getAttribute("href")
+ }, getMulti(links[_i], ["text", "type"])));
+ }
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
- context.enter(modeSelect(context, [_houseID]));
- }
+ return prop;
+ }
- context.history().checkpoint('hasHouse');
- context.on('exit.intro', function () {
- continueTo(rightClickHouse);
- });
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.buildings.close', {
- button: icon('#iD-icon-close', 'inline')
- }));
- }, 500);
+ function coordPair$1(x) {
+ var ll = [parseFloat(x.getAttribute("lon")), parseFloat(x.getAttribute("lat"))];
+ var ele = get1(x, "ele"); // handle namespaced attribute in browser
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ var heart = get1(x, "gpxtpx:hr") || get1(x, "hr");
+ var time = get1(x, "time");
+ var e;
- function rightClickHouse() {
- if (!_houseID) return chapter.restart();
- context.enter(modeBrowse(context));
- context.history().reset('hasHouse');
- var zoom = context.map().zoom();
+ if (ele) {
+ e = parseFloat(nodeVal(ele));
- if (zoom < 20) {
- zoom = 20;
+ if (!isNaN(e)) {
+ ll.push(e);
}
+ }
- context.map().centerZoomEase(house, zoom, 500);
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return;
- var ids = context.selectedIDs();
- if (ids.length !== 1 || ids[0] !== _houseID) return;
- timeout(function () {
- var node = selectMenuItem(context, 'orthogonalize').node();
- if (!node) return;
- continueTo(clickSquare);
- }, 50); // after menu visible
- });
- context.map().on('move.intro drawn.intro', function () {
- var rightclickString = helpHtml('intro.buildings.' + (context.lastPointerType() === 'mouse' ? 'rightclick_building' : 'edit_menu_building_touch'));
- revealHouse(house, rightclickString, {
- duration: 0
- });
- });
- context.history().on('change.intro', function () {
- continueTo(rightClickHouse);
- });
+ var result = {
+ coordinates: ll,
+ time: time ? nodeVal(time) : null,
+ extendedValues: []
+ };
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
+ if (heart) {
+ result.extendedValues.push(["heart", parseFloat(nodeVal(heart))]);
}
- function clickSquare() {
- if (!_houseID) return chapter.restart();
- var entity = context.hasEntity(_houseID);
- if (!entity) return continueTo(rightClickHouse);
- var node = selectMenuItem(context, 'orthogonalize').node();
+ var extensions = get1(x, "extensions");
- if (!node) {
- return continueTo(rightClickHouse);
- }
+ if (extensions !== null) {
+ for (var _i2 = 0, _arr = ["speed", "course", "hAcc", "vAcc"]; _i2 < _arr.length; _i2++) {
+ var name = _arr[_i2];
+ var v = parseFloat(nodeVal(get1(extensions, name)));
- var wasChanged = false;
- reveal('.edit-menu', helpHtml('intro.buildings.square_building'), {
- padding: 50
- });
- context.on('enter.intro', function (mode) {
- if (mode.id === 'browse') {
- continueTo(rightClickHouse);
- } else if (mode.id === 'move' || mode.id === 'rotate') {
- continueTo(retryClickSquare);
+ if (!isNaN(v)) {
+ result.extendedValues.push([name, v]);
}
- });
- context.map().on('move.intro', function () {
- var node = selectMenuItem(context, 'orthogonalize').node();
+ }
+ }
- if (!wasChanged && !node) {
- return continueTo(rightClickHouse);
- }
+ return result;
+ }
- reveal('.edit-menu', helpHtml('intro.buildings.square_building'), {
- duration: 0,
- padding: 50
- });
- });
- context.history().on('change.intro', function () {
- wasChanged = true;
- context.history().on('change.intro', null); // Something changed. Wait for transition to complete and check undo annotation.
+ function getRoute(node) {
+ var line = getPoints$1(node, "rtept");
+ if (!line) return;
+ return {
+ type: "Feature",
+ properties: Object.assign(getProperties$1(node), getLineStyle(get1(node, "extensions")), {
+ _gpxType: "rte"
+ }),
+ geometry: {
+ type: "LineString",
+ coordinates: line.line
+ }
+ };
+ }
- timeout(function () {
- if (context.history().undoAnnotation() === _t('operations.orthogonalize.annotation.feature', {
- n: 1
- })) {
- continueTo(doneSquare);
- } else {
- continueTo(retryClickSquare);
- }
- }, 500); // after transitioned actions
- });
+ function getPoints$1(node, pointname) {
+ var pts = node.getElementsByTagName(pointname);
+ if (pts.length < 2) return; // Invalid line in GeoJSON
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ var line = [];
+ var times = [];
+ var extendedValues = {};
- function retryClickSquare() {
- context.enter(modeBrowse(context));
- revealHouse(house, helpHtml('intro.buildings.retry_square'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(rightClickHouse);
- }
- });
+ for (var i = 0; i < pts.length; i++) {
+ var c = coordPair$1(pts[i]);
+ line.push(c.coordinates);
+ if (c.time) times.push(c.time);
- function continueTo(nextStep) {
- nextStep();
- }
- }
+ for (var j = 0; j < c.extendedValues.length; j++) {
+ var _c$extendedValues$j = _slicedToArray(c.extendedValues[j], 2),
+ name = _c$extendedValues$j[0],
+ val = _c$extendedValues$j[1];
- function doneSquare() {
- context.history().checkpoint('doneSquare');
- revealHouse(house, helpHtml('intro.buildings.done_square'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(addTank);
+ var plural = name === "heart" ? name : name + "s";
+
+ if (!extendedValues[plural]) {
+ extendedValues[plural] = Array(pts.length).fill(null);
}
- });
- function continueTo(nextStep) {
- nextStep();
+ extendedValues[plural][i] = val;
}
}
- function addTank() {
- context.enter(modeBrowse(context));
- context.history().reset('doneSquare');
- _tankID = null;
- var msec = transitionTime(tank, context.map().center());
+ return {
+ line: line,
+ times: times,
+ extendedValues: extendedValues
+ };
+ }
- if (msec) {
- reveal(null, null, {
- duration: 0
- });
- }
+ function getTrack(node) {
+ var segments = node.getElementsByTagName("trkseg");
+ var track = [];
+ var times = [];
+ var extractedLines = [];
- context.map().centerZoomEase(tank, 19.5, msec);
- timeout(function () {
- reveal('button.add-area', helpHtml('intro.buildings.add_tank'));
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'add-area') return;
- continueTo(startTank);
- });
- }, msec + 100);
+ for (var i = 0; i < segments.length; i++) {
+ var line = getPoints$1(segments[i], "trkpt");
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- nextStep();
+ if (line) {
+ extractedLines.push(line);
+ if (line.times && line.times.length) times.push(line.times);
}
}
- function startTank() {
- if (context.mode().id !== 'add-area') {
- return continueTo(addTank);
+ if (extractedLines.length === 0) return;
+ var multi = extractedLines.length > 1;
+ var properties = Object.assign(getProperties$1(node), getLineStyle(get1(node, "extensions")), {
+ _gpxType: "trk"
+ }, times.length ? {
+ coordinateProperties: {
+ times: multi ? times : times[0]
}
+ } : {});
- _tankID = null;
- timeout(function () {
- var startString = helpHtml('intro.buildings.start_tank') + helpHtml('intro.buildings.tank_edge_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap'));
- revealTank(tank, startString);
- context.map().on('move.intro drawn.intro', function () {
- revealTank(tank, startString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'draw-area') return chapter.restart();
- continueTo(continueTank);
- });
- }, 550); // after easing
+ for (var _i3 = 0; _i3 < extractedLines.length; _i3++) {
+ var _line = extractedLines[_i3];
+ track.push(_line.line);
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
- }
- }
+ for (var _i4 = 0, _Object$entries = Object.entries(_line.extendedValues); _i4 < _Object$entries.length; _i4++) {
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i4], 2),
+ name = _Object$entries$_i[0],
+ val = _Object$entries$_i[1];
- function continueTank() {
- if (context.mode().id !== 'draw-area') {
- return continueTo(addTank);
- }
+ var props = properties;
- _tankID = null;
- var continueString = helpHtml('intro.buildings.continue_tank') + '{br}' + helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.buildings.finish_tank');
- revealTank(tank, continueString);
- context.map().on('move.intro drawn.intro', function () {
- revealTank(tank, continueString, {
- duration: 0
- });
- });
- context.on('enter.intro', function (mode) {
- if (mode.id === 'draw-area') {
- return;
- } else if (mode.id === 'select') {
- _tankID = context.selectedIDs()[0];
- return continueTo(searchPresetTank);
- } else {
- return continueTo(addTank);
+ if (name === "heart") {
+ if (!properties.coordinateProperties) {
+ properties.coordinateProperties = {};
+ }
+
+ props = properties.coordinateProperties;
}
- });
- function continueTo(nextStep) {
- context.map().on('move.intro drawn.intro', null);
- context.on('enter.intro', null);
- nextStep();
+ if (multi) {
+ if (!props[name]) props[name] = extractedLines.map(function (line) {
+ return new Array(line.line.length).fill(null);
+ });
+ props[name][_i3] = val;
+ } else {
+ props[name] = val;
+ }
}
}
- function searchPresetTank() {
- if (!_tankID || !context.hasEntity(_tankID)) {
- return addTank();
+ return {
+ type: "Feature",
+ properties: properties,
+ geometry: multi ? {
+ type: "MultiLineString",
+ coordinates: track
+ } : {
+ type: "LineString",
+ coordinates: track[0]
}
+ };
+ }
- var ids = context.selectedIDs();
+ function getPoint(node) {
+ return {
+ type: "Feature",
+ properties: Object.assign(getProperties$1(node), getMulti(node, ["sym"])),
+ geometry: {
+ type: "Point",
+ coordinates: coordPair$1(node).coordinates
+ }
+ };
+ }
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
- context.enter(modeSelect(context, [_tankID]));
- } // disallow scrolling
+ function gpxGen(doc) {
+ var tracks, routes, waypoints, i, feature, _i5, _feature, _i6;
+ return regeneratorRuntime.wrap(function gpxGen$(_context) {
+ while (1) {
+ switch (_context.prev = _context.next) {
+ case 0:
+ tracks = doc.getElementsByTagName("trk");
+ routes = doc.getElementsByTagName("rte");
+ waypoints = doc.getElementsByTagName("wpt");
+ i = 0;
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- timeout(function () {
- // reset pane, in case user somehow happened to change it..
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.buildings.search_tank', {
- preset: tankPreset.name()
- }));
- }, 400); // after preset list pane visible..
+ case 4:
+ if (!(i < tracks.length)) {
+ _context.next = 12;
+ break;
+ }
- context.on('enter.intro', function (mode) {
- if (!_tankID || !context.hasEntity(_tankID)) {
- return continueTo(addTank);
- }
+ feature = getTrack(tracks[i]);
- var ids = context.selectedIDs();
+ if (!feature) {
+ _context.next = 9;
+ break;
+ }
- if (mode.id !== 'select' || !ids.length || ids[0] !== _tankID) {
- // keep the user's area selected..
- context.enter(modeSelect(context, [_tankID])); // reset pane, in case user somehow happened to change it..
+ _context.next = 9;
+ return feature;
- context.container().select('.inspector-wrap .panewrap').style('right', '-100%'); // disallow scrolling
+ case 9:
+ i++;
+ _context.next = 4;
+ break;
- context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
- context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
- reveal('.preset-search-input', helpHtml('intro.buildings.search_tank', {
- preset: tankPreset.name()
- }));
- context.history().on('change.intro', null);
- }
- });
+ case 12:
+ _i5 = 0;
- function checkPresetSearch() {
- var first = context.container().select('.preset-list-item:first-child');
+ case 13:
+ if (!(_i5 < routes.length)) {
+ _context.next = 21;
+ break;
+ }
- if (first.classed('preset-man_made-storage_tank')) {
- reveal(first.select('.preset-list-button').node(), helpHtml('intro.buildings.choose_tank', {
- preset: tankPreset.name()
- }), {
- duration: 300
- });
- context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
- context.history().on('change.intro', function () {
- continueTo(closeEditorTank);
- });
- }
- }
+ _feature = getRoute(routes[_i5]);
- function continueTo(nextStep) {
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.on('enter.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- nextStep();
- }
- }
+ if (!_feature) {
+ _context.next = 18;
+ break;
+ }
- function closeEditorTank() {
- if (!_tankID || !context.hasEntity(_tankID)) {
- return addTank();
- }
+ _context.next = 18;
+ return _feature;
- var ids = context.selectedIDs();
+ case 18:
+ _i5++;
+ _context.next = 13;
+ break;
- if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
- context.enter(modeSelect(context, [_tankID]));
- }
+ case 21:
+ _i6 = 0;
- context.history().checkpoint('hasTank');
- context.on('exit.intro', function () {
- continueTo(rightClickTank);
- });
- timeout(function () {
- reveal('.entity-editor-pane', helpHtml('intro.buildings.close', {
- button: icon('#iD-icon-close', 'inline')
- }));
- }, 500);
+ case 22:
+ if (!(_i6 < waypoints.length)) {
+ _context.next = 28;
+ break;
+ }
- function continueTo(nextStep) {
- context.on('exit.intro', null);
- nextStep();
- }
- }
+ _context.next = 25;
+ return getPoint(waypoints[_i6]);
- function rightClickTank() {
- if (!_tankID) return continueTo(addTank);
- context.enter(modeBrowse(context));
- context.history().reset('hasTank');
- context.map().centerEase(tank, 500);
- timeout(function () {
- context.on('enter.intro', function (mode) {
- if (mode.id !== 'select') return;
- var ids = context.selectedIDs();
- if (ids.length !== 1 || ids[0] !== _tankID) return;
- timeout(function () {
- var node = selectMenuItem(context, 'circularize').node();
- if (!node) return;
- continueTo(clickCircle);
- }, 50); // after menu visible
- });
- var rightclickString = helpHtml('intro.buildings.' + (context.lastPointerType() === 'mouse' ? 'rightclick_tank' : 'edit_menu_tank_touch'));
- revealTank(tank, rightclickString);
- context.map().on('move.intro drawn.intro', function () {
- revealTank(tank, rightclickString, {
- duration: 0
- });
- });
- context.history().on('change.intro', function () {
- continueTo(rightClickTank);
- });
- }, 600);
+ case 25:
+ _i6++;
+ _context.next = 22;
+ break;
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- nextStep();
+ case 28:
+ case "end":
+ return _context.stop();
+ }
}
+ }, _marked);
+ }
+
+ function gpx(doc) {
+ return {
+ type: "FeatureCollection",
+ features: Array.from(gpxGen(doc))
+ };
+ }
+
+ var removeSpace = /\s*/g;
+ var trimSpace = /^\s*|\s*$/g;
+ var splitSpace = /\s+/; // generate a short, numeric hash of a string
+
+ function okhash(x) {
+ if (!x || !x.length) return 0;
+ var h = 0;
+
+ for (var i = 0; i < x.length; i++) {
+ h = (h << 5) - h + x.charCodeAt(i) | 0;
}
- function clickCircle() {
- if (!_tankID) return chapter.restart();
- var entity = context.hasEntity(_tankID);
- if (!entity) return continueTo(rightClickTank);
- var node = selectMenuItem(context, 'circularize').node();
+ return h;
+ } // get one coordinate from a coordinate array, if any
- if (!node) {
- return continueTo(rightClickTank);
- }
- var wasChanged = false;
- reveal('.edit-menu', helpHtml('intro.buildings.circle_tank'), {
- padding: 50
- });
- context.on('enter.intro', function (mode) {
- if (mode.id === 'browse') {
- continueTo(rightClickTank);
- } else if (mode.id === 'move' || mode.id === 'rotate') {
- continueTo(retryClickCircle);
- }
- });
- context.map().on('move.intro', function () {
- var node = selectMenuItem(context, 'circularize').node();
+ function coord1(v) {
+ return v.replace(removeSpace, "").split(",").map(parseFloat);
+ } // get all coordinates from a coordinate array as [[],[]]
- if (!wasChanged && !node) {
- return continueTo(rightClickTank);
- }
- reveal('.edit-menu', helpHtml('intro.buildings.circle_tank'), {
- duration: 0,
- padding: 50
- });
- });
- context.history().on('change.intro', function () {
- wasChanged = true;
- context.history().on('change.intro', null); // Something changed. Wait for transition to complete and check undo annotation.
+ function coord(v) {
+ return v.replace(trimSpace, "").split(splitSpace).map(coord1);
+ }
- timeout(function () {
- if (context.history().undoAnnotation() === _t('operations.circularize.annotation.feature', {
- n: 1
- })) {
- continueTo(play);
- } else {
- continueTo(retryClickCircle);
- }
- }, 500); // after transitioned actions
- });
+ function xml2str(node) {
+ if (node.xml !== undefined) return node.xml;
- function continueTo(nextStep) {
- context.on('enter.intro', null);
- context.map().on('move.intro', null);
- context.history().on('change.intro', null);
- nextStep();
- }
- }
+ if (node.tagName) {
+ var output = node.tagName;
- function retryClickCircle() {
- context.enter(modeBrowse(context));
- revealTank(tank, helpHtml('intro.buildings.retry_circle'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- continueTo(rightClickTank);
- }
- });
+ for (var i = 0; i < node.attributes.length; i++) {
+ output += node.attributes[i].name + node.attributes[i].value;
+ }
- function continueTo(nextStep) {
- nextStep();
+ for (var _i9 = 0; _i9 < node.childNodes.length; _i9++) {
+ output += xml2str(node.childNodes[_i9]);
}
- }
- function play() {
- dispatch.call('done');
- reveal('.ideditor', helpHtml('intro.buildings.play', {
- next: _t('intro.startediting.title')
- }), {
- tooltipBox: '.intro-nav-wrap .chapter-startEditing',
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- reveal('.ideditor');
- }
- });
+ return output;
}
- chapter.enter = function () {
- addHouse();
- };
-
- chapter.exit = function () {
- timeouts.forEach(window.clearTimeout);
- context.on('enter.intro exit.intro', null);
- context.map().on('move.intro drawn.intro', null);
- context.history().on('change.intro', null);
- context.container().select('.inspector-wrap').on('wheel.intro', null);
- context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
- context.container().select('.more-fields .combobox-input').on('click.intro', null);
- };
+ if (node.nodeName === "#text") {
+ return (node.nodeValue || node.value || "").trim();
+ }
- chapter.restart = function () {
- chapter.exit();
- chapter.enter();
- };
+ if (node.nodeName === "#cdata-section") {
+ return node.nodeValue;
+ }
- return utilRebind(chapter, dispatch, 'on');
+ return "";
}
- function uiIntroStartEditing(context, reveal) {
- var dispatch = dispatch$8('done', 'startEditing');
- var modalSelection = select(null);
- var chapter = {
- title: 'intro.startediting.title'
- };
+ var geotypes = ["Polygon", "LineString", "Point", "Track", "gx:Track"];
- function showHelp() {
- reveal('.map-control.help-control', helpHtml('intro.startediting.help'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- shortcuts();
- }
- });
+ function kmlColor(properties, elem, prefix) {
+ var v = nodeVal(get1(elem, "color")) || "";
+ var colorProp = prefix == "stroke" || prefix === "fill" ? prefix : prefix + "-color";
+
+ if (v.substr(0, 1) === "#") {
+ v = v.substr(1);
}
- function shortcuts() {
- reveal('.map-control.help-control', helpHtml('intro.startediting.shortcuts'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- showSave();
- }
- });
+ if (v.length === 6 || v.length === 3) {
+ properties[colorProp] = v;
+ } else if (v.length === 8) {
+ properties[prefix + "-opacity"] = parseInt(v.substr(0, 2), 16) / 255;
+ properties[colorProp] = "#" + v.substr(6, 2) + v.substr(4, 2) + v.substr(2, 2);
}
+ }
- function showSave() {
- context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+ function numericProperty(properties, elem, source, target) {
+ var val = parseFloat(nodeVal(get1(elem, source)));
+ if (!isNaN(val)) properties[target] = val;
+ }
- reveal('.top-toolbar button.save', helpHtml('intro.startediting.save'), {
- buttonText: _t.html('intro.ok'),
- buttonCallback: function buttonCallback() {
- showStart();
- }
- });
+ function gxCoords(root) {
+ var elems = root.getElementsByTagName("coord");
+ var coords = [];
+ var times = [];
+ if (elems.length === 0) elems = root.getElementsByTagName("gx:coord");
+
+ for (var i = 0; i < elems.length; i++) {
+ coords.push(nodeVal(elems[i]).split(" ").map(parseFloat));
}
- function showStart() {
- context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+ var timeElems = root.getElementsByTagName("when");
- modalSelection = uiModal(context.container());
- modalSelection.select('.modal').attr('class', 'modal-splash modal');
- modalSelection.selectAll('.close').remove();
- var startbutton = modalSelection.select('.content').attr('class', 'fillL').append('button').attr('class', 'modal-section huge-modal-button').on('click', function () {
- modalSelection.remove();
- });
- startbutton.append('svg').attr('class', 'illustration').append('use').attr('xlink:href', '#iD-logo-walkthrough');
- startbutton.append('h2').html(_t.html('intro.startediting.start'));
- dispatch.call('startEditing');
+ for (var j = 0; j < timeElems.length; j++) {
+ times.push(nodeVal(timeElems[j]));
}
- chapter.enter = function () {
- showHelp();
- };
-
- chapter.exit = function () {
- modalSelection.remove();
- context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+ return {
+ coords: coords,
+ times: times
};
-
- return utilRebind(chapter, dispatch, 'on');
}
- var chapterUi = {
- welcome: uiIntroWelcome,
- navigation: uiIntroNavigation,
- point: uiIntroPoint,
- area: uiIntroArea,
- line: uiIntroLine,
- building: uiIntroBuilding,
- startEditing: uiIntroStartEditing
- };
- var chapterFlow = ['welcome', 'navigation', 'point', 'area', 'line', 'building', 'startEditing'];
- function uiIntro(context) {
- var INTRO_IMAGERY = 'EsriWorldImageryClarity';
- var _introGraph = {};
+ function getGeometry(root) {
+ var geomNode;
+ var geomNodes;
+ var i;
+ var j;
+ var k;
+ var geoms = [];
+ var coordTimes = [];
- var _currChapter;
+ if (get1(root, "MultiGeometry")) {
+ return getGeometry(get1(root, "MultiGeometry"));
+ }
- function intro(selection) {
- _mainFileFetcher.get('intro_graph').then(function (dataIntroGraph) {
- // create entities for intro graph and localize names
- for (var id in dataIntroGraph) {
- if (!_introGraph[id]) {
- _introGraph[id] = osmEntity(localize(dataIntroGraph[id]));
- }
- }
+ if (get1(root, "MultiTrack")) {
+ return getGeometry(get1(root, "MultiTrack"));
+ }
- selection.call(startIntro);
- })["catch"](function () {
- /* ignore */
- });
+ if (get1(root, "gx:MultiTrack")) {
+ return getGeometry(get1(root, "gx:MultiTrack"));
}
- function startIntro(selection) {
- context.enter(modeBrowse(context)); // Save current map state
+ for (i = 0; i < geotypes.length; i++) {
+ geomNodes = root.getElementsByTagName(geotypes[i]);
- var osm = context.connection();
- var history = context.history().toJSON();
- var hash = window.location.hash;
- var center = context.map().center();
- var zoom = context.map().zoom();
- var background = context.background().baseLayerSource();
- var overlays = context.background().overlayLayerSources();
- var opacity = context.container().selectAll('.main-map .layer-background').style('opacity');
- var caches = osm && osm.caches();
- var baseEntities = context.history().graph().base().entities; // Show sidebar and disable the sidebar resizing button
- // (this needs to be before `context.inIntro(true)`)
+ if (geomNodes) {
+ for (j = 0; j < geomNodes.length; j++) {
+ geomNode = geomNodes[j];
- context.ui().sidebar.expand();
- context.container().selectAll('button.sidebar-toggle').classed('disabled', true); // Block saving
+ if (geotypes[i] === "Point") {
+ geoms.push({
+ type: "Point",
+ coordinates: coord1(nodeVal(get1(geomNode, "coordinates")))
+ });
+ } else if (geotypes[i] === "LineString") {
+ geoms.push({
+ type: "LineString",
+ coordinates: coord(nodeVal(get1(geomNode, "coordinates")))
+ });
+ } else if (geotypes[i] === "Polygon") {
+ var rings = geomNode.getElementsByTagName("LinearRing"),
+ coords = [];
- context.inIntro(true); // Load semi-real data used in intro
+ for (k = 0; k < rings.length; k++) {
+ coords.push(coord(nodeVal(get1(rings[k], "coordinates"))));
+ }
- if (osm) {
- osm.toggle(false).reset();
+ geoms.push({
+ type: "Polygon",
+ coordinates: coords
+ });
+ } else if (geotypes[i] === "Track" || geotypes[i] === "gx:Track") {
+ var track = gxCoords(geomNode);
+ geoms.push({
+ type: "LineString",
+ coordinates: track.coords
+ });
+ if (track.times.length) coordTimes.push(track.times);
+ }
+ }
}
+ }
- context.history().reset();
- context.history().merge(Object.values(coreGraph().load(_introGraph).entities));
- context.history().checkpoint('initial'); // Setup imagery
+ return {
+ geoms: geoms,
+ coordTimes: coordTimes
+ };
+ }
- var imagery = context.background().findSource(INTRO_IMAGERY);
+ function getPlacemark(root, styleIndex, styleMapIndex, styleByHash) {
+ var geomsAndTimes = getGeometry(root);
+ var i;
+ var properties = {};
+ var name = nodeVal(get1(root, "name"));
+ var address = nodeVal(get1(root, "address"));
+ var styleUrl = nodeVal(get1(root, "styleUrl"));
+ var description = nodeVal(get1(root, "description"));
+ var timeSpan = get1(root, "TimeSpan");
+ var timeStamp = get1(root, "TimeStamp");
+ var extendedData = get1(root, "ExtendedData");
+ var iconStyle = get1(root, "IconStyle");
+ var labelStyle = get1(root, "LabelStyle");
+ var lineStyle = get1(root, "LineStyle");
+ var polyStyle = get1(root, "PolyStyle");
+ var visibility = get1(root, "visibility");
+ if (name) properties.name = name;
+ if (address) properties.address = address;
- if (imagery) {
- context.background().baseLayerSource(imagery);
- } else {
- context.background().bing();
+ if (styleUrl) {
+ if (styleUrl[0] !== "#") {
+ styleUrl = "#" + styleUrl;
}
- overlays.forEach(function (d) {
- return context.background().toggleOverlayLayer(d);
- }); // Setup data layers (only OSM)
+ properties.styleUrl = styleUrl;
- var layers = context.layers();
- layers.all().forEach(function (item) {
- // if the layer has the function `enabled`
- if (typeof item.layer.enabled === 'function') {
- item.layer.enabled(item.id === 'osm');
- }
- });
- context.container().selectAll('.main-map .layer-background').style('opacity', 1);
- var curtain = uiCurtain(context.container().node());
- selection.call(curtain); // Store that the user started the walkthrough..
+ if (styleIndex[styleUrl]) {
+ properties.styleHash = styleIndex[styleUrl];
+ }
- corePreferences('walkthrough_started', 'yes'); // Restore previous walkthrough progress..
+ if (styleMapIndex[styleUrl]) {
+ properties.styleMapHash = styleMapIndex[styleUrl];
+ properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal];
+ } // Try to populate the lineStyle or polyStyle since we got the style hash
- var storedProgress = corePreferences('walkthrough_progress') || '';
- var progress = storedProgress.split(';').filter(Boolean);
- var chapters = chapterFlow.map(function (chapter, i) {
- var s = chapterUi[chapter](context, curtain.reveal).on('done', function () {
- buttons.filter(function (d) {
- return d.title === s.title;
- }).classed('finished', true);
- if (i < chapterFlow.length - 1) {
- var next = chapterFlow[i + 1];
- context.container().select("button.chapter-".concat(next)).classed('next', true);
- } // Store walkthrough progress..
+ var style = styleByHash[properties.styleHash];
+ if (style) {
+ if (!iconStyle) iconStyle = get1(style, "IconStyle");
+ if (!labelStyle) labelStyle = get1(style, "LabelStyle");
+ if (!lineStyle) lineStyle = get1(style, "LineStyle");
+ if (!polyStyle) polyStyle = get1(style, "PolyStyle");
+ }
+ }
- progress.push(chapter);
- corePreferences('walkthrough_progress', utilArrayUniq(progress).join(';'));
- });
- return s;
- });
- chapters[chapters.length - 1].on('startEditing', function () {
- // Store walkthrough progress..
- progress.push('startEditing');
- corePreferences('walkthrough_progress', utilArrayUniq(progress).join(';')); // Store if walkthrough is completed..
+ if (description) properties.description = description;
- var incomplete = utilArrayDifference(chapterFlow, progress);
+ if (timeSpan) {
+ var begin = nodeVal(get1(timeSpan, "begin"));
+ var end = nodeVal(get1(timeSpan, "end"));
+ properties.timespan = {
+ begin: begin,
+ end: end
+ };
+ }
- if (!incomplete.length) {
- corePreferences('walkthrough_completed', 'yes');
- }
+ if (timeStamp) {
+ properties.timestamp = nodeVal(get1(timeStamp, "when"));
+ }
- curtain.remove();
- navwrap.remove();
- context.container().selectAll('.main-map .layer-background').style('opacity', opacity);
- context.container().selectAll('button.sidebar-toggle').classed('disabled', false);
+ if (iconStyle) {
+ kmlColor(properties, iconStyle, "icon");
+ numericProperty(properties, iconStyle, "scale", "icon-scale");
+ numericProperty(properties, iconStyle, "heading", "icon-heading");
+ var hotspot = get1(iconStyle, "hotSpot");
- if (osm) {
- osm.toggle(true).reset().caches(caches);
- }
+ if (hotspot) {
+ var left = parseFloat(hotspot.getAttribute("x"));
+ var top = parseFloat(hotspot.getAttribute("y"));
+ if (!isNaN(left) && !isNaN(top)) properties["icon-offset"] = [left, top];
+ }
- context.history().reset().merge(Object.values(baseEntities));
- context.background().baseLayerSource(background);
- overlays.forEach(function (d) {
- return context.background().toggleOverlayLayer(d);
- });
+ var icon = get1(iconStyle, "Icon");
- if (history) {
- context.history().fromJSON(history, false);
- }
+ if (icon) {
+ var href = nodeVal(get1(icon, "href"));
+ if (href) properties.icon = href;
+ }
+ }
- context.map().centerZoom(center, zoom);
- window.location.replace(hash);
- context.inIntro(false);
- });
- var navwrap = selection.append('div').attr('class', 'intro-nav-wrap fillD');
- navwrap.append('svg').attr('class', 'intro-nav-wrap-logo').append('use').attr('xlink:href', '#iD-logo-walkthrough');
- var buttonwrap = navwrap.append('div').attr('class', 'joined').selectAll('button.chapter');
- var buttons = buttonwrap.data(chapters).enter().append('button').attr('class', function (d, i) {
- return "chapter chapter-".concat(chapterFlow[i]);
- }).on('click', enterChapter);
- buttons.append('span').html(function (d) {
- return _t.html(d.title);
- });
- buttons.append('span').attr('class', 'status').call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward', 'inline'));
- enterChapter(null, chapters[0]);
+ if (labelStyle) {
+ kmlColor(properties, labelStyle, "label");
+ numericProperty(properties, labelStyle, "scale", "label-scale");
+ }
- function enterChapter(d3_event, newChapter) {
- if (_currChapter) {
- _currChapter.exit();
- }
+ if (lineStyle) {
+ kmlColor(properties, lineStyle, "stroke");
+ numericProperty(properties, lineStyle, "width", "stroke-width");
+ }
- context.enter(modeBrowse(context));
- _currChapter = newChapter;
+ if (polyStyle) {
+ kmlColor(properties, polyStyle, "fill");
+ var fill = nodeVal(get1(polyStyle, "fill"));
+ var outline = nodeVal(get1(polyStyle, "outline"));
+ if (fill) properties["fill-opacity"] = fill === "1" ? properties["fill-opacity"] || 1 : 0;
+ if (outline) properties["stroke-opacity"] = outline === "1" ? properties["stroke-opacity"] || 1 : 0;
+ }
- _currChapter.enter();
+ if (extendedData) {
+ var datas = extendedData.getElementsByTagName("Data"),
+ simpleDatas = extendedData.getElementsByTagName("SimpleData");
- buttons.classed('next', false).classed('active', function (d) {
- return d.title === _currChapter.title;
- });
+ for (i = 0; i < datas.length; i++) {
+ properties[datas[i].getAttribute("name")] = nodeVal(get1(datas[i], "value"));
+ }
+
+ for (i = 0; i < simpleDatas.length; i++) {
+ properties[simpleDatas[i].getAttribute("name")] = nodeVal(simpleDatas[i]);
}
}
- return intro;
- }
+ if (visibility) {
+ properties.visibility = nodeVal(visibility);
+ }
- function uiIssuesInfo(context) {
- var warningsItem = {
- id: 'warnings',
- count: 0,
- iconID: 'iD-icon-alert',
- descriptionID: 'issues.warnings_and_errors'
- };
- var resolvedItem = {
- id: 'resolved',
- count: 0,
- iconID: 'iD-icon-apply',
- descriptionID: 'issues.user_resolved_issues'
+ if (geomsAndTimes.coordTimes.length) {
+ properties.coordinateProperties = {
+ times: geomsAndTimes.coordTimes.length === 1 ? geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes
+ };
+ }
+
+ var feature = {
+ type: "Feature",
+ geometry: geomsAndTimes.geoms.length === 0 ? null : geomsAndTimes.geoms.length === 1 ? geomsAndTimes.geoms[0] : {
+ type: "GeometryCollection",
+ geometries: geomsAndTimes.geoms
+ },
+ properties: properties
};
+ if (root.getAttribute("id")) feature.id = root.getAttribute("id");
+ return feature;
+ }
- function update(selection) {
- var shownItems = [];
- var liveIssues = context.validator().getIssues({
- what: corePreferences('validate-what') || 'edited',
- where: corePreferences('validate-where') || 'all'
- });
+ function kmlGen(doc) {
+ var styleIndex, styleByHash, styleMapIndex, placemarks, styles, styleMaps, k, hash, l, pairs, pairsMap, m, j, feature;
+ return regeneratorRuntime.wrap(function kmlGen$(_context3) {
+ while (1) {
+ switch (_context3.prev = _context3.next) {
+ case 0:
+ // styleindex keeps track of hashed styles in order to match feature
+ styleIndex = {};
+ styleByHash = {}; // stylemapindex keeps track of style maps to expose in properties
- if (liveIssues.length) {
- warningsItem.count = liveIssues.length;
- shownItems.push(warningsItem);
- }
+ styleMapIndex = {}; // atomic geospatial types supported by KML - MultiGeometry is
+ // handled separately
+ // all root placemarks in the file
- if (corePreferences('validate-what') === 'all') {
- var resolvedIssues = context.validator().getResolvedIssues();
+ placemarks = doc.getElementsByTagName("Placemark");
+ styles = doc.getElementsByTagName("Style");
+ styleMaps = doc.getElementsByTagName("StyleMap");
- if (resolvedIssues.length) {
- resolvedItem.count = resolvedIssues.length;
- shownItems.push(resolvedItem);
- }
- }
+ for (k = 0; k < styles.length; k++) {
+ hash = okhash(xml2str(styles[k])).toString(16);
+ styleIndex["#" + styles[k].getAttribute("id")] = hash;
+ styleByHash[hash] = styles[k];
+ }
- var chips = selection.selectAll('.chip').data(shownItems, function (d) {
- return d.id;
- });
- chips.exit().remove();
- var enter = chips.enter().append('a').attr('class', function (d) {
- return 'chip ' + d.id + '-count';
- }).attr('href', '#').each(function (d) {
- var chipSelection = select(this);
- var tooltipBehavior = uiTooltip().placement('top').title(_t.html(d.descriptionID));
- chipSelection.call(tooltipBehavior).on('click', function (d3_event) {
- d3_event.preventDefault();
- tooltipBehavior.hide(select(this)); // open the Issues pane
+ for (l = 0; l < styleMaps.length; l++) {
+ styleIndex["#" + styleMaps[l].getAttribute("id")] = okhash(xml2str(styleMaps[l])).toString(16);
+ pairs = styleMaps[l].getElementsByTagName("Pair");
+ pairsMap = {};
- context.ui().togglePanes(context.container().select('.map-panes .issues-pane'));
- });
- chipSelection.call(svgIcon('#' + d.iconID));
- });
- enter.append('span').attr('class', 'count');
- enter.merge(chips).selectAll('span.count').html(function (d) {
- return d.count.toString();
- });
- }
+ for (m = 0; m < pairs.length; m++) {
+ pairsMap[nodeVal(get1(pairs[m], "key"))] = nodeVal(get1(pairs[m], "styleUrl"));
+ }
- return function (selection) {
- update(selection);
- context.validator().on('validated.infobox', function () {
- update(selection);
- });
- };
- }
+ styleMapIndex["#" + styleMaps[l].getAttribute("id")] = pairsMap;
+ }
- function uiMapInMap(context) {
- function mapInMap(selection) {
- var backgroundLayer = rendererTileLayer(context);
- var overlayLayers = {};
- var projection = geoRawMercator();
- var dataLayer = svgData(projection, context).showLabels(false);
- var debugLayer = svgDebug(projection, context);
- var zoom = d3_zoom().scaleExtent([geoZoomToScale(0.5), geoZoomToScale(24)]).on('start', zoomStarted).on('zoom', zoomed).on('end', zoomEnded);
- var wrap = select(null);
- var tiles = select(null);
- var viewport = select(null);
- var _isTransformed = false;
- var _isHidden = true;
- var _skipEvents = false;
- var _gesture = null;
- var _zDiff = 6; // by default, minimap renders at (main zoom - 6)
+ j = 0;
- var _dMini; // dimensions of minimap
+ case 9:
+ if (!(j < placemarks.length)) {
+ _context3.next = 17;
+ break;
+ }
+ feature = getPlacemark(placemarks[j], styleIndex, styleMapIndex, styleByHash);
- var _cMini; // center pixel of minimap
+ if (!feature) {
+ _context3.next = 14;
+ break;
+ }
+ _context3.next = 14;
+ return feature;
- var _tStart; // transform at start of gesture
+ case 14:
+ j++;
+ _context3.next = 9;
+ break;
+ case 17:
+ case "end":
+ return _context3.stop();
+ }
+ }
+ }, _marked3);
+ }
- var _tCurr; // transform at most recent event
+ function kml(doc) {
+ return {
+ type: "FeatureCollection",
+ features: Array.from(kmlGen(doc))
+ };
+ }
+ var _initialized = false;
+ var _enabled = false;
- var _timeoutID;
+ var _geojson;
- function zoomStarted() {
- if (_skipEvents) return;
- _tStart = _tCurr = projection.transform();
- _gesture = null;
- }
+ function svgData(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
- function zoomed(d3_event) {
- if (_skipEvents) return;
- var x = d3_event.transform.x;
- var y = d3_event.transform.y;
- var k = d3_event.transform.k;
- var isZooming = k !== _tStart.k;
- var isPanning = x !== _tStart.x || y !== _tStart.y;
+ var _showLabels = true;
+ var detected = utilDetect();
+ var layer = select(null);
- if (!isZooming && !isPanning) {
- return; // no change
- } // lock in either zooming or panning, don't allow both in minimap.
+ var _vtService;
+ var _fileList;
- if (!_gesture) {
- _gesture = isZooming ? 'zoom' : 'pan';
- }
+ var _template;
- var tMini = projection.transform();
- var tX, tY, scale;
+ var _src;
- if (_gesture === 'zoom') {
- scale = k / tMini.k;
- tX = (_cMini[0] / scale - _cMini[0]) * scale;
- tY = (_cMini[1] / scale - _cMini[1]) * scale;
- } else {
- k = tMini.k;
- scale = 1;
- tX = x - tMini.x;
- tY = y - tMini.y;
- }
+ function init() {
+ if (_initialized) return; // run once
- utilSetTransform(tiles, tX, tY, scale);
- utilSetTransform(viewport, 0, 0, scale);
- _isTransformed = true;
- _tCurr = identity$2.translate(x, y).scale(k);
- var zMain = geoScaleToZoom(context.projection.scale());
- var zMini = geoScaleToZoom(k);
- _zDiff = zMain - zMini;
- queueRedraw();
- }
+ _geojson = {};
+ _enabled = true;
- function zoomEnded() {
- if (_skipEvents) return;
- if (_gesture !== 'pan') return;
- updateProjection();
- _gesture = null;
- context.map().center(projection.invert(_cMini)); // recenter main map..
+ function over(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ d3_event.dataTransfer.dropEffect = 'copy';
}
- function updateProjection() {
- var loc = context.map().center();
- var tMain = context.projection.transform();
- var zMain = geoScaleToZoom(tMain.k);
- var zMini = Math.max(zMain - _zDiff, 0.5);
- var kMini = geoZoomToScale(zMini);
- projection.translate([tMain.x, tMain.y]).scale(kMini);
- var point = projection(loc);
- var mouse = _gesture === 'pan' ? geoVecSubtract([_tCurr.x, _tCurr.y], [_tStart.x, _tStart.y]) : [0, 0];
- var xMini = _cMini[0] - point[0] + tMain.x + mouse[0];
- var yMini = _cMini[1] - point[1] + tMain.y + mouse[1];
- projection.translate([xMini, yMini]).clipExtent([[0, 0], _dMini]);
- _tCurr = projection.transform();
+ context.container().attr('dropzone', 'copy').on('drop.svgData', function (d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ if (!detected.filedrop) return;
+ drawData.fileList(d3_event.dataTransfer.files);
+ }).on('dragenter.svgData', over).on('dragexit.svgData', over).on('dragover.svgData', over);
+ _initialized = true;
+ }
- if (_isTransformed) {
- utilSetTransform(tiles, 0, 0);
- utilSetTransform(viewport, 0, 0);
- _isTransformed = false;
- }
+ function getService() {
+ if (services.vectorTile && !_vtService) {
+ _vtService = services.vectorTile;
- zoom.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(zMain - 3)]);
- _skipEvents = true;
- wrap.call(zoom.transform, _tCurr);
- _skipEvents = false;
+ _vtService.event.on('loadedData', throttledRedraw);
+ } else if (!services.vectorTile && _vtService) {
+ _vtService = null;
}
- function redraw() {
- clearTimeout(_timeoutID);
- if (_isHidden) return;
- updateProjection();
- var zMini = geoScaleToZoom(projection.scale()); // setup tile container
-
- tiles = wrap.selectAll('.map-in-map-tiles').data([0]);
- tiles = tiles.enter().append('div').attr('class', 'map-in-map-tiles').merge(tiles); // redraw background
-
- backgroundLayer.source(context.background().baseLayerSource()).projection(projection).dimensions(_dMini);
- var background = tiles.selectAll('.map-in-map-background').data([0]);
- background.enter().append('div').attr('class', 'map-in-map-background').merge(background).call(backgroundLayer); // redraw overlay
+ return _vtService;
+ }
- var overlaySources = context.background().overlayLayerSources();
- var activeOverlayLayers = [];
+ function showLayer() {
+ layerOn();
+ layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
+ dispatch.call('change');
+ });
+ }
- for (var i = 0; i < overlaySources.length; i++) {
- if (overlaySources[i].validZoom(zMini)) {
- if (!overlayLayers[i]) overlayLayers[i] = rendererTileLayer(context);
- activeOverlayLayers.push(overlayLayers[i].source(overlaySources[i]).projection(projection).dimensions(_dMini));
- }
- }
+ function hideLayer() {
+ throttledRedraw.cancel();
+ layer.transition().duration(250).style('opacity', 0).on('end', layerOff);
+ }
- var overlay = tiles.selectAll('.map-in-map-overlay').data([0]);
- overlay = overlay.enter().append('div').attr('class', 'map-in-map-overlay').merge(overlay);
- var overlays = overlay.selectAll('div').data(activeOverlayLayers, function (d) {
- return d.source().name();
- });
- overlays.exit().remove();
- overlays = overlays.enter().append('div').merge(overlays).each(function (layer) {
- select(this).call(layer);
- });
- var dataLayers = tiles.selectAll('.map-in-map-data').data([0]);
- dataLayers.exit().remove();
- dataLayers = dataLayers.enter().append('svg').attr('class', 'map-in-map-data').merge(dataLayers).call(dataLayer).call(debugLayer); // redraw viewport bounding box
+ function layerOn() {
+ layer.style('display', 'block');
+ }
- if (_gesture !== 'pan') {
- var getPath = d3_geoPath(projection);
- var bbox = {
- type: 'Polygon',
- coordinates: [context.map().extent().polygon()]
- };
- viewport = wrap.selectAll('.map-in-map-viewport').data([0]);
- viewport = viewport.enter().append('svg').attr('class', 'map-in-map-viewport').merge(viewport);
- var path = viewport.selectAll('.map-in-map-bbox').data([bbox]);
- path.enter().append('path').attr('class', 'map-in-map-bbox').merge(path).attr('d', getPath).classed('thick', function (d) {
- return getPath.area(d) < 30;
- });
- }
- }
+ function layerOff() {
+ layer.selectAll('.viewfield-group').remove();
+ layer.style('display', 'none');
+ } // ensure that all geojson features in a collection have IDs
- function queueRedraw() {
- clearTimeout(_timeoutID);
- _timeoutID = setTimeout(function () {
- redraw();
- }, 750);
- }
- function toggle(d3_event) {
- if (d3_event) d3_event.preventDefault();
- _isHidden = !_isHidden;
- context.container().select('.minimap-toggle-item').classed('active', !_isHidden).select('input').property('checked', !_isHidden);
+ function ensureIDs(gj) {
+ if (!gj) return null;
- if (_isHidden) {
- wrap.style('display', 'block').style('opacity', '1').transition().duration(200).style('opacity', '0').on('end', function () {
- selection.selectAll('.map-in-map').style('display', 'none');
- });
- } else {
- wrap.style('display', 'block').style('opacity', '0').transition().duration(200).style('opacity', '1').on('end', function () {
- redraw();
- });
+ if (gj.type === 'FeatureCollection') {
+ for (var i = 0; i < gj.features.length; i++) {
+ ensureFeatureID(gj.features[i]);
}
+ } else {
+ ensureFeatureID(gj);
}
- uiMapInMap.toggle = toggle;
- wrap = selection.selectAll('.map-in-map').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'map-in-map').style('display', _isHidden ? 'none' : 'block').call(zoom).on('dblclick.zoom', null).merge(wrap); // reflow warning: Hardcode dimensions - currently can't resize it anyway..
+ return gj;
+ } // ensure that each single Feature object has a unique ID
- _dMini = [200, 150]; //utilGetDimensions(wrap);
- _cMini = geoVecScale(_dMini, 0.5);
- context.map().on('drawn.map-in-map', function (drawn) {
- if (drawn.full === true) {
- redraw();
- }
- });
- redraw();
- context.keybinding().on(_t('background.minimap.key'), toggle);
- }
+ function ensureFeatureID(feature) {
+ if (!feature) return;
+ feature.__featurehash__ = utilHashcode(fastJsonStableStringify(feature));
+ return feature;
+ } // Prefer an array of Features instead of a FeatureCollection
- return mapInMap;
- }
- function uiNotice(context) {
- return function (selection) {
- var div = selection.append('div').attr('class', 'notice');
- var button = div.append('button').attr('class', 'zoom-to notice fillD').on('click', function () {
- context.map().zoomEase(context.minEditableZoom());
- }).on('wheel', function (d3_event) {
- // let wheel events pass through #4482
- var e2 = new WheelEvent(d3_event.type, d3_event);
- context.surface().node().dispatchEvent(e2);
- });
- button.call(svgIcon('#iD-icon-plus', 'pre-text')).append('span').attr('class', 'label').html(_t.html('zoom_in_edit'));
+ function getFeatures(gj) {
+ if (!gj) return [];
- function disableTooHigh() {
- var canEdit = context.map().zoom() >= context.minEditableZoom();
- div.style('display', canEdit ? 'none' : 'block');
+ if (gj.type === 'FeatureCollection') {
+ return gj.features;
+ } else {
+ return [gj];
}
+ }
- context.map().on('move.notice', debounce(disableTooHigh, 500));
- disableTooHigh();
- };
- }
+ function featureKey(d) {
+ return d.__featurehash__;
+ }
- function uiPhotoviewer(context) {
- var dispatch = dispatch$8('resize');
+ function isPolygon(d) {
+ return d.geometry.type === 'Polygon' || d.geometry.type === 'MultiPolygon';
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ function clipPathID(d) {
+ return 'ideditor-data-' + d.__featurehash__ + '-clippath';
+ }
- function photoviewer(selection) {
- selection.append('button').attr('class', 'thumb-hide').on('click', function () {
- if (services.streetside) {
- services.streetside.hideViewer(context);
- }
+ function featureClasses(d) {
+ return ['data' + d.__featurehash__, d.geometry.type, isPolygon(d) ? 'area' : '', d.__layerID__ || ''].filter(Boolean).join(' ');
+ }
- if (services.mapillary) {
- services.mapillary.hideViewer(context);
- }
+ function drawData(selection) {
+ var vtService = getService();
+ var getPath = svgPath(projection).geojson;
+ var getAreaPath = svgPath(projection, null, true).geojson;
+ var hasData = drawData.hasData();
+ layer = selection.selectAll('.layer-mapdata').data(_enabled && hasData ? [0] : []);
+ layer.exit().remove();
+ layer = layer.enter().append('g').attr('class', 'layer-mapdata').merge(layer);
+ var surface = context.surface();
+ if (!surface || surface.empty()) return; // not ready to draw yet, starting up
+ // Gather data
- if (services.openstreetcam) {
- services.openstreetcam.hideViewer(context);
- }
- }).append('div').call(svgIcon('#iD-icon-close'));
+ var geoData, polygonData;
- function preventDefault(d3_event) {
- d3_event.preventDefault();
+ if (_template && vtService) {
+ // fetch data from vector tile service
+ var sourceID = _template;
+ vtService.loadTiles(sourceID, _template, projection);
+ geoData = vtService.data(sourceID, projection);
+ } else {
+ geoData = getFeatures(_geojson);
}
- selection.append('button').attr('class', 'resize-handle-xy').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
- resizeOnX: true,
- resizeOnY: true
- }));
- selection.append('button').attr('class', 'resize-handle-x').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
- resizeOnX: true
- }));
- selection.append('button').attr('class', 'resize-handle-y').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
- resizeOnY: true
- }));
+ geoData = geoData.filter(getPath);
+ polygonData = geoData.filter(isPolygon); // Draw clip paths for polygons
- function buildResizeListener(target, eventName, dispatch, options) {
- var resizeOnX = !!options.resizeOnX;
- var resizeOnY = !!options.resizeOnY;
- var minHeight = options.minHeight || 240;
- var minWidth = options.minWidth || 320;
- var pointerId;
- var startX;
- var startY;
- var startWidth;
- var startHeight;
+ var clipPaths = surface.selectAll('defs').selectAll('.clipPath-data').data(polygonData, featureKey);
+ clipPaths.exit().remove();
+ var clipPathsEnter = clipPaths.enter().append('clipPath').attr('class', 'clipPath-data').attr('id', clipPathID);
+ clipPathsEnter.append('path');
+ clipPaths.merge(clipPathsEnter).selectAll('path').attr('d', getAreaPath); // Draw fill, shadow, stroke layers
- function startResize(d3_event) {
- if (pointerId !== (d3_event.pointerId || 'mouse')) return;
- d3_event.preventDefault();
- d3_event.stopPropagation();
- var mapSize = context.map().dimensions();
+ var datagroups = layer.selectAll('g.datagroup').data(['fill', 'shadow', 'stroke']);
+ datagroups = datagroups.enter().append('g').attr('class', function (d) {
+ return 'datagroup datagroup-' + d;
+ }).merge(datagroups); // Draw paths
- if (resizeOnX) {
- var maxWidth = mapSize[0];
- var newWidth = clamp(startWidth + d3_event.clientX - startX, minWidth, maxWidth);
- target.style('width', newWidth + 'px');
- }
+ var pathData = {
+ fill: polygonData,
+ shadow: geoData,
+ stroke: geoData
+ };
+ var paths = datagroups.selectAll('path').data(function (layer) {
+ return pathData[layer];
+ }, featureKey); // exit
- if (resizeOnY) {
- var maxHeight = mapSize[1] - 90; // preserve space at top/bottom of map
+ paths.exit().remove(); // enter/update
- var newHeight = clamp(startHeight + startY - d3_event.clientY, minHeight, maxHeight);
- target.style('height', newHeight + 'px');
- }
+ paths = paths.enter().append('path').attr('class', function (d) {
+ var datagroup = this.parentNode.__data__;
+ return 'pathdata ' + datagroup + ' ' + featureClasses(d);
+ }).attr('clip-path', function (d) {
+ var datagroup = this.parentNode.__data__;
+ return datagroup === 'fill' ? 'url(#' + clipPathID(d) + ')' : null;
+ }).merge(paths).attr('d', function (d) {
+ var datagroup = this.parentNode.__data__;
+ return datagroup === 'fill' ? getAreaPath(d) : getPath(d);
+ }); // Draw labels
- dispatch.call(eventName, target, utilGetDimensions(target, true));
- }
+ layer.call(drawLabels, 'label-halo', geoData).call(drawLabels, 'label', geoData);
- function clamp(num, min, max) {
- return Math.max(min, Math.min(num, max));
- }
+ function drawLabels(selection, textClass, data) {
+ var labelPath = d3_geoPath(projection);
+ var labelData = data.filter(function (d) {
+ return _showLabels && d.properties && (d.properties.desc || d.properties.name);
+ });
+ var labels = selection.selectAll('text.' + textClass).data(labelData, featureKey); // exit
- function stopResize(d3_event) {
- if (pointerId !== (d3_event.pointerId || 'mouse')) return;
- d3_event.preventDefault();
- d3_event.stopPropagation(); // remove all the listeners we added
+ labels.exit().remove(); // enter/update
- select(window).on('.' + eventName, null);
- }
+ labels = labels.enter().append('text').attr('class', function (d) {
+ return textClass + ' ' + featureClasses(d);
+ }).merge(labels).text(function (d) {
+ return d.properties.desc || d.properties.name;
+ }).attr('x', function (d) {
+ var centroid = labelPath.centroid(d);
+ return centroid[0] + 11;
+ }).attr('y', function (d) {
+ var centroid = labelPath.centroid(d);
+ return centroid[1];
+ });
+ }
+ }
- return function initResize(d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- pointerId = d3_event.pointerId || 'mouse';
- startX = d3_event.clientX;
- startY = d3_event.clientY;
- var targetRect = target.node().getBoundingClientRect();
- startWidth = targetRect.width;
- startHeight = targetRect.height;
- select(window).on(_pointerPrefix + 'move.' + eventName, startResize, false).on(_pointerPrefix + 'up.' + eventName, stopResize, false);
+ function getExtension(fileName) {
+ if (!fileName) return;
+ var re = /\.(gpx|kml|(geo)?json)$/i;
+ var match = fileName.toLowerCase().match(re);
+ return match && match.length && match[0];
+ }
- if (_pointerPrefix === 'pointer') {
- select(window).on('pointercancel.' + eventName, stopResize, false);
- }
- };
- }
+ function xmlToDom(textdata) {
+ return new DOMParser().parseFromString(textdata, 'text/xml');
}
- photoviewer.onMapResize = function () {
- var photoviewer = context.container().select('.photoviewer');
- var content = context.container().select('.main-content');
- var mapDimensions = utilGetDimensions(content, true); // shrink photo viewer if it is too big
- // (-90 preserves space at top and bottom of map used by menus)
+ function stringifyGeojsonProperties(feature) {
+ var properties = feature.properties;
- var photoDimensions = utilGetDimensions(photoviewer, true);
+ for (var key in properties) {
+ var property = properties[key];
- if (photoDimensions[0] > mapDimensions[0] || photoDimensions[1] > mapDimensions[1] - 90) {
- var setPhotoDimensions = [Math.min(photoDimensions[0], mapDimensions[0]), Math.min(photoDimensions[1], mapDimensions[1] - 90)];
- photoviewer.style('width', setPhotoDimensions[0] + 'px').style('height', setPhotoDimensions[1] + 'px');
- dispatch.call('resize', photoviewer, setPhotoDimensions);
+ if (typeof property === 'number' || typeof property === 'boolean' || Array.isArray(property)) {
+ properties[key] = property.toString();
+ } else if (property === null) {
+ properties[key] = 'null';
+ } else if (_typeof(property) === 'object') {
+ properties[key] = JSON.stringify(property);
+ }
}
- };
-
- return utilRebind(photoviewer, dispatch, 'on');
- }
-
- function uiRestore(context) {
- return function (selection) {
- if (!context.history().hasRestorableChanges()) return;
- var modalSelection = uiModal(selection, true);
- modalSelection.select('.modal').attr('class', 'modal fillL');
- var introModal = modalSelection.select('.content');
- introModal.append('div').attr('class', 'modal-section').append('h3').html(_t.html('restore.heading'));
- introModal.append('div').attr('class', 'modal-section').append('p').html(_t.html('restore.description'));
- var buttonWrap = introModal.append('div').attr('class', 'modal-actions');
- var restore = buttonWrap.append('button').attr('class', 'restore').on('click', function () {
- context.history().restore();
- modalSelection.remove();
- });
- restore.append('svg').attr('class', 'logo logo-restore').append('use').attr('xlink:href', '#iD-logo-restore');
- restore.append('div').html(_t.html('restore.restore'));
- var reset = buttonWrap.append('button').attr('class', 'reset').on('click', function () {
- context.history().clearSaved();
- modalSelection.remove();
- });
- reset.append('svg').attr('class', 'logo logo-reset').append('use').attr('xlink:href', '#iD-logo-reset');
- reset.append('div').html(_t.html('restore.reset'));
- restore.node().focus();
- };
- }
+ }
- function uiScale(context) {
- var projection = context.projection,
- isImperial = !_mainLocalizer.usesMetric(),
- maxLength = 180,
- tickHeight = 8;
+ drawData.setFile = function (extension, data) {
+ _template = null;
+ _fileList = null;
+ _geojson = null;
+ _src = null;
+ var gj;
- function scaleDefs(loc1, loc2) {
- var lat = (loc2[1] + loc1[1]) / 2,
- conversion = isImperial ? 3.28084 : 1,
- dist = geoLonToMeters(loc2[0] - loc1[0], lat) * conversion,
- scale = {
- dist: 0,
- px: 0,
- text: ''
- },
- buckets,
- i,
- val,
- dLon;
+ switch (extension) {
+ case '.gpx':
+ gj = gpx(xmlToDom(data));
+ break;
- if (isImperial) {
- buckets = [5280000, 528000, 52800, 5280, 500, 50, 5, 1];
- } else {
- buckets = [5000000, 500000, 50000, 5000, 500, 50, 5, 1];
- } // determine a user-friendly endpoint for the scale
+ case '.kml':
+ gj = kml(xmlToDom(data));
+ break;
+ case '.geojson':
+ case '.json':
+ gj = JSON.parse(data);
- for (i = 0; i < buckets.length; i++) {
- val = buckets[i];
+ if (gj.type === 'FeatureCollection') {
+ gj.features.forEach(stringifyGeojsonProperties);
+ } else if (gj.type === 'Feature') {
+ stringifyGeojsonProperties(gj);
+ }
- if (dist >= val) {
- scale.dist = Math.floor(dist / val) * val;
break;
- } else {
- scale.dist = +dist.toFixed(2);
- }
}
- dLon = geoMetersToLon(scale.dist / conversion, lat);
- scale.px = Math.round(projection([loc1[0] + dLon, loc1[1]])[0]);
- scale.text = displayLength(scale.dist / conversion, isImperial);
- return scale;
- }
-
- function update(selection) {
- // choose loc1, loc2 along bottom of viewport (near where the scale will be drawn)
- var dims = context.map().dimensions(),
- loc1 = projection.invert([0, dims[1]]),
- loc2 = projection.invert([maxLength, dims[1]]),
- scale = scaleDefs(loc1, loc2);
- selection.select('.scale-path').attr('d', 'M0.5,0.5v' + tickHeight + 'h' + scale.px + 'v-' + tickHeight);
- selection.select('.scale-text').style(_mainLocalizer.textDirection() === 'ltr' ? 'left' : 'right', scale.px + 16 + 'px').html(scale.text);
- }
+ gj = gj || {};
- return function (selection) {
- function switchUnits() {
- isImperial = !isImperial;
- selection.call(update);
+ if (Object.keys(gj).length) {
+ _geojson = ensureIDs(gj);
+ _src = extension + ' data file';
+ this.fitZoom();
}
- var scalegroup = selection.append('svg').attr('class', 'scale').on('click', switchUnits).append('g').attr('transform', 'translate(10,11)');
- scalegroup.append('path').attr('class', 'scale-path');
- selection.append('div').attr('class', 'scale-text');
- selection.call(update);
- context.map().on('move.scale', function () {
- update(selection);
- });
+ dispatch.call('change');
+ return this;
};
- }
- function uiShortcuts(context) {
- var detected = utilDetect();
- var _activeTab = 0;
+ drawData.showLabels = function (val) {
+ if (!arguments.length) return _showLabels;
+ _showLabels = val;
+ return this;
+ };
- var _modalSelection;
+ drawData.enabled = function (val) {
+ if (!arguments.length) return _enabled;
+ _enabled = val;
- var _selection = select(null);
+ if (_enabled) {
+ showLayer();
+ } else {
+ hideLayer();
+ }
- var _dataShortcuts;
+ dispatch.call('change');
+ return this;
+ };
- function shortcutsModal(_modalSelection) {
- _modalSelection.select('.modal').classed('modal-shortcuts', true);
+ drawData.hasData = function () {
+ var gj = _geojson || {};
+ return !!(_template || Object.keys(gj).length);
+ };
- var content = _modalSelection.select('.content');
+ drawData.template = function (val, src) {
+ if (!arguments.length) return _template; // test source against OSM imagery blocklists..
- content.append('div').attr('class', 'modal-section').append('h3').html(_t.html('shortcuts.title'));
- _mainFileFetcher.get('shortcuts').then(function (data) {
- _dataShortcuts = data;
- content.call(render);
- })["catch"](function () {
- /* ignore */
- });
- }
+ var osm = context.connection();
- function render(selection) {
- if (!_dataShortcuts) return;
- var wrapper = selection.selectAll('.wrapper').data([0]);
- var wrapperEnter = wrapper.enter().append('div').attr('class', 'wrapper modal-section');
- var tabsBar = wrapperEnter.append('div').attr('class', 'tabs-bar');
- var shortcutsList = wrapperEnter.append('div').attr('class', 'shortcuts-list');
- wrapper = wrapper.merge(wrapperEnter);
- var tabs = tabsBar.selectAll('.tab').data(_dataShortcuts);
- var tabsEnter = tabs.enter().append('a').attr('class', 'tab').attr('href', '#').on('click', function (d3_event, d) {
- d3_event.preventDefault();
+ if (osm) {
+ var blocklists = osm.imageryBlocklists();
+ var fail = false;
+ var tested = 0;
+ var regex;
- var i = _dataShortcuts.indexOf(d);
+ for (var i = 0; i < blocklists.length; i++) {
+ regex = blocklists[i];
+ fail = regex.test(val);
+ tested++;
+ if (fail) break;
+ } // ensure at least one test was run.
- _activeTab = i;
- render(selection);
- });
- tabsEnter.append('span').html(function (d) {
- return _t.html(d.text);
- }); // Update
- wrapper.selectAll('.tab').classed('active', function (d, i) {
- return i === _activeTab;
- });
- var shortcuts = shortcutsList.selectAll('.shortcut-tab').data(_dataShortcuts);
- var shortcutsEnter = shortcuts.enter().append('div').attr('class', function (d) {
- return 'shortcut-tab shortcut-tab-' + d.tab;
- });
- var columnsEnter = shortcutsEnter.selectAll('.shortcut-column').data(function (d) {
- return d.columns;
- }).enter().append('table').attr('class', 'shortcut-column');
- var rowsEnter = columnsEnter.selectAll('.shortcut-row').data(function (d) {
- return d.rows;
- }).enter().append('tr').attr('class', 'shortcut-row');
- var sectionRows = rowsEnter.filter(function (d) {
- return !d.shortcuts;
- });
- sectionRows.append('td');
- sectionRows.append('td').attr('class', 'shortcut-section').append('h3').html(function (d) {
- return _t.html(d.text);
- });
- var shortcutRows = rowsEnter.filter(function (d) {
- return d.shortcuts;
- });
- var shortcutKeys = shortcutRows.append('td').attr('class', 'shortcut-keys');
- var modifierKeys = shortcutKeys.filter(function (d) {
- return d.modifiers;
- });
- modifierKeys.selectAll('kbd.modifier').data(function (d) {
- if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
- return ['⌘'];
- } else if (detected.os !== 'mac' && d.text === 'shortcuts.browsing.display_options.fullscreen') {
- return [];
- } else {
- return d.modifiers;
+ if (!tested) {
+ regex = /.*\.google(apis)?\..*\/(vt|kh)[\?\/].*([xyz]=.*){3}.*/;
+ fail = regex.test(val);
}
- }).enter().each(function () {
- var selection = select(this);
- selection.append('kbd').attr('class', 'modifier').html(function (d) {
- return uiCmd.display(d);
- });
- selection.append('span').html('+');
- });
- shortcutKeys.selectAll('kbd.shortcut').data(function (d) {
- var arr = d.shortcuts;
+ }
- if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
- arr = ['Y'];
- } else if (detected.os !== 'mac' && d.text === 'shortcuts.browsing.display_options.fullscreen') {
- arr = ['F11'];
- } // replace translations
+ _template = val;
+ _fileList = null;
+ _geojson = null; // strip off the querystring/hash from the template,
+ // it often includes the access token
+ _src = src || 'vectortile:' + val.split(/[?#]/)[0];
+ dispatch.call('change');
+ return this;
+ };
- arr = arr.map(function (s) {
- return uiCmd.display(s.indexOf('.') !== -1 ? _t(s) : s);
- });
- return utilArrayUniq(arr).map(function (s) {
- return {
- shortcut: s,
- separator: d.separator,
- suffix: d.suffix
- };
- });
- }).enter().each(function (d, i, nodes) {
- var selection = select(this);
- var click = d.shortcut.toLowerCase().match(/(.*).click/);
+ drawData.geojson = function (gj, src) {
+ if (!arguments.length) return _geojson;
+ _template = null;
+ _fileList = null;
+ _geojson = null;
+ _src = null;
+ gj = gj || {};
- if (click && click[1]) {
- // replace "left_click", "right_click" with mouse icon
- selection.call(svgIcon('#iD-walkthrough-mouse-' + click[1], 'operation'));
- } else if (d.shortcut.toLowerCase() === 'long-press') {
- selection.call(svgIcon('#iD-walkthrough-longpress', 'longpress operation'));
- } else if (d.shortcut.toLowerCase() === 'tap') {
- selection.call(svgIcon('#iD-walkthrough-tap', 'tap operation'));
- } else {
- selection.append('kbd').attr('class', 'shortcut').html(function (d) {
- return d.shortcut;
- });
- }
+ if (Object.keys(gj).length) {
+ _geojson = ensureIDs(gj);
+ _src = src || 'unknown.geojson';
+ }
- if (i < nodes.length - 1) {
- selection.append('span').html(d.separator || "\xA0" + _t.html('shortcuts.or') + "\xA0");
- } else if (i === nodes.length - 1 && d.suffix) {
- selection.append('span').html(d.suffix);
- }
- });
- shortcutKeys.filter(function (d) {
- return d.gesture;
- }).each(function () {
- var selection = select(this);
- selection.append('span').html('+');
- selection.append('span').attr('class', 'gesture').html(function (d) {
- return _t.html(d.gesture);
- });
- });
- shortcutRows.append('td').attr('class', 'shortcut-desc').html(function (d) {
- return d.text ? _t.html(d.text) : "\xA0";
- }); // Update
+ dispatch.call('change');
+ return this;
+ };
- wrapper.selectAll('.shortcut-tab').style('display', function (d, i) {
- return i === _activeTab ? 'flex' : 'none';
- });
- }
+ drawData.fileList = function (fileList) {
+ if (!arguments.length) return _fileList;
+ _template = null;
+ _fileList = fileList;
+ _geojson = null;
+ _src = null;
+ if (!fileList || !fileList.length) return this;
+ var f = fileList[0];
+ var extension = getExtension(f.name);
+ var reader = new FileReader();
- return function (selection, show) {
- _selection = selection;
+ reader.onload = function () {
+ return function (e) {
+ drawData.setFile(extension, e.target.result);
+ };
+ }();
- if (show) {
- _modalSelection = uiModal(selection);
+ reader.readAsText(f);
+ return this;
+ };
- _modalSelection.call(shortcutsModal);
- } else {
- context.keybinding().on([_t('shortcuts.toggle.key'), '?'], function () {
- if (context.container().selectAll('.modal-shortcuts').size()) {
- // already showing
- if (_modalSelection) {
- _modalSelection.close();
+ drawData.url = function (url, defaultExtension) {
+ _template = null;
+ _fileList = null;
+ _geojson = null;
+ _src = null; // strip off any querystring/hash from the url before checking extension
- _modalSelection = null;
- }
- } else {
- _modalSelection = uiModal(_selection);
+ var testUrl = url.split(/[?#]/)[0];
+ var extension = getExtension(testUrl) || defaultExtension;
- _modalSelection.call(shortcutsModal);
- }
+ if (extension) {
+ _template = null;
+ d3_text(url).then(function (data) {
+ drawData.setFile(extension, data);
+ })["catch"](function () {
+ /* ignore */
});
+ } else {
+ drawData.template(url);
}
- };
- }
-
- function uiDataHeader() {
- var _datum;
-
- function dataHeader(selection) {
- var header = selection.selectAll('.data-header').data(_datum ? [_datum] : [], function (d) {
- return d.__featurehash__;
- });
- header.exit().remove();
- var headerEnter = header.enter().append('div').attr('class', 'data-header');
- var iconEnter = headerEnter.append('div').attr('class', 'data-header-icon');
- iconEnter.append('div').attr('class', 'preset-icon-28').call(svgIcon('#iD-icon-data', 'note-fill'));
- headerEnter.append('div').attr('class', 'data-header-label').html(_t.html('map_data.layers.custom.title'));
- }
- dataHeader.datum = function (val) {
- if (!arguments.length) return _datum;
- _datum = val;
return this;
};
- return dataHeader;
- }
-
- // It is keyed on the `value` of the entry. Data should be an array of objects like:
- // [{
- // value: 'string value', // required
- // display: 'label html' // optional
- // title: 'hover text' // optional
- // terms: ['search terms'] // optional
- // }, ...]
-
- var _comboHideTimerID;
-
- function uiCombobox(context, klass) {
- var dispatch = dispatch$8('accept', 'cancel');
- var container = context.container();
- var _suggestions = [];
- var _data = [];
- var _fetched = {};
- var _selected = null;
- var _canAutocomplete = true;
- var _caseSensitive = false;
- var _cancelFetch = false;
- var _minItems = 2;
- var _tDown = 0;
-
- var _mouseEnterHandler, _mouseLeaveHandler;
-
- var _fetcher = function _fetcher(val, cb) {
- cb(_data.filter(function (d) {
- var terms = d.terms || [];
- terms.push(d.value);
- return terms.some(function (term) {
- return term.toString().toLowerCase().indexOf(val.toLowerCase()) !== -1;
- });
- }));
+ drawData.getSrc = function () {
+ return _src || '';
};
- var combobox = function combobox(input, attachTo) {
- if (!input || input.empty()) return;
- input.classed('combobox-input', true).on('focus.combo-input', focus).on('blur.combo-input', blur).on('keydown.combo-input', keydown).on('keyup.combo-input', keyup).on('input.combo-input', change).on('mousedown.combo-input', mousedown).each(function () {
- var parent = this.parentNode;
- var sibling = this.nextSibling;
- select(parent).selectAll('.combobox-caret').filter(function (d) {
- return d === input.node();
- }).data([input.node()]).enter().insert('div', function () {
- return sibling;
- }).attr('class', 'combobox-caret').on('mousedown.combo-caret', function (d3_event) {
- d3_event.preventDefault(); // don't steal focus from input
-
- input.node().focus(); // focus the input as if it was clicked
-
- mousedown(d3_event);
- }).on('mouseup.combo-caret', function (d3_event) {
- d3_event.preventDefault(); // don't steal focus from input
-
- mouseup(d3_event);
- });
- });
+ drawData.fitZoom = function () {
+ var features = getFeatures(_geojson);
+ if (!features.length) return;
+ var map = context.map();
+ var viewport = map.trimmedExtent().polygon();
+ var coords = features.reduce(function (coords, feature) {
+ var geom = feature.geometry;
+ if (!geom) return coords;
+ var c = geom.coordinates;
+ /* eslint-disable no-fallthrough */
- function mousedown(d3_event) {
- if (d3_event.button !== 0) return; // left click only
+ switch (geom.type) {
+ case 'Point':
+ c = [c];
- _tDown = +new Date(); // clear selection
+ case 'MultiPoint':
+ case 'LineString':
+ break;
- var start = input.property('selectionStart');
- var end = input.property('selectionEnd');
+ case 'MultiPolygon':
+ c = utilArrayFlatten(c);
- if (start !== end) {
- var val = utilGetSetValue(input);
- input.node().setSelectionRange(val.length, val.length);
- return;
+ case 'Polygon':
+ case 'MultiLineString':
+ c = utilArrayFlatten(c);
+ break;
}
+ /* eslint-enable no-fallthrough */
- input.on('mouseup.combo-input', mouseup);
- }
- function mouseup(d3_event) {
- input.on('mouseup.combo-input', null);
- if (d3_event.button !== 0) return; // left click only
+ return utilArrayUnion(coords, c);
+ }, []);
- if (input.node() !== document.activeElement) return; // exit if this input is not focused
+ if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
+ var extent = geoExtent(d3_geoBounds({
+ type: 'LineString',
+ coordinates: coords
+ }));
+ map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
+ }
- var start = input.property('selectionStart');
- var end = input.property('selectionEnd');
- if (start !== end) return; // exit if user is selecting
- // not showing or showing for a different field - try to show it.
+ return this;
+ };
- var combo = container.selectAll('.combobox');
+ init();
+ return drawData;
+ }
- if (combo.empty() || combo.datum() !== input.node()) {
- var tOrig = _tDown;
- window.setTimeout(function () {
- if (tOrig !== _tDown) return; // exit if user double clicked
+ function svgDebug(projection, context) {
+ function drawDebug(selection) {
+ var showTile = context.getDebug('tile');
+ var showCollision = context.getDebug('collision');
+ var showImagery = context.getDebug('imagery');
+ var showTouchTargets = context.getDebug('target');
+ var showDownloaded = context.getDebug('downloaded');
+ var debugData = [];
- fetchComboData('', function () {
- show();
- render();
- });
- }, 250);
- } else {
- hide();
- }
+ if (showTile) {
+ debugData.push({
+ "class": 'red',
+ label: 'tile'
+ });
}
- function focus() {
- fetchComboData(''); // prefetch values (may warm taginfo cache)
+ if (showCollision) {
+ debugData.push({
+ "class": 'yellow',
+ label: 'collision'
+ });
}
- function blur() {
- _comboHideTimerID = window.setTimeout(hide, 75);
- }
-
- function show() {
- hide(); // remove any existing
-
- container.insert('div', ':first-child').datum(input.node()).attr('class', 'combobox' + (klass ? ' combobox-' + klass : '')).style('position', 'absolute').style('display', 'block').style('left', '0px').on('mousedown.combo-container', function (d3_event) {
- // prevent moving focus out of the input field
- d3_event.preventDefault();
+ if (showImagery) {
+ debugData.push({
+ "class": 'orange',
+ label: 'imagery'
});
- container.on('scroll.combo-scroll', render, true);
}
- function hide() {
- if (_comboHideTimerID) {
- window.clearTimeout(_comboHideTimerID);
- _comboHideTimerID = undefined;
- }
-
- container.selectAll('.combobox').remove();
- container.on('scroll.combo-scroll', null);
+ if (showTouchTargets) {
+ debugData.push({
+ "class": 'pink',
+ label: 'touchTargets'
+ });
}
- function keydown(d3_event) {
- var shown = !container.selectAll('.combobox').empty();
- var tagName = input.node() ? input.node().tagName.toLowerCase() : '';
-
- switch (d3_event.keyCode) {
- case 8: // ⌫ Backspace
-
- case 46:
- // ⌦ Delete
- d3_event.stopPropagation();
- _selected = null;
- render();
- input.on('input.combo-input', function () {
- var start = input.property('selectionStart');
- input.node().setSelectionRange(start, start);
- input.on('input.combo-input', change);
- });
- break;
+ if (showDownloaded) {
+ debugData.push({
+ "class": 'purple',
+ label: 'downloaded'
+ });
+ }
- case 9:
- // ⇥ Tab
- accept();
- break;
+ var legend = context.container().select('.main-content').selectAll('.debug-legend').data(debugData.length ? [0] : []);
+ legend.exit().remove();
+ legend = legend.enter().append('div').attr('class', 'fillD debug-legend').merge(legend);
+ var legendItems = legend.selectAll('.debug-legend-item').data(debugData, function (d) {
+ return d.label;
+ });
+ legendItems.exit().remove();
+ legendItems.enter().append('span').attr('class', function (d) {
+ return "debug-legend-item ".concat(d["class"]);
+ }).text(function (d) {
+ return d.label;
+ });
+ var layer = selection.selectAll('.layer-debug').data(showImagery || showDownloaded ? [0] : []);
+ layer.exit().remove();
+ layer = layer.enter().append('g').attr('class', 'layer-debug').merge(layer); // imagery
- case 13:
- // ↩ Return
- d3_event.preventDefault();
- d3_event.stopPropagation();
- break;
+ var extent = context.map().extent();
+ _mainFileFetcher.get('imagery').then(function (d) {
+ var hits = showImagery && d.query.bbox(extent.rectangle(), true) || [];
+ var features = hits.map(function (d) {
+ return d.features[d.id];
+ });
+ var imagery = layer.selectAll('path.debug-imagery').data(features);
+ imagery.exit().remove();
+ imagery.enter().append('path').attr('class', 'debug-imagery debug orange');
+ })["catch"](function () {
+ /* ignore */
+ }); // downloaded
- case 38:
- // ↑ Up arrow
- if (tagName === 'textarea' && !shown) return;
- d3_event.preventDefault();
+ var osm = context.connection();
+ var dataDownloaded = [];
- if (tagName === 'input' && !shown) {
- show();
+ if (osm && showDownloaded) {
+ var rtree = osm.caches('get').tile.rtree;
+ dataDownloaded = rtree.all().map(function (bbox) {
+ return {
+ type: 'Feature',
+ properties: {
+ id: bbox.id
+ },
+ geometry: {
+ type: 'Polygon',
+ coordinates: [[[bbox.minX, bbox.minY], [bbox.minX, bbox.maxY], [bbox.maxX, bbox.maxY], [bbox.maxX, bbox.minY], [bbox.minX, bbox.minY]]]
}
+ };
+ });
+ }
- nav(-1);
- break;
+ var downloaded = layer.selectAll('path.debug-downloaded').data(showDownloaded ? dataDownloaded : []);
+ downloaded.exit().remove();
+ downloaded.enter().append('path').attr('class', 'debug-downloaded debug purple'); // update
- case 40:
- // ↓ Down arrow
- if (tagName === 'textarea' && !shown) return;
- d3_event.preventDefault();
+ layer.selectAll('path').attr('d', svgPath(projection).geojson);
+ } // This looks strange because `enabled` methods on other layers are
+ // chainable getter/setters, and this one is just a getter.
- if (tagName === 'input' && !shown) {
- show();
- }
- nav(+1);
- break;
- }
+ drawDebug.enabled = function () {
+ if (!arguments.length) {
+ return context.getDebug('tile') || context.getDebug('collision') || context.getDebug('imagery') || context.getDebug('target') || context.getDebug('downloaded');
+ } else {
+ return this;
}
+ };
- function keyup(d3_event) {
- switch (d3_event.keyCode) {
- case 27:
- // ⎋ Escape
- cancel();
- break;
-
- case 13:
- // ↩ Return
- accept();
- break;
- }
- } // Called whenever the input value is changed (e.g. on typing)
-
-
- function change() {
- fetchComboData(value(), function () {
- _selected = null;
- var val = input.property('value');
+ return drawDebug;
+ }
- if (_suggestions.length) {
- if (input.property('selectionEnd') === val.length) {
- _selected = tryAutocomplete();
- }
+ /*
+ A standalone SVG element that contains only a `defs` sub-element. To be
+ used once globally, since defs IDs must be unique within a document.
+ */
- if (!_selected) {
- _selected = val;
- }
- }
+ function svgDefs(context) {
+ var _defsSelection = select(null);
- if (val.length) {
- var combo = container.selectAll('.combobox');
+ var _spritesheetIds = ['iD-sprite', 'maki-sprite', 'temaki-sprite', 'fa-sprite', 'community-sprite'];
- if (combo.empty()) {
- show();
- }
- } else {
- hide();
- }
+ function drawDefs(selection) {
+ _defsSelection = selection.append('defs'); // add markers
- render();
- });
- } // Called when the user presses up/down arrows to navigate the list
+ _defsSelection.append('marker').attr('id', 'ideditor-oneway-marker').attr('viewBox', '0 0 10 5').attr('refX', 2.5).attr('refY', 2.5).attr('markerWidth', 2).attr('markerHeight', 2).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'oneway-marker-path').attr('d', 'M 5,3 L 0,3 L 0,2 L 5,2 L 5,0 L 10,2.5 L 5,5 z').attr('stroke', 'none').attr('fill', '#000').attr('opacity', '0.75'); // SVG markers have to be given a colour where they're defined
+ // (they can't inherit it from the line they're attached to),
+ // so we need to manually define markers for each color of tag
+ // (also, it's slightly nicer if we can control the
+ // positioning for different tags)
- function nav(dir) {
- if (_suggestions.length) {
- // try to determine previously selected index..
- var index = -1;
+ function addSidedMarker(name, color, offset) {
+ _defsSelection.append('marker').attr('id', 'ideditor-sided-marker-' + name).attr('viewBox', '0 0 2 2').attr('refX', 1).attr('refY', -offset).attr('markerWidth', 1.5).attr('markerHeight', 1.5).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'sided-marker-path sided-marker-' + name + '-path').attr('d', 'M 0,0 L 1,1 L 2,0 z').attr('stroke', 'none').attr('fill', color);
+ }
- for (var i = 0; i < _suggestions.length; i++) {
- if (_selected && _suggestions[i].value === _selected) {
- index = i;
- break;
- }
- } // pick new _selected
+ addSidedMarker('natural', 'rgb(170, 170, 170)', 0); // for a coastline, the arrows are (somewhat unintuitively) on
+ // the water side, so let's color them blue (with a gap) to
+ // give a stronger indication
+ addSidedMarker('coastline', '#77dede', 1);
+ addSidedMarker('waterway', '#77dede', 1); // barriers have a dashed line, and separating the triangle
+ // from the line visually suits that
- index = Math.max(Math.min(index + dir, _suggestions.length - 1), 0);
- _selected = _suggestions[index].value;
- input.property('value', _selected);
- }
+ addSidedMarker('barrier', '#ddd', 1);
+ addSidedMarker('man_made', '#fff', 0);
- render();
- ensureVisible();
- }
+ _defsSelection.append('marker').attr('id', 'ideditor-viewfield-marker').attr('viewBox', '0 0 16 16').attr('refX', 8).attr('refY', 16).attr('markerWidth', 4).attr('markerHeight', 4).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'viewfield-marker-path').attr('d', 'M 6,14 C 8,13.4 8,13.4 10,14 L 16,3 C 12,0 4,0 0,3 z').attr('fill', '#333').attr('fill-opacity', '0.75').attr('stroke', '#fff').attr('stroke-width', '0.5px').attr('stroke-opacity', '0.75');
- function ensureVisible() {
- var combo = container.selectAll('.combobox');
- if (combo.empty()) return;
- var containerRect = container.node().getBoundingClientRect();
- var comboRect = combo.node().getBoundingClientRect();
+ _defsSelection.append('marker').attr('id', 'ideditor-viewfield-marker-wireframe').attr('viewBox', '0 0 16 16').attr('refX', 8).attr('refY', 16).attr('markerWidth', 4).attr('markerHeight', 4).attr('markerUnits', 'strokeWidth').attr('orient', 'auto').append('path').attr('class', 'viewfield-marker-path').attr('d', 'M 6,14 C 8,13.4 8,13.4 10,14 L 16,3 C 12,0 4,0 0,3 z').attr('fill', 'none').attr('stroke', '#fff').attr('stroke-width', '0.5px').attr('stroke-opacity', '0.75'); // add patterns
- if (comboRect.bottom > containerRect.bottom) {
- var node = attachTo ? attachTo.node() : input.node();
- node.scrollIntoView({
- behavior: 'instant',
- block: 'center'
- });
- render();
- } // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
+ var patterns = _defsSelection.selectAll('pattern').data([// pattern name, pattern image name
+ ['beach', 'dots'], ['construction', 'construction'], ['cemetery', 'cemetery'], ['cemetery_christian', 'cemetery_christian'], ['cemetery_buddhist', 'cemetery_buddhist'], ['cemetery_muslim', 'cemetery_muslim'], ['cemetery_jewish', 'cemetery_jewish'], ['farmland', 'farmland'], ['farmyard', 'farmyard'], ['forest', 'forest'], ['forest_broadleaved', 'forest_broadleaved'], ['forest_needleleaved', 'forest_needleleaved'], ['forest_leafless', 'forest_leafless'], ['golf_green', 'grass'], ['grass', 'grass'], ['landfill', 'landfill'], ['meadow', 'grass'], ['orchard', 'orchard'], ['pond', 'pond'], ['quarry', 'quarry'], ['scrub', 'bushes'], ['vineyard', 'vineyard'], ['water_standing', 'lines'], ['waves', 'waves'], ['wetland', 'wetland'], ['wetland_marsh', 'wetland_marsh'], ['wetland_swamp', 'wetland_swamp'], ['wetland_bog', 'wetland_bog'], ['wetland_reedbed', 'wetland_reedbed']]).enter().append('pattern').attr('id', function (d) {
+ return 'ideditor-pattern-' + d[0];
+ }).attr('width', 32).attr('height', 32).attr('patternUnits', 'userSpaceOnUse');
- var selected = combo.selectAll('.combobox-option.selected').node();
+ patterns.append('rect').attr('x', 0).attr('y', 0).attr('width', 32).attr('height', 32).attr('class', function (d) {
+ return 'pattern-color-' + d[0];
+ });
+ patterns.append('image').attr('x', 0).attr('y', 0).attr('width', 32).attr('height', 32).attr('xlink:href', function (d) {
+ return context.imagePath('pattern/' + d[1] + '.png');
+ }); // add clip paths
- if (selected) {
- selected.scrollIntoView({
- behavior: 'smooth',
- block: 'nearest'
- });
- }
- }
+ _defsSelection.selectAll('clipPath').data([12, 18, 20, 32, 45]).enter().append('clipPath').attr('id', function (d) {
+ return 'ideditor-clip-square-' + d;
+ }).append('rect').attr('x', 0).attr('y', 0).attr('width', function (d) {
+ return d;
+ }).attr('height', function (d) {
+ return d;
+ }); // add symbol spritesheets
- function value() {
- var value = input.property('value');
- var start = input.property('selectionStart');
- var end = input.property('selectionEnd');
- if (start && end) {
- value = value.substring(0, start);
- }
+ addSprites(_spritesheetIds, true);
+ }
- return value;
- }
+ function addSprites(ids, overrideColors) {
+ _spritesheetIds = utilArrayUniq(_spritesheetIds.concat(ids));
- function fetchComboData(v, cb) {
- _cancelFetch = false;
+ var spritesheets = _defsSelection.selectAll('.spritesheet').data(_spritesheetIds);
- _fetcher.call(input, v, function (results) {
- // already chose a value, don't overwrite or autocomplete it
- if (_cancelFetch) return;
- _suggestions = results;
- results.forEach(function (d) {
- _fetched[d.value] = d;
- });
+ spritesheets.enter().append('g').attr('class', function (d) {
+ return 'spritesheet spritesheet-' + d;
+ }).each(function (d) {
+ var url = context.imagePath(d + '.svg');
+ var node = select(this).node();
+ svg(url).then(function (svg) {
+ node.appendChild(select(svg.documentElement).attr('id', 'ideditor-' + d).node());
- if (cb) {
- cb();
+ if (overrideColors && d !== 'iD-sprite') {
+ // allow icon colors to be overridden..
+ select(node).selectAll('path').attr('fill', 'currentColor');
}
+ })["catch"](function () {
+ /* ignore */
});
- }
-
- function tryAutocomplete() {
- if (!_canAutocomplete) return;
- var val = _caseSensitive ? value() : value().toLowerCase();
- if (!val) return; // Don't autocomplete if user is typing a number - #4935
-
- if (!isNaN(parseFloat(val)) && isFinite(val)) return;
- var bestIndex = -1;
-
- for (var i = 0; i < _suggestions.length; i++) {
- var suggestion = _suggestions[i].value;
- var compare = _caseSensitive ? suggestion : suggestion.toLowerCase(); // if search string matches suggestion exactly, pick it..
+ });
+ spritesheets.exit().remove();
+ }
- if (compare === val) {
- bestIndex = i;
- break; // otherwise lock in the first result that starts with the search string..
- } else if (bestIndex === -1 && compare.indexOf(val) === 0) {
- bestIndex = i;
- }
- }
+ drawDefs.addSprites = addSprites;
+ return drawDefs;
+ }
- if (bestIndex !== -1) {
- var bestVal = _suggestions[bestIndex].value;
- input.property('value', bestVal);
- input.node().setSelectionRange(val.length, bestVal.length);
- return bestVal;
- }
- }
+ var _layerEnabled$2 = false;
- function render() {
- if (_suggestions.length < _minItems || document.activeElement !== input.node()) {
- hide();
- return;
- }
+ var _qaService$2;
- var shown = !container.selectAll('.combobox').empty();
- if (!shown) return;
- var combo = container.selectAll('.combobox');
- var options = combo.selectAll('.combobox-option').data(_suggestions, function (d) {
- return d.value;
- });
- options.exit().remove(); // enter/update
+ function svgKeepRight(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ return dispatch.call('change');
+ }, 1000);
- options.enter().append('a').attr('class', function (d) {
- return 'combobox-option ' + (d.klass || '');
- }).attr('title', function (d) {
- return d.title;
- }).html(function (d) {
- return d.display || d.value;
- }).on('mouseenter', _mouseEnterHandler).on('mouseleave', _mouseLeaveHandler).merge(options).classed('selected', function (d) {
- return d.value === _selected;
- }).on('click.combo-option', accept).order();
- var node = attachTo ? attachTo.node() : input.node();
- var containerRect = container.node().getBoundingClientRect();
- var rect = node.getBoundingClientRect();
- combo.style('left', rect.left + 5 - containerRect.left + 'px').style('width', rect.width - 10 + 'px').style('top', rect.height + rect.top - containerRect.top + 'px');
- } // Dispatches an 'accept' event
- // Then hides the combobox.
+ var minZoom = 12;
+ var touchLayer = select(null);
+ var drawLayer = select(null);
+ var layerVisible = false;
+ function markerPath(selection, klass) {
+ selection.attr('class', klass).attr('transform', 'translate(-4, -24)').attr('d', 'M11.6,6.2H7.1l1.4-5.1C8.6,0.6,8.1,0,7.5,0H2.2C1.7,0,1.3,0.3,1.3,0.8L0,10.2c-0.1,0.6,0.4,1.1,0.9,1.1h4.6l-1.8,7.6C3.6,19.4,4.1,20,4.7,20c0.3,0,0.6-0.2,0.8-0.5l6.9-11.9C12.7,7,12.3,6.2,11.6,6.2z');
+ } // Loosely-coupled keepRight service for fetching issues.
- function accept(d3_event, d) {
- _cancelFetch = true;
- var thiz = input.node();
- if (d) {
- // user clicked on a suggestion
- utilGetSetValue(input, d.value); // replace field contents
+ function getService() {
+ if (services.keepRight && !_qaService$2) {
+ _qaService$2 = services.keepRight;
- utilTriggerEvent(input, 'change');
- } // clear (and keep) selection
+ _qaService$2.on('loaded', throttledRedraw);
+ } else if (!services.keepRight && _qaService$2) {
+ _qaService$2 = null;
+ }
+ return _qaService$2;
+ } // Show the markers
- var val = utilGetSetValue(input);
- thiz.setSelectionRange(val.length, val.length);
- d = _fetched[val];
- dispatch.call('accept', thiz, d, val);
- hide();
- } // Dispatches an 'cancel' event
- // Then hides the combobox.
+ function editOn() {
+ if (!layerVisible) {
+ layerVisible = true;
+ drawLayer.style('display', 'block');
+ }
+ } // Immediately remove the markers and their touch targets
- function cancel() {
- _cancelFetch = true;
- var thiz = input.node(); // clear (and remove) selection, and replace field contents
- var val = utilGetSetValue(input);
- var start = input.property('selectionStart');
- var end = input.property('selectionEnd');
- val = val.slice(0, start) + val.slice(end);
- utilGetSetValue(input, val);
- thiz.setSelectionRange(val.length, val.length);
- dispatch.call('cancel', thiz);
- hide();
+ function editOff() {
+ if (layerVisible) {
+ layerVisible = false;
+ drawLayer.style('display', 'none');
+ drawLayer.selectAll('.qaItem.keepRight').remove();
+ touchLayer.selectAll('.qaItem.keepRight').remove();
}
- };
-
- combobox.canAutocomplete = function (val) {
- if (!arguments.length) return _canAutocomplete;
- _canAutocomplete = val;
- return combobox;
- };
+ } // Enable the layer. This shows the markers and transitions them to visible.
- combobox.caseSensitive = function (val) {
- if (!arguments.length) return _caseSensitive;
- _caseSensitive = val;
- return combobox;
- };
- combobox.data = function (val) {
- if (!arguments.length) return _data;
- _data = val;
- return combobox;
- };
+ function layerOn() {
+ editOn();
+ drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
+ return dispatch.call('change');
+ });
+ } // Disable the layer. This transitions the layer invisible and then hides the markers.
- combobox.fetcher = function (val) {
- if (!arguments.length) return _fetcher;
- _fetcher = val;
- return combobox;
- };
- combobox.minItems = function (val) {
- if (!arguments.length) return _minItems;
- _minItems = val;
- return combobox;
- };
+ function layerOff() {
+ throttledRedraw.cancel();
+ drawLayer.interrupt();
+ touchLayer.selectAll('.qaItem.keepRight').remove();
+ drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
+ editOff();
+ dispatch.call('change');
+ });
+ } // Update the issue markers
- combobox.itemsMouseEnter = function (val) {
- if (!arguments.length) return _mouseEnterHandler;
- _mouseEnterHandler = val;
- return combobox;
- };
- combobox.itemsMouseLeave = function (val) {
- if (!arguments.length) return _mouseLeaveHandler;
- _mouseLeaveHandler = val;
- return combobox;
- };
+ function updateMarkers() {
+ if (!layerVisible || !_layerEnabled$2) return;
+ var service = getService();
+ var selectedID = context.selectedErrorID();
+ var data = service ? service.getItems(projection) : [];
+ var getTransform = svgPointTransform(projection); // Draw markers..
- return utilRebind(combobox, dispatch, 'on');
- }
+ var markers = drawLayer.selectAll('.qaItem.keepRight').data(data, function (d) {
+ return d.id;
+ }); // exit
- uiCombobox.off = function (input, context) {
- input.on('focus.combo-input', null).on('blur.combo-input', null).on('keydown.combo-input', null).on('keyup.combo-input', null).on('input.combo-input', null).on('mousedown.combo-input', null).on('mouseup.combo-input', null);
- context.container().on('scroll.combo-scroll', null);
- };
+ markers.exit().remove(); // enter
- function uiDisclosure(context, key, expandedDefault) {
- var dispatch = dispatch$8('toggled');
+ var markersEnter = markers.enter().append('g').attr('class', function (d) {
+ return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.parentIssueType);
+ });
+ markersEnter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
+ markersEnter.append('path').call(markerPath, 'shadow');
+ markersEnter.append('use').attr('class', 'qaItem-fill').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').attr('xlink:href', '#iD-icon-bolt'); // update
- var _expanded;
+ markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
+ return d.id === selectedID;
+ }).attr('transform', getTransform); // Draw targets..
- var _label = utilFunctor('');
+ if (touchLayer.empty()) return;
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var targets = touchLayer.selectAll('.qaItem.keepRight').data(data, function (d) {
+ return d.id;
+ }); // exit
- var _updatePreference = true;
+ targets.exit().remove(); // enter/update
- var _content = function _content() {};
+ targets.enter().append('rect').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').merge(targets).sort(sortY).attr('class', function (d) {
+ return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
+ }).attr('transform', getTransform);
- var disclosure = function disclosure(selection) {
- if (_expanded === undefined || _expanded === null) {
- // loading _expanded here allows it to be reset by calling `disclosure.expanded(null)`
- var preference = corePreferences('disclosure.' + key + '.expanded');
- _expanded = preference === null ? !!expandedDefault : preference === 'true';
+ function sortY(a, b) {
+ return a.id === selectedID ? 1 : b.id === selectedID ? -1 : a.severity === 'error' && b.severity !== 'error' ? 1 : b.severity === 'error' && a.severity !== 'error' ? -1 : b.loc[1] - a.loc[1];
}
+ } // Draw the keepRight layer and schedule loading issues and updating markers.
- var hideToggle = selection.selectAll('.hide-toggle-' + key).data([0]); // enter
- var hideToggleEnter = hideToggle.enter().append('a').attr('href', '#').attr('class', 'hide-toggle hide-toggle-' + key).call(svgIcon('', 'pre-text', 'hide-toggle-icon'));
- hideToggleEnter.append('span').attr('class', 'hide-toggle-text'); // update
+ function drawKeepRight(selection) {
+ var service = getService();
+ var surface = context.surface();
- hideToggle = hideToggleEnter.merge(hideToggle);
- hideToggle.on('click', toggle).classed('expanded', _expanded);
- hideToggle.selectAll('.hide-toggle-text').html(_label());
- hideToggle.selectAll('.hide-toggle-icon').attr('xlink:href', _expanded ? '#iD-icon-down' : _mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward');
- var wrap = selection.selectAll('.disclosure-wrap').data([0]); // enter/update
+ if (surface && !surface.empty()) {
+ touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
+ }
- wrap = wrap.enter().append('div').attr('class', 'disclosure-wrap disclosure-wrap-' + key).merge(wrap).classed('hide', !_expanded);
+ drawLayer = selection.selectAll('.layer-keepRight').data(service ? [0] : []);
+ drawLayer.exit().remove();
+ drawLayer = drawLayer.enter().append('g').attr('class', 'layer-keepRight').style('display', _layerEnabled$2 ? 'block' : 'none').merge(drawLayer);
- if (_expanded) {
- wrap.call(_content);
+ if (_layerEnabled$2) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ service.loadIssues(projection);
+ updateMarkers();
+ } else {
+ editOff();
+ }
}
+ } // Toggles the layer on and off
- function toggle(d3_event) {
- d3_event.preventDefault();
- _expanded = !_expanded;
- if (_updatePreference) {
- corePreferences('disclosure.' + key + '.expanded', _expanded);
- }
+ drawKeepRight.enabled = function (val) {
+ if (!arguments.length) return _layerEnabled$2;
+ _layerEnabled$2 = val;
- hideToggle.classed('expanded', _expanded);
- hideToggle.selectAll('.hide-toggle-icon').attr('xlink:href', _expanded ? '#iD-icon-down' : _mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward');
- wrap.call(uiToggle(_expanded));
+ if (_layerEnabled$2) {
+ layerOn();
+ } else {
+ layerOff();
- if (_expanded) {
- wrap.call(_content);
+ if (context.selectedErrorID()) {
+ context.enter(modeBrowse(context));
}
-
- dispatch.call('toggled', this, _expanded);
}
- };
- disclosure.label = function (val) {
- if (!arguments.length) return _label;
- _label = utilFunctor(val);
- return disclosure;
- };
-
- disclosure.expanded = function (val) {
- if (!arguments.length) return _expanded;
- _expanded = val;
- return disclosure;
- };
-
- disclosure.updatePreference = function (val) {
- if (!arguments.length) return _updatePreference;
- _updatePreference = val;
- return disclosure;
+ dispatch.call('change');
+ return this;
};
- disclosure.content = function (val) {
- if (!arguments.length) return _content;
- _content = val;
- return disclosure;
+ drawKeepRight.supported = function () {
+ return !!getService();
};
- return utilRebind(disclosure, dispatch, 'on');
+ return drawKeepRight;
}
- // Can be labeled and collapsible.
+ function svgGeolocate(projection) {
+ var layer = select(null);
- function uiSection(id, context) {
- var _classes = utilFunctor('');
+ var _position;
- var _shouldDisplay;
+ function init() {
+ if (svgGeolocate.initialized) return; // run once
- var _content;
+ svgGeolocate.enabled = false;
+ svgGeolocate.initialized = true;
+ }
- var _disclosure;
+ function showLayer() {
+ layer.style('display', 'block');
+ }
- var _label;
+ function hideLayer() {
+ layer.transition().duration(250).style('opacity', 0);
+ }
- var _expandedByDefault = utilFunctor(true);
+ function layerOn() {
+ layer.style('opacity', 0).transition().duration(250).style('opacity', 1);
+ }
- var _disclosureContent;
+ function layerOff() {
+ layer.style('display', 'none');
+ }
- var _disclosureExpanded;
+ function transform(d) {
+ return svgPointTransform(projection)(d);
+ }
- var _containerSelection = select(null);
+ function accuracy(accuracy, loc) {
+ // converts accuracy to pixels...
+ var degreesRadius = geoMetersToLat(accuracy),
+ tangentLoc = [loc[0], loc[1] + degreesRadius],
+ projectedTangent = projection(tangentLoc),
+ projectedLoc = projection([loc[0], loc[1]]); // southern most point will have higher pixel value...
- var section = {
- id: id
- };
+ return Math.round(projectedLoc[1] - projectedTangent[1]).toString();
+ }
- section.classes = function (val) {
- if (!arguments.length) return _classes;
- _classes = utilFunctor(val);
- return section;
- };
+ function update() {
+ var geolocation = {
+ loc: [_position.coords.longitude, _position.coords.latitude]
+ };
+ var groups = layer.selectAll('.geolocations').selectAll('.geolocation').data([geolocation]);
+ groups.exit().remove();
+ var pointsEnter = groups.enter().append('g').attr('class', 'geolocation');
+ pointsEnter.append('circle').attr('class', 'geolocate-radius').attr('dx', '0').attr('dy', '0').attr('fill', 'rgb(15,128,225)').attr('fill-opacity', '0.3').attr('r', '0');
+ pointsEnter.append('circle').attr('dx', '0').attr('dy', '0').attr('fill', 'rgb(15,128,225)').attr('stroke', 'white').attr('stroke-width', '1.5').attr('r', '6');
+ groups.merge(pointsEnter).attr('transform', transform);
+ layer.select('.geolocate-radius').attr('r', accuracy(_position.coords.accuracy, geolocation.loc));
+ }
- section.label = function (val) {
- if (!arguments.length) return _label;
- _label = utilFunctor(val);
- return section;
- };
+ function drawLocation(selection) {
+ var enabled = svgGeolocate.enabled;
+ layer = selection.selectAll('.layer-geolocate').data([0]);
+ layer.exit().remove();
+ var layerEnter = layer.enter().append('g').attr('class', 'layer-geolocate').style('display', enabled ? 'block' : 'none');
+ layerEnter.append('g').attr('class', 'geolocations');
+ layer = layerEnter.merge(layer);
- section.expandedByDefault = function (val) {
- if (!arguments.length) return _expandedByDefault;
- _expandedByDefault = utilFunctor(val);
- return section;
- };
+ if (enabled) {
+ update();
+ } else {
+ layerOff();
+ }
+ }
- section.shouldDisplay = function (val) {
- if (!arguments.length) return _shouldDisplay;
- _shouldDisplay = utilFunctor(val);
- return section;
- };
+ drawLocation.enabled = function (position, enabled) {
+ if (!arguments.length) return svgGeolocate.enabled;
+ _position = position;
+ svgGeolocate.enabled = enabled;
- section.content = function (val) {
- if (!arguments.length) return _content;
- _content = val;
- return section;
- };
+ if (svgGeolocate.enabled) {
+ showLayer();
+ layerOn();
+ } else {
+ hideLayer();
+ }
- section.disclosureContent = function (val) {
- if (!arguments.length) return _disclosureContent;
- _disclosureContent = val;
- return section;
+ return this;
};
- section.disclosureExpanded = function (val) {
- if (!arguments.length) return _disclosureExpanded;
- _disclosureExpanded = val;
- return section;
- }; // may be called multiple times
-
-
- section.render = function (selection) {
- _containerSelection = selection.selectAll('.section-' + id).data([0]);
+ init();
+ return drawLocation;
+ }
- var sectionEnter = _containerSelection.enter().append('div').attr('class', 'section section-' + id + ' ' + (_classes && _classes() || ''));
+ function svgLabels(projection, context) {
+ var path = d3_geoPath(projection);
+ var detected = utilDetect();
+ var baselineHack = detected.ie || detected.browser.toLowerCase() === 'edge' || detected.browser.toLowerCase() === 'firefox' && detected.version >= 70;
- _containerSelection = sectionEnter.merge(_containerSelection);
+ var _rdrawn = new RBush();
- _containerSelection.call(renderContent);
- };
+ var _rskipped = new RBush();
- section.reRender = function () {
- _containerSelection.call(renderContent);
- };
+ var _textWidthCache = {};
+ var _entitybboxes = {}; // Listed from highest to lowest priority
- section.selection = function () {
- return _containerSelection;
- };
+ var labelStack = [['line', 'aeroway', '*', 12], ['line', 'highway', 'motorway', 12], ['line', 'highway', 'trunk', 12], ['line', 'highway', 'primary', 12], ['line', 'highway', 'secondary', 12], ['line', 'highway', 'tertiary', 12], ['line', 'highway', '*', 12], ['line', 'railway', '*', 12], ['line', 'waterway', '*', 12], ['area', 'aeroway', '*', 12], ['area', 'amenity', '*', 12], ['area', 'building', '*', 12], ['area', 'historic', '*', 12], ['area', 'leisure', '*', 12], ['area', 'man_made', '*', 12], ['area', 'natural', '*', 12], ['area', 'shop', '*', 12], ['area', 'tourism', '*', 12], ['area', 'camp_site', '*', 12], ['point', 'aeroway', '*', 10], ['point', 'amenity', '*', 10], ['point', 'building', '*', 10], ['point', 'historic', '*', 10], ['point', 'leisure', '*', 10], ['point', 'man_made', '*', 10], ['point', 'natural', '*', 10], ['point', 'shop', '*', 10], ['point', 'tourism', '*', 10], ['point', 'camp_site', '*', 10], ['line', 'name', '*', 12], ['area', 'name', '*', 12], ['point', 'name', '*', 10]];
- section.disclosure = function () {
- return _disclosure;
- }; // may be called multiple times
+ function shouldSkipIcon(preset) {
+ var noIcons = ['building', 'landuse', 'natural'];
+ return noIcons.some(function (s) {
+ return preset.id.indexOf(s) >= 0;
+ });
+ }
+ function get(array, prop) {
+ return function (d, i) {
+ return array[i][prop];
+ };
+ }
- function renderContent(selection) {
- if (_shouldDisplay) {
- var shouldDisplay = _shouldDisplay();
+ function textWidth(text, size, elem) {
+ var c = _textWidthCache[size];
+ if (!c) c = _textWidthCache[size] = {};
- selection.classed('hide', !shouldDisplay);
+ if (c[text]) {
+ return c[text];
+ } else if (elem) {
+ c[text] = elem.getComputedTextLength();
+ return c[text];
+ } else {
+ var str = encodeURIComponent(text).match(/%[CDEFcdef]/g);
- if (!shouldDisplay) {
- selection.html('');
- return;
+ if (str === null) {
+ return size / 3 * 2 * text.length;
+ } else {
+ return size / 3 * (2 * text.length + str.length);
}
}
+ }
- if (_disclosureContent) {
- if (!_disclosure) {
- _disclosure = uiDisclosure(context, id.replace(/-/g, '_'), _expandedByDefault()).label(_label || '')
- /*.on('toggled', function(expanded) {
- if (expanded) { selection.node().parentNode.scrollTop += 200; }
- })*/
- .content(_disclosureContent);
- }
-
- if (_disclosureExpanded !== undefined) {
- _disclosure.expanded(_disclosureExpanded);
-
- _disclosureExpanded = undefined;
- }
+ function drawLinePaths(selection, entities, filter, classes, labels) {
+ var paths = selection.selectAll('path').filter(filter).data(entities, osmEntity.key); // exit
- selection.call(_disclosure);
- return;
- }
+ paths.exit().remove(); // enter/update
- if (_content) {
- selection.call(_content);
- }
+ paths.enter().append('path').style('stroke-width', get(labels, 'font-size')).attr('id', function (d) {
+ return 'ideditor-labelpath-' + d.id;
+ }).attr('class', classes).merge(paths).attr('d', get(labels, 'lineString'));
}
- return section;
- }
-
- // {
- // key: 'string', // required
- // value: 'string' // optional
- // }
- // -or-
- // {
- // qid: 'string' // brand wikidata (e.g. 'Q37158')
- // }
- //
+ function drawLineLabels(selection, entities, filter, classes, labels) {
+ var texts = selection.selectAll('text.' + classes).filter(filter).data(entities, osmEntity.key); // exit
- function uiTagReference(what) {
- var wikibase = what.qid ? services.wikidata : services.osmWikibase;
- var tagReference = {};
+ texts.exit().remove(); // enter
- var _button = select(null);
+ texts.enter().append('text').attr('class', function (d, i) {
+ return classes + ' ' + labels[i].classes + ' ' + d.id;
+ }).attr('dy', baselineHack ? '0.35em' : null).append('textPath').attr('class', 'textpath'); // update
- var _body = select(null);
+ selection.selectAll('text.' + classes).selectAll('.textpath').filter(filter).data(entities, osmEntity.key).attr('startOffset', '50%').attr('xlink:href', function (d) {
+ return '#ideditor-labelpath-' + d.id;
+ }).text(utilDisplayNameForPath);
+ }
- var _loaded;
+ function drawPointLabels(selection, entities, filter, classes, labels) {
+ var texts = selection.selectAll('text.' + classes).filter(filter).data(entities, osmEntity.key); // exit
- var _showing;
+ texts.exit().remove(); // enter/update
- function load() {
- if (!wikibase) return;
+ texts.enter().append('text').attr('class', function (d, i) {
+ return classes + ' ' + labels[i].classes + ' ' + d.id;
+ }).merge(texts).attr('x', get(labels, 'x')).attr('y', get(labels, 'y')).style('text-anchor', get(labels, 'textAnchor')).text(utilDisplayName).each(function (d, i) {
+ textWidth(utilDisplayName(d), labels[i].height, this);
+ });
+ }
- _button.classed('tag-reference-loading', true);
+ function drawAreaLabels(selection, entities, filter, classes, labels) {
+ entities = entities.filter(hasText);
+ labels = labels.filter(hasText);
+ drawPointLabels(selection, entities, filter, classes, labels);
- wikibase.getDocs(what, gotDocs);
+ function hasText(d, i) {
+ return labels[i].hasOwnProperty('x') && labels[i].hasOwnProperty('y');
+ }
}
- function gotDocs(err, docs) {
- _body.html('');
+ function drawAreaIcons(selection, entities, filter, classes, labels) {
+ var icons = selection.selectAll('use.' + classes).filter(filter).data(entities, osmEntity.key); // exit
- if (!docs || !docs.title) {
- _body.append('p').attr('class', 'tag-reference-description').html(_t.html('inspector.no_documentation_key'));
+ icons.exit().remove(); // enter/update
- done();
- return;
- }
+ icons.enter().append('use').attr('class', 'icon ' + classes).attr('width', '17px').attr('height', '17px').merge(icons).attr('transform', get(labels, 'transform')).attr('xlink:href', function (d) {
+ var preset = _mainPresetIndex.match(d, context.graph());
+ var picon = preset && preset.icon;
- if (docs.imageURL) {
- _body.append('img').attr('class', 'tag-reference-wiki-image').attr('src', docs.imageURL).on('load', function () {
- done();
- }).on('error', function () {
- select(this).remove();
- done();
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return '#' + picon + (isMaki ? '-15' : '');
+ }
+ });
+ }
+
+ function drawCollisionBoxes(selection, rtree, which) {
+ var classes = 'debug ' + which + ' ' + (which === 'debug-skipped' ? 'orange' : 'yellow');
+ var gj = [];
+
+ if (context.getDebug('collision')) {
+ gj = rtree.all().map(function (d) {
+ return {
+ type: 'Polygon',
+ coordinates: [[[d.minX, d.minY], [d.maxX, d.minY], [d.maxX, d.maxY], [d.minX, d.maxY], [d.minX, d.minY]]]
+ };
});
- } else {
- done();
}
- _body.append('p').attr('class', 'tag-reference-description').html(docs.description ? _mainLocalizer.htmlForLocalizedText(docs.description, docs.descriptionLocaleCode) : _t.html('inspector.no_documentation_key')).append('a').attr('class', 'tag-reference-edit').attr('target', '_blank').attr('title', _t('inspector.edit_reference')).attr('href', docs.editURL).call(svgIcon('#iD-icon-edit', 'inline'));
-
- if (docs.wiki) {
- _body.append('a').attr('class', 'tag-reference-link').attr('target', '_blank').attr('href', docs.wiki.url).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').html(_t.html(docs.wiki.text));
- } // Add link to info about "good changeset comments" - #2923
+ var boxes = selection.selectAll('.' + which).data(gj); // exit
+ boxes.exit().remove(); // enter/update
- if (what.key === 'comment') {
- _body.append('a').attr('class', 'tag-reference-comment-link').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', _t('commit.about_changeset_comments_link')).append('span').html(_t.html('commit.about_changeset_comments'));
- }
+ boxes.enter().append('path').attr('class', classes).merge(boxes).attr('d', d3_geoPath());
}
- function done() {
- _loaded = true;
+ function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) {
+ var wireframe = context.surface().classed('fill-wireframe');
+ var zoom = geoScaleToZoom(projection.scale());
+ var labelable = [];
+ var renderNodeAs = {};
+ var i, j, k, entity, geometry;
- _button.classed('tag-reference-loading', false);
+ for (i = 0; i < labelStack.length; i++) {
+ labelable.push([]);
+ }
- _body.classed('expanded', true).transition().duration(200).style('max-height', '200px').style('opacity', '1');
+ if (fullRedraw) {
+ _rdrawn.clear();
- _showing = true;
+ _rskipped.clear();
- _button.selectAll('svg.icon use').each(function () {
- var iconUse = select(this);
+ _entitybboxes = {};
+ } else {
+ for (i = 0; i < entities.length; i++) {
+ entity = entities[i];
+ var toRemove = [].concat(_entitybboxes[entity.id] || []).concat(_entitybboxes[entity.id + 'I'] || []);
- if (iconUse.attr('href') === '#iD-icon-info') {
- iconUse.attr('href', '#iD-icon-info-filled');
+ for (j = 0; j < toRemove.length; j++) {
+ _rdrawn.remove(toRemove[j]);
+
+ _rskipped.remove(toRemove[j]);
+ }
}
- });
- }
+ } // Loop through all the entities to do some preprocessing
- function hide() {
- _body.transition().duration(200).style('max-height', '0px').style('opacity', '0').on('end', function () {
- _body.classed('expanded', false);
- });
- _showing = false;
+ for (i = 0; i < entities.length; i++) {
+ entity = entities[i];
+ geometry = entity.geometry(graph); // Insert collision boxes around interesting points/vertices
- _button.selectAll('svg.icon use').each(function () {
- var iconUse = select(this);
+ if (geometry === 'point' || geometry === 'vertex' && isInterestingVertex(entity)) {
+ var hasDirections = entity.directions(graph, projection).length;
+ var markerPadding;
- if (iconUse.attr('href') === '#iD-icon-info-filled') {
- iconUse.attr('href', '#iD-icon-info');
- }
- });
- }
+ if (!wireframe && geometry === 'point' && !(zoom >= 18 && hasDirections)) {
+ renderNodeAs[entity.id] = 'point';
+ markerPadding = 20; // extra y for marker height
+ } else {
+ renderNodeAs[entity.id] = 'vertex';
+ markerPadding = 0;
+ }
- tagReference.button = function (selection, klass, iconName) {
- _button = selection.selectAll('.tag-reference-button').data([0]);
- _button = _button.enter().append('button').attr('class', 'tag-reference-button ' + (klass || '')).attr('title', _t('icons.information')).call(svgIcon('#iD-icon-' + (iconName || 'inspect'))).merge(_button);
+ var coord = projection(entity.loc);
+ var nodePadding = 10;
+ var bbox = {
+ minX: coord[0] - nodePadding,
+ minY: coord[1] - nodePadding - markerPadding,
+ maxX: coord[0] + nodePadding,
+ maxY: coord[1] + nodePadding
+ };
+ doInsert(bbox, entity.id + 'P');
+ } // From here on, treat vertices like points
- _button.on('click', function (d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- this.blur(); // avoid keeping focus on the button - #4641
- if (_showing) {
- hide();
- } else if (_loaded) {
- done();
- } else {
- load();
- }
- });
- };
+ if (geometry === 'vertex') {
+ geometry = 'point';
+ } // Determine which entities are label-able
- tagReference.body = function (selection) {
- var itemID = what.qid || what.key + '-' + (what.value || '');
- _body = selection.selectAll('.tag-reference-body').data([itemID], function (d) {
- return d;
- });
- _body.exit().remove();
+ var preset = geometry === 'area' && _mainPresetIndex.match(entity, graph);
+ var icon = preset && !shouldSkipIcon(preset) && preset.icon;
+ if (!icon && !utilDisplayName(entity)) continue;
- _body = _body.enter().append('div').attr('class', 'tag-reference-body').style('max-height', '0').style('opacity', '0').merge(_body);
+ for (k = 0; k < labelStack.length; k++) {
+ var matchGeom = labelStack[k][0];
+ var matchKey = labelStack[k][1];
+ var matchVal = labelStack[k][2];
+ var hasVal = entity.tags[matchKey];
- if (_showing === false) {
- hide();
+ if (geometry === matchGeom && hasVal && (matchVal === '*' || matchVal === hasVal)) {
+ labelable[k].push(entity);
+ break;
+ }
+ }
}
- };
- tagReference.showing = function (val) {
- if (!arguments.length) return _showing;
- _showing = val;
- return tagReference;
- };
+ var positions = {
+ point: [],
+ line: [],
+ area: []
+ };
+ var labelled = {
+ point: [],
+ line: [],
+ area: []
+ }; // Try and find a valid label for labellable entities
- return tagReference;
- }
+ for (k = 0; k < labelable.length; k++) {
+ var fontSize = labelStack[k][3];
- function uiSectionRawTagEditor(id, context) {
- var section = uiSection(id, context).classes('raw-tag-editor').label(function () {
- var count = Object.keys(_tags).filter(function (d) {
- return d;
- }).length;
- return _t('inspector.title_count', {
- title: _t.html('inspector.tags'),
- count: count
- });
- }).expandedByDefault(false).disclosureContent(renderDisclosureContent);
- var taginfo = services.taginfo;
- var dispatch = dispatch$8('change');
- var availableViews = [{
- id: 'list',
- icon: '#fas-th-list'
- }, {
- id: 'text',
- icon: '#fas-i-cursor'
- }];
+ for (i = 0; i < labelable[k].length; i++) {
+ entity = labelable[k][i];
+ geometry = entity.geometry(graph);
+ var getName = geometry === 'line' ? utilDisplayNameForPath : utilDisplayName;
+ var name = getName(entity);
+ var width = name && textWidth(name, fontSize);
+ var p = null;
- var _tagView = corePreferences('raw-tag-editor-view') || 'list'; // 'list, 'text'
+ if (geometry === 'point' || geometry === 'vertex') {
+ // no point or vertex labels in wireframe mode
+ // no vertex labels at low zooms (vertices have no icons)
+ if (wireframe) continue;
+ var renderAs = renderNodeAs[entity.id];
+ if (renderAs === 'vertex' && zoom < 17) continue;
+ p = getPointLabel(entity, width, fontSize, renderAs);
+ } else if (geometry === 'line') {
+ p = getLineLabel(entity, width, fontSize);
+ } else if (geometry === 'area') {
+ p = getAreaLabel(entity, width, fontSize);
+ }
+ if (p) {
+ if (geometry === 'vertex') {
+ geometry = 'point';
+ } // treat vertex like point
- var _readOnlyTags = []; // the keys in the order we want them to display
- var _orderedKeys = [];
- var _showBlank = false;
- var _pendingChange = null;
+ p.classes = geometry + ' tag-' + labelStack[k][1];
+ positions[geometry].push(p);
+ labelled[geometry].push(entity);
+ }
+ }
+ }
- var _state;
+ function isInterestingVertex(entity) {
+ var selectedIDs = context.selectedIDs();
+ return entity.hasInterestingTags() || entity.isEndpoint(graph) || entity.isConnected(graph) || selectedIDs.indexOf(entity.id) !== -1 || graph.parentWays(entity).some(function (parent) {
+ return selectedIDs.indexOf(parent.id) !== -1;
+ });
+ }
- var _presets;
+ function getPointLabel(entity, width, height, geometry) {
+ var y = geometry === 'point' ? -12 : 0;
+ var pointOffsets = {
+ ltr: [15, y, 'start'],
+ rtl: [-15, y, 'end']
+ };
+ var textDirection = _mainLocalizer.textDirection();
+ var coord = projection(entity.loc);
+ var textPadding = 2;
+ var offset = pointOffsets[textDirection];
+ var p = {
+ height: height,
+ width: width,
+ x: coord[0] + offset[0],
+ y: coord[1] + offset[1],
+ textAnchor: offset[2]
+ }; // insert a collision box for the text label..
- var _tags;
+ var bbox;
- var _entityIDs;
+ if (textDirection === 'rtl') {
+ bbox = {
+ minX: p.x - width - textPadding,
+ minY: p.y - height / 2 - textPadding,
+ maxX: p.x + textPadding,
+ maxY: p.y + height / 2 + textPadding
+ };
+ } else {
+ bbox = {
+ minX: p.x - textPadding,
+ minY: p.y - height / 2 - textPadding,
+ maxX: p.x + width + textPadding,
+ maxY: p.y + height / 2 + textPadding
+ };
+ }
- var _didInteract = false;
+ if (tryInsert([bbox], entity.id, true)) {
+ return p;
+ }
+ }
- function interacted() {
- _didInteract = true;
- }
+ function getLineLabel(entity, width, height) {
+ var viewport = geoExtent(context.projection.clipExtent()).polygon();
+ var points = graph.childNodes(entity).map(function (node) {
+ return projection(node.loc);
+ });
+ var length = geoPathLength(points);
+ if (length < width + 20) return; // % along the line to attempt to place the label
- function renderDisclosureContent(wrap) {
- // remove deleted keys
- _orderedKeys = _orderedKeys.filter(function (key) {
- return _tags[key] !== undefined;
- }); // When switching to a different entity or changing the state (hover/select)
- // reorder the keys alphabetically.
- // We trigger this by emptying the `_orderedKeys` array, then it will be rebuilt here.
- // Otherwise leave their order alone - #5857, #5927
+ var lineOffsets = [50, 45, 55, 40, 60, 35, 65, 30, 70, 25, 75, 20, 80, 15, 95, 10, 90, 5, 95];
+ var padding = 3;
- var all = Object.keys(_tags).sort();
- var missingKeys = utilArrayDifference(all, _orderedKeys);
+ for (var i = 0; i < lineOffsets.length; i++) {
+ var offset = lineOffsets[i];
+ var middle = offset / 100 * length;
+ var start = middle - width / 2;
+ if (start < 0 || start + width > length) continue; // generate subpath and ignore paths that are invalid or don't cross viewport.
- for (var i in missingKeys) {
- _orderedKeys.push(missingKeys[i]);
- } // assemble row data
+ var sub = subpath(points, start, start + width);
+ if (!sub || !geoPolygonIntersectsPolygon(viewport, sub, true)) {
+ continue;
+ }
- var rowData = _orderedKeys.map(function (key, i) {
- return {
- index: i,
- key: key,
- value: _tags[key]
- };
- }); // append blank row last, if necessary
+ var isReverse = reverse(sub);
+ if (isReverse) {
+ sub = sub.reverse();
+ }
- if (!rowData.length || _showBlank) {
- _showBlank = false;
- rowData.push({
- index: rowData.length,
- key: '',
- value: ''
- });
- } // View Options
+ var bboxes = [];
+ var boxsize = (height + 2) / 2;
+ for (var j = 0; j < sub.length - 1; j++) {
+ var a = sub[j];
+ var b = sub[j + 1]; // split up the text into small collision boxes
- var options = wrap.selectAll('.raw-tag-options').data([0]);
- options.exit().remove();
- var optionsEnter = options.enter().insert('div', ':first-child').attr('class', 'raw-tag-options');
- var optionEnter = optionsEnter.selectAll('.raw-tag-option').data(availableViews, function (d) {
- return d.id;
- }).enter();
- optionEnter.append('button').attr('class', function (d) {
- return 'raw-tag-option raw-tag-option-' + d.id + (_tagView === d.id ? ' selected' : '');
- }).attr('title', function (d) {
- return _t('icons.' + d.id);
- }).on('click', function (d3_event, d) {
- _tagView = d.id;
- corePreferences('raw-tag-editor-view', d.id);
- wrap.selectAll('.raw-tag-option').classed('selected', function (datum) {
- return datum === d;
- });
- wrap.selectAll('.tag-text').classed('hide', d.id !== 'text').each(setTextareaHeight);
- wrap.selectAll('.tag-list, .add-row').classed('hide', d.id !== 'list');
- }).each(function (d) {
- select(this).call(svgIcon(d.icon));
- }); // View as Text
+ var num = Math.max(1, Math.floor(geoVecLength(a, b) / boxsize / 2));
- var textData = rowsToText(rowData);
- var textarea = wrap.selectAll('.tag-text').data([0]);
- textarea = textarea.enter().append('textarea').attr('class', 'tag-text' + (_tagView !== 'text' ? ' hide' : '')).call(utilNoAuto).attr('placeholder', _t('inspector.key_value')).attr('spellcheck', 'false').merge(textarea);
- textarea.call(utilGetSetValue, textData).each(setTextareaHeight).on('input', setTextareaHeight).on('focus', interacted).on('blur', textChanged).on('change', textChanged); // View as List
+ for (var box = 0; box < num; box++) {
+ var p = geoVecInterp(a, b, box / num);
+ var x0 = p[0] - boxsize - padding;
+ var y0 = p[1] - boxsize - padding;
+ var x1 = p[0] + boxsize + padding;
+ var y1 = p[1] + boxsize + padding;
+ bboxes.push({
+ minX: Math.min(x0, x1),
+ minY: Math.min(y0, y1),
+ maxX: Math.max(x0, x1),
+ maxY: Math.max(y0, y1)
+ });
+ }
+ }
- var list = wrap.selectAll('.tag-list').data([0]);
- list = list.enter().append('ul').attr('class', 'tag-list' + (_tagView !== 'list' ? ' hide' : '')).merge(list); // Container for the Add button
+ if (tryInsert(bboxes, entity.id, false)) {
+ // accept this one
+ return {
+ 'font-size': height + 2,
+ lineString: lineString(sub),
+ startOffset: offset + '%'
+ };
+ }
+ }
- var addRowEnter = wrap.selectAll('.add-row').data([0]).enter().append('div').attr('class', 'add-row' + (_tagView !== 'list' ? ' hide' : ''));
- addRowEnter.append('button').attr('class', 'add-tag').call(svgIcon('#iD-icon-plus', 'light')).on('click', addTag);
- addRowEnter.append('div').attr('class', 'space-value'); // preserve space
+ function reverse(p) {
+ var angle = Math.atan2(p[1][1] - p[0][1], p[1][0] - p[0][0]);
+ return !(p[0][0] < p[p.length - 1][0] && angle < Math.PI / 2 && angle > -Math.PI / 2);
+ }
- addRowEnter.append('div').attr('class', 'space-buttons'); // preserve space
- // Tag list items
+ function lineString(points) {
+ return 'M' + points.join('L');
+ }
- var items = list.selectAll('.tag-row').data(rowData, function (d) {
- return d.key;
- });
- items.exit().each(unbind).remove(); // Enter
+ function subpath(points, from, to) {
+ var sofar = 0;
+ var start, end, i0, i1;
- var itemsEnter = items.enter().append('li').attr('class', 'tag-row').classed('readonly', isReadOnly);
- var innerWrap = itemsEnter.append('div').attr('class', 'inner-wrap');
- innerWrap.append('div').attr('class', 'key-wrap').append('input').property('type', 'text').attr('class', 'key').call(utilNoAuto).on('focus', interacted).on('blur', keyChange).on('change', keyChange);
- innerWrap.append('div').attr('class', 'value-wrap').append('input').property('type', 'text').attr('class', 'value').call(utilNoAuto).on('focus', interacted).on('blur', valueChange).on('change', valueChange).on('keydown.push-more', pushMore);
- innerWrap.append('button').attr('class', 'form-field-button remove').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete')); // Update
+ for (var i = 0; i < points.length - 1; i++) {
+ var a = points[i];
+ var b = points[i + 1];
+ var current = geoVecLength(a, b);
+ var portion;
- items = items.merge(itemsEnter).sort(function (a, b) {
- return a.index - b.index;
- });
- items.each(function (d) {
- var row = select(this);
- var key = row.select('input.key'); // propagate bound data
+ if (!start && sofar + current >= from) {
+ portion = (from - sofar) / current;
+ start = [a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1])];
+ i0 = i + 1;
+ }
- var value = row.select('input.value'); // propagate bound data
+ if (!end && sofar + current >= to) {
+ portion = (to - sofar) / current;
+ end = [a[0] + portion * (b[0] - a[0]), a[1] + portion * (b[1] - a[1])];
+ i1 = i + 1;
+ }
- if (_entityIDs && taginfo && _state !== 'hover') {
- bindTypeahead(key, value);
+ sofar += current;
+ }
+
+ var result = points.slice(i0, i1);
+ result.unshift(start);
+ result.push(end);
+ return result;
}
+ }
- var referenceOptions = {
- key: d.key
- };
+ function getAreaLabel(entity, width, height) {
+ var centroid = path.centroid(entity.asGeoJSON(graph));
+ var extent = entity.extent(graph);
+ var areaWidth = projection(extent[1])[0] - projection(extent[0])[0];
+ if (isNaN(centroid[0]) || areaWidth < 20) return;
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ var picon = preset && preset.icon;
+ var iconSize = 17;
+ var padding = 2;
+ var p = {};
- if (typeof d.value === 'string') {
- referenceOptions.value = d.value;
+ if (picon) {
+ // icon and label..
+ if (addIcon()) {
+ addLabel(iconSize + padding);
+ return p;
+ }
+ } else {
+ // label only..
+ if (addLabel(0)) {
+ return p;
+ }
}
- var reference = uiTagReference(referenceOptions);
+ function addIcon() {
+ var iconX = centroid[0] - iconSize / 2;
+ var iconY = centroid[1] - iconSize / 2;
+ var bbox = {
+ minX: iconX,
+ minY: iconY,
+ maxX: iconX + iconSize,
+ maxY: iconY + iconSize
+ };
- if (_state === 'hover') {
- reference.showing(false);
+ if (tryInsert([bbox], entity.id + 'I', true)) {
+ p.transform = 'translate(' + iconX + ',' + iconY + ')';
+ return true;
+ }
+
+ return false;
}
- row.select('.inner-wrap') // propagate bound data
- .call(reference.button);
- row.call(reference.body);
- row.select('button.remove'); // propagate bound data
- });
- items.selectAll('input.key').attr('title', function (d) {
- return d.key;
- }).call(utilGetSetValue, function (d) {
- return d.key;
- }).attr('readonly', function (d) {
- return isReadOnly(d) || typeof d.value !== 'string' || null;
- });
- items.selectAll('input.value').attr('title', function (d) {
- return Array.isArray(d.value) ? d.value.filter(Boolean).join('\n') : d.value;
- }).classed('mixed', function (d) {
- return Array.isArray(d.value);
- }).attr('placeholder', function (d) {
- return typeof d.value === 'string' ? null : _t('inspector.multiple_values');
- }).call(utilGetSetValue, function (d) {
- return typeof d.value === 'string' ? d.value : '';
- }).attr('readonly', function (d) {
- return isReadOnly(d) || null;
- });
- items.selectAll('button.remove').on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'down', removeTag); // 'click' fires too late - #5878
- }
+ function addLabel(yOffset) {
+ if (width && areaWidth >= width + 20) {
+ var labelX = centroid[0];
+ var labelY = centroid[1] + yOffset;
+ var bbox = {
+ minX: labelX - width / 2 - padding,
+ minY: labelY - height / 2 - padding,
+ maxX: labelX + width / 2 + padding,
+ maxY: labelY + height / 2 + padding
+ };
- function isReadOnly(d) {
- for (var i = 0; i < _readOnlyTags.length; i++) {
- if (d.key.match(_readOnlyTags[i]) !== null) {
- return true;
- }
- }
+ if (tryInsert([bbox], entity.id, true)) {
+ p.x = labelX;
+ p.y = labelY;
+ p.textAnchor = 'middle';
+ p.height = height;
+ return true;
+ }
+ }
- return false;
- }
+ return false;
+ }
+ } // force insert a singular bounding box
+ // singular box only, no array, id better be unique
- function setTextareaHeight() {
- if (_tagView !== 'text') return;
- var selection = select(this);
- var matches = selection.node().value.match(/\n/g);
- var lineCount = 2 + Number(matches && matches.length);
- var lineHeight = 20;
- selection.style('height', lineCount * lineHeight + 'px');
- }
- function stringify(s) {
- return JSON.stringify(s).slice(1, -1); // without leading/trailing "
- }
+ function doInsert(bbox, id) {
+ bbox.id = id;
+ var oldbox = _entitybboxes[id];
- function unstringify(s) {
- var leading = '';
- var trailing = '';
+ if (oldbox) {
+ _rdrawn.remove(oldbox);
+ }
- if (s.length < 1 || s.charAt(0) !== '"') {
- leading = '"';
- }
+ _entitybboxes[id] = bbox;
- if (s.length < 2 || s.charAt(s.length - 1) !== '"' || s.charAt(s.length - 1) === '"' && s.charAt(s.length - 2) === '\\') {
- trailing = '"';
+ _rdrawn.insert(bbox);
}
- return JSON.parse(leading + s + trailing);
- }
+ function tryInsert(bboxes, id, saveSkipped) {
+ var skipped = false;
- function rowsToText(rows) {
- var str = rows.filter(function (row) {
- return row.key && row.key.trim() !== '';
- }).map(function (row) {
- var rawVal = row.value;
- if (typeof rawVal !== 'string') rawVal = '*';
- var val = rawVal ? stringify(rawVal) : '';
- return stringify(row.key) + '=' + val;
- }).join('\n');
+ for (var i = 0; i < bboxes.length; i++) {
+ var bbox = bboxes[i];
+ bbox.id = id; // Check that label is visible
- if (_state !== 'hover' && str.length) {
- return str + '\n';
- }
+ if (bbox.minX < 0 || bbox.minY < 0 || bbox.maxX > dimensions[0] || bbox.maxY > dimensions[1]) {
+ skipped = true;
+ break;
+ }
- return str;
- }
+ if (_rdrawn.collides(bbox)) {
+ skipped = true;
+ break;
+ }
+ }
- function textChanged() {
- var newText = this.value.trim();
- var newTags = {};
- newText.split('\n').forEach(function (row) {
- var m = row.match(/^\s*([^=]+)=(.*)$/);
+ _entitybboxes[id] = bboxes;
- if (m !== null) {
- var k = context.cleanTagKey(unstringify(m[1].trim()));
- var v = context.cleanTagValue(unstringify(m[2].trim()));
- newTags[k] = v;
+ if (skipped) {
+ if (saveSkipped) {
+ _rskipped.load(bboxes);
+ }
+ } else {
+ _rdrawn.load(bboxes);
}
- });
- var tagDiff = utilTagDiff(_tags, newTags);
- if (!tagDiff.length) return;
- _pendingChange = _pendingChange || {};
- tagDiff.forEach(function (change) {
- if (isReadOnly({
- key: change.key
- })) return; // skip unchanged multiselection placeholders
- if (change.newVal === '*' && typeof change.oldVal !== 'string') return;
+ return !skipped;
+ }
- if (change.type === '-') {
- _pendingChange[change.key] = undefined;
- } else if (change.type === '+') {
- _pendingChange[change.key] = change.newVal || '';
- }
+ var layer = selection.selectAll('.layer-osm.labels');
+ layer.selectAll('.labels-group').data(['halo', 'label', 'debug']).enter().append('g').attr('class', function (d) {
+ return 'labels-group ' + d;
});
+ var halo = layer.selectAll('.labels-group.halo');
+ var label = layer.selectAll('.labels-group.label');
+ var debug = layer.selectAll('.labels-group.debug'); // points
- if (Object.keys(_pendingChange).length === 0) {
- _pendingChange = null;
- return;
- }
+ drawPointLabels(label, labelled.point, filter, 'pointlabel', positions.point);
+ drawPointLabels(halo, labelled.point, filter, 'pointlabel-halo', positions.point); // lines
- scheduleChange();
- }
+ drawLinePaths(layer, labelled.line, filter, '', positions.line);
+ drawLineLabels(label, labelled.line, filter, 'linelabel', positions.line);
+ drawLineLabels(halo, labelled.line, filter, 'linelabel-halo', positions.line); // areas
- function pushMore(d3_event) {
- // if pressing Tab on the last value field with content, add a blank row
- if (d3_event.keyCode === 9 && !d3_event.shiftKey && section.selection().selectAll('.tag-list li:last-child input.value').node() === this && utilGetSetValue(select(this))) {
- addTag();
- }
+ drawAreaLabels(label, labelled.area, filter, 'arealabel', positions.area);
+ drawAreaLabels(halo, labelled.area, filter, 'arealabel-halo', positions.area);
+ drawAreaIcons(label, labelled.area, filter, 'areaicon', positions.area);
+ drawAreaIcons(halo, labelled.area, filter, 'areaicon-halo', positions.area); // debug
+
+ drawCollisionBoxes(debug, _rskipped, 'debug-skipped');
+ drawCollisionBoxes(debug, _rdrawn, 'debug-drawn');
+ layer.call(filterLabels);
}
- function bindTypeahead(key, value) {
- if (isReadOnly(key.datum())) return;
+ function filterLabels(selection) {
+ var drawLayer = selection.selectAll('.layer-osm.labels');
+ var layers = drawLayer.selectAll('.labels-group.halo, .labels-group.label');
+ layers.selectAll('.nolabel').classed('nolabel', false);
+ var mouse = context.map().mouse();
+ var graph = context.graph();
+ var selectedIDs = context.selectedIDs();
+ var ids = [];
+ var pad, bbox; // hide labels near the mouse
- if (Array.isArray(value.datum().value)) {
- value.call(uiCombobox(context, 'tag-value').minItems(1).fetcher(function (value, callback) {
- var keyString = utilGetSetValue(key);
- if (!_tags[keyString]) return;
+ if (mouse) {
+ pad = 20;
+ bbox = {
+ minX: mouse[0] - pad,
+ minY: mouse[1] - pad,
+ maxX: mouse[0] + pad,
+ maxY: mouse[1] + pad
+ };
- var data = _tags[keyString].filter(Boolean).map(function (tagValue) {
- return {
- value: tagValue,
- title: tagValue
- };
- });
+ var nearMouse = _rdrawn.search(bbox).map(function (entity) {
+ return entity.id;
+ });
- callback(data);
- }));
- return;
- }
+ ids.push.apply(ids, nearMouse);
+ } // hide labels on selected nodes (they look weird when dragging / haloed)
- var geometry = context.graph().geometry(_entityIDs[0]);
- key.call(uiCombobox(context, 'tag-key').fetcher(function (value, callback) {
- taginfo.keys({
- debounce: true,
- geometry: geometry,
- query: value
- }, function (err, data) {
- if (!err) {
- var filtered = data.filter(function (d) {
- return _tags[d.value] === undefined;
- });
- callback(sort(value, filtered));
- }
- });
- }));
- value.call(uiCombobox(context, 'tag-value').fetcher(function (value, callback) {
- taginfo.values({
- debounce: true,
- key: utilGetSetValue(key),
- geometry: geometry,
- query: value
- }, function (err, data) {
- if (!err) callback(sort(value, data));
- });
- }));
- function sort(value, data) {
- var sameletter = [];
- var other = [];
+ for (var i = 0; i < selectedIDs.length; i++) {
+ var entity = graph.hasEntity(selectedIDs[i]);
- for (var i = 0; i < data.length; i++) {
- if (data[i].value.substring(0, value.length) === value) {
- sameletter.push(data[i]);
- } else {
- other.push(data[i]);
- }
+ if (entity && entity.type === 'node') {
+ ids.push(selectedIDs[i]);
}
-
- return sameletter.concat(other);
}
- }
-
- function unbind() {
- var row = select(this);
- row.selectAll('input.key').call(uiCombobox.off, context);
- row.selectAll('input.value').call(uiCombobox.off, context);
- }
-
- function keyChange(d3_event, d) {
- if (select(this).attr('readonly')) return;
- var kOld = d.key; // exit if we are currently about to delete this row anyway - #6366
-
- if (_pendingChange && _pendingChange.hasOwnProperty(kOld) && _pendingChange[kOld] === undefined) return;
- var kNew = context.cleanTagKey(this.value.trim()); // allow no change if the key should be readonly
- if (isReadOnly({
- key: kNew
- })) {
- this.value = kOld;
- return;
- }
+ layers.selectAll(utilEntitySelector(ids)).classed('nolabel', true); // draw the mouse bbox if debugging is on..
- if (kNew && kNew !== kOld && _tags[kNew] !== undefined) {
- // new key is already in use, switch focus to the existing row
- this.value = kOld; // reset the key
+ var debug = selection.selectAll('.labels-group.debug');
+ var gj = [];
- section.selection().selectAll('.tag-list input.value').each(function (d) {
- if (d.key === kNew) {
- // send focus to that other value combo instead
- var input = select(this).node();
- input.focus();
- input.select();
- }
- });
- return;
+ if (context.getDebug('collision')) {
+ gj = bbox ? [{
+ type: 'Polygon',
+ coordinates: [[[bbox.minX, bbox.minY], [bbox.maxX, bbox.minY], [bbox.maxX, bbox.maxY], [bbox.minX, bbox.maxY], [bbox.minX, bbox.minY]]]
+ }] : [];
}
- var row = this.parentNode.parentNode;
- var inputVal = select(row).selectAll('input.value');
- var vNew = context.cleanTagValue(utilGetSetValue(inputVal));
- _pendingChange = _pendingChange || {};
+ var box = debug.selectAll('.debug-mouse').data(gj); // exit
- if (kOld) {
- _pendingChange[kOld] = undefined;
- }
+ box.exit().remove(); // enter/update
- _pendingChange[kNew] = vNew; // update the ordered key index so this row doesn't change position
+ box.enter().append('path').attr('class', 'debug debug-mouse yellow').merge(box).attr('d', d3_geoPath());
+ }
- var existingKeyIndex = _orderedKeys.indexOf(kOld);
+ var throttleFilterLabels = throttle(filterLabels, 100);
- if (existingKeyIndex !== -1) _orderedKeys[existingKeyIndex] = kNew;
- d.key = kNew; // update datum to avoid exit/enter on tag update
+ drawLabels.observe = function (selection) {
+ var listener = function listener() {
+ throttleFilterLabels(selection);
+ };
- d.value = vNew;
- this.value = kNew;
- utilGetSetValue(inputVal, vNew);
- scheduleChange();
- }
+ selection.on('mousemove.hidelabels', listener);
+ context.on('enter.hidelabels', listener);
+ };
- function valueChange(d3_event, d) {
- if (isReadOnly(d)) return; // exit if this is a multiselection and no value was entered
+ drawLabels.off = function (selection) {
+ throttleFilterLabels.cancel();
+ selection.on('mousemove.hidelabels', null);
+ context.on('enter.hidelabels', null);
+ };
- if (typeof d.value !== 'string' && !this.value) return; // exit if we are currently about to delete this row anyway - #6366
+ return drawLabels;
+ }
- if (_pendingChange && _pendingChange.hasOwnProperty(d.key) && _pendingChange[d.key] === undefined) return;
- _pendingChange = _pendingChange || {};
- _pendingChange[d.key] = context.cleanTagValue(this.value);
- scheduleChange();
- }
+ var _layerEnabled$1 = false;
- function removeTag(d3_event, d) {
- if (isReadOnly(d)) return;
+ var _qaService$1;
- if (d.key === '') {
- // removing the blank row
- _showBlank = false;
- section.reRender();
- } else {
- // remove the key from the ordered key index
- _orderedKeys = _orderedKeys.filter(function (key) {
- return key !== d.key;
- });
- _pendingChange = _pendingChange || {};
- _pendingChange[d.key] = undefined;
- scheduleChange();
- }
- }
+ function svgImproveOSM(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ return dispatch.call('change');
+ }, 1000);
- function addTag() {
- // Delay render in case this click is blurring an edited combo.
- // Without the setTimeout, the `content` render would wipe out the pending tag change.
- window.setTimeout(function () {
- _showBlank = true;
- section.reRender();
- section.selection().selectAll('.tag-list li:last-child input.key').node().focus();
- }, 20);
- }
+ var minZoom = 12;
+ var touchLayer = select(null);
+ var drawLayer = select(null);
+ var layerVisible = false;
- function scheduleChange() {
- // Cache IDs in case the editor is reloaded before the change event is called. - #6028
- var entityIDs = _entityIDs; // Delay change in case this change is blurring an edited combo. - #5878
+ function markerPath(selection, klass) {
+ selection.attr('class', klass).attr('transform', 'translate(-10, -28)').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
+ } // Loosely-coupled improveOSM service for fetching issues
- window.setTimeout(function () {
- if (!_pendingChange) return;
- dispatch.call('change', this, entityIDs, _pendingChange);
- _pendingChange = null;
- }, 10);
- }
- section.state = function (val) {
- if (!arguments.length) return _state;
+ function getService() {
+ if (services.improveOSM && !_qaService$1) {
+ _qaService$1 = services.improveOSM;
- if (_state !== val) {
- _orderedKeys = [];
- _state = val;
+ _qaService$1.on('loaded', throttledRedraw);
+ } else if (!services.improveOSM && _qaService$1) {
+ _qaService$1 = null;
}
- return section;
- };
+ return _qaService$1;
+ } // Show the markers
- section.presets = function (val) {
- if (!arguments.length) return _presets;
- _presets = val;
- if (_presets && _presets.length && _presets[0].isFallback()) {
- section.disclosureExpanded(true); // don't collapse the disclosure if the mapper used the raw tag editor - #1881
- } else if (!_didInteract) {
- section.disclosureExpanded(null);
+ function editOn() {
+ if (!layerVisible) {
+ layerVisible = true;
+ drawLayer.style('display', 'block');
}
+ } // Immediately remove the markers and their touch targets
- return section;
- };
-
- section.tags = function (val) {
- if (!arguments.length) return _tags;
- _tags = val;
- return section;
- };
-
- section.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- if (!_entityIDs || !val || !utilArrayIdentical(_entityIDs, val)) {
- _entityIDs = val;
- _orderedKeys = [];
+ function editOff() {
+ if (layerVisible) {
+ layerVisible = false;
+ drawLayer.style('display', 'none');
+ drawLayer.selectAll('.qaItem.improveOSM').remove();
+ touchLayer.selectAll('.qaItem.improveOSM').remove();
}
+ } // Enable the layer. This shows the markers and transitions them to visible.
- return section;
- }; // pass an array of regular expressions to test against the tag key
+ function layerOn() {
+ editOn();
+ drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
+ return dispatch.call('change');
+ });
+ } // Disable the layer. This transitions the layer invisible and then hides the markers.
- section.readOnlyTags = function (val) {
- if (!arguments.length) return _readOnlyTags;
- _readOnlyTags = val;
- return section;
- };
- return utilRebind(section, dispatch, 'on');
- }
+ function layerOff() {
+ throttledRedraw.cancel();
+ drawLayer.interrupt();
+ touchLayer.selectAll('.qaItem.improveOSM').remove();
+ drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
+ editOff();
+ dispatch.call('change');
+ });
+ } // Update the issue markers
- function uiDataEditor(context) {
- var dataHeader = uiDataHeader();
- var rawTagEditor = uiSectionRawTagEditor('custom-data-tag-editor', context).expandedByDefault(true).readOnlyTags([/./]);
- var _datum;
+ function updateMarkers() {
+ if (!layerVisible || !_layerEnabled$1) return;
+ var service = getService();
+ var selectedID = context.selectedErrorID();
+ var data = service ? service.getItems(projection) : [];
+ var getTransform = svgPointTransform(projection); // Draw markers..
- function dataEditor(selection) {
- var header = selection.selectAll('.header').data([0]);
- var headerEnter = header.enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'close').on('click', function () {
- context.enter(modeBrowse(context));
- }).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('map_data.title'));
- var body = selection.selectAll('.body').data([0]);
- body = body.enter().append('div').attr('class', 'body').merge(body);
- var editor = body.selectAll('.data-editor').data([0]); // enter/update
+ var markers = drawLayer.selectAll('.qaItem.improveOSM').data(data, function (d) {
+ return d.id;
+ }); // exit
- editor.enter().append('div').attr('class', 'modal-section data-editor').merge(editor).call(dataHeader.datum(_datum));
- var rte = body.selectAll('.raw-tag-editor').data([0]); // enter/update
+ markers.exit().remove(); // enter
- rte.enter().append('div').attr('class', 'raw-tag-editor data-editor').merge(rte).call(rawTagEditor.tags(_datum && _datum.properties || {}).state('hover').render).selectAll('textarea.tag-text').attr('readonly', true).classed('readonly', true);
- }
+ var markersEnter = markers.enter().append('g').attr('class', function (d) {
+ return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
+ });
+ markersEnter.append('polygon').call(markerPath, 'shadow');
+ markersEnter.append('ellipse').attr('cx', 0).attr('cy', 0).attr('rx', 4.5).attr('ry', 2).attr('class', 'stroke');
+ markersEnter.append('polygon').attr('fill', 'currentColor').call(markerPath, 'qaItem-fill');
+ markersEnter.append('use').attr('transform', 'translate(-6.5, -23)').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('xlink:href', function (d) {
+ var picon = d.icon;
- dataEditor.datum = function (val) {
- if (!arguments.length) return _datum;
- _datum = val;
- return this;
- };
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return "#".concat(picon).concat(isMaki ? '-11' : '');
+ }
+ }); // update
- return dataEditor;
- }
+ markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
+ return d.id === selectedID;
+ }).attr('transform', getTransform); // Draw targets..
- var sexagesimal = {exports: {}};
+ if (touchLayer.empty()) return;
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var targets = touchLayer.selectAll('.qaItem.improveOSM').data(data, function (d) {
+ return d.id;
+ }); // exit
- sexagesimal.exports = element;
- var pair_1 = sexagesimal.exports.pair = pair;
- sexagesimal.exports.format = format;
- sexagesimal.exports.formatPair = formatPair;
- sexagesimal.exports.coordToDMS = coordToDMS;
+ targets.exit().remove(); // enter/update
- function element(input, dims) {
- var result = search(input, dims);
- return result === null ? null : result.val;
- }
+ targets.enter().append('rect').attr('width', '20px').attr('height', '30px').attr('x', '-10px').attr('y', '-28px').merge(targets).sort(sortY).attr('class', function (d) {
+ return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
+ }).attr('transform', getTransform);
- function formatPair(input) {
- return format(input.lat, 'lat') + ' ' + format(input.lon, 'lon');
- } // Is 0 North or South?
+ function sortY(a, b) {
+ return a.id === selectedID ? 1 : b.id === selectedID ? -1 : b.loc[1] - a.loc[1];
+ }
+ } // Draw the ImproveOSM layer and schedule loading issues and updating markers.
- function format(input, dim) {
- var dms = coordToDMS(input, dim);
- return dms.whole + '° ' + (dms.minutes ? dms.minutes + '\' ' : '') + (dms.seconds ? dms.seconds + '" ' : '') + dms.dir;
- }
+ function drawImproveOSM(selection) {
+ var service = getService();
+ var surface = context.surface();
- function coordToDMS(input, dim) {
- var dirs = {
- lat: ['N', 'S'],
- lon: ['E', 'W']
- }[dim] || '';
- var dir = dirs[input >= 0 ? 0 : 1];
- var abs = Math.abs(input);
- var whole = Math.floor(abs);
- var fraction = abs - whole;
- var fractionMinutes = fraction * 60;
- var minutes = Math.floor(fractionMinutes);
- var seconds = Math.floor((fractionMinutes - minutes) * 60);
- return {
- whole: whole,
- minutes: minutes,
- seconds: seconds,
- dir: dir
- };
- }
+ if (surface && !surface.empty()) {
+ touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
+ }
- function search(input, dims) {
- if (!dims) dims = 'NSEW';
- if (typeof input !== 'string') return null;
- input = input.toUpperCase();
- var regex = /^[\s\,]*([NSEW])?\s*([\-|\—|\―]?[0-9.]+)[°º˚]?\s*(?:([0-9.]+)['’′‘]\s*)?(?:([0-9.]+)(?:''|"|”|″)\s*)?([NSEW])?/;
- var m = input.match(regex);
- if (!m) return null; // no match
+ drawLayer = selection.selectAll('.layer-improveOSM').data(service ? [0] : []);
+ drawLayer.exit().remove();
+ drawLayer = drawLayer.enter().append('g').attr('class', 'layer-improveOSM').style('display', _layerEnabled$1 ? 'block' : 'none').merge(drawLayer);
- var matched = m[0]; // extract dimension.. m[1] = leading, m[5] = trailing
+ if (_layerEnabled$1) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ service.loadIssues(projection);
+ updateMarkers();
+ } else {
+ editOff();
+ }
+ }
+ } // Toggles the layer on and off
- var dim;
- if (m[1] && m[5]) {
- // if matched both..
- dim = m[1]; // keep leading
+ drawImproveOSM.enabled = function (val) {
+ if (!arguments.length) return _layerEnabled$1;
+ _layerEnabled$1 = val;
- matched = matched.slice(0, -1); // remove trailing dimension from match
- } else {
- dim = m[1] || m[5];
- } // if unrecognized dimension
+ if (_layerEnabled$1) {
+ layerOn();
+ } else {
+ layerOff();
+ if (context.selectedErrorID()) {
+ context.enter(modeBrowse(context));
+ }
+ }
- if (dim && dims.indexOf(dim) === -1) return null; // extract DMS
+ dispatch.call('change');
+ return this;
+ };
- var deg = m[2] ? parseFloat(m[2]) : 0;
- var min = m[3] ? parseFloat(m[3]) / 60 : 0;
- var sec = m[4] ? parseFloat(m[4]) / 3600 : 0;
- var sign = deg < 0 ? -1 : 1;
- if (dim === 'S' || dim === 'W') sign *= -1;
- return {
- val: (Math.abs(deg) + min + sec) * sign,
- dim: dim,
- matched: matched,
- remain: input.slice(matched.length)
+ drawImproveOSM.supported = function () {
+ return !!getService();
};
+
+ return drawImproveOSM;
}
- function pair(input, dims) {
- input = input.trim();
- var one = search(input, dims);
- if (!one) return null;
- input = one.remain.trim();
- var two = search(input, dims);
- if (!two || two.remain) return null;
+ var _layerEnabled = false;
- if (one.dim) {
- return swapdim(one.val, two.val, one.dim);
- } else {
- return [one.val, two.val];
- }
- }
+ var _qaService;
- function swapdim(a, b, dim) {
- if (dim === 'N' || dim === 'S') return [a, b];
- if (dim === 'W' || dim === 'E') return [b, a];
- }
+ function svgOsmose(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ return dispatch.call('change');
+ }, 1000);
- function uiFeatureList(context) {
- var _geocodeResults;
+ var minZoom = 12;
+ var touchLayer = select(null);
+ var drawLayer = select(null);
+ var layerVisible = false;
- function featureList(selection) {
- var header = selection.append('div').attr('class', 'header fillL');
- header.append('h3').html(_t.html('inspector.feature_list'));
- var searchWrap = selection.append('div').attr('class', 'search-header');
- searchWrap.call(svgIcon('#iD-icon-search', 'pre-text'));
- var search = searchWrap.append('input').attr('placeholder', _t('inspector.search')).attr('type', 'search').call(utilNoAuto).on('keypress', keypress).on('keydown', keydown).on('input', inputevent);
- var listWrap = selection.append('div').attr('class', 'inspector-body');
- var list = listWrap.append('div').attr('class', 'feature-list');
- context.on('exit.feature-list', clearSearch);
- context.map().on('drawn.feature-list', mapDrawn);
- context.keybinding().on(uiCmd('⌘F'), focusSearch);
+ function markerPath(selection, klass) {
+ selection.attr('class', klass).attr('transform', 'translate(-10, -28)').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
+ } // Loosely-coupled osmose service for fetching issues
- function focusSearch(d3_event) {
- var mode = context.mode() && context.mode().id;
- if (mode !== 'browse') return;
- d3_event.preventDefault();
- search.node().focus();
- }
- function keydown(d3_event) {
- if (d3_event.keyCode === 27) {
- // escape
- search.node().blur();
- }
+ function getService() {
+ if (services.osmose && !_qaService) {
+ _qaService = services.osmose;
+
+ _qaService.on('loaded', throttledRedraw);
+ } else if (!services.osmose && _qaService) {
+ _qaService = null;
}
- function keypress(d3_event) {
- var q = search.property('value'),
- items = list.selectAll('.feature-list-item');
+ return _qaService;
+ } // Show the markers
- if (d3_event.keyCode === 13 && // ↩ Return
- q.length && items.size()) {
- click(d3_event, items.datum());
- }
- }
- function inputevent() {
- _geocodeResults = undefined;
- drawList();
+ function editOn() {
+ if (!layerVisible) {
+ layerVisible = true;
+ drawLayer.style('display', 'block');
}
+ } // Immediately remove the markers and their touch targets
- function clearSearch() {
- search.property('value', '');
- drawList();
- }
- function mapDrawn(e) {
- if (e.full) {
- drawList();
- }
+ function editOff() {
+ if (layerVisible) {
+ layerVisible = false;
+ drawLayer.style('display', 'none');
+ drawLayer.selectAll('.qaItem.osmose').remove();
+ touchLayer.selectAll('.qaItem.osmose').remove();
}
+ } // Enable the layer. This shows the markers and transitions them to visible.
- function features() {
- var result = [];
- var graph = context.graph();
- var visibleCenter = context.map().extent().center();
- var q = search.property('value').toLowerCase();
- if (!q) return result;
- var locationMatch = pair_1(q.toUpperCase()) || q.match(/^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)$/);
- if (locationMatch) {
- var loc = [parseFloat(locationMatch[0]), parseFloat(locationMatch[1])];
- result.push({
- id: -1,
- geometry: 'point',
- type: _t('inspector.location'),
- name: dmsCoordinatePair([loc[1], loc[0]]),
- location: loc
- });
- } // A location search takes priority over an ID search
+ function layerOn() {
+ editOn();
+ drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
+ return dispatch.call('change');
+ });
+ } // Disable the layer. This transitions the layer invisible and then hides the markers.
- var idMatch = !locationMatch && q.match(/(?:^|\W)(node|way|relation|[nwr])\W?0*([1-9]\d*)(?:\W|$)/i);
+ function layerOff() {
+ throttledRedraw.cancel();
+ drawLayer.interrupt();
+ touchLayer.selectAll('.qaItem.osmose').remove();
+ drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
+ editOff();
+ dispatch.call('change');
+ });
+ } // Update the issue markers
- if (idMatch) {
- var elemType = idMatch[1].charAt(0);
- var elemId = idMatch[2];
- result.push({
- id: elemType + elemId,
- geometry: elemType === 'n' ? 'point' : elemType === 'w' ? 'line' : 'relation',
- type: elemType === 'n' ? _t('inspector.node') : elemType === 'w' ? _t('inspector.way') : _t('inspector.relation'),
- name: elemId
- });
+
+ function updateMarkers() {
+ if (!layerVisible || !_layerEnabled) return;
+ var service = getService();
+ var selectedID = context.selectedErrorID();
+ var data = service ? service.getItems(projection) : [];
+ var getTransform = svgPointTransform(projection); // Draw markers..
+
+ var markers = drawLayer.selectAll('.qaItem.osmose').data(data, function (d) {
+ return d.id;
+ }); // exit
+
+ markers.exit().remove(); // enter
+
+ var markersEnter = markers.enter().append('g').attr('class', function (d) {
+ return "qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
+ });
+ markersEnter.append('polygon').call(markerPath, 'shadow');
+ markersEnter.append('ellipse').attr('cx', 0).attr('cy', 0).attr('rx', 4.5).attr('ry', 2).attr('class', 'stroke');
+ markersEnter.append('polygon').attr('fill', function (d) {
+ return service.getColor(d.item);
+ }).call(markerPath, 'qaItem-fill');
+ markersEnter.append('use').attr('transform', 'translate(-6.5, -23)').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('xlink:href', function (d) {
+ var picon = d.icon;
+
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return "#".concat(picon).concat(isMaki ? '-11' : '');
}
+ }); // update
- var allEntities = graph.entities;
- var localResults = [];
+ markers.merge(markersEnter).sort(sortY).classed('selected', function (d) {
+ return d.id === selectedID;
+ }).attr('transform', getTransform); // Draw targets..
- for (var id in allEntities) {
- var entity = allEntities[id];
- if (!entity) continue;
- var name = utilDisplayName(entity) || '';
- if (name.toLowerCase().indexOf(q) < 0) continue;
- var matched = _mainPresetIndex.match(entity, graph);
- var type = matched && matched.name() || utilDisplayType(entity.id);
- var extent = entity.extent(graph);
- var distance = extent ? geoSphericalDistance(visibleCenter, extent.center()) : 0;
- localResults.push({
- id: entity.id,
- entity: entity,
- geometry: entity.geometry(graph),
- type: type,
- name: name,
- distance: distance
- });
- if (localResults.length > 100) break;
+ if (touchLayer.empty()) return;
+ var fillClass = context.getDebug('target') ? 'pink' : 'nocolor';
+ var targets = touchLayer.selectAll('.qaItem.osmose').data(data, function (d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove(); // enter/update
+
+ targets.enter().append('rect').attr('width', '20px').attr('height', '30px').attr('x', '-10px').attr('y', '-28px').merge(targets).sort(sortY).attr('class', function (d) {
+ return "qaItem ".concat(d.service, " target ").concat(fillClass, " itemId-").concat(d.id);
+ }).attr('transform', getTransform);
+
+ function sortY(a, b) {
+ return a.id === selectedID ? 1 : b.id === selectedID ? -1 : b.loc[1] - a.loc[1];
+ }
+ } // Draw the Osmose layer and schedule loading issues and updating markers.
+
+
+ function drawOsmose(selection) {
+ var service = getService();
+ var surface = context.surface();
+
+ if (surface && !surface.empty()) {
+ touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
+ }
+
+ drawLayer = selection.selectAll('.layer-osmose').data(service ? [0] : []);
+ drawLayer.exit().remove();
+ drawLayer = drawLayer.enter().append('g').attr('class', 'layer-osmose').style('display', _layerEnabled ? 'block' : 'none').merge(drawLayer);
+
+ if (_layerEnabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ service.loadIssues(projection);
+ updateMarkers();
+ } else {
+ editOff();
}
+ }
+ } // Toggles the layer on and off
- localResults = localResults.sort(function byDistance(a, b) {
- return a.distance - b.distance;
+
+ drawOsmose.enabled = function (val) {
+ if (!arguments.length) return _layerEnabled;
+ _layerEnabled = val;
+
+ if (_layerEnabled) {
+ // Strings supplied by Osmose fetched before showing layer for first time
+ // NOTE: Currently no way to change locale in iD at runtime, would need to re-call this method if that's ever implemented
+ // Also, If layer is toggled quickly multiple requests are sent
+ getService().loadStrings().then(layerOn)["catch"](function (err) {
+ console.log(err); // eslint-disable-line no-console
});
- result = result.concat(localResults);
+ } else {
+ layerOff();
- (_geocodeResults || []).forEach(function (d) {
- if (d.osm_type && d.osm_id) {
- // some results may be missing these - #1890
- // Make a temporary osmEntity so we can preset match
- // and better localize the search result - #4725
- var id = osmEntity.id.fromOSM(d.osm_type, d.osm_id);
- var tags = {};
- tags[d["class"]] = d.type;
- var attrs = {
- id: id,
- type: d.osm_type,
- tags: tags
- };
+ if (context.selectedErrorID()) {
+ context.enter(modeBrowse(context));
+ }
+ }
- if (d.osm_type === 'way') {
- // for ways, add some fake closed nodes
- attrs.nodes = ['a', 'a']; // so that geometry area is possible
- }
+ dispatch.call('change');
+ return this;
+ };
- var tempEntity = osmEntity(attrs);
- var tempGraph = coreGraph([tempEntity]);
- var matched = _mainPresetIndex.match(tempEntity, tempGraph);
- var type = matched && matched.name() || utilDisplayType(id);
- result.push({
- id: tempEntity.id,
- geometry: tempEntity.geometry(tempGraph),
- type: type,
- name: d.display_name,
- extent: new geoExtent([parseFloat(d.boundingbox[3]), parseFloat(d.boundingbox[0])], [parseFloat(d.boundingbox[2]), parseFloat(d.boundingbox[1])])
- });
- }
+ drawOsmose.supported = function () {
+ return !!getService();
+ };
+
+ return drawOsmose;
+ }
+
+ function svgStreetside(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 14;
+ var minMarkerZoom = 16;
+ var minViewfieldZoom = 18;
+ var layer = select(null);
+ var _viewerYaw = 0;
+ var _selectedSequence = null;
+
+ var _streetside;
+ /**
+ * init().
+ */
+
+
+ function init() {
+ if (svgStreetside.initialized) return; // run once
+
+ svgStreetside.enabled = false;
+ svgStreetside.initialized = true;
+ }
+ /**
+ * getService().
+ */
+
+
+ function getService() {
+ if (services.streetside && !_streetside) {
+ _streetside = services.streetside;
+
+ _streetside.event.on('viewerChanged.svgStreetside', viewerChanged).on('loadedImages.svgStreetside', throttledRedraw);
+ } else if (!services.streetside && _streetside) {
+ _streetside = null;
+ }
+
+ return _streetside;
+ }
+ /**
+ * showLayer().
+ */
+
+
+ function showLayer() {
+ var service = getService();
+ if (!service) return;
+ editOn();
+ layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
+ dispatch.call('change');
+ });
+ }
+ /**
+ * hideLayer().
+ */
+
+
+ function hideLayer() {
+ throttledRedraw.cancel();
+ layer.transition().duration(250).style('opacity', 0).on('end', editOff);
+ }
+ /**
+ * editOn().
+ */
+
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+ /**
+ * editOff().
+ */
+
+
+ function editOff() {
+ layer.selectAll('.viewfield-group').remove();
+ layer.style('display', 'none');
+ }
+ /**
+ * click() Handles 'bubble' point click event.
+ */
+
+
+ function click(d3_event, d) {
+ var service = getService();
+ if (!service) return; // try to preserve the viewer rotation when staying on the same sequence
+
+ if (d.sequenceKey !== _selectedSequence) {
+ _viewerYaw = 0; // reset
+ }
+
+ _selectedSequence = d.sequenceKey;
+ service.ensureViewerLoaded(context).then(function () {
+ service.selectImage(context, d.key).yaw(_viewerYaw).showViewer(context);
+ });
+ context.map().centerEase(d.loc);
+ }
+ /**
+ * mouseover().
+ */
+
+
+ function mouseover(d3_event, d) {
+ var service = getService();
+ if (service) service.setStyles(context, d);
+ }
+ /**
+ * mouseout().
+ */
+
+
+ function mouseout() {
+ var service = getService();
+ if (service) service.setStyles(context, null);
+ }
+ /**
+ * transform().
+ */
+
+
+ function transform(d) {
+ var t = svgPointTransform(projection)(d);
+ var rot = d.ca + _viewerYaw;
+
+ if (rot) {
+ t += ' rotate(' + Math.floor(rot) + ',0,0)';
+ }
+
+ return t;
+ }
+
+ function viewerChanged() {
+ var service = getService();
+ if (!service) return;
+ var viewer = service.viewer();
+ if (!viewer) return; // update viewfield rotation
+
+ _viewerYaw = viewer.getYaw(); // avoid updating if the map is currently transformed
+ // e.g. during drags or easing.
+
+ if (context.map().isTransformed()) return;
+ layer.selectAll('.viewfield-group.currentView').attr('transform', transform);
+ }
+
+ function filterBubbles(bubbles) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+ var usernames = context.photos().usernames();
+
+ if (fromDate) {
+ var fromTimestamp = new Date(fromDate).getTime();
+ bubbles = bubbles.filter(function (bubble) {
+ return new Date(bubble.captured_at).getTime() >= fromTimestamp;
});
+ }
- if (q.match(/^[0-9]+$/)) {
- // if query is just a number, possibly an OSM ID without a prefix
- result.push({
- id: 'n' + q,
- geometry: 'point',
- type: _t('inspector.node'),
- name: q
- });
- result.push({
- id: 'w' + q,
- geometry: 'line',
- type: _t('inspector.way'),
- name: q
- });
- result.push({
- id: 'r' + q,
- geometry: 'relation',
- type: _t('inspector.relation'),
- name: q
- });
+ if (toDate) {
+ var toTimestamp = new Date(toDate).getTime();
+ bubbles = bubbles.filter(function (bubble) {
+ return new Date(bubble.captured_at).getTime() <= toTimestamp;
+ });
+ }
+
+ if (usernames) {
+ bubbles = bubbles.filter(function (bubble) {
+ return usernames.indexOf(bubble.captured_by) !== -1;
+ });
+ }
+
+ return bubbles;
+ }
+
+ function filterSequences(sequences) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+ var usernames = context.photos().usernames();
+
+ if (fromDate) {
+ var fromTimestamp = new Date(fromDate).getTime();
+ sequences = sequences.filter(function (sequences) {
+ return new Date(sequences.properties.captured_at).getTime() >= fromTimestamp;
+ });
+ }
+
+ if (toDate) {
+ var toTimestamp = new Date(toDate).getTime();
+ sequences = sequences.filter(function (sequences) {
+ return new Date(sequences.properties.captured_at).getTime() <= toTimestamp;
+ });
+ }
+
+ if (usernames) {
+ sequences = sequences.filter(function (sequences) {
+ return usernames.indexOf(sequences.properties.captured_by) !== -1;
+ });
+ }
+
+ return sequences;
+ }
+ /**
+ * update().
+ */
+
+
+ function update() {
+ var viewer = context.container().select('.photoviewer');
+ var selected = viewer.empty() ? undefined : viewer.datum();
+ var z = ~~context.map().zoom();
+ var showMarkers = z >= minMarkerZoom;
+ var showViewfields = z >= minViewfieldZoom;
+ var service = getService();
+ var sequences = [];
+ var bubbles = [];
+
+ if (context.photos().showsPanoramic()) {
+ sequences = service ? service.sequences(projection) : [];
+ bubbles = service && showMarkers ? service.bubbles(projection) : [];
+ sequences = filterSequences(sequences);
+ bubbles = filterBubbles(bubbles);
+ }
+
+ var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
+ return d.properties.key;
+ }); // exit
+
+ traces.exit().remove(); // enter/update
+
+ traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
+ var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(bubbles, function (d) {
+ // force reenter once bubbles are attached to a sequence
+ return d.key + (d.sequenceKey ? 'v1' : 'v0');
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
+ groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+
+ var markers = groups.merge(groupsEnter).sort(function (a, b) {
+ return a === selected ? 1 : b === selected ? -1 : b.loc[1] - a.loc[1];
+ }).attr('transform', transform).select('.viewfield-scale');
+ markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
+ var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
+ viewfields.exit().remove(); // viewfields may or may not be drawn...
+ // but if they are, draw below the circles
+
+ viewfields.enter().insert('path', 'circle').attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', viewfieldPath);
+
+ function viewfieldPath() {
+ var d = this.parentNode.__data__;
+
+ if (d.pano) {
+ return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0';
+ } else {
+ return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
+ }
+ }
+ }
+ /**
+ * drawImages()
+ * drawImages is the method that is returned (and that runs) every time 'svgStreetside()' is called.
+ * 'svgStreetside()' is called from index.js
+ */
+
+
+ function drawImages(selection) {
+ var enabled = svgStreetside.enabled;
+ var service = getService();
+ layer = selection.selectAll('.layer-streetside-images').data(service ? [0] : []);
+ layer.exit().remove();
+ var layerEnter = layer.enter().append('g').attr('class', 'layer-streetside-images').style('display', enabled ? 'block' : 'none');
+ layerEnter.append('g').attr('class', 'sequences');
+ layerEnter.append('g').attr('class', 'markers');
+ layer = layerEnter.merge(layer);
+
+ if (enabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ service.loadBubbles(projection);
+ } else {
+ editOff();
}
+ }
+ }
+ /**
+ * drawImages.enabled().
+ */
- return result;
+
+ drawImages.enabled = function (_) {
+ if (!arguments.length) return svgStreetside.enabled;
+ svgStreetside.enabled = _;
+
+ if (svgStreetside.enabled) {
+ showLayer();
+ context.photos().on('change.streetside', update);
+ } else {
+ hideLayer();
+ context.photos().on('change.streetside', null);
}
- function drawList() {
- var value = search.property('value');
- var results = features();
- list.classed('filtered', value.length);
- var resultsIndicator = list.selectAll('.no-results-item').data([0]).enter().append('button').property('disabled', true).attr('class', 'no-results-item').call(svgIcon('#iD-icon-alert', 'pre-text'));
- resultsIndicator.append('span').attr('class', 'entity-name');
- list.selectAll('.no-results-item .entity-name').html(_t.html('geocoder.no_results_worldwide'));
+ dispatch.call('change');
+ return this;
+ };
+ /**
+ * drawImages.supported().
+ */
+
+
+ drawImages.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawImages;
+ }
+
+ function svgMapillaryImages(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 12;
+ var minMarkerZoom = 16;
+ var minViewfieldZoom = 18;
+ var layer = select(null);
+
+ var _mapillary;
+
+ function init() {
+ if (svgMapillaryImages.initialized) return; // run once
+
+ svgMapillaryImages.enabled = false;
+ svgMapillaryImages.initialized = true;
+ }
+
+ function getService() {
+ if (services.mapillary && !_mapillary) {
+ _mapillary = services.mapillary;
+
+ _mapillary.event.on('loadedImages', throttledRedraw);
+ } else if (!services.mapillary && _mapillary) {
+ _mapillary = null;
+ }
+
+ return _mapillary;
+ }
+
+ function showLayer() {
+ var service = getService();
+ if (!service) return;
+ editOn();
+ layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
+ dispatch.call('change');
+ });
+ }
+
+ function hideLayer() {
+ throttledRedraw.cancel();
+ layer.transition().duration(250).style('opacity', 0).on('end', editOff);
+ }
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+
+ function editOff() {
+ layer.selectAll('.viewfield-group').remove();
+ layer.style('display', 'none');
+ }
+
+ function click(d3_event, image) {
+ var service = getService();
+ if (!service) return;
+ service.ensureViewerLoaded(context).then(function () {
+ service.selectImage(context, image.id).showViewer(context);
+ });
+ context.map().centerEase(image.loc);
+ }
+
+ function mouseover(d3_event, image) {
+ var service = getService();
+ if (service) service.setStyles(context, image);
+ }
+
+ function mouseout() {
+ var service = getService();
+ if (service) service.setStyles(context, null);
+ }
+
+ function transform(d) {
+ var t = svgPointTransform(projection)(d);
+
+ if (d.ca) {
+ t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
+ }
+
+ return t;
+ }
+
+ function filterImages(images) {
+ var showsPano = context.photos().showsPanoramic();
+ var showsFlat = context.photos().showsFlat();
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+
+ if (!showsPano || !showsFlat) {
+ images = images.filter(function (image) {
+ if (image.is_pano) return showsPano;
+ return showsFlat;
+ });
+ }
+
+ if (fromDate) {
+ images = images.filter(function (image) {
+ return new Date(image.captured_at).getTime() >= new Date(fromDate).getTime();
+ });
+ }
+
+ if (toDate) {
+ images = images.filter(function (image) {
+ return new Date(image.captured_at).getTime() <= new Date(toDate).getTime();
+ });
+ }
+
+ return images;
+ }
+
+ function filterSequences(sequences) {
+ var showsPano = context.photos().showsPanoramic();
+ var showsFlat = context.photos().showsFlat();
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+
+ if (!showsPano || !showsFlat) {
+ sequences = sequences.filter(function (sequence) {
+ if (sequence.properties.hasOwnProperty('is_pano')) {
+ if (sequence.properties.is_pano) return showsPano;
+ return showsFlat;
+ }
+
+ return false;
+ });
+ }
+
+ if (fromDate) {
+ sequences = sequences.filter(function (sequence) {
+ return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime().toString();
+ });
+ }
+
+ if (toDate) {
+ sequences = sequences.filter(function (sequence) {
+ return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime().toString();
+ });
+ }
+
+ return sequences;
+ }
+
+ function update() {
+ var z = ~~context.map().zoom();
+ var showMarkers = z >= minMarkerZoom;
+ var showViewfields = z >= minViewfieldZoom;
+ var service = getService();
+ var sequences = service ? service.sequences(projection) : [];
+ var images = service && showMarkers ? service.images(projection) : [];
+ images = filterImages(images);
+ sequences = filterSequences(sequences);
+ service.filterViewer(context);
+ var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
+ return d.properties.id;
+ }); // exit
+
+ traces.exit().remove(); // enter/update
+
+ traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
+ var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(images, function (d) {
+ return d.id;
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
+ groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+
+ var markers = groups.merge(groupsEnter).sort(function (a, b) {
+ return b.loc[1] - a.loc[1]; // sort Y
+ }).attr('transform', transform).select('.viewfield-scale');
+ markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
+ var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
+ viewfields.exit().remove();
+ viewfields.enter() // viewfields may or may not be drawn...
+ .insert('path', 'circle') // but if they are, draw below the circles
+ .attr('class', 'viewfield').classed('pano', function () {
+ return this.parentNode.__data__.is_pano;
+ }).attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', viewfieldPath);
+
+ function viewfieldPath() {
+ if (this.parentNode.__data__.is_pano) {
+ return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0';
+ } else {
+ return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
+ }
+ }
+ }
+
+ function drawImages(selection) {
+ var enabled = svgMapillaryImages.enabled;
+ var service = getService();
+ layer = selection.selectAll('.layer-mapillary').data(service ? [0] : []);
+ layer.exit().remove();
+ var layerEnter = layer.enter().append('g').attr('class', 'layer-mapillary').style('display', enabled ? 'block' : 'none');
+ layerEnter.append('g').attr('class', 'sequences');
+ layerEnter.append('g').attr('class', 'markers');
+ layer = layerEnter.merge(layer);
+
+ if (enabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ service.loadImages(projection);
+ } else {
+ editOff();
+ }
+ }
+ }
+
+ drawImages.enabled = function (_) {
+ if (!arguments.length) return svgMapillaryImages.enabled;
+ svgMapillaryImages.enabled = _;
+
+ if (svgMapillaryImages.enabled) {
+ showLayer();
+ context.photos().on('change.mapillary_images', update);
+ } else {
+ hideLayer();
+ context.photos().on('change.mapillary_images', null);
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ drawImages.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawImages;
+ }
+
+ function svgMapillaryPosition(projection, context) {
+ var throttledRedraw = throttle(function () {
+ update();
+ }, 1000);
+
+ var minZoom = 12;
+ var minViewfieldZoom = 18;
+ var layer = select(null);
+
+ var _mapillary;
+
+ var viewerCompassAngle;
+
+ function init() {
+ if (svgMapillaryPosition.initialized) return; // run once
+
+ svgMapillaryPosition.initialized = true;
+ }
+
+ function getService() {
+ if (services.mapillary && !_mapillary) {
+ _mapillary = services.mapillary;
+
+ _mapillary.event.on('imageChanged', throttledRedraw);
+
+ _mapillary.event.on('bearingChanged', function (e) {
+ viewerCompassAngle = e.bearing;
+ if (context.map().isTransformed()) return;
+ layer.selectAll('.viewfield-group.currentView').filter(function (d) {
+ return d.is_pano;
+ }).attr('transform', transform);
+ });
+ } else if (!services.mapillary && _mapillary) {
+ _mapillary = null;
+ }
+
+ return _mapillary;
+ }
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+
+ function editOff() {
+ layer.selectAll('.viewfield-group').remove();
+ layer.style('display', 'none');
+ }
+
+ function transform(d) {
+ var t = svgPointTransform(projection)(d);
+
+ if (d.is_pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
+ t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)';
+ } else if (d.ca) {
+ t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
+ }
+
+ return t;
+ }
+
+ function update() {
+ var z = ~~context.map().zoom();
+ var showViewfields = z >= minViewfieldZoom;
+ var service = getService();
+ var image = service && service.getActiveImage();
+ var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(image ? [image] : [], function (d) {
+ return d.id;
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group currentView highlighted');
+ groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+
+ var markers = groups.merge(groupsEnter).attr('transform', transform).select('.viewfield-scale');
+ markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
+ var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
+ viewfields.exit().remove();
+ viewfields.enter().insert('path', 'circle').attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
+ }
+
+ function drawImages(selection) {
+ var service = getService();
+ layer = selection.selectAll('.layer-mapillary-position').data(service ? [0] : []);
+ layer.exit().remove();
+ var layerEnter = layer.enter().append('g').attr('class', 'layer-mapillary-position');
+ layerEnter.append('g').attr('class', 'markers');
+ layer = layerEnter.merge(layer);
+
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ } else {
+ editOff();
+ }
+ }
+
+ drawImages.enabled = function () {
+ update();
+ return this;
+ };
+
+ drawImages.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawImages;
+ }
+
+ function svgMapillarySigns(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 12;
+ var layer = select(null);
+
+ var _mapillary;
+
+ function init() {
+ if (svgMapillarySigns.initialized) return; // run once
+
+ svgMapillarySigns.enabled = false;
+ svgMapillarySigns.initialized = true;
+ }
+
+ function getService() {
+ if (services.mapillary && !_mapillary) {
+ _mapillary = services.mapillary;
+
+ _mapillary.event.on('loadedSigns', throttledRedraw);
+ } else if (!services.mapillary && _mapillary) {
+ _mapillary = null;
+ }
+
+ return _mapillary;
+ }
+
+ function showLayer() {
+ var service = getService();
+ if (!service) return;
+ service.loadSignResources(context);
+ editOn();
+ }
+
+ function hideLayer() {
+ throttledRedraw.cancel();
+ editOff();
+ }
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+
+ function editOff() {
+ layer.selectAll('.icon-sign').remove();
+ layer.style('display', 'none');
+ }
+
+ function click(d3_event, d) {
+ var service = getService();
+ if (!service) return;
+ context.map().centerEase(d.loc);
+ var selectedImageId = service.getActiveImage() && service.getActiveImage().id;
+ service.getDetections(d.id).then(function (detections) {
+ if (detections.length) {
+ var imageId = detections[0].image.id;
+
+ if (imageId === selectedImageId) {
+ service.highlightDetection(detections[0]).selectImage(context, imageId);
+ } else {
+ service.ensureViewerLoaded(context).then(function () {
+ service.highlightDetection(detections[0]).selectImage(context, imageId).showViewer(context);
+ });
+ }
+ }
+ });
+ }
+
+ function filterData(detectedFeatures) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+
+ if (fromDate) {
+ var fromTimestamp = new Date(fromDate).getTime();
+ detectedFeatures = detectedFeatures.filter(function (feature) {
+ return new Date(feature.last_seen_at).getTime() >= fromTimestamp;
+ });
+ }
+
+ if (toDate) {
+ var toTimestamp = new Date(toDate).getTime();
+ detectedFeatures = detectedFeatures.filter(function (feature) {
+ return new Date(feature.first_seen_at).getTime() <= toTimestamp;
+ });
+ }
+
+ return detectedFeatures;
+ }
+
+ function update() {
+ var service = getService();
+ var data = service ? service.signs(projection) : [];
+ data = filterData(data);
+ var transform = svgPointTransform(projection);
+ var signs = layer.selectAll('.icon-sign').data(data, function (d) {
+ return d.id;
+ }); // exit
+
+ signs.exit().remove(); // enter
+
+ var enter = signs.enter().append('g').attr('class', 'icon-sign icon-detected').on('click', click);
+ enter.append('use').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px').attr('xlink:href', function (d) {
+ return '#' + d.value;
+ });
+ enter.append('rect').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px'); // update
+
+ signs.merge(enter).attr('transform', transform);
+ }
+
+ function drawSigns(selection) {
+ var enabled = svgMapillarySigns.enabled;
+ var service = getService();
+ layer = selection.selectAll('.layer-mapillary-signs').data(service ? [0] : []);
+ layer.exit().remove();
+ layer = layer.enter().append('g').attr('class', 'layer-mapillary-signs layer-mapillary-detections').style('display', enabled ? 'block' : 'none').merge(layer);
+
+ if (enabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ service.loadSigns(projection);
+ service.showSignDetections(true);
+ } else {
+ editOff();
+ }
+ } else if (service) {
+ service.showSignDetections(false);
+ }
+ }
+
+ drawSigns.enabled = function (_) {
+ if (!arguments.length) return svgMapillarySigns.enabled;
+ svgMapillarySigns.enabled = _;
+
+ if (svgMapillarySigns.enabled) {
+ showLayer();
+ context.photos().on('change.mapillary_signs', update);
+ } else {
+ hideLayer();
+ context.photos().on('change.mapillary_signs', null);
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ drawSigns.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawSigns;
+ }
+
+ function svgMapillaryMapFeatures(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 12;
+ var layer = select(null);
+
+ var _mapillary;
+
+ function init() {
+ if (svgMapillaryMapFeatures.initialized) return; // run once
+
+ svgMapillaryMapFeatures.enabled = false;
+ svgMapillaryMapFeatures.initialized = true;
+ }
+
+ function getService() {
+ if (services.mapillary && !_mapillary) {
+ _mapillary = services.mapillary;
+
+ _mapillary.event.on('loadedMapFeatures', throttledRedraw);
+ } else if (!services.mapillary && _mapillary) {
+ _mapillary = null;
+ }
+
+ return _mapillary;
+ }
+
+ function showLayer() {
+ var service = getService();
+ if (!service) return;
+ service.loadObjectResources(context);
+ editOn();
+ }
+
+ function hideLayer() {
+ throttledRedraw.cancel();
+ editOff();
+ }
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+
+ function editOff() {
+ layer.selectAll('.icon-map-feature').remove();
+ layer.style('display', 'none');
+ }
+
+ function click(d3_event, d) {
+ var service = getService();
+ if (!service) return;
+ context.map().centerEase(d.loc);
+ var selectedImageId = service.getActiveImage() && service.getActiveImage().id;
+ service.getDetections(d.id).then(function (detections) {
+ if (detections.length) {
+ var imageId = detections[0].image.id;
+
+ if (imageId === selectedImageId) {
+ service.highlightDetection(detections[0]).selectImage(context, imageId);
+ } else {
+ service.ensureViewerLoaded(context).then(function () {
+ service.highlightDetection(detections[0]).selectImage(context, imageId).showViewer(context);
+ });
+ }
+ }
+ });
+ }
+
+ function filterData(detectedFeatures) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+
+ if (fromDate) {
+ detectedFeatures = detectedFeatures.filter(function (feature) {
+ return new Date(feature.last_seen_at).getTime() >= new Date(fromDate).getTime();
+ });
+ }
+
+ if (toDate) {
+ detectedFeatures = detectedFeatures.filter(function (feature) {
+ return new Date(feature.first_seen_at).getTime() <= new Date(toDate).getTime();
+ });
+ }
+
+ return detectedFeatures;
+ }
+
+ function update() {
+ var service = getService();
+ var data = service ? service.mapFeatures(projection) : [];
+ data = filterData(data);
+ var transform = svgPointTransform(projection);
+ var mapFeatures = layer.selectAll('.icon-map-feature').data(data, function (d) {
+ return d.id;
+ }); // exit
+
+ mapFeatures.exit().remove(); // enter
+
+ var enter = mapFeatures.enter().append('g').attr('class', 'icon-map-feature icon-detected').on('click', click);
+ enter.append('title').text(function (d) {
+ var id = d.value.replace(/--/g, '.').replace(/-/g, '_');
+ return _t('mapillary_map_features.' + id);
+ });
+ enter.append('use').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px').attr('xlink:href', function (d) {
+ if (d.value === 'object--billboard') {
+ // no billboard icon right now, so use the advertisement icon
+ return '#object--sign--advertisement';
+ }
+
+ return '#' + d.value;
+ });
+ enter.append('rect').attr('width', '24px').attr('height', '24px').attr('x', '-12px').attr('y', '-12px'); // update
+
+ mapFeatures.merge(enter).attr('transform', transform);
+ }
+
+ function drawMapFeatures(selection) {
+ var enabled = svgMapillaryMapFeatures.enabled;
+ var service = getService();
+ layer = selection.selectAll('.layer-mapillary-map-features').data(service ? [0] : []);
+ layer.exit().remove();
+ layer = layer.enter().append('g').attr('class', 'layer-mapillary-map-features layer-mapillary-detections').style('display', enabled ? 'block' : 'none').merge(layer);
+
+ if (enabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ service.loadMapFeatures(projection);
+ service.showFeatureDetections(true);
+ } else {
+ editOff();
+ }
+ } else if (service) {
+ service.showFeatureDetections(false);
+ }
+ }
+
+ drawMapFeatures.enabled = function (_) {
+ if (!arguments.length) return svgMapillaryMapFeatures.enabled;
+ svgMapillaryMapFeatures.enabled = _;
+
+ if (svgMapillaryMapFeatures.enabled) {
+ showLayer();
+ context.photos().on('change.mapillary_map_features', update);
+ } else {
+ hideLayer();
+ context.photos().on('change.mapillary_map_features', null);
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ drawMapFeatures.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawMapFeatures;
+ }
+
+ function svgKartaviewImages(projection, context, dispatch) {
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 12;
+ var minMarkerZoom = 16;
+ var minViewfieldZoom = 18;
+ var layer = select(null);
+
+ var _kartaview;
+
+ function init() {
+ if (svgKartaviewImages.initialized) return; // run once
+
+ svgKartaviewImages.enabled = false;
+ svgKartaviewImages.initialized = true;
+ }
+
+ function getService() {
+ if (services.kartaview && !_kartaview) {
+ _kartaview = services.kartaview;
+
+ _kartaview.event.on('loadedImages', throttledRedraw);
+ } else if (!services.kartaview && _kartaview) {
+ _kartaview = null;
+ }
+
+ return _kartaview;
+ }
+
+ function showLayer() {
+ var service = getService();
+ if (!service) return;
+ editOn();
+ layer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end', function () {
+ dispatch.call('change');
+ });
+ }
+
+ function hideLayer() {
+ throttledRedraw.cancel();
+ layer.transition().duration(250).style('opacity', 0).on('end', editOff);
+ }
+
+ function editOn() {
+ layer.style('display', 'block');
+ }
+
+ function editOff() {
+ layer.selectAll('.viewfield-group').remove();
+ layer.style('display', 'none');
+ }
+
+ function click(d3_event, d) {
+ var service = getService();
+ if (!service) return;
+ service.ensureViewerLoaded(context).then(function () {
+ service.selectImage(context, d.key).showViewer(context);
+ });
+ context.map().centerEase(d.loc);
+ }
+
+ function mouseover(d3_event, d) {
+ var service = getService();
+ if (service) service.setStyles(context, d);
+ }
+
+ function mouseout() {
+ var service = getService();
+ if (service) service.setStyles(context, null);
+ }
+
+ function transform(d) {
+ var t = svgPointTransform(projection)(d);
+
+ if (d.ca) {
+ t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
+ }
+
+ return t;
+ }
+
+ function filterImages(images) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+ var usernames = context.photos().usernames();
+
+ if (fromDate) {
+ var fromTimestamp = new Date(fromDate).getTime();
+ images = images.filter(function (item) {
+ return new Date(item.captured_at).getTime() >= fromTimestamp;
+ });
+ }
+
+ if (toDate) {
+ var toTimestamp = new Date(toDate).getTime();
+ images = images.filter(function (item) {
+ return new Date(item.captured_at).getTime() <= toTimestamp;
+ });
+ }
+
+ if (usernames) {
+ images = images.filter(function (item) {
+ return usernames.indexOf(item.captured_by) !== -1;
+ });
+ }
+
+ return images;
+ }
+
+ function filterSequences(sequences) {
+ var fromDate = context.photos().fromDate();
+ var toDate = context.photos().toDate();
+ var usernames = context.photos().usernames();
+
+ if (fromDate) {
+ var fromTimestamp = new Date(fromDate).getTime();
+ sequences = sequences.filter(function (image) {
+ return new Date(image.properties.captured_at).getTime() >= fromTimestamp;
+ });
+ }
+
+ if (toDate) {
+ var toTimestamp = new Date(toDate).getTime();
+ sequences = sequences.filter(function (image) {
+ return new Date(image.properties.captured_at).getTime() <= toTimestamp;
+ });
+ }
+
+ if (usernames) {
+ sequences = sequences.filter(function (image) {
+ return usernames.indexOf(image.properties.captured_by) !== -1;
+ });
+ }
+
+ return sequences;
+ }
+
+ function update() {
+ var viewer = context.container().select('.photoviewer');
+ var selected = viewer.empty() ? undefined : viewer.datum();
+ var z = ~~context.map().zoom();
+ var showMarkers = z >= minMarkerZoom;
+ var showViewfields = z >= minViewfieldZoom;
+ var service = getService();
+ var sequences = [];
+ var images = [];
+
+ if (context.photos().showsFlat()) {
+ sequences = service ? service.sequences(projection) : [];
+ images = service && showMarkers ? service.images(projection) : [];
+ sequences = filterSequences(sequences);
+ images = filterImages(images);
+ }
+
+ var traces = layer.selectAll('.sequences').selectAll('.sequence').data(sequences, function (d) {
+ return d.properties.key;
+ }); // exit
+
+ traces.exit().remove(); // enter/update
+
+ traces = traces.enter().append('path').attr('class', 'sequence').merge(traces).attr('d', svgPath(projection).geojson);
+ var groups = layer.selectAll('.markers').selectAll('.viewfield-group').data(images, function (d) {
+ return d.key;
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ var groupsEnter = groups.enter().append('g').attr('class', 'viewfield-group').on('mouseenter', mouseover).on('mouseleave', mouseout).on('click', click);
+ groupsEnter.append('g').attr('class', 'viewfield-scale'); // update
+
+ var markers = groups.merge(groupsEnter).sort(function (a, b) {
+ return a === selected ? 1 : b === selected ? -1 : b.loc[1] - a.loc[1]; // sort Y
+ }).attr('transform', transform).select('.viewfield-scale');
+ markers.selectAll('circle').data([0]).enter().append('circle').attr('dx', '0').attr('dy', '0').attr('r', '6');
+ var viewfields = markers.selectAll('.viewfield').data(showViewfields ? [0] : []);
+ viewfields.exit().remove();
+ viewfields.enter() // viewfields may or may not be drawn...
+ .insert('path', 'circle') // but if they are, draw below the circles
+ .attr('class', 'viewfield').attr('transform', 'scale(1.5,1.5),translate(-8, -13)').attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
+ }
+
+ function drawImages(selection) {
+ var enabled = svgKartaviewImages.enabled,
+ service = getService();
+ layer = selection.selectAll('.layer-kartaview').data(service ? [0] : []);
+ layer.exit().remove();
+ var layerEnter = layer.enter().append('g').attr('class', 'layer-kartaview').style('display', enabled ? 'block' : 'none');
+ layerEnter.append('g').attr('class', 'sequences');
+ layerEnter.append('g').attr('class', 'markers');
+ layer = layerEnter.merge(layer);
+
+ if (enabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ update();
+ service.loadImages(projection);
+ } else {
+ editOff();
+ }
+ }
+ }
+
+ drawImages.enabled = function (_) {
+ if (!arguments.length) return svgKartaviewImages.enabled;
+ svgKartaviewImages.enabled = _;
+
+ if (svgKartaviewImages.enabled) {
+ showLayer();
+ context.photos().on('change.kartaview_images', update);
+ } else {
+ hideLayer();
+ context.photos().on('change.kartaview_images', null);
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ drawImages.supported = function () {
+ return !!getService();
+ };
+
+ init();
+ return drawImages;
+ }
+
+ function svgOsm(projection, context, dispatch) {
+ var enabled = true;
+
+ function drawOsm(selection) {
+ selection.selectAll('.layer-osm').data(['covered', 'areas', 'lines', 'points', 'labels']).enter().append('g').attr('class', function (d) {
+ return 'layer-osm ' + d;
+ });
+ selection.selectAll('.layer-osm.points').selectAll('.points-group').data(['points', 'midpoints', 'vertices', 'turns']).enter().append('g').attr('class', function (d) {
+ return 'points-group ' + d;
+ });
+ }
+
+ function showLayer() {
+ var layer = context.surface().selectAll('.data-layer.osm');
+ layer.interrupt();
+ layer.classed('disabled', false).style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
+ dispatch.call('change');
+ });
+ }
+
+ function hideLayer() {
+ var layer = context.surface().selectAll('.data-layer.osm');
+ layer.interrupt();
+ layer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
+ layer.classed('disabled', true);
+ dispatch.call('change');
+ });
+ }
+
+ drawOsm.enabled = function (val) {
+ if (!arguments.length) return enabled;
+ enabled = val;
+
+ if (enabled) {
+ showLayer();
+ } else {
+ hideLayer();
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ return drawOsm;
+ }
+
+ var _notesEnabled = false;
+
+ var _osmService;
+
+ function svgNotes(projection, context, dispatch) {
+ if (!dispatch) {
+ dispatch = dispatch$8('change');
+ }
+
+ var throttledRedraw = throttle(function () {
+ dispatch.call('change');
+ }, 1000);
+
+ var minZoom = 12;
+ var touchLayer = select(null);
+ var drawLayer = select(null);
+ var _notesVisible = false;
+
+ function markerPath(selection, klass) {
+ selection.attr('class', klass).attr('transform', 'translate(-8, -22)').attr('d', 'm17.5,0l-15,0c-1.37,0 -2.5,1.12 -2.5,2.5l0,11.25c0,1.37 1.12,2.5 2.5,2.5l3.75,0l0,3.28c0,0.38 0.43,0.6 0.75,0.37l4.87,-3.65l5.62,0c1.37,0 2.5,-1.12 2.5,-2.5l0,-11.25c0,-1.37 -1.12,-2.5 -2.5,-2.5z');
+ } // Loosely-coupled osm service for fetching notes.
+
+
+ function getService() {
+ if (services.osm && !_osmService) {
+ _osmService = services.osm;
+
+ _osmService.on('loadedNotes', throttledRedraw);
+ } else if (!services.osm && _osmService) {
+ _osmService = null;
+ }
+
+ return _osmService;
+ } // Show the notes
+
+
+ function editOn() {
+ if (!_notesVisible) {
+ _notesVisible = true;
+ drawLayer.style('display', 'block');
+ }
+ } // Immediately remove the notes and their touch targets
+
+
+ function editOff() {
+ if (_notesVisible) {
+ _notesVisible = false;
+ drawLayer.style('display', 'none');
+ drawLayer.selectAll('.note').remove();
+ touchLayer.selectAll('.note').remove();
+ }
+ } // Enable the layer. This shows the notes and transitions them to visible.
+
+
+ function layerOn() {
+ editOn();
+ drawLayer.style('opacity', 0).transition().duration(250).style('opacity', 1).on('end interrupt', function () {
+ dispatch.call('change');
+ });
+ } // Disable the layer. This transitions the layer invisible and then hides the notes.
+
+
+ function layerOff() {
+ throttledRedraw.cancel();
+ drawLayer.interrupt();
+ touchLayer.selectAll('.note').remove();
+ drawLayer.transition().duration(250).style('opacity', 0).on('end interrupt', function () {
+ editOff();
+ dispatch.call('change');
+ });
+ } // Update the note markers
+
+
+ function updateMarkers() {
+ if (!_notesVisible || !_notesEnabled) return;
+ var service = getService();
+ var selectedID = context.selectedNoteID();
+ var data = service ? service.notes(projection) : [];
+ var getTransform = svgPointTransform(projection); // Draw markers..
+
+ var notes = drawLayer.selectAll('.note').data(data, function (d) {
+ return d.status + d.id;
+ }); // exit
+
+ notes.exit().remove(); // enter
+
+ var notesEnter = notes.enter().append('g').attr('class', function (d) {
+ return 'note note-' + d.id + ' ' + d.status;
+ }).classed('new', function (d) {
+ return d.id < 0;
+ });
+ notesEnter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
+ notesEnter.append('path').call(markerPath, 'shadow');
+ notesEnter.append('use').attr('class', 'note-fill').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').attr('xlink:href', '#iD-icon-note');
+ notesEnter.selectAll('.icon-annotation').data(function (d) {
+ return [d];
+ }).enter().append('use').attr('class', 'icon-annotation').attr('width', '10px').attr('height', '10px').attr('x', '-3px').attr('y', '-19px').attr('xlink:href', function (d) {
+ if (d.id < 0) return '#iD-icon-plus';
+ if (d.status === 'open') return '#iD-icon-close';
+ return '#iD-icon-apply';
+ }); // update
+
+ notes.merge(notesEnter).sort(sortY).classed('selected', function (d) {
+ var mode = context.mode();
+ var isMoving = mode && mode.id === 'drag-note'; // no shadows when dragging
+
+ return !isMoving && d.id === selectedID;
+ }).attr('transform', getTransform); // Draw targets..
+
+ if (touchLayer.empty()) return;
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var targets = touchLayer.selectAll('.note').data(data, function (d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove(); // enter/update
+
+ targets.enter().append('rect').attr('width', '20px').attr('height', '20px').attr('x', '-8px').attr('y', '-22px').merge(targets).sort(sortY).attr('class', function (d) {
+ var newClass = d.id < 0 ? 'new' : '';
+ return 'note target note-' + d.id + ' ' + fillClass + newClass;
+ }).attr('transform', getTransform);
+
+ function sortY(a, b) {
+ if (a.id === selectedID) return 1;
+ if (b.id === selectedID) return -1;
+ return b.loc[1] - a.loc[1];
+ }
+ } // Draw the notes layer and schedule loading notes and updating markers.
+
+
+ function drawNotes(selection) {
+ var service = getService();
+ var surface = context.surface();
+
+ if (surface && !surface.empty()) {
+ touchLayer = surface.selectAll('.data-layer.touch .layer-touch.markers');
+ }
+
+ drawLayer = selection.selectAll('.layer-notes').data(service ? [0] : []);
+ drawLayer.exit().remove();
+ drawLayer = drawLayer.enter().append('g').attr('class', 'layer-notes').style('display', _notesEnabled ? 'block' : 'none').merge(drawLayer);
+
+ if (_notesEnabled) {
+ if (service && ~~context.map().zoom() >= minZoom) {
+ editOn();
+ service.loadNotes(projection);
+ updateMarkers();
+ } else {
+ editOff();
+ }
+ }
+ } // Toggles the layer on and off
+
+
+ drawNotes.enabled = function (val) {
+ if (!arguments.length) return _notesEnabled;
+ _notesEnabled = val;
+
+ if (_notesEnabled) {
+ layerOn();
+ } else {
+ layerOff();
+
+ if (context.selectedNoteID()) {
+ context.enter(modeBrowse(context));
+ }
+ }
+
+ dispatch.call('change');
+ return this;
+ };
+
+ return drawNotes;
+ }
+
+ function svgTouch() {
+ function drawTouch(selection) {
+ selection.selectAll('.layer-touch').data(['areas', 'lines', 'points', 'turns', 'markers']).enter().append('g').attr('class', function (d) {
+ return 'layer-touch ' + d;
+ });
+ }
+
+ return drawTouch;
+ }
+
+ function refresh(selection, node) {
+ var cr = node.getBoundingClientRect();
+ var prop = [cr.width, cr.height];
+ selection.property('__dimensions__', prop);
+ return prop;
+ }
+
+ function utilGetDimensions(selection, force) {
+ if (!selection || selection.empty()) {
+ return [0, 0];
+ }
+
+ var node = selection.node(),
+ cached = selection.property('__dimensions__');
+ return !cached || force ? refresh(selection, node) : cached;
+ }
+ function utilSetDimensions(selection, dimensions) {
+ if (!selection || selection.empty()) {
+ return selection;
+ }
+
+ var node = selection.node();
+
+ if (dimensions === null) {
+ refresh(selection, node);
+ return selection;
+ }
+
+ return selection.property('__dimensions__', [dimensions[0], dimensions[1]]).attr('width', dimensions[0]).attr('height', dimensions[1]);
+ }
+
+ function svgLayers(projection, context) {
+ var dispatch = dispatch$8('change');
+ var svg = select(null);
+ var _layers = [{
+ id: 'osm',
+ layer: svgOsm(projection, context, dispatch)
+ }, {
+ id: 'notes',
+ layer: svgNotes(projection, context, dispatch)
+ }, {
+ id: 'data',
+ layer: svgData(projection, context, dispatch)
+ }, {
+ id: 'keepRight',
+ layer: svgKeepRight(projection, context, dispatch)
+ }, {
+ id: 'improveOSM',
+ layer: svgImproveOSM(projection, context, dispatch)
+ }, {
+ id: 'osmose',
+ layer: svgOsmose(projection, context, dispatch)
+ }, {
+ id: 'streetside',
+ layer: svgStreetside(projection, context, dispatch)
+ }, {
+ id: 'mapillary',
+ layer: svgMapillaryImages(projection, context, dispatch)
+ }, {
+ id: 'mapillary-position',
+ layer: svgMapillaryPosition(projection, context)
+ }, {
+ id: 'mapillary-map-features',
+ layer: svgMapillaryMapFeatures(projection, context, dispatch)
+ }, {
+ id: 'mapillary-signs',
+ layer: svgMapillarySigns(projection, context, dispatch)
+ }, {
+ id: 'kartaview',
+ layer: svgKartaviewImages(projection, context, dispatch)
+ }, {
+ id: 'debug',
+ layer: svgDebug(projection, context)
+ }, {
+ id: 'geolocate',
+ layer: svgGeolocate(projection)
+ }, {
+ id: 'touch',
+ layer: svgTouch()
+ }];
+
+ function drawLayers(selection) {
+ svg = selection.selectAll('.surface').data([0]);
+ svg = svg.enter().append('svg').attr('class', 'surface').merge(svg);
+ var defs = svg.selectAll('.surface-defs').data([0]);
+ defs.enter().append('defs').attr('class', 'surface-defs');
+ var groups = svg.selectAll('.data-layer').data(_layers);
+ groups.exit().remove();
+ groups.enter().append('g').attr('class', function (d) {
+ return 'data-layer ' + d.id;
+ }).merge(groups).each(function (d) {
+ select(this).call(d.layer);
+ });
+ }
+
+ drawLayers.all = function () {
+ return _layers;
+ };
+
+ drawLayers.layer = function (id) {
+ var obj = _layers.find(function (o) {
+ return o.id === id;
+ });
+
+ return obj && obj.layer;
+ };
+
+ drawLayers.only = function (what) {
+ var arr = [].concat(what);
+
+ var all = _layers.map(function (layer) {
+ return layer.id;
+ });
+
+ return drawLayers.remove(utilArrayDifference(all, arr));
+ };
+
+ drawLayers.remove = function (what) {
+ var arr = [].concat(what);
+ arr.forEach(function (id) {
+ _layers = _layers.filter(function (o) {
+ return o.id !== id;
+ });
+ });
+ dispatch.call('change');
+ return this;
+ };
+
+ drawLayers.add = function (what) {
+ var arr = [].concat(what);
+ arr.forEach(function (obj) {
+ if ('id' in obj && 'layer' in obj) {
+ _layers.push(obj);
+ }
+ });
+ dispatch.call('change');
+ return this;
+ };
+
+ drawLayers.dimensions = function (val) {
+ if (!arguments.length) return utilGetDimensions(svg);
+ utilSetDimensions(svg, val);
+ return this;
+ };
+
+ return utilRebind(drawLayers, dispatch, 'on');
+ }
+
+ function svgLines(projection, context) {
+ var detected = utilDetect();
+ var highway_stack = {
+ motorway: 0,
+ motorway_link: 1,
+ trunk: 2,
+ trunk_link: 3,
+ primary: 4,
+ primary_link: 5,
+ secondary: 6,
+ tertiary: 7,
+ unclassified: 8,
+ residential: 9,
+ service: 10,
+ footway: 11
+ };
+
+ function drawTargets(selection, graph, entities, filter) {
+ var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
+ var getPath = svgPath(projection).geojson;
+ var activeID = context.activeID();
+ var base = context.history().base(); // The targets and nopes will be MultiLineString sub-segments of the ways
+
+ var data = {
+ targets: [],
+ nopes: []
+ };
+ entities.forEach(function (way) {
+ var features = svgSegmentWay(way, graph, activeID);
+ data.targets.push.apply(data.targets, features.passive);
+ data.nopes.push.apply(data.nopes, features.active);
+ }); // Targets allow hover and vertex snapping
+
+ var targetData = data.targets.filter(getPath);
+ var targets = selection.selectAll('.line.target-allowed').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(targetData, function key(d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove();
+
+ var segmentWasEdited = function segmentWasEdited(d) {
+ var wayID = d.properties.entity.id; // if the whole line was edited, don't draw segment changes
+
+ if (!base.entities[wayID] || !fastDeepEqual(graph.entities[wayID].nodes, base.entities[wayID].nodes)) {
+ return false;
+ }
+
+ return d.properties.nodes.some(function (n) {
+ return !base.entities[n.id] || !fastDeepEqual(graph.entities[n.id].loc, base.entities[n.id].loc);
+ });
+ }; // enter/update
+
+
+ targets.enter().append('path').merge(targets).attr('d', getPath).attr('class', function (d) {
+ return 'way line target target-allowed ' + targetClass + d.id;
+ }).classed('segment-edited', segmentWasEdited); // NOPE
+
+ var nopeData = data.nopes.filter(getPath);
+ var nopes = selection.selectAll('.line.target-nope').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(nopeData, function key(d) {
+ return d.id;
+ }); // exit
+
+ nopes.exit().remove(); // enter/update
+
+ nopes.enter().append('path').merge(nopes).attr('d', getPath).attr('class', function (d) {
+ return 'way line target target-nope ' + nopeClass + d.id;
+ }).classed('segment-edited', segmentWasEdited);
+ }
+
+ function drawLines(selection, graph, entities, filter) {
+ var base = context.history().base();
+
+ function waystack(a, b) {
+ var selected = context.selectedIDs();
+ var scoreA = selected.indexOf(a.id) !== -1 ? 20 : 0;
+ var scoreB = selected.indexOf(b.id) !== -1 ? 20 : 0;
+
+ if (a.tags.highway) {
+ scoreA -= highway_stack[a.tags.highway];
+ }
+
+ if (b.tags.highway) {
+ scoreB -= highway_stack[b.tags.highway];
+ }
+
+ return scoreA - scoreB;
+ }
+
+ function drawLineGroup(selection, klass, isSelected) {
+ // Note: Don't add `.selected` class in draw modes
+ var mode = context.mode();
+ var isDrawing = mode && /^draw/.test(mode.id);
+ var selectedClass = !isDrawing && isSelected ? 'selected ' : '';
+ var lines = selection.selectAll('path').filter(filter).data(getPathData(isSelected), osmEntity.key);
+ lines.exit().remove(); // Optimization: Call expensive TagClasses only on enter selection. This
+ // works because osmEntity.key is defined to include the entity v attribute.
+
+ lines.enter().append('path').attr('class', function (d) {
+ var prefix = 'way line'; // if this line isn't styled by its own tags
+
+ if (!d.hasInterestingTags()) {
+ var parentRelations = graph.parentRelations(d);
+ var parentMultipolygons = parentRelations.filter(function (relation) {
+ return relation.isMultipolygon();
+ }); // and if it's a member of at least one multipolygon relation
+
+ if (parentMultipolygons.length > 0 && // and only multipolygon relations
+ parentRelations.length === parentMultipolygons.length) {
+ // then fudge the classes to style this as an area edge
+ prefix = 'relation area';
+ }
+ }
+
+ var oldMPClass = oldMultiPolygonOuters[d.id] ? 'old-multipolygon ' : '';
+ return prefix + ' ' + klass + ' ' + selectedClass + oldMPClass + d.id;
+ }).classed('added', function (d) {
+ return !base.entities[d.id];
+ }).classed('geometry-edited', function (d) {
+ return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].nodes, base.entities[d.id].nodes);
+ }).classed('retagged', function (d) {
+ return graph.entities[d.id] && base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
+ }).call(svgTagClasses()).merge(lines).sort(waystack).attr('d', getPath).call(svgTagClasses().tags(svgRelationMemberTags(graph)));
+ return selection;
+ }
+
+ function getPathData(isSelected) {
+ return function () {
+ var layer = this.parentNode.__data__;
+ var data = pathdata[layer] || [];
+ return data.filter(function (d) {
+ if (isSelected) {
+ return context.selectedIDs().indexOf(d.id) !== -1;
+ } else {
+ return context.selectedIDs().indexOf(d.id) === -1;
+ }
+ });
+ };
+ }
+
+ function addMarkers(layergroup, pathclass, groupclass, groupdata, marker) {
+ var markergroup = layergroup.selectAll('g.' + groupclass).data([pathclass]);
+ markergroup = markergroup.enter().append('g').attr('class', groupclass).merge(markergroup);
+ var markers = markergroup.selectAll('path').filter(filter).data(function data() {
+ return groupdata[this.parentNode.__data__] || [];
+ }, function key(d) {
+ return [d.id, d.index];
+ });
+ markers.exit().remove();
+ markers = markers.enter().append('path').attr('class', pathclass).merge(markers).attr('marker-mid', marker).attr('d', function (d) {
+ return d.d;
+ });
+
+ if (detected.ie) {
+ markers.each(function () {
+ this.parentNode.insertBefore(this, this);
+ });
+ }
+ }
+
+ var getPath = svgPath(projection, graph);
+ var ways = [];
+ var onewaydata = {};
+ var sideddata = {};
+ var oldMultiPolygonOuters = {};
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ var outer = osmOldMultipolygonOuterMember(entity, graph);
+
+ if (outer) {
+ ways.push(entity.mergeTags(outer.tags));
+ oldMultiPolygonOuters[outer.id] = true;
+ } else if (entity.geometry(graph) === 'line') {
+ ways.push(entity);
+ }
+ }
+
+ ways = ways.filter(getPath);
+ var pathdata = utilArrayGroupBy(ways, function (way) {
+ return way.layer();
+ });
+ Object.keys(pathdata).forEach(function (k) {
+ var v = pathdata[k];
+ var onewayArr = v.filter(function (d) {
+ return d.isOneWay();
+ });
+ var onewaySegments = svgMarkerSegments(projection, graph, 35, function shouldReverse(entity) {
+ return entity.tags.oneway === '-1';
+ }, function bothDirections(entity) {
+ return entity.tags.oneway === 'reversible' || entity.tags.oneway === 'alternating';
+ });
+ onewaydata[k] = utilArrayFlatten(onewayArr.map(onewaySegments));
+ var sidedArr = v.filter(function (d) {
+ return d.isSided();
+ });
+ var sidedSegments = svgMarkerSegments(projection, graph, 30, function shouldReverse() {
+ return false;
+ }, function bothDirections() {
+ return false;
+ });
+ sideddata[k] = utilArrayFlatten(sidedArr.map(sidedSegments));
+ });
+ var covered = selection.selectAll('.layer-osm.covered'); // under areas
+
+ var uncovered = selection.selectAll('.layer-osm.lines'); // over areas
+
+ var touchLayer = selection.selectAll('.layer-touch.lines'); // Draw lines..
+
+ [covered, uncovered].forEach(function (selection) {
+ var range = selection === covered ? range$1(-10, 0) : range$1(0, 11);
+ var layergroup = selection.selectAll('g.layergroup').data(range);
+ layergroup = layergroup.enter().append('g').attr('class', function (d) {
+ return 'layergroup layer' + String(d);
+ }).merge(layergroup);
+ layergroup.selectAll('g.linegroup').data(['shadow', 'casing', 'stroke', 'shadow-highlighted', 'casing-highlighted', 'stroke-highlighted']).enter().append('g').attr('class', function (d) {
+ return 'linegroup line-' + d;
+ });
+ layergroup.selectAll('g.line-shadow').call(drawLineGroup, 'shadow', false);
+ layergroup.selectAll('g.line-casing').call(drawLineGroup, 'casing', false);
+ layergroup.selectAll('g.line-stroke').call(drawLineGroup, 'stroke', false);
+ layergroup.selectAll('g.line-shadow-highlighted').call(drawLineGroup, 'shadow', true);
+ layergroup.selectAll('g.line-casing-highlighted').call(drawLineGroup, 'casing', true);
+ layergroup.selectAll('g.line-stroke-highlighted').call(drawLineGroup, 'stroke', true);
+ addMarkers(layergroup, 'oneway', 'onewaygroup', onewaydata, 'url(#ideditor-oneway-marker)');
+ addMarkers(layergroup, 'sided', 'sidedgroup', sideddata, function marker(d) {
+ var category = graph.entity(d.id).sidednessIdentifier();
+ return 'url(#ideditor-sided-marker-' + category + ')';
+ });
+ }); // Draw touch targets..
+
+ touchLayer.call(drawTargets, graph, ways, filter);
+ }
+
+ return drawLines;
+ }
+
+ function svgMidpoints(projection, context) {
+ var targetRadius = 8;
+
+ function drawTargets(selection, graph, entities, filter) {
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var getTransform = svgPointTransform(projection).geojson;
+ var data = entities.map(function (midpoint) {
+ return {
+ type: 'Feature',
+ id: midpoint.id,
+ properties: {
+ target: true,
+ entity: midpoint
+ },
+ geometry: {
+ type: 'Point',
+ coordinates: midpoint.loc
+ }
+ };
+ });
+ var targets = selection.selectAll('.midpoint.target').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(data, function key(d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove(); // enter/update
+
+ targets.enter().append('circle').attr('r', targetRadius).merge(targets).attr('class', function (d) {
+ return 'node midpoint target ' + fillClass + d.id;
+ }).attr('transform', getTransform);
+ }
+
+ function drawMidpoints(selection, graph, entities, filter, extent) {
+ var drawLayer = selection.selectAll('.layer-osm.points .points-group.midpoints');
+ var touchLayer = selection.selectAll('.layer-touch.points');
+ var mode = context.mode();
+
+ if (mode && mode.id !== 'select' || !context.map().withinEditableZoom()) {
+ drawLayer.selectAll('.midpoint').remove();
+ touchLayer.selectAll('.midpoint.target').remove();
+ return;
+ }
+
+ var poly = extent.polygon();
+ var midpoints = {};
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ if (entity.type !== 'way') continue;
+ if (!filter(entity)) continue;
+ if (context.selectedIDs().indexOf(entity.id) < 0) continue;
+ var nodes = graph.childNodes(entity);
+
+ for (var j = 0; j < nodes.length - 1; j++) {
+ var a = nodes[j];
+ var b = nodes[j + 1];
+ var id = [a.id, b.id].sort().join('-');
+
+ if (midpoints[id]) {
+ midpoints[id].parents.push(entity);
+ } else if (geoVecLength(projection(a.loc), projection(b.loc)) > 40) {
+ var point = geoVecInterp(a.loc, b.loc, 0.5);
+ var loc = null;
+
+ if (extent.intersects(point)) {
+ loc = point;
+ } else {
+ for (var k = 0; k < 4; k++) {
+ point = geoLineIntersection([a.loc, b.loc], [poly[k], poly[k + 1]]);
+
+ if (point && geoVecLength(projection(a.loc), projection(point)) > 20 && geoVecLength(projection(b.loc), projection(point)) > 20) {
+ loc = point;
+ break;
+ }
+ }
+ }
+
+ if (loc) {
+ midpoints[id] = {
+ type: 'midpoint',
+ id: id,
+ loc: loc,
+ edge: [a.id, b.id],
+ parents: [entity]
+ };
+ }
+ }
+ }
+ }
+
+ function midpointFilter(d) {
+ if (midpoints[d.id]) return true;
+
+ for (var i = 0; i < d.parents.length; i++) {
+ if (filter(d.parents[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ var groups = drawLayer.selectAll('.midpoint').filter(midpointFilter).data(Object.values(midpoints), function (d) {
+ return d.id;
+ });
+ groups.exit().remove();
+ var enter = groups.enter().insert('g', ':first-child').attr('class', 'midpoint');
+ enter.append('polygon').attr('points', '-6,8 10,0 -6,-8').attr('class', 'shadow');
+ enter.append('polygon').attr('points', '-3,4 5,0 -3,-4').attr('class', 'fill');
+ groups = groups.merge(enter).attr('transform', function (d) {
+ var translate = svgPointTransform(projection);
+ var a = graph.entity(d.edge[0]);
+ var b = graph.entity(d.edge[1]);
+ var angle = geoAngle(a, b, projection) * (180 / Math.PI);
+ return translate(d) + ' rotate(' + angle + ')';
+ }).call(svgTagClasses().tags(function (d) {
+ return d.parents[0].tags;
+ })); // Propagate data bindings.
+
+ groups.select('polygon.shadow');
+ groups.select('polygon.fill'); // Draw touch targets..
+
+ touchLayer.call(drawTargets, graph, Object.values(midpoints), midpointFilter);
+ }
+
+ return drawMidpoints;
+ }
+
+ function svgPoints(projection, context) {
+ function markerPath(selection, klass) {
+ selection.attr('class', klass).attr('transform', 'translate(-8, -23)').attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
+ }
+
+ function sortY(a, b) {
+ return b.loc[1] - a.loc[1];
+ } // Avoid exit/enter if we're just moving stuff around.
+ // The node will get a new version but we only need to run the update selection.
+
+
+ function fastEntityKey(d) {
+ var mode = context.mode();
+ var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
+ return isMoving ? d.id : osmEntity.key(d);
+ }
+
+ function drawTargets(selection, graph, entities, filter) {
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var getTransform = svgPointTransform(projection).geojson;
+ var activeID = context.activeID();
+ var data = [];
+ entities.forEach(function (node) {
+ if (activeID === node.id) return; // draw no target on the activeID
+
+ data.push({
+ type: 'Feature',
+ id: node.id,
+ properties: {
+ target: true,
+ entity: node
+ },
+ geometry: node.asGeoJSON()
+ });
+ });
+ var targets = selection.selectAll('.point.target').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(data, function key(d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove(); // enter/update
+
+ targets.enter().append('rect').attr('x', -10).attr('y', -26).attr('width', 20).attr('height', 30).merge(targets).attr('class', function (d) {
+ return 'node point target ' + fillClass + d.id;
+ }).attr('transform', getTransform);
+ }
+
+ function drawPoints(selection, graph, entities, filter) {
+ var wireframe = context.surface().classed('fill-wireframe');
+ var zoom = geoScaleToZoom(projection.scale());
+ var base = context.history().base(); // Points with a direction will render as vertices at higher zooms..
+
+ function renderAsPoint(entity) {
+ return entity.geometry(graph) === 'point' && !(zoom >= 18 && entity.directions(graph, projection).length);
+ } // All points will render as vertices in wireframe mode too..
+
+
+ var points = wireframe ? [] : entities.filter(renderAsPoint);
+ points.sort(sortY);
+ var drawLayer = selection.selectAll('.layer-osm.points .points-group.points');
+ var touchLayer = selection.selectAll('.layer-touch.points'); // Draw points..
+
+ var groups = drawLayer.selectAll('g.point').filter(filter).data(points, fastEntityKey);
+ groups.exit().remove();
+ var enter = groups.enter().append('g').attr('class', function (d) {
+ return 'node point ' + d.id;
+ }).order();
+ enter.append('path').call(markerPath, 'shadow');
+ enter.append('ellipse').attr('cx', 0.5).attr('cy', 1).attr('rx', 6.5).attr('ry', 3).attr('class', 'stroke');
+ enter.append('path').call(markerPath, 'stroke');
+ enter.append('use').attr('transform', 'translate(-5, -19)').attr('class', 'icon').attr('width', '11px').attr('height', '11px');
+ groups = groups.merge(enter).attr('transform', svgPointTransform(projection)).classed('added', function (d) {
+ return !base.entities[d.id]; // if it doesn't exist in the base graph, it's new
+ }).classed('moved', function (d) {
+ return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].loc, base.entities[d.id].loc);
+ }).classed('retagged', function (d) {
+ return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
+ }).call(svgTagClasses());
+ groups.select('.shadow'); // propagate bound data
+
+ groups.select('.stroke'); // propagate bound data
+
+ groups.select('.icon') // propagate bound data
+ .attr('xlink:href', function (entity) {
+ var preset = _mainPresetIndex.match(entity, graph);
+ var picon = preset && preset.icon;
+
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return '#' + picon + (isMaki ? '-11' : '');
+ }
+ }); // Draw touch targets..
+
+ touchLayer.call(drawTargets, graph, points, filter);
+ }
+
+ return drawPoints;
+ }
+
+ function svgTurns(projection, context) {
+ function icon(turn) {
+ var u = turn.u ? '-u' : '';
+ if (turn.no) return '#iD-turn-no' + u;
+ if (turn.only) return '#iD-turn-only' + u;
+ return '#iD-turn-yes' + u;
+ }
+
+ function drawTurns(selection, graph, turns) {
+ function turnTransform(d) {
+ var pxRadius = 50;
+ var toWay = graph.entity(d.to.way);
+ var toPoints = graph.childNodes(toWay).map(function (n) {
+ return n.loc;
+ }).map(projection);
+ var toLength = geoPathLength(toPoints);
+ var mid = toLength / 2; // midpoint of destination way
+
+ var toNode = graph.entity(d.to.node);
+ var toVertex = graph.entity(d.to.vertex);
+ var a = geoAngle(toVertex, toNode, projection);
+ var o = projection(toVertex.loc);
+ var r = d.u ? 0 // u-turn: no radius
+ : !toWay.__via ? pxRadius // leaf way: put marker at pxRadius
+ : Math.min(mid, pxRadius); // via way: prefer pxRadius, fallback to mid for very short ways
+
+ return 'translate(' + (r * Math.cos(a) + o[0]) + ',' + (r * Math.sin(a) + o[1]) + ') ' + 'rotate(' + a * 180 / Math.PI + ')';
+ }
+
+ var drawLayer = selection.selectAll('.layer-osm.points .points-group.turns');
+ var touchLayer = selection.selectAll('.layer-touch.turns'); // Draw turns..
+
+ var groups = drawLayer.selectAll('g.turn').data(turns, function (d) {
+ return d.key;
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ var groupsEnter = groups.enter().append('g').attr('class', function (d) {
+ return 'turn ' + d.key;
+ });
+ var turnsEnter = groupsEnter.filter(function (d) {
+ return !d.u;
+ });
+ turnsEnter.append('rect').attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
+ turnsEnter.append('use').attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
+ var uEnter = groupsEnter.filter(function (d) {
+ return d.u;
+ });
+ uEnter.append('circle').attr('r', '16');
+ uEnter.append('use').attr('transform', 'translate(-16, -16)').attr('width', '32').attr('height', '32'); // update
+
+ groups = groups.merge(groupsEnter).attr('opacity', function (d) {
+ return d.direct === false ? '0.7' : null;
+ }).attr('transform', turnTransform);
+ groups.select('use').attr('xlink:href', icon);
+ groups.select('rect'); // propagate bound data
+
+ groups.select('circle'); // propagate bound data
+ // Draw touch targets..
+
+ var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ groups = touchLayer.selectAll('g.turn').data(turns, function (d) {
+ return d.key;
+ }); // exit
+
+ groups.exit().remove(); // enter
+
+ groupsEnter = groups.enter().append('g').attr('class', function (d) {
+ return 'turn ' + d.key;
+ });
+ turnsEnter = groupsEnter.filter(function (d) {
+ return !d.u;
+ });
+ turnsEnter.append('rect').attr('class', 'target ' + fillClass).attr('transform', 'translate(-22, -12)').attr('width', '44').attr('height', '24');
+ uEnter = groupsEnter.filter(function (d) {
+ return d.u;
+ });
+ uEnter.append('circle').attr('class', 'target ' + fillClass).attr('r', '16'); // update
+
+ groups = groups.merge(groupsEnter).attr('transform', turnTransform);
+ groups.select('rect'); // propagate bound data
+
+ groups.select('circle'); // propagate bound data
+
+ return this;
+ }
+
+ return drawTurns;
+ }
+
+ function svgVertices(projection, context) {
+ var radiuses = {
+ // z16-, z17, z18+, w/icon
+ shadow: [6, 7.5, 7.5, 12],
+ stroke: [2.5, 3.5, 3.5, 8],
+ fill: [1, 1.5, 1.5, 1.5]
+ };
+
+ var _currHoverTarget;
+
+ var _currPersistent = {};
+ var _currHover = {};
+ var _prevHover = {};
+ var _currSelected = {};
+ var _prevSelected = {};
+ var _radii = {};
+
+ function sortY(a, b) {
+ return b.loc[1] - a.loc[1];
+ } // Avoid exit/enter if we're just moving stuff around.
+ // The node will get a new version but we only need to run the update selection.
+
+
+ function fastEntityKey(d) {
+ var mode = context.mode();
+ var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
+ return isMoving ? d.id : osmEntity.key(d);
+ }
+
+ function draw(selection, graph, vertices, sets, filter) {
+ sets = sets || {
+ selected: {},
+ important: {},
+ hovered: {}
+ };
+ var icons = {};
+ var directions = {};
+ var wireframe = context.surface().classed('fill-wireframe');
+ var zoom = geoScaleToZoom(projection.scale());
+ var z = zoom < 17 ? 0 : zoom < 18 ? 1 : 2;
+ var activeID = context.activeID();
+ var base = context.history().base();
+
+ function getIcon(d) {
+ // always check latest entity, as fastEntityKey avoids enter/exit now
+ var entity = graph.entity(d.id);
+ if (entity.id in icons) return icons[entity.id];
+ icons[entity.id] = entity.hasInterestingTags() && _mainPresetIndex.match(entity, graph).icon;
+ return icons[entity.id];
+ } // memoize directions results, return false for empty arrays (for use in filter)
+
+
+ function getDirections(entity) {
+ if (entity.id in directions) return directions[entity.id];
+ var angles = entity.directions(graph, projection);
+ directions[entity.id] = angles.length ? angles : false;
+ return angles;
+ }
+
+ function updateAttributes(selection) {
+ ['shadow', 'stroke', 'fill'].forEach(function (klass) {
+ var rads = radiuses[klass];
+ selection.selectAll('.' + klass).each(function (entity) {
+ var i = z && getIcon(entity);
+ var r = rads[i ? 3 : z]; // slightly increase the size of unconnected endpoints #3775
+
+ if (entity.id !== activeID && entity.isEndpoint(graph) && !entity.isConnected(graph)) {
+ r += 1.5;
+ }
+
+ if (klass === 'shadow') {
+ // remember this value, so we don't need to
+ _radii[entity.id] = r; // recompute it when we draw the touch targets
+ }
+
+ select(this).attr('r', r).attr('visibility', i && klass === 'fill' ? 'hidden' : null);
+ });
+ });
+ }
+
+ vertices.sort(sortY);
+ var groups = selection.selectAll('g.vertex').filter(filter).data(vertices, fastEntityKey); // exit
+
+ groups.exit().remove(); // enter
+
+ var enter = groups.enter().append('g').attr('class', function (d) {
+ return 'node vertex ' + d.id;
+ }).order();
+ enter.append('circle').attr('class', 'shadow');
+ enter.append('circle').attr('class', 'stroke'); // Vertices with tags get a fill.
+
+ enter.filter(function (d) {
+ return d.hasInterestingTags();
+ }).append('circle').attr('class', 'fill'); // update
+
+ groups = groups.merge(enter).attr('transform', svgPointTransform(projection)).classed('sibling', function (d) {
+ return d.id in sets.selected;
+ }).classed('shared', function (d) {
+ return graph.isShared(d);
+ }).classed('endpoint', function (d) {
+ return d.isEndpoint(graph);
+ }).classed('added', function (d) {
+ return !base.entities[d.id]; // if it doesn't exist in the base graph, it's new
+ }).classed('moved', function (d) {
+ return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].loc, base.entities[d.id].loc);
+ }).classed('retagged', function (d) {
+ return base.entities[d.id] && !fastDeepEqual(graph.entities[d.id].tags, base.entities[d.id].tags);
+ }).call(updateAttributes); // Vertices with icons get a `use`.
+
+ var iconUse = groups.selectAll('.icon').data(function data(d) {
+ return zoom >= 17 && getIcon(d) ? [d] : [];
+ }, fastEntityKey); // exit
+
+ iconUse.exit().remove(); // enter
+
+ iconUse.enter().append('use').attr('class', 'icon').attr('width', '11px').attr('height', '11px').attr('transform', 'translate(-5.5, -5.5)').attr('xlink:href', function (d) {
+ var picon = getIcon(d);
+ var isMaki = /^maki-/.test(picon);
+ return '#' + picon + (isMaki ? '-11' : '');
+ }); // Vertices with directions get viewfields
+
+ var dgroups = groups.selectAll('.viewfieldgroup').data(function data(d) {
+ return zoom >= 18 && getDirections(d) ? [d] : [];
+ }, fastEntityKey); // exit
+
+ dgroups.exit().remove(); // enter/update
+
+ dgroups = dgroups.enter().insert('g', '.shadow').attr('class', 'viewfieldgroup').merge(dgroups);
+ var viewfields = dgroups.selectAll('.viewfield').data(getDirections, function key(d) {
+ return osmEntity.key(d);
+ }); // exit
+
+ viewfields.exit().remove(); // enter/update
+
+ viewfields.enter().append('path').attr('class', 'viewfield').attr('d', 'M0,0H0').merge(viewfields).attr('marker-start', 'url(#ideditor-viewfield-marker' + (wireframe ? '-wireframe' : '') + ')').attr('transform', function (d) {
+ return 'rotate(' + d + ')';
+ });
+ }
+
+ function drawTargets(selection, graph, entities, filter) {
+ var targetClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
+ var nopeClass = context.getDebug('target') ? 'red ' : 'nocolor ';
+ var getTransform = svgPointTransform(projection).geojson;
+ var activeID = context.activeID();
+ var data = {
+ targets: [],
+ nopes: []
+ };
+ entities.forEach(function (node) {
+ if (activeID === node.id) return; // draw no target on the activeID
+
+ var vertexType = svgPassiveVertex(node, graph, activeID);
+
+ if (vertexType !== 0) {
+ // passive or adjacent - allow to connect
+ data.targets.push({
+ type: 'Feature',
+ id: node.id,
+ properties: {
+ target: true,
+ entity: node
+ },
+ geometry: node.asGeoJSON()
+ });
+ } else {
+ data.nopes.push({
+ type: 'Feature',
+ id: node.id + '-nope',
+ properties: {
+ nope: true,
+ target: true,
+ entity: node
+ },
+ geometry: node.asGeoJSON()
+ });
+ }
+ }); // Targets allow hover and vertex snapping
+
+ var targets = selection.selectAll('.vertex.target-allowed').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(data.targets, function key(d) {
+ return d.id;
+ }); // exit
+
+ targets.exit().remove(); // enter/update
+
+ targets.enter().append('circle').attr('r', function (d) {
+ return _radii[d.id] || radiuses.shadow[3];
+ }).merge(targets).attr('class', function (d) {
+ return 'node vertex target target-allowed ' + targetClass + d.id;
+ }).attr('transform', getTransform); // NOPE
+
+ var nopes = selection.selectAll('.vertex.target-nope').filter(function (d) {
+ return filter(d.properties.entity);
+ }).data(data.nopes, function key(d) {
+ return d.id;
+ }); // exit
+
+ nopes.exit().remove(); // enter/update
+
+ nopes.enter().append('circle').attr('r', function (d) {
+ return _radii[d.properties.entity.id] || radiuses.shadow[3];
+ }).merge(nopes).attr('class', function (d) {
+ return 'node vertex target target-nope ' + nopeClass + d.id;
+ }).attr('transform', getTransform);
+ } // Points can also render as vertices:
+ // 1. in wireframe mode or
+ // 2. at higher zooms if they have a direction
+
+
+ function renderAsVertex(entity, graph, wireframe, zoom) {
+ var geometry = entity.geometry(graph);
+ return geometry === 'vertex' || geometry === 'point' && (wireframe || zoom >= 18 && entity.directions(graph, projection).length);
+ }
+
+ function isEditedNode(node, base, head) {
+ var baseNode = base.entities[node.id];
+ var headNode = head.entities[node.id];
+ return !headNode || !baseNode || !fastDeepEqual(headNode.tags, baseNode.tags) || !fastDeepEqual(headNode.loc, baseNode.loc);
+ }
+
+ function getSiblingAndChildVertices(ids, graph, wireframe, zoom) {
+ var results = {};
+ var seenIds = {};
+
+ function addChildVertices(entity) {
+ // avoid redundant work and infinite recursion of circular relations
+ if (seenIds[entity.id]) return;
+ seenIds[entity.id] = true;
+ var geometry = entity.geometry(graph);
+
+ if (!context.features().isHiddenFeature(entity, graph, geometry)) {
+ var i;
+
+ if (entity.type === 'way') {
+ for (i = 0; i < entity.nodes.length; i++) {
+ var child = graph.hasEntity(entity.nodes[i]);
+
+ if (child) {
+ addChildVertices(child);
+ }
+ }
+ } else if (entity.type === 'relation') {
+ for (i = 0; i < entity.members.length; i++) {
+ var member = graph.hasEntity(entity.members[i].id);
+
+ if (member) {
+ addChildVertices(member);
+ }
+ }
+ } else if (renderAsVertex(entity, graph, wireframe, zoom)) {
+ results[entity.id] = entity;
+ }
+ }
+ }
+
+ ids.forEach(function (id) {
+ var entity = graph.hasEntity(id);
+ if (!entity) return;
+
+ if (entity.type === 'node') {
+ if (renderAsVertex(entity, graph, wireframe, zoom)) {
+ results[entity.id] = entity;
+ graph.parentWays(entity).forEach(function (entity) {
+ addChildVertices(entity);
+ });
+ }
+ } else {
+ // way, relation
+ addChildVertices(entity);
+ }
+ });
+ return results;
+ }
+
+ function drawVertices(selection, graph, entities, filter, extent, fullRedraw) {
+ var wireframe = context.surface().classed('fill-wireframe');
+ var visualDiff = context.surface().classed('highlight-edited');
+ var zoom = geoScaleToZoom(projection.scale());
+ var mode = context.mode();
+ var isMoving = mode && /^(add|draw|drag|move|rotate)/.test(mode.id);
+ var base = context.history().base();
+ var drawLayer = selection.selectAll('.layer-osm.points .points-group.vertices');
+ var touchLayer = selection.selectAll('.layer-touch.points');
+
+ if (fullRedraw) {
+ _currPersistent = {};
+ _radii = {};
+ } // Collect important vertices from the `entities` list..
+ // (during a partial redraw, it will not contain everything)
+
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ var geometry = entity.geometry(graph);
+ var keep = false; // a point that looks like a vertex..
+
+ if (geometry === 'point' && renderAsVertex(entity, graph, wireframe, zoom)) {
+ _currPersistent[entity.id] = entity;
+ keep = true; // a vertex of some importance..
+ } else if (geometry === 'vertex' && (entity.hasInterestingTags() || entity.isEndpoint(graph) || entity.isConnected(graph) || visualDiff && isEditedNode(entity, base, graph))) {
+ _currPersistent[entity.id] = entity;
+ keep = true;
+ } // whatever this is, it's not a persistent vertex..
+
+
+ if (!keep && !fullRedraw) {
+ delete _currPersistent[entity.id];
+ }
+ } // 3 sets of vertices to consider:
+
+
+ var sets = {
+ persistent: _currPersistent,
+ // persistent = important vertices (render always)
+ selected: _currSelected,
+ // selected + siblings of selected (render always)
+ hovered: _currHover // hovered + siblings of hovered (render only in draw modes)
+
+ };
+ var all = Object.assign({}, isMoving ? _currHover : {}, _currSelected, _currPersistent); // Draw the vertices..
+ // The filter function controls the scope of what objects d3 will touch (exit/enter/update)
+ // Adjust the filter function to expand the scope beyond whatever entities were passed in.
+
+ var filterRendered = function filterRendered(d) {
+ return d.id in _currPersistent || d.id in _currSelected || d.id in _currHover || filter(d);
+ };
+
+ drawLayer.call(draw, graph, currentVisible(all), sets, filterRendered); // Draw touch targets..
+ // When drawing, render all targets (not just those affected by a partial redraw)
+
+ var filterTouch = function filterTouch(d) {
+ return isMoving ? true : filterRendered(d);
+ };
+
+ touchLayer.call(drawTargets, graph, currentVisible(all), filterTouch);
+
+ function currentVisible(which) {
+ return Object.keys(which).map(graph.hasEntity, graph) // the current version of this entity
+ .filter(function (entity) {
+ return entity && entity.intersects(extent, graph);
+ });
+ }
+ } // partial redraw - only update the selected items..
+
+
+ drawVertices.drawSelected = function (selection, graph, extent) {
+ var wireframe = context.surface().classed('fill-wireframe');
+ var zoom = geoScaleToZoom(projection.scale());
+ _prevSelected = _currSelected || {};
+
+ if (context.map().isInWideSelection()) {
+ _currSelected = {};
+ context.selectedIDs().forEach(function (id) {
+ var entity = graph.hasEntity(id);
+ if (!entity) return;
+
+ if (entity.type === 'node') {
+ if (renderAsVertex(entity, graph, wireframe, zoom)) {
+ _currSelected[entity.id] = entity;
+ }
+ }
+ });
+ } else {
+ _currSelected = getSiblingAndChildVertices(context.selectedIDs(), graph, wireframe, zoom);
+ } // note that drawVertices will add `_currSelected` automatically if needed..
+
+
+ var filter = function filter(d) {
+ return d.id in _prevSelected;
+ };
+
+ drawVertices(selection, graph, Object.values(_prevSelected), filter, extent, false);
+ }; // partial redraw - only update the hovered items..
+
+
+ drawVertices.drawHover = function (selection, graph, target, extent) {
+ if (target === _currHoverTarget) return; // continue only if something changed
+
+ var wireframe = context.surface().classed('fill-wireframe');
+ var zoom = geoScaleToZoom(projection.scale());
+ _prevHover = _currHover || {};
+ _currHoverTarget = target;
+ var entity = target && target.properties && target.properties.entity;
+
+ if (entity) {
+ _currHover = getSiblingAndChildVertices([entity.id], graph, wireframe, zoom);
+ } else {
+ _currHover = {};
+ } // note that drawVertices will add `_currHover` automatically if needed..
+
+
+ var filter = function filter(d) {
+ return d.id in _prevHover;
+ };
+
+ drawVertices(selection, graph, Object.values(_prevHover), filter, extent, false);
+ };
+
+ return drawVertices;
+ }
+
+ function utilBindOnce(target, type, listener, capture) {
+ var typeOnce = type + '.once';
+
+ function one() {
+ target.on(typeOnce, null);
+ listener.apply(this, arguments);
+ }
+
+ target.on(typeOnce, one, capture);
+ return this;
+ }
+
+ function defaultFilter(d3_event) {
+ return !d3_event.ctrlKey && !d3_event.button;
+ }
+
+ function defaultExtent() {
+ var e = this;
+
+ if (e instanceof SVGElement) {
+ e = e.ownerSVGElement || e;
+
+ if (e.hasAttribute('viewBox')) {
+ e = e.viewBox.baseVal;
+ return [[e.x, e.y], [e.x + e.width, e.y + e.height]];
+ }
+
+ return [[0, 0], [e.width.baseVal.value, e.height.baseVal.value]];
+ }
+
+ return [[0, 0], [e.clientWidth, e.clientHeight]];
+ }
+
+ function defaultWheelDelta(d3_event) {
+ return -d3_event.deltaY * (d3_event.deltaMode === 1 ? 0.05 : d3_event.deltaMode ? 1 : 0.002);
+ }
+
+ function defaultConstrain(transform, extent, translateExtent) {
+ var dx0 = transform.invertX(extent[0][0]) - translateExtent[0][0],
+ dx1 = transform.invertX(extent[1][0]) - translateExtent[1][0],
+ dy0 = transform.invertY(extent[0][1]) - translateExtent[0][1],
+ dy1 = transform.invertY(extent[1][1]) - translateExtent[1][1];
+ return transform.translate(dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1), dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1));
+ }
+
+ function utilZoomPan() {
+ var filter = defaultFilter,
+ extent = defaultExtent,
+ constrain = defaultConstrain,
+ wheelDelta = defaultWheelDelta,
+ scaleExtent = [0, Infinity],
+ translateExtent = [[-Infinity, -Infinity], [Infinity, Infinity]],
+ interpolate = interpolateZoom,
+ dispatch = dispatch$8('start', 'zoom', 'end'),
+ _wheelDelay = 150,
+ _transform = identity$2,
+ _activeGesture;
+
+ function zoom(selection) {
+ selection.on('pointerdown.zoom', pointerdown).on('wheel.zoom', wheeled).style('touch-action', 'none').style('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
+ select(window).on('pointermove.zoompan', pointermove).on('pointerup.zoompan pointercancel.zoompan', pointerup);
+ }
+
+ zoom.transform = function (collection, transform, point) {
+ var selection = collection.selection ? collection.selection() : collection;
+
+ if (collection !== selection) {
+ schedule(collection, transform, point);
+ } else {
+ selection.interrupt().each(function () {
+ gesture(this, arguments).start(null).zoom(null, null, typeof transform === 'function' ? transform.apply(this, arguments) : transform).end(null);
+ });
+ }
+ };
+
+ zoom.scaleBy = function (selection, k, p) {
+ zoom.scaleTo(selection, function () {
+ var k0 = _transform.k,
+ k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
+ return k0 * k1;
+ }, p);
+ };
+
+ zoom.scaleTo = function (selection, k, p) {
+ zoom.transform(selection, function () {
+ var e = extent.apply(this, arguments),
+ t0 = _transform,
+ p0 = !p ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p,
+ p1 = t0.invert(p0),
+ k1 = typeof k === 'function' ? k.apply(this, arguments) : k;
+ return constrain(translate(scale(t0, k1), p0, p1), e, translateExtent);
+ }, p);
+ };
+
+ zoom.translateBy = function (selection, x, y) {
+ zoom.transform(selection, function () {
+ return constrain(_transform.translate(typeof x === 'function' ? x.apply(this, arguments) : x, typeof y === 'function' ? y.apply(this, arguments) : y), extent.apply(this, arguments), translateExtent);
+ });
+ };
+
+ zoom.translateTo = function (selection, x, y, p) {
+ zoom.transform(selection, function () {
+ var e = extent.apply(this, arguments),
+ t = _transform,
+ p0 = !p ? centroid(e) : typeof p === 'function' ? p.apply(this, arguments) : p;
+ return constrain(identity$2.translate(p0[0], p0[1]).scale(t.k).translate(typeof x === 'function' ? -x.apply(this, arguments) : -x, typeof y === 'function' ? -y.apply(this, arguments) : -y), e, translateExtent);
+ }, p);
+ };
+
+ function scale(transform, k) {
+ k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], k));
+ return k === transform.k ? transform : new Transform(k, transform.x, transform.y);
+ }
+
+ function translate(transform, p0, p1) {
+ var x = p0[0] - p1[0] * transform.k,
+ y = p0[1] - p1[1] * transform.k;
+ return x === transform.x && y === transform.y ? transform : new Transform(transform.k, x, y);
+ }
+
+ function centroid(extent) {
+ return [(+extent[0][0] + +extent[1][0]) / 2, (+extent[0][1] + +extent[1][1]) / 2];
+ }
+
+ function schedule(transition, transform, point) {
+ transition.on('start.zoom', function () {
+ gesture(this, arguments).start(null);
+ }).on('interrupt.zoom end.zoom', function () {
+ gesture(this, arguments).end(null);
+ }).tween('zoom', function () {
+ var that = this,
+ args = arguments,
+ g = gesture(that, args),
+ e = extent.apply(that, args),
+ p = !point ? centroid(e) : typeof point === 'function' ? point.apply(that, args) : point,
+ w = Math.max(e[1][0] - e[0][0], e[1][1] - e[0][1]),
+ a = _transform,
+ b = typeof transform === 'function' ? transform.apply(that, args) : transform,
+ i = interpolate(a.invert(p).concat(w / a.k), b.invert(p).concat(w / b.k));
+ return function (t) {
+ if (t === 1) {
+ // Avoid rounding error on end.
+ t = b;
+ } else {
+ var l = i(t);
+ var k = w / l[2];
+ t = new Transform(k, p[0] - l[0] * k, p[1] - l[1] * k);
+ }
+
+ g.zoom(null, null, t);
+ };
+ });
+ }
+
+ function gesture(that, args, clean) {
+ return !clean && _activeGesture || new Gesture(that, args);
+ }
+
+ function Gesture(that, args) {
+ this.that = that;
+ this.args = args;
+ this.active = 0;
+ this.extent = extent.apply(that, args);
+ }
+
+ Gesture.prototype = {
+ start: function start(d3_event) {
+ if (++this.active === 1) {
+ _activeGesture = this;
+ dispatch.call('start', this, d3_event);
+ }
+
+ return this;
+ },
+ zoom: function zoom(d3_event, key, transform) {
+ if (this.mouse && key !== 'mouse') this.mouse[1] = transform.invert(this.mouse[0]);
+ if (this.pointer0 && key !== 'touch') this.pointer0[1] = transform.invert(this.pointer0[0]);
+ if (this.pointer1 && key !== 'touch') this.pointer1[1] = transform.invert(this.pointer1[0]);
+ _transform = transform;
+ dispatch.call('zoom', this, d3_event, key, transform);
+ return this;
+ },
+ end: function end(d3_event) {
+ if (--this.active === 0) {
+ _activeGesture = null;
+ dispatch.call('end', this, d3_event);
+ }
+
+ return this;
+ }
+ };
+
+ function wheeled(d3_event) {
+ if (!filter.apply(this, arguments)) return;
+ var g = gesture(this, arguments),
+ t = _transform,
+ k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], t.k * Math.pow(2, wheelDelta.apply(this, arguments)))),
+ p = utilFastMouse(this)(d3_event); // If the mouse is in the same location as before, reuse it.
+ // If there were recent wheel events, reset the wheel idle timeout.
+
+ if (g.wheel) {
+ if (g.mouse[0][0] !== p[0] || g.mouse[0][1] !== p[1]) {
+ g.mouse[1] = t.invert(g.mouse[0] = p);
+ }
+
+ clearTimeout(g.wheel); // Otherwise, capture the mouse point and location at the start.
+ } else {
+ g.mouse = [p, t.invert(p)];
+ interrupt(this);
+ g.start(d3_event);
+ }
+
+ d3_event.preventDefault();
+ d3_event.stopImmediatePropagation();
+ g.wheel = setTimeout(wheelidled, _wheelDelay);
+ g.zoom(d3_event, 'mouse', constrain(translate(scale(t, k), g.mouse[0], g.mouse[1]), g.extent, translateExtent));
+
+ function wheelidled() {
+ g.wheel = null;
+ g.end(d3_event);
+ }
+ }
+
+ var _downPointerIDs = new Set();
+
+ var _pointerLocGetter;
+
+ function pointerdown(d3_event) {
+ _downPointerIDs.add(d3_event.pointerId);
+
+ if (!filter.apply(this, arguments)) return;
+ var g = gesture(this, arguments, _downPointerIDs.size === 1);
+ var started;
+ d3_event.stopImmediatePropagation();
+ _pointerLocGetter = utilFastMouse(this);
+
+ var loc = _pointerLocGetter(d3_event);
+
+ var p = [loc, _transform.invert(loc), d3_event.pointerId];
+
+ if (!g.pointer0) {
+ g.pointer0 = p;
+ started = true;
+ } else if (!g.pointer1 && g.pointer0[2] !== p[2]) {
+ g.pointer1 = p;
+ }
+
+ if (started) {
+ interrupt(this);
+ g.start(d3_event);
+ }
+ }
+
+ function pointermove(d3_event) {
+ if (!_downPointerIDs.has(d3_event.pointerId)) return;
+ if (!_activeGesture || !_pointerLocGetter) return;
+ var g = gesture(this, arguments);
+ var isPointer0 = g.pointer0 && g.pointer0[2] === d3_event.pointerId;
+ var isPointer1 = !isPointer0 && g.pointer1 && g.pointer1[2] === d3_event.pointerId;
+
+ if ((isPointer0 || isPointer1) && 'buttons' in d3_event && !d3_event.buttons) {
+ // The pointer went up without ending the gesture somehow, e.g.
+ // a down mouse was moved off the map and released. End it here.
+ if (g.pointer0) _downPointerIDs["delete"](g.pointer0[2]);
+ if (g.pointer1) _downPointerIDs["delete"](g.pointer1[2]);
+ g.end(d3_event);
+ return;
+ }
+
+ d3_event.preventDefault();
+ d3_event.stopImmediatePropagation();
+
+ var loc = _pointerLocGetter(d3_event);
+
+ var t, p, l;
+ if (isPointer0) g.pointer0[0] = loc;else if (isPointer1) g.pointer1[0] = loc;
+ t = _transform;
+
+ if (g.pointer1) {
+ var p0 = g.pointer0[0],
+ l0 = g.pointer0[1],
+ p1 = g.pointer1[0],
+ l1 = g.pointer1[1],
+ dp = (dp = p1[0] - p0[0]) * dp + (dp = p1[1] - p0[1]) * dp,
+ dl = (dl = l1[0] - l0[0]) * dl + (dl = l1[1] - l0[1]) * dl;
+ t = scale(t, Math.sqrt(dp / dl));
+ p = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
+ l = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
+ } else if (g.pointer0) {
+ p = g.pointer0[0];
+ l = g.pointer0[1];
+ } else {
+ return;
+ }
+
+ g.zoom(d3_event, 'touch', constrain(translate(t, p, l), g.extent, translateExtent));
+ }
+
+ function pointerup(d3_event) {
+ if (!_downPointerIDs.has(d3_event.pointerId)) return;
+
+ _downPointerIDs["delete"](d3_event.pointerId);
+
+ if (!_activeGesture) return;
+ var g = gesture(this, arguments);
+ d3_event.stopImmediatePropagation();
+ if (g.pointer0 && g.pointer0[2] === d3_event.pointerId) delete g.pointer0;else if (g.pointer1 && g.pointer1[2] === d3_event.pointerId) delete g.pointer1;
+
+ if (g.pointer1 && !g.pointer0) {
+ g.pointer0 = g.pointer1;
+ delete g.pointer1;
+ }
+
+ if (g.pointer0) {
+ g.pointer0[1] = _transform.invert(g.pointer0[0]);
+ } else {
+ g.end(d3_event);
+ }
+ }
+
+ zoom.wheelDelta = function (_) {
+ return arguments.length ? (wheelDelta = utilFunctor(+_), zoom) : wheelDelta;
+ };
+
+ zoom.filter = function (_) {
+ return arguments.length ? (filter = utilFunctor(!!_), zoom) : filter;
+ };
+
+ zoom.extent = function (_) {
+ return arguments.length ? (extent = utilFunctor([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), zoom) : extent;
+ };
+
+ zoom.scaleExtent = function (_) {
+ return arguments.length ? (scaleExtent[0] = +_[0], scaleExtent[1] = +_[1], zoom) : [scaleExtent[0], scaleExtent[1]];
+ };
+
+ zoom.translateExtent = function (_) {
+ return arguments.length ? (translateExtent[0][0] = +_[0][0], translateExtent[1][0] = +_[1][0], translateExtent[0][1] = +_[0][1], translateExtent[1][1] = +_[1][1], zoom) : [[translateExtent[0][0], translateExtent[0][1]], [translateExtent[1][0], translateExtent[1][1]]];
+ };
+
+ zoom.constrain = function (_) {
+ return arguments.length ? (constrain = _, zoom) : constrain;
+ };
+
+ zoom.interpolate = function (_) {
+ return arguments.length ? (interpolate = _, zoom) : interpolate;
+ };
+
+ zoom._transform = function (_) {
+ return arguments.length ? (_transform = _, zoom) : _transform;
+ };
+
+ return utilRebind(zoom, dispatch, 'on');
+ }
+
+ // if pointer events are supported. Falls back to default `dblclick` event.
+
+ function utilDoubleUp() {
+ var dispatch = dispatch$8('doubleUp');
+ var _maxTimespan = 500; // milliseconds
+
+ var _maxDistance = 20; // web pixels; be somewhat generous to account for touch devices
+
+ var _pointer; // object representing the pointer that could trigger double up
+
+
+ function pointerIsValidFor(loc) {
+ // second pointerup must occur within a small timeframe after the first pointerdown
+ return new Date().getTime() - _pointer.startTime <= _maxTimespan && // all pointer events must occur within a small distance of the first pointerdown
+ geoVecLength(_pointer.startLoc, loc) <= _maxDistance;
+ }
+
+ function pointerdown(d3_event) {
+ // ignore right-click
+ if (d3_event.ctrlKey || d3_event.button === 2) return;
+ var loc = [d3_event.clientX, d3_event.clientY]; // Don't rely on pointerId here since it can change between pointerdown
+ // events on touch devices
+
+ if (_pointer && !pointerIsValidFor(loc)) {
+ // if this pointer is no longer valid, clear it so another can be started
+ _pointer = undefined;
+ }
+
+ if (!_pointer) {
+ _pointer = {
+ startLoc: loc,
+ startTime: new Date().getTime(),
+ upCount: 0,
+ pointerId: d3_event.pointerId
+ };
+ } else {
+ // double down
+ _pointer.pointerId = d3_event.pointerId;
+ }
+ }
+
+ function pointerup(d3_event) {
+ // ignore right-click
+ if (d3_event.ctrlKey || d3_event.button === 2) return;
+ if (!_pointer || _pointer.pointerId !== d3_event.pointerId) return;
+ _pointer.upCount += 1;
+
+ if (_pointer.upCount === 2) {
+ // double up!
+ var loc = [d3_event.clientX, d3_event.clientY];
+
+ if (pointerIsValidFor(loc)) {
+ var locInThis = utilFastMouse(this)(d3_event);
+ dispatch.call('doubleUp', this, d3_event, locInThis);
+ } // clear the pointer info in any case
+
+
+ _pointer = undefined;
+ }
+ }
+
+ function doubleUp(selection) {
+ if ('PointerEvent' in window) {
+ // dblclick isn't well supported on touch devices so manually use
+ // pointer events if they're available
+ selection.on('pointerdown.doubleUp', pointerdown).on('pointerup.doubleUp', pointerup);
+ } else {
+ // fallback to dblclick
+ selection.on('dblclick.doubleUp', function (d3_event) {
+ dispatch.call('doubleUp', this, d3_event, utilFastMouse(this)(d3_event));
+ });
+ }
+ }
+
+ doubleUp.off = function (selection) {
+ selection.on('pointerdown.doubleUp', null).on('pointerup.doubleUp', null).on('dblclick.doubleUp', null);
+ };
+
+ return utilRebind(doubleUp, dispatch, 'on');
+ }
+
+ var TILESIZE = 256;
+ var minZoom = 2;
+ var maxZoom = 24;
+ var kMin = geoZoomToScale(minZoom, TILESIZE);
+ var kMax = geoZoomToScale(maxZoom, TILESIZE);
+
+ function clamp$1(num, min, max) {
+ return Math.max(min, Math.min(num, max));
+ }
+
+ function rendererMap(context) {
+ var dispatch = dispatch$8('move', 'drawn', 'crossEditableZoom', 'hitMinZoom', 'changeHighlighting', 'changeAreaFill');
+ var projection = context.projection;
+ var curtainProjection = context.curtainProjection;
+ var drawLayers;
+ var drawPoints;
+ var drawVertices;
+ var drawLines;
+ var drawAreas;
+ var drawMidpoints;
+ var drawLabels;
+
+ var _selection = select(null);
+
+ var supersurface = select(null);
+ var wrapper = select(null);
+ var surface = select(null);
+ var _dimensions = [1, 1];
+ var _dblClickZoomEnabled = true;
+ var _redrawEnabled = true;
+
+ var _gestureTransformStart;
+
+ var _transformStart = projection.transform();
+
+ var _transformLast;
+
+ var _isTransformed = false;
+ var _minzoom = 0;
+
+ var _getMouseCoords;
+
+ var _lastPointerEvent;
+
+ var _lastWithinEditableZoom; // whether a pointerdown event started the zoom
+
+
+ var _pointerDown = false; // use pointer events on supported platforms; fallback to mouse events
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // use pointer event interaction if supported; fallback to touch/mouse events in d3-zoom
+
+
+ var _zoomerPannerFunction = 'PointerEvent' in window ? utilZoomPan : d3_zoom;
+
+ var _zoomerPanner = _zoomerPannerFunction().scaleExtent([kMin, kMax]).interpolate(interpolate$1).filter(zoomEventFilter).on('zoom.map', zoomPan).on('start.map', function (d3_event) {
+ _pointerDown = d3_event && (d3_event.type === 'pointerdown' || d3_event.sourceEvent && d3_event.sourceEvent.type === 'pointerdown');
+ }).on('end.map', function () {
+ _pointerDown = false;
+ });
+
+ var _doubleUpHandler = utilDoubleUp();
+
+ var scheduleRedraw = throttle(redraw, 750); // var isRedrawScheduled = false;
+ // var pendingRedrawCall;
+ // function scheduleRedraw() {
+ // // Only schedule the redraw if one has not already been set.
+ // if (isRedrawScheduled) return;
+ // isRedrawScheduled = true;
+ // var that = this;
+ // var args = arguments;
+ // pendingRedrawCall = window.requestIdleCallback(function () {
+ // // Reset the boolean so future redraws can be set.
+ // isRedrawScheduled = false;
+ // redraw.apply(that, args);
+ // }, { timeout: 1400 });
+ // }
+
+
+ function cancelPendingRedraw() {
+ scheduleRedraw.cancel(); // isRedrawScheduled = false;
+ // window.cancelIdleCallback(pendingRedrawCall);
+ }
+
+ function map(selection) {
+ _selection = selection;
+ context.on('change.map', immediateRedraw);
+ var osm = context.connection();
+
+ if (osm) {
+ osm.on('change.map', immediateRedraw);
+ }
+
+ function didUndoOrRedo(targetTransform) {
+ var mode = context.mode().id;
+ if (mode !== 'browse' && mode !== 'select') return;
+
+ if (targetTransform) {
+ map.transformEase(targetTransform);
+ }
+ }
+
+ context.history().on('merge.map', function () {
+ scheduleRedraw();
+ }).on('change.map', immediateRedraw).on('undone.map', function (stack, fromStack) {
+ didUndoOrRedo(fromStack.transform);
+ }).on('redone.map', function (stack) {
+ didUndoOrRedo(stack.transform);
+ });
+ context.background().on('change.map', immediateRedraw);
+ context.features().on('redraw.map', immediateRedraw);
+ drawLayers.on('change.map', function () {
+ context.background().updateImagery();
+ immediateRedraw();
+ });
+ selection.on('wheel.map mousewheel.map', function (d3_event) {
+ // disable swipe-to-navigate browser pages on trackpad/magic mouse – #5552
+ d3_event.preventDefault();
+ }).call(_zoomerPanner).call(_zoomerPanner.transform, projection.transform()).on('dblclick.zoom', null); // override d3-zoom dblclick handling
+
+ map.supersurface = supersurface = selection.append('div').attr('class', 'supersurface').call(utilSetTransform, 0, 0); // Need a wrapper div because Opera can't cope with an absolutely positioned
+ // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
+
+ wrapper = supersurface.append('div').attr('class', 'layer layer-data');
+ map.surface = surface = wrapper.call(drawLayers).selectAll('.surface');
+ surface.call(drawLabels.observe).call(_doubleUpHandler).on(_pointerPrefix + 'down.zoom', function (d3_event) {
+ _lastPointerEvent = d3_event;
+
+ if (d3_event.button === 2) {
+ d3_event.stopPropagation();
+ }
+ }, true).on(_pointerPrefix + 'up.zoom', function (d3_event) {
+ _lastPointerEvent = d3_event;
+
+ if (resetTransform()) {
+ immediateRedraw();
+ }
+ }).on(_pointerPrefix + 'move.map', function (d3_event) {
+ _lastPointerEvent = d3_event;
+ }).on(_pointerPrefix + 'over.vertices', function (d3_event) {
+ if (map.editableDataEnabled() && !_isTransformed) {
+ var hover = d3_event.target.__data__;
+ surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
+ dispatch.call('drawn', this, {
+ full: false
+ });
+ }
+ }).on(_pointerPrefix + 'out.vertices', function (d3_event) {
+ if (map.editableDataEnabled() && !_isTransformed) {
+ var hover = d3_event.relatedTarget && d3_event.relatedTarget.__data__;
+ surface.call(drawVertices.drawHover, context.graph(), hover, map.extent());
+ dispatch.call('drawn', this, {
+ full: false
+ });
+ }
+ });
+ var detected = utilDetect(); // only WebKit supports gesture events
+
+ if ('GestureEvent' in window && // Listening for gesture events on iOS 13.4+ breaks double-tapping,
+ // but we only need to do this on desktop Safari anyway. – #7694
+ !detected.isMobileWebKit) {
+ // Desktop Safari sends gesture events for multitouch trackpad pinches.
+ // We can listen for these and translate them into map zooms.
+ surface.on('gesturestart.surface', function (d3_event) {
+ d3_event.preventDefault();
+ _gestureTransformStart = projection.transform();
+ }).on('gesturechange.surface', gestureChange);
+ } // must call after surface init
+
+
+ updateAreaFill();
+
+ _doubleUpHandler.on('doubleUp.map', function (d3_event, p0) {
+ if (!_dblClickZoomEnabled) return; // don't zoom if targeting something other than the map itself
+
+ if (_typeof(d3_event.target.__data__) === 'object' && // or area fills
+ !select(d3_event.target).classed('fill')) return;
+ var zoomOut = d3_event.shiftKey;
+ var t = projection.transform();
+ var p1 = t.invert(p0);
+ t = t.scale(zoomOut ? 0.5 : 2);
+ t.x = p0[0] - p1[0] * t.k;
+ t.y = p0[1] - p1[1] * t.k;
+ map.transformEase(t);
+ });
+
+ context.on('enter.map', function () {
+ if (!map.editableDataEnabled(true
+ /* skip zoom check */
+ )) return;
+ if (_isTransformed) return; // redraw immediately any objects affected by a change in selectedIDs.
+
+ var graph = context.graph();
+ var selectedAndParents = {};
+ context.selectedIDs().forEach(function (id) {
+ var entity = graph.hasEntity(id);
+
+ if (entity) {
+ selectedAndParents[entity.id] = entity;
+
+ if (entity.type === 'node') {
+ graph.parentWays(entity).forEach(function (parent) {
+ selectedAndParents[parent.id] = parent;
+ });
+ }
+ }
+ });
+ var data = Object.values(selectedAndParents);
+
+ var filter = function filter(d) {
+ return d.id in selectedAndParents;
+ };
+
+ data = context.features().filter(data, graph);
+ surface.call(drawVertices.drawSelected, graph, map.extent()).call(drawLines, graph, data, filter).call(drawAreas, graph, data, filter).call(drawMidpoints, graph, data, filter, map.trimmedExtent());
+ dispatch.call('drawn', this, {
+ full: false
+ }); // redraw everything else later
+
+ scheduleRedraw();
+ });
+ map.dimensions(utilGetDimensions(selection));
+ }
+
+ function zoomEventFilter(d3_event) {
+ // Fix for #2151, (see also d3/d3-zoom#60, d3/d3-brush#18)
+ // Intercept `mousedown` and check if there is an orphaned zoom gesture.
+ // This can happen if a previous `mousedown` occurred without a `mouseup`.
+ // If we detect this, dispatch `mouseup` to complete the orphaned gesture,
+ // so that d3-zoom won't stop propagation of new `mousedown` events.
+ if (d3_event.type === 'mousedown') {
+ var hasOrphan = false;
+ var listeners = window.__on;
+
+ for (var i = 0; i < listeners.length; i++) {
+ var listener = listeners[i];
+
+ if (listener.name === 'zoom' && listener.type === 'mouseup') {
+ hasOrphan = true;
+ break;
+ }
+ }
+
+ if (hasOrphan) {
+ var event = window.CustomEvent;
+
+ if (event) {
+ event = new event('mouseup');
+ } else {
+ event = window.document.createEvent('Event');
+ event.initEvent('mouseup', false, false);
+ } // Event needs to be dispatched with an event.view property.
+
+
+ event.view = window;
+ window.dispatchEvent(event);
+ }
+ }
+
+ return d3_event.button !== 2; // ignore right clicks
+ }
+
+ function pxCenter() {
+ return [_dimensions[0] / 2, _dimensions[1] / 2];
+ }
+
+ function drawEditable(difference, extent) {
+ var mode = context.mode();
+ var graph = context.graph();
+ var features = context.features();
+ var all = context.history().intersects(map.extent());
+ var fullRedraw = false;
+ var data;
+ var set;
+ var filter;
+ var applyFeatureLayerFilters = true;
+
+ if (map.isInWideSelection()) {
+ data = [];
+ utilEntityAndDeepMemberIDs(mode.selectedIDs(), context.graph()).forEach(function (id) {
+ var entity = context.hasEntity(id);
+ if (entity) data.push(entity);
+ });
+ fullRedraw = true;
+ filter = utilFunctor(true); // selected features should always be visible, so we can skip filtering
+
+ applyFeatureLayerFilters = false;
+ } else if (difference) {
+ var complete = difference.complete(map.extent());
+ data = Object.values(complete).filter(Boolean);
+ set = new Set(Object.keys(complete));
+
+ filter = function filter(d) {
+ return set.has(d.id);
+ };
+
+ features.clear(data);
+ } else {
+ // force a full redraw if gatherStats detects that a feature
+ // should be auto-hidden (e.g. points or buildings)..
+ if (features.gatherStats(all, graph, _dimensions)) {
+ extent = undefined;
+ }
+
+ if (extent) {
+ data = context.history().intersects(map.extent().intersection(extent));
+ set = new Set(data.map(function (entity) {
+ return entity.id;
+ }));
+
+ filter = function filter(d) {
+ return set.has(d.id);
+ };
+ } else {
+ data = all;
+ fullRedraw = true;
+ filter = utilFunctor(true);
+ }
+ }
+
+ if (applyFeatureLayerFilters) {
+ data = features.filter(data, graph);
+ } else {
+ context.features().resetStats();
+ }
+
+ if (mode && mode.id === 'select') {
+ // update selected vertices - the user might have just double-clicked a way,
+ // creating a new vertex, triggering a partial redraw without a mode change
+ surface.call(drawVertices.drawSelected, graph, map.extent());
+ }
+
+ surface.call(drawVertices, graph, data, filter, map.extent(), fullRedraw).call(drawLines, graph, data, filter).call(drawAreas, graph, data, filter).call(drawMidpoints, graph, data, filter, map.trimmedExtent()).call(drawLabels, graph, data, filter, _dimensions, fullRedraw).call(drawPoints, graph, data, filter);
+ dispatch.call('drawn', this, {
+ full: true
+ });
+ }
+
+ map.init = function () {
+ drawLayers = svgLayers(projection, context);
+ drawPoints = svgPoints(projection, context);
+ drawVertices = svgVertices(projection, context);
+ drawLines = svgLines(projection, context);
+ drawAreas = svgAreas(projection, context);
+ drawMidpoints = svgMidpoints(projection, context);
+ drawLabels = svgLabels(projection, context);
+ };
+
+ function editOff() {
+ context.features().resetStats();
+ surface.selectAll('.layer-osm *').remove();
+ surface.selectAll('.layer-touch:not(.markers) *').remove();
+ var allowed = {
+ 'browse': true,
+ 'save': true,
+ 'select-note': true,
+ 'select-data': true,
+ 'select-error': true
+ };
+ var mode = context.mode();
+
+ if (mode && !allowed[mode.id]) {
+ context.enter(modeBrowse(context));
+ }
+
+ dispatch.call('drawn', this, {
+ full: true
+ });
+ }
+
+ function gestureChange(d3_event) {
+ // Remap Safari gesture events to wheel events - #5492
+ // We want these disabled most places, but enabled for zoom/unzoom on map surface
+ // https://developer.mozilla.org/en-US/docs/Web/API/GestureEvent
+ var e = d3_event;
+ e.preventDefault();
+ var props = {
+ deltaMode: 0,
+ // dummy values to ignore in zoomPan
+ deltaY: 1,
+ // dummy values to ignore in zoomPan
+ clientX: e.clientX,
+ clientY: e.clientY,
+ screenX: e.screenX,
+ screenY: e.screenY,
+ x: e.x,
+ y: e.y
+ };
+ var e2 = new WheelEvent('wheel', props);
+ e2._scale = e.scale; // preserve the original scale
+
+ e2._rotation = e.rotation; // preserve the original rotation
+
+ _selection.node().dispatchEvent(e2);
+ }
+
+ function zoomPan(event, key, transform) {
+ var source = event && event.sourceEvent || event;
+ var eventTransform = transform || event && event.transform;
+ var x = eventTransform.x;
+ var y = eventTransform.y;
+ var k = eventTransform.k; // Special handling of 'wheel' events:
+ // They might be triggered by the user scrolling the mouse wheel,
+ // or 2-finger pinch/zoom gestures, the transform may need adjustment.
+
+ if (source && source.type === 'wheel') {
+ // assume that the gesture is already handled by pointer events
+ if (_pointerDown) return;
+ var detected = utilDetect();
+ var dX = source.deltaX;
+ var dY = source.deltaY;
+ var x2 = x;
+ var y2 = y;
+ var k2 = k;
+ var t0, p0, p1; // Normalize mousewheel scroll speed (Firefox) - #3029
+ // If wheel delta is provided in LINE units, recalculate it in PIXEL units
+ // We are essentially redoing the calculations that occur here:
+ // https://github.com/d3/d3-zoom/blob/78563a8348aa4133b07cac92e2595c2227ca7cd7/src/zoom.js#L203
+ // See this for more info:
+ // https://github.com/basilfx/normalize-wheel/blob/master/src/normalizeWheel.js
+
+ if (source.deltaMode === 1
+ /* LINE */
+ ) {
+ // Convert from lines to pixels, more if the user is scrolling fast.
+ // (I made up the exp function to roughly match Firefox to what Chrome does)
+ // These numbers should be floats, because integers are treated as pan gesture below.
+ var lines = Math.abs(source.deltaY);
+ var sign = source.deltaY > 0 ? 1 : -1;
+ dY = sign * clamp$1(Math.exp((lines - 1) * 0.75) * 4.000244140625, 4.000244140625, // min
+ 350.000244140625 // max
+ ); // On Firefox Windows and Linux we always get +/- the scroll line amount (default 3)
+ // There doesn't seem to be any scroll acceleration.
+ // This multiplier increases the speed a little bit - #5512
+
+ if (detected.os !== 'mac') {
+ dY *= 5;
+ } // recalculate x2,y2,k2
+
+
+ t0 = _isTransformed ? _transformLast : _transformStart;
+ p0 = _getMouseCoords(source);
+ p1 = t0.invert(p0);
+ k2 = t0.k * Math.pow(2, -dY / 500);
+ k2 = clamp$1(k2, kMin, kMax);
+ x2 = p0[0] - p1[0] * k2;
+ y2 = p0[1] - p1[1] * k2; // 2 finger map pinch zooming (Safari) - #5492
+ // These are fake `wheel` events we made from Safari `gesturechange` events..
+ } else if (source._scale) {
+ // recalculate x2,y2,k2
+ t0 = _gestureTransformStart;
+ p0 = _getMouseCoords(source);
+ p1 = t0.invert(p0);
+ k2 = t0.k * source._scale;
+ k2 = clamp$1(k2, kMin, kMax);
+ x2 = p0[0] - p1[0] * k2;
+ y2 = p0[1] - p1[1] * k2; // 2 finger map pinch zooming (all browsers except Safari) - #5492
+ // Pinch zooming via the `wheel` event will always have:
+ // - `ctrlKey = true`
+ // - `deltaY` is not round integer pixels (ignore `deltaX`)
+ } else if (source.ctrlKey && !isInteger(dY)) {
+ dY *= 6; // slightly scale up whatever the browser gave us
+ // recalculate x2,y2,k2
+
+ t0 = _isTransformed ? _transformLast : _transformStart;
+ p0 = _getMouseCoords(source);
+ p1 = t0.invert(p0);
+ k2 = t0.k * Math.pow(2, -dY / 500);
+ k2 = clamp$1(k2, kMin, kMax);
+ x2 = p0[0] - p1[0] * k2;
+ y2 = p0[1] - p1[1] * k2; // Trackpad scroll zooming with shift or alt/option key down
+ } else if ((source.altKey || source.shiftKey) && isInteger(dY)) {
+ // recalculate x2,y2,k2
+ t0 = _isTransformed ? _transformLast : _transformStart;
+ p0 = _getMouseCoords(source);
+ p1 = t0.invert(p0);
+ k2 = t0.k * Math.pow(2, -dY / 500);
+ k2 = clamp$1(k2, kMin, kMax);
+ x2 = p0[0] - p1[0] * k2;
+ y2 = p0[1] - p1[1] * k2; // 2 finger map panning (Mac only, all browsers except Firefox #8595) - #5492, #5512
+ // Panning via the `wheel` event will always have:
+ // - `ctrlKey = false`
+ // - `deltaX`,`deltaY` are round integer pixels
+ } else if (detected.os === 'mac' && detected.browser !== 'Firefox' && !source.ctrlKey && isInteger(dX) && isInteger(dY)) {
+ p1 = projection.translate();
+ x2 = p1[0] - dX;
+ y2 = p1[1] - dY;
+ k2 = projection.scale();
+ k2 = clamp$1(k2, kMin, kMax);
+ } // something changed - replace the event transform
+
+
+ if (x2 !== x || y2 !== y || k2 !== k) {
+ x = x2;
+ y = y2;
+ k = k2;
+ eventTransform = identity$2.translate(x2, y2).scale(k2);
+
+ if (_zoomerPanner._transform) {
+ // utilZoomPan interface
+ _zoomerPanner._transform(eventTransform);
+ } else {
+ // d3_zoom interface
+ _selection.node().__zoom = eventTransform;
+ }
+ }
+ }
+
+ if (_transformStart.x === x && _transformStart.y === y && _transformStart.k === k) {
+ return; // no change
+ }
+
+ if (geoScaleToZoom(k, TILESIZE) < _minzoom) {
+ surface.interrupt();
+ dispatch.call('hitMinZoom', this, map);
+ setCenterZoom(map.center(), context.minEditableZoom(), 0, true);
+ scheduleRedraw();
+ dispatch.call('move', this, map);
+ return;
+ }
+
+ projection.transform(eventTransform);
+ var withinEditableZoom = map.withinEditableZoom();
+
+ if (_lastWithinEditableZoom !== withinEditableZoom) {
+ if (_lastWithinEditableZoom !== undefined) {
+ // notify that the map zoomed in or out over the editable zoom threshold
+ dispatch.call('crossEditableZoom', this, withinEditableZoom);
+ }
+
+ _lastWithinEditableZoom = withinEditableZoom;
+ }
+
+ var scale = k / _transformStart.k;
+ var tX = (x / scale - _transformStart.x) * scale;
+ var tY = (y / scale - _transformStart.y) * scale;
+
+ if (context.inIntro()) {
+ curtainProjection.transform({
+ x: x - tX,
+ y: y - tY,
+ k: k
+ });
+ }
+
+ if (source) {
+ _lastPointerEvent = event;
+ }
+
+ _isTransformed = true;
+ _transformLast = eventTransform;
+ utilSetTransform(supersurface, tX, tY, scale);
+ scheduleRedraw();
+ dispatch.call('move', this, map);
+
+ function isInteger(val) {
+ return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
+ }
+ }
+
+ function resetTransform() {
+ if (!_isTransformed) return false;
+ utilSetTransform(supersurface, 0, 0);
+ _isTransformed = false;
+
+ if (context.inIntro()) {
+ curtainProjection.transform(projection.transform());
+ }
+
+ return true;
+ }
+
+ function redraw(difference, extent) {
+ if (surface.empty() || !_redrawEnabled) return; // If we are in the middle of a zoom/pan, we can't do differenced redraws.
+ // It would result in artifacts where differenced entities are redrawn with
+ // one transform and unchanged entities with another.
+
+ if (resetTransform()) {
+ difference = extent = undefined;
+ }
+
+ var zoom = map.zoom();
+ var z = String(~~zoom);
+
+ if (surface.attr('data-zoom') !== z) {
+ surface.attr('data-zoom', z);
+ } // class surface as `lowzoom` around z17-z18.5 (based on latitude)
+
+
+ var lat = map.center()[1];
+ var lowzoom = linear().domain([-60, 0, 60]).range([17, 18.5, 17]).clamp(true);
+ surface.classed('low-zoom', zoom <= lowzoom(lat));
+
+ if (!difference) {
+ supersurface.call(context.background());
+ wrapper.call(drawLayers);
+ } // OSM
+
+
+ if (map.editableDataEnabled() || map.isInWideSelection()) {
+ context.loadTiles(projection);
+ drawEditable(difference, extent);
+ } else {
+ editOff();
+ }
+
+ _transformStart = projection.transform();
+ return map;
+ }
+
+ var immediateRedraw = function immediateRedraw(difference, extent) {
+ if (!difference && !extent) cancelPendingRedraw();
+ redraw(difference, extent);
+ };
+
+ map.lastPointerEvent = function () {
+ return _lastPointerEvent;
+ };
+
+ map.mouse = function (d3_event) {
+ var event = d3_event || _lastPointerEvent;
+
+ if (event) {
+ var s;
+
+ while (s = event.sourceEvent) {
+ event = s;
+ }
+
+ return _getMouseCoords(event);
+ }
+
+ return null;
+ }; // returns Lng/Lat
+
+
+ map.mouseCoordinates = function () {
+ var coord = map.mouse() || pxCenter();
+ return projection.invert(coord);
+ };
+
+ map.dblclickZoomEnable = function (val) {
+ if (!arguments.length) return _dblClickZoomEnabled;
+ _dblClickZoomEnabled = val;
+ return map;
+ };
+
+ map.redrawEnable = function (val) {
+ if (!arguments.length) return _redrawEnabled;
+ _redrawEnabled = val;
+ return map;
+ };
+
+ map.isTransformed = function () {
+ return _isTransformed;
+ };
+
+ function setTransform(t2, duration, force) {
+ var t = projection.transform();
+ if (!force && t2.k === t.k && t2.x === t.x && t2.y === t.y) return false;
+
+ if (duration) {
+ _selection.transition().duration(duration).on('start', function () {
+ map.startEase();
+ }).call(_zoomerPanner.transform, identity$2.translate(t2.x, t2.y).scale(t2.k));
+ } else {
+ projection.transform(t2);
+ _transformStart = t2;
+
+ _selection.call(_zoomerPanner.transform, _transformStart);
+ }
+
+ return true;
+ }
+
+ function setCenterZoom(loc2, z2, duration, force) {
+ var c = map.center();
+ var z = map.zoom();
+ if (loc2[0] === c[0] && loc2[1] === c[1] && z2 === z && !force) return false;
+ var proj = geoRawMercator().transform(projection.transform()); // copy projection
+
+ var k2 = clamp$1(geoZoomToScale(z2, TILESIZE), kMin, kMax);
+ proj.scale(k2);
+ var t = proj.translate();
+ var point = proj(loc2);
+ var center = pxCenter();
+ t[0] += center[0] - point[0];
+ t[1] += center[1] - point[1];
+ return setTransform(identity$2.translate(t[0], t[1]).scale(k2), duration, force);
+ }
+
+ map.pan = function (delta, duration) {
+ var t = projection.translate();
+ var k = projection.scale();
+ t[0] += delta[0];
+ t[1] += delta[1];
+
+ if (duration) {
+ _selection.transition().duration(duration).on('start', function () {
+ map.startEase();
+ }).call(_zoomerPanner.transform, identity$2.translate(t[0], t[1]).scale(k));
+ } else {
+ projection.translate(t);
+ _transformStart = projection.transform();
+
+ _selection.call(_zoomerPanner.transform, _transformStart);
+
+ dispatch.call('move', this, map);
+ immediateRedraw();
+ }
+
+ return map;
+ };
+
+ map.dimensions = function (val) {
+ if (!arguments.length) return _dimensions;
+ _dimensions = val;
+ drawLayers.dimensions(_dimensions);
+ context.background().dimensions(_dimensions);
+ projection.clipExtent([[0, 0], _dimensions]);
+ _getMouseCoords = utilFastMouse(supersurface.node());
+ scheduleRedraw();
+ return map;
+ };
+
+ function zoomIn(delta) {
+ setCenterZoom(map.center(), ~~map.zoom() + delta, 250, true);
+ }
+
+ function zoomOut(delta) {
+ setCenterZoom(map.center(), ~~map.zoom() - delta, 250, true);
+ }
+
+ map.zoomIn = function () {
+ zoomIn(1);
+ };
+
+ map.zoomInFurther = function () {
+ zoomIn(4);
+ };
+
+ map.canZoomIn = function () {
+ return map.zoom() < maxZoom;
+ };
+
+ map.zoomOut = function () {
+ zoomOut(1);
+ };
+
+ map.zoomOutFurther = function () {
+ zoomOut(4);
+ };
+
+ map.canZoomOut = function () {
+ return map.zoom() > minZoom;
+ };
+
+ map.center = function (loc2) {
+ if (!arguments.length) {
+ return projection.invert(pxCenter());
+ }
+
+ if (setCenterZoom(loc2, map.zoom())) {
+ dispatch.call('move', this, map);
+ }
+
+ scheduleRedraw();
+ return map;
+ };
+
+ map.unobscuredCenterZoomEase = function (loc, zoom) {
+ var offset = map.unobscuredOffsetPx();
+ var proj = geoRawMercator().transform(projection.transform()); // copy projection
+ // use the target zoom to calculate the offset center
+
+ proj.scale(geoZoomToScale(zoom, TILESIZE));
+ var locPx = proj(loc);
+ var offsetLocPx = [locPx[0] + offset[0], locPx[1] + offset[1]];
+ var offsetLoc = proj.invert(offsetLocPx);
+ map.centerZoomEase(offsetLoc, zoom);
+ };
+
+ map.unobscuredOffsetPx = function () {
+ var openPane = context.container().select('.map-panes .map-pane.shown');
+
+ if (!openPane.empty()) {
+ return [openPane.node().offsetWidth / 2, 0];
+ }
+
+ return [0, 0];
+ };
+
+ map.zoom = function (z2) {
+ if (!arguments.length) {
+ return Math.max(geoScaleToZoom(projection.scale(), TILESIZE), 0);
+ }
+
+ if (z2 < _minzoom) {
+ surface.interrupt();
+ dispatch.call('hitMinZoom', this, map);
+ z2 = context.minEditableZoom();
+ }
+
+ if (setCenterZoom(map.center(), z2)) {
+ dispatch.call('move', this, map);
+ }
+
+ scheduleRedraw();
+ return map;
+ };
+
+ map.centerZoom = function (loc2, z2) {
+ if (setCenterZoom(loc2, z2)) {
+ dispatch.call('move', this, map);
+ }
+
+ scheduleRedraw();
+ return map;
+ };
+
+ map.zoomTo = function (entity) {
+ var extent = entity.extent(context.graph());
+ if (!isFinite(extent.area())) return map;
+ var z2 = clamp$1(map.trimmedExtentZoom(extent), 0, 20);
+ return map.centerZoom(extent.center(), z2);
+ };
+
+ map.centerEase = function (loc2, duration) {
+ duration = duration || 250;
+ setCenterZoom(loc2, map.zoom(), duration);
+ return map;
+ };
+
+ map.zoomEase = function (z2, duration) {
+ duration = duration || 250;
+ setCenterZoom(map.center(), z2, duration, false);
+ return map;
+ };
+
+ map.centerZoomEase = function (loc2, z2, duration) {
+ duration = duration || 250;
+ setCenterZoom(loc2, z2, duration, false);
+ return map;
+ };
+
+ map.transformEase = function (t2, duration) {
+ duration = duration || 250;
+ setTransform(t2, duration, false
+ /* don't force */
+ );
+ return map;
+ };
+
+ map.zoomToEase = function (obj, duration) {
+ var extent;
+
+ if (Array.isArray(obj)) {
+ obj.forEach(function (entity) {
+ var entityExtent = entity.extent(context.graph());
+
+ if (!extent) {
+ extent = entityExtent;
+ } else {
+ extent = extent.extend(entityExtent);
+ }
+ });
+ } else {
+ extent = obj.extent(context.graph());
+ }
+
+ if (!isFinite(extent.area())) return map;
+ var z2 = clamp$1(map.trimmedExtentZoom(extent), 0, 20);
+ return map.centerZoomEase(extent.center(), z2, duration);
+ };
+
+ map.startEase = function () {
+ utilBindOnce(surface, _pointerPrefix + 'down.ease', function () {
+ map.cancelEase();
+ });
+ return map;
+ };
+
+ map.cancelEase = function () {
+ _selection.interrupt();
+
+ return map;
+ };
+
+ map.extent = function (val) {
+ if (!arguments.length) {
+ return new geoExtent(projection.invert([0, _dimensions[1]]), projection.invert([_dimensions[0], 0]));
+ } else {
+ var extent = geoExtent(val);
+ map.centerZoom(extent.center(), map.extentZoom(extent));
+ }
+ };
+
+ map.trimmedExtent = function (val) {
+ if (!arguments.length) {
+ var headerY = 71;
+ var footerY = 30;
+ var pad = 10;
+ return new geoExtent(projection.invert([pad, _dimensions[1] - footerY - pad]), projection.invert([_dimensions[0] - pad, headerY + pad]));
+ } else {
+ var extent = geoExtent(val);
+ map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
+ }
+ };
+
+ function calcExtentZoom(extent, dim) {
+ var tl = projection([extent[0][0], extent[1][1]]);
+ var br = projection([extent[1][0], extent[0][1]]); // Calculate maximum zoom that fits extent
+
+ var hFactor = (br[0] - tl[0]) / dim[0];
+ var vFactor = (br[1] - tl[1]) / dim[1];
+ var hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2;
+ var vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2;
+ var newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff);
+ return newZoom;
+ }
+
+ map.extentZoom = function (val) {
+ return calcExtentZoom(geoExtent(val), _dimensions);
+ };
+
+ map.trimmedExtentZoom = function (val) {
+ var trimY = 120;
+ var trimX = 40;
+ var trimmed = [_dimensions[0] - trimX, _dimensions[1] - trimY];
+ return calcExtentZoom(geoExtent(val), trimmed);
+ };
+
+ map.withinEditableZoom = function () {
+ return map.zoom() >= context.minEditableZoom();
+ };
+
+ map.isInWideSelection = function () {
+ return !map.withinEditableZoom() && context.selectedIDs().length;
+ };
+
+ map.editableDataEnabled = function (skipZoomCheck) {
+ var layer = context.layers().layer('osm');
+ if (!layer || !layer.enabled()) return false;
+ return skipZoomCheck || map.withinEditableZoom();
+ };
+
+ map.notesEditable = function () {
+ var layer = context.layers().layer('notes');
+ if (!layer || !layer.enabled()) return false;
+ return map.withinEditableZoom();
+ };
+
+ map.minzoom = function (val) {
+ if (!arguments.length) return _minzoom;
+ _minzoom = val;
+ return map;
+ };
+
+ map.toggleHighlightEdited = function () {
+ surface.classed('highlight-edited', !surface.classed('highlight-edited'));
+ map.pan([0, 0]); // trigger a redraw
+
+ dispatch.call('changeHighlighting', this);
+ };
+
+ map.areaFillOptions = ['wireframe', 'partial', 'full'];
+
+ map.activeAreaFill = function (val) {
+ if (!arguments.length) return corePreferences('area-fill') || 'partial';
+ corePreferences('area-fill', val);
+
+ if (val !== 'wireframe') {
+ corePreferences('area-fill-toggle', val);
+ }
+
+ updateAreaFill();
+ map.pan([0, 0]); // trigger a redraw
+
+ dispatch.call('changeAreaFill', this);
+ return map;
+ };
+
+ map.toggleWireframe = function () {
+ var activeFill = map.activeAreaFill();
+
+ if (activeFill === 'wireframe') {
+ activeFill = corePreferences('area-fill-toggle') || 'partial';
+ } else {
+ activeFill = 'wireframe';
+ }
+
+ map.activeAreaFill(activeFill);
+ };
+
+ function updateAreaFill() {
+ var activeFill = map.activeAreaFill();
+ map.areaFillOptions.forEach(function (opt) {
+ surface.classed('fill-' + opt, Boolean(opt === activeFill));
+ });
+ }
+
+ map.layers = function () {
+ return drawLayers;
+ };
+
+ map.doubleUpHandler = function () {
+ return _doubleUpHandler;
+ };
+
+ return utilRebind(map, dispatch, 'on');
+ }
+
+ function rendererPhotos(context) {
+ var dispatch = dispatch$8('change');
+ var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'kartaview'];
+ var _allPhotoTypes = ['flat', 'panoramic'];
+
+ var _shownPhotoTypes = _allPhotoTypes.slice(); // shallow copy
+
+
+ var _dateFilters = ['fromDate', 'toDate'];
+
+ var _fromDate;
+
+ var _toDate;
+
+ var _usernames;
+
+ function photos() {}
+
+ function updateStorage() {
+ if (window.mocha) return;
+ var hash = utilStringQs(window.location.hash);
+ var enabled = context.layers().all().filter(function (d) {
+ return _layerIDs.indexOf(d.id) !== -1 && d.layer && d.layer.supported() && d.layer.enabled();
+ }).map(function (d) {
+ return d.id;
+ });
+
+ if (enabled.length) {
+ hash.photo_overlay = enabled.join(',');
+ } else {
+ delete hash.photo_overlay;
+ }
+
+ window.location.replace('#' + utilQsString(hash, true));
+ }
+
+ photos.overlayLayerIDs = function () {
+ return _layerIDs;
+ };
+
+ photos.allPhotoTypes = function () {
+ return _allPhotoTypes;
+ };
+
+ photos.dateFilters = function () {
+ return _dateFilters;
+ };
+
+ photos.dateFilterValue = function (val) {
+ return val === _dateFilters[0] ? _fromDate : _toDate;
+ };
+
+ photos.setDateFilter = function (type, val, updateUrl) {
+ // validate the date
+ var date = val && new Date(val);
+
+ if (date && !isNaN(date)) {
+ val = date.toISOString().substr(0, 10);
+ } else {
+ val = null;
+ }
+
+ if (type === _dateFilters[0]) {
+ _fromDate = val;
+
+ if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
+ _toDate = _fromDate;
+ }
+ }
+
+ if (type === _dateFilters[1]) {
+ _toDate = val;
+
+ if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
+ _fromDate = _toDate;
+ }
+ }
+
+ dispatch.call('change', this);
+
+ if (updateUrl) {
+ var rangeString;
+
+ if (_fromDate || _toDate) {
+ rangeString = (_fromDate || '') + '_' + (_toDate || '');
+ }
+
+ setUrlFilterValue('photo_dates', rangeString);
+ }
+ };
+
+ photos.setUsernameFilter = function (val, updateUrl) {
+ if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(',');
+
+ if (val) {
+ val = val.map(function (d) {
+ return d.trim();
+ }).filter(Boolean);
+
+ if (!val.length) {
+ val = null;
+ }
+ }
+
+ _usernames = val;
+ dispatch.call('change', this);
+
+ if (updateUrl) {
+ var hashString;
+
+ if (_usernames) {
+ hashString = _usernames.join(',');
+ }
+
+ setUrlFilterValue('photo_username', hashString);
+ }
+ };
+
+ function setUrlFilterValue(property, val) {
+ if (!window.mocha) {
+ var hash = utilStringQs(window.location.hash);
+
+ if (val) {
+ if (hash[property] === val) return;
+ hash[property] = val;
+ } else {
+ if (!(property in hash)) return;
+ delete hash[property];
+ }
+
+ window.location.replace('#' + utilQsString(hash, true));
+ }
+ }
+
+ function showsLayer(id) {
+ var layer = context.layers().layer(id);
+ return layer && layer.supported() && layer.enabled();
+ }
+
+ photos.shouldFilterByDate = function () {
+ return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside');
+ };
+
+ photos.shouldFilterByPhotoType = function () {
+ return showsLayer('mapillary') || showsLayer('streetside') && showsLayer('kartaview');
+ };
+
+ photos.shouldFilterByUsername = function () {
+ return !showsLayer('mapillary') && showsLayer('kartaview') && !showsLayer('streetside');
+ };
+
+ photos.showsPhotoType = function (val) {
+ if (!photos.shouldFilterByPhotoType()) return true;
+ return _shownPhotoTypes.indexOf(val) !== -1;
+ };
+
+ photos.showsFlat = function () {
+ return photos.showsPhotoType('flat');
+ };
+
+ photos.showsPanoramic = function () {
+ return photos.showsPhotoType('panoramic');
+ };
+
+ photos.fromDate = function () {
+ return _fromDate;
+ };
+
+ photos.toDate = function () {
+ return _toDate;
+ };
+
+ photos.togglePhotoType = function (val) {
+ var index = _shownPhotoTypes.indexOf(val);
+
+ if (index !== -1) {
+ _shownPhotoTypes.splice(index, 1);
+ } else {
+ _shownPhotoTypes.push(val);
+ }
+
+ dispatch.call('change', this);
+ return photos;
+ };
+
+ photos.usernames = function () {
+ return _usernames;
+ };
+
+ photos.init = function () {
+ var hash = utilStringQs(window.location.hash);
+
+ if (hash.photo_dates) {
+ // expect format like `photo_dates=2019-01-01_2020-12-31`, but allow a couple different separators
+ var parts = /^(.*)[–_](.*)$/g.exec(hash.photo_dates.trim());
+ this.setDateFilter('fromDate', parts && parts.length >= 2 && parts[1], false);
+ this.setDateFilter('toDate', parts && parts.length >= 3 && parts[2], false);
+ }
+
+ if (hash.photo_username) {
+ this.setUsernameFilter(hash.photo_username, false);
+ }
+
+ if (hash.photo_overlay) {
+ // support enabling photo layers by default via a URL parameter, e.g. `photo_overlay=kartaview;mapillary;streetside`
+ var hashOverlayIDs = hash.photo_overlay.replace(/;/g, ',').split(',');
+ hashOverlayIDs.forEach(function (id) {
+ if (id === 'openstreetcam') id = 'kartaview'; // legacy alias
+
+ var layer = _layerIDs.indexOf(id) !== -1 && context.layers().layer(id);
+ if (layer && !layer.enabled()) layer.enabled(true);
+ });
+ }
+
+ if (hash.photo) {
+ // support opening a photo via a URL parameter, e.g. `photo=mapillary-fztgSDtLpa08ohPZFZjeRQ`
+ var photoIds = hash.photo.replace(/;/g, ',').split(',');
+ var photoId = photoIds.length && photoIds[0].trim();
+ var results = /(.*)\/(.*)/g.exec(photoId);
+
+ if (results && results.length >= 3) {
+ var serviceId = results[1];
+ if (serviceId === 'openstreetcam') serviceId = 'kartaview'; // legacy alias
+
+ var photoKey = results[2];
+ var service = services[serviceId];
+
+ if (service && service.ensureViewerLoaded) {
+ // if we're showing a photo then make sure its layer is enabled too
+ var layer = _layerIDs.indexOf(serviceId) !== -1 && context.layers().layer(serviceId);
+ if (layer && !layer.enabled()) layer.enabled(true);
+ var baselineTime = Date.now();
+ service.on('loadedImages.rendererPhotos', function () {
+ // don't open the viewer if too much time has elapsed
+ if (Date.now() - baselineTime > 45000) {
+ service.on('loadedImages.rendererPhotos', null);
+ return;
+ }
+
+ if (!service.cachedImage(photoKey)) return;
+ service.on('loadedImages.rendererPhotos', null);
+ service.ensureViewerLoaded(context).then(function () {
+ service.selectImage(context, photoKey).showViewer(context);
+ });
+ });
+ }
+ }
+ }
+
+ context.layers().on('change.rendererPhotos', updateStorage);
+ };
+
+ return utilRebind(photos, dispatch, 'on');
+ }
+
+ function uiAccount(context) {
+ var osm = context.connection();
+
+ function update(selection) {
+ if (!osm) return;
+
+ if (!osm.authenticated()) {
+ selection.selectAll('.userLink, .logoutLink').classed('hide', true);
+ return;
+ }
+
+ osm.userDetails(function (err, details) {
+ var userLink = selection.select('.userLink'),
+ logoutLink = selection.select('.logoutLink');
+ userLink.html('');
+ logoutLink.html('');
+ if (err || !details) return;
+ selection.selectAll('.userLink, .logoutLink').classed('hide', false); // Link
+
+ var userLinkA = userLink.append('a').attr('href', osm.userURL(details.display_name)).attr('target', '_blank'); // Add thumbnail or dont
+
+ if (details.image_url) {
+ userLinkA.append('img').attr('class', 'icon pre-text user-icon').attr('src', details.image_url);
+ } else {
+ userLinkA.call(svgIcon('#iD-icon-avatar', 'pre-text light'));
+ } // Add user name
+
+
+ userLinkA.append('span').attr('class', 'label').html(details.display_name);
+ logoutLink.append('a').attr('class', 'logout').attr('href', '#').call(_t.append('logout')).on('click.logout', function (d3_event) {
+ d3_event.preventDefault();
+ osm.logout();
+ });
+ });
+ }
+
+ return function (selection) {
+ selection.append('li').attr('class', 'userLink').classed('hide', true);
+ selection.append('li').attr('class', 'logoutLink').classed('hide', true);
+
+ if (osm) {
+ osm.on('change.account', function () {
+ update(selection);
+ });
+ update(selection);
+ }
+ };
+ }
+
+ function uiAttribution(context) {
+ var _selection = select(null);
+
+ function render(selection, data, klass) {
+ var div = selection.selectAll(".".concat(klass)).data([0]);
+ div = div.enter().append('div').attr('class', klass).merge(div);
+ var attributions = div.selectAll('.attribution').data(data, function (d) {
+ return d.id;
+ });
+ attributions.exit().remove();
+ attributions = attributions.enter().append('span').attr('class', 'attribution').each(function (d, i, nodes) {
+ var attribution = select(nodes[i]);
+
+ if (d.terms_html) {
+ attribution.html(d.terms_html);
+ return;
+ }
+
+ if (d.terms_url) {
+ attribution = attribution.append('a').attr('href', d.terms_url).attr('target', '_blank');
+ }
+
+ var sourceID = d.id.replace(/\./g, '<TX_DOT>');
+ var terms_text = _t("imagery.".concat(sourceID, ".attribution.text"), {
+ "default": d.terms_text || d.id || d.name()
+ });
+
+ if (d.icon && !d.overlay) {
+ attribution.append('img').attr('class', 'source-image').attr('src', d.icon);
+ }
+
+ attribution.append('span').attr('class', 'attribution-text').text(terms_text);
+ }).merge(attributions);
+ var copyright = attributions.selectAll('.copyright-notice').data(function (d) {
+ var notice = d.copyrightNotices(context.map().zoom(), context.map().extent());
+ return notice ? [notice] : [];
+ });
+ copyright.exit().remove();
+ copyright = copyright.enter().append('span').attr('class', 'copyright-notice').merge(copyright);
+ copyright.text(String);
+ }
+
+ function update() {
+ var baselayer = context.background().baseLayerSource();
+
+ _selection.call(render, baselayer ? [baselayer] : [], 'base-layer-attribution');
+
+ var z = context.map().zoom();
+ var overlays = context.background().overlayLayerSources() || [];
+
+ _selection.call(render, overlays.filter(function (s) {
+ return s.validZoom(z);
+ }), 'overlay-layer-attribution');
+ }
+
+ return function (selection) {
+ _selection = selection;
+ context.background().on('change.attribution', update);
+ context.map().on('move.attribution', throttle(update, 400, {
+ leading: false
+ }));
+ update();
+ };
+ }
+
+ function uiContributors(context) {
+ var osm = context.connection(),
+ debouncedUpdate = debounce(function () {
+ update();
+ }, 1000),
+ limit = 4,
+ hidden = false,
+ wrap = select(null);
+
+ function update() {
+ if (!osm) return;
+ var users = {},
+ entities = context.history().intersects(context.map().extent());
+ entities.forEach(function (entity) {
+ if (entity && entity.user) users[entity.user] = true;
+ });
+ var u = Object.keys(users),
+ subset = u.slice(0, u.length > limit ? limit - 1 : limit);
+ wrap.html('').call(svgIcon('#iD-icon-nearby', 'pre-text light'));
+ var userList = select(document.createElement('span'));
+ userList.selectAll().data(subset).enter().append('a').attr('class', 'user-link').attr('href', function (d) {
+ return osm.userURL(d);
+ }).attr('target', '_blank').text(String);
+
+ if (u.length > limit) {
+ var count = select(document.createElement('span'));
+ var othersNum = u.length - limit + 1;
+ count.append('a').attr('target', '_blank').attr('href', function () {
+ return osm.changesetsURL(context.map().center(), context.map().zoom());
+ }).text(othersNum);
+ wrap.append('span').html(_t.html('contributors.truncated_list', {
+ n: othersNum,
+ users: {
+ html: userList.html()
+ },
+ count: {
+ html: count.html()
+ }
+ }));
+ } else {
+ wrap.append('span').html(_t.html('contributors.list', {
+ users: {
+ html: userList.html()
+ }
+ }));
+ }
+
+ if (!u.length) {
+ hidden = true;
+ wrap.transition().style('opacity', 0);
+ } else if (hidden) {
+ wrap.transition().style('opacity', 1);
+ }
+ }
+
+ return function (selection) {
+ if (!osm) return;
+ wrap = selection;
+ update();
+ osm.on('loaded.contributors', debouncedUpdate);
+ context.map().on('move.contributors', debouncedUpdate);
+ };
+ }
+
+ var _popoverID = 0;
+ function uiPopover(klass) {
+ var _id = _popoverID++;
+
+ var _anchorSelection = select(null);
+
+ var popover = function popover(selection) {
+ _anchorSelection = selection;
+ selection.each(setup);
+ };
+
+ var _animation = utilFunctor(false);
+
+ var _placement = utilFunctor('top'); // top, bottom, left, right
+
+
+ var _alignment = utilFunctor('center'); // leading, center, trailing
+
+
+ var _scrollContainer = utilFunctor(select(null));
+
+ var _content;
+
+ var _displayType = utilFunctor('');
+
+ var _hasArrow = utilFunctor(true); // use pointer events on supported platforms; fallback to mouse events
+
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ popover.displayType = function (val) {
+ if (arguments.length) {
+ _displayType = utilFunctor(val);
+ return popover;
+ } else {
+ return _displayType;
+ }
+ };
+
+ popover.hasArrow = function (val) {
+ if (arguments.length) {
+ _hasArrow = utilFunctor(val);
+ return popover;
+ } else {
+ return _hasArrow;
+ }
+ };
+
+ popover.placement = function (val) {
+ if (arguments.length) {
+ _placement = utilFunctor(val);
+ return popover;
+ } else {
+ return _placement;
+ }
+ };
+
+ popover.alignment = function (val) {
+ if (arguments.length) {
+ _alignment = utilFunctor(val);
+ return popover;
+ } else {
+ return _alignment;
+ }
+ };
+
+ popover.scrollContainer = function (val) {
+ if (arguments.length) {
+ _scrollContainer = utilFunctor(val);
+ return popover;
+ } else {
+ return _scrollContainer;
+ }
+ };
+
+ popover.content = function (val) {
+ if (arguments.length) {
+ _content = val;
+ return popover;
+ } else {
+ return _content;
+ }
+ };
+
+ popover.isShown = function () {
+ var popoverSelection = _anchorSelection.select('.popover-' + _id);
+
+ return !popoverSelection.empty() && popoverSelection.classed('in');
+ };
+
+ popover.show = function () {
+ _anchorSelection.each(show);
+ };
+
+ popover.updateContent = function () {
+ _anchorSelection.each(updateContent);
+ };
+
+ popover.hide = function () {
+ _anchorSelection.each(hide);
+ };
+
+ popover.toggle = function () {
+ _anchorSelection.each(toggle);
+ };
+
+ popover.destroy = function (selection, selector) {
+ // by default, just destroy the current popover
+ selector = selector || '.popover-' + _id;
+ selection.on(_pointerPrefix + 'enter.popover', null).on(_pointerPrefix + 'leave.popover', null).on(_pointerPrefix + 'up.popover', null).on(_pointerPrefix + 'down.popover', null).on('click.popover', null).attr('title', function () {
+ return this.getAttribute('data-original-title') || this.getAttribute('title');
+ }).attr('data-original-title', null).selectAll(selector).remove();
+ };
+
+ popover.destroyAny = function (selection) {
+ selection.call(popover.destroy, '.popover');
+ };
+
+ function setup() {
+ var anchor = select(this);
+
+ var animate = _animation.apply(this, arguments);
+
+ var popoverSelection = anchor.selectAll('.popover-' + _id).data([0]);
+ var enter = popoverSelection.enter().append('div').attr('class', 'popover popover-' + _id + ' ' + (klass ? klass : '')).classed('arrowed', _hasArrow.apply(this, arguments));
+ enter.append('div').attr('class', 'popover-arrow');
+ enter.append('div').attr('class', 'popover-inner');
+ popoverSelection = enter.merge(popoverSelection);
+
+ if (animate) {
+ popoverSelection.classed('fade', true);
+ }
+
+ var display = _displayType.apply(this, arguments);
+
+ if (display === 'hover') {
+ var _lastNonMouseEnterTime;
+
+ anchor.on(_pointerPrefix + 'enter.popover', function (d3_event) {
+ if (d3_event.pointerType) {
+ if (d3_event.pointerType !== 'mouse') {
+ _lastNonMouseEnterTime = d3_event.timeStamp; // only allow hover behavior for mouse input
+
+ return;
+ } else if (_lastNonMouseEnterTime && d3_event.timeStamp - _lastNonMouseEnterTime < 1500) {
+ // HACK: iOS 13.4 sends an erroneous `mouse` type pointerenter
+ // event for non-mouse interactions right after sending
+ // the correct type pointerenter event. Workaround by discarding
+ // any mouse event that occurs immediately after a non-mouse event.
+ return;
+ }
+ } // don't show if buttons are pressed, e.g. during click and drag of map
+
+
+ if (d3_event.buttons !== 0) return;
+ show.apply(this, arguments);
+ }).on(_pointerPrefix + 'leave.popover', function () {
+ hide.apply(this, arguments);
+ }) // show on focus too for better keyboard navigation support
+ .on('focus.popover', function () {
+ show.apply(this, arguments);
+ }).on('blur.popover', function () {
+ hide.apply(this, arguments);
+ });
+ } else if (display === 'clickFocus') {
+ anchor.on(_pointerPrefix + 'down.popover', function (d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ }).on(_pointerPrefix + 'up.popover', function (d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ }).on('click.popover', toggle);
+ popoverSelection // This attribute lets the popover take focus
+ .attr('tabindex', 0).on('blur.popover', function () {
+ anchor.each(function () {
+ hide.apply(this, arguments);
+ });
+ });
+ }
+ }
+
+ function show() {
+ var anchor = select(this);
+ var popoverSelection = anchor.selectAll('.popover-' + _id);
+
+ if (popoverSelection.empty()) {
+ // popover was removed somehow, put it back
+ anchor.call(popover.destroy);
+ anchor.each(setup);
+ popoverSelection = anchor.selectAll('.popover-' + _id);
+ }
+
+ popoverSelection.classed('in', true);
+
+ var displayType = _displayType.apply(this, arguments);
+
+ if (displayType === 'clickFocus') {
+ anchor.classed('active', true);
+ popoverSelection.node().focus();
+ }
+
+ anchor.each(updateContent);
+ }
+
+ function updateContent() {
+ var anchor = select(this);
+
+ if (_content) {
+ anchor.selectAll('.popover-' + _id + ' > .popover-inner').call(_content.apply(this, arguments));
+ }
+
+ updatePosition.apply(this, arguments); // hack: update multiple times to fix instances where the absolute offset is
+ // set before the dynamic popover size is calculated by the browser
+
+ updatePosition.apply(this, arguments);
+ updatePosition.apply(this, arguments);
+ }
+
+ function updatePosition() {
+ var anchor = select(this);
+ var popoverSelection = anchor.selectAll('.popover-' + _id);
+
+ var scrollContainer = _scrollContainer && _scrollContainer.apply(this, arguments);
+
+ var scrollNode = scrollContainer && !scrollContainer.empty() && scrollContainer.node();
+ var scrollLeft = scrollNode ? scrollNode.scrollLeft : 0;
+ var scrollTop = scrollNode ? scrollNode.scrollTop : 0;
+
+ var placement = _placement.apply(this, arguments);
+
+ popoverSelection.classed('left', false).classed('right', false).classed('top', false).classed('bottom', false).classed(placement, true);
+
+ var alignment = _alignment.apply(this, arguments);
+
+ var alignFactor = 0.5;
+
+ if (alignment === 'leading') {
+ alignFactor = 0;
+ } else if (alignment === 'trailing') {
+ alignFactor = 1;
+ }
+
+ var anchorFrame = getFrame(anchor.node());
+ var popoverFrame = getFrame(popoverSelection.node());
+ var position;
+
+ switch (placement) {
+ case 'top':
+ position = {
+ x: anchorFrame.x + (anchorFrame.w - popoverFrame.w) * alignFactor,
+ y: anchorFrame.y - popoverFrame.h
+ };
+ break;
+
+ case 'bottom':
+ position = {
+ x: anchorFrame.x + (anchorFrame.w - popoverFrame.w) * alignFactor,
+ y: anchorFrame.y + anchorFrame.h
+ };
+ break;
+
+ case 'left':
+ position = {
+ x: anchorFrame.x - popoverFrame.w,
+ y: anchorFrame.y + (anchorFrame.h - popoverFrame.h) * alignFactor
+ };
+ break;
+
+ case 'right':
+ position = {
+ x: anchorFrame.x + anchorFrame.w,
+ y: anchorFrame.y + (anchorFrame.h - popoverFrame.h) * alignFactor
+ };
+ break;
+ }
+
+ if (position) {
+ if (scrollNode && (placement === 'top' || placement === 'bottom')) {
+ var initialPosX = position.x;
+
+ if (position.x + popoverFrame.w > scrollNode.offsetWidth - 10) {
+ position.x = scrollNode.offsetWidth - 10 - popoverFrame.w;
+ } else if (position.x < 10) {
+ position.x = 10;
+ }
+
+ var arrow = anchor.selectAll('.popover-' + _id + ' > .popover-arrow'); // keep the arrow centered on the button, or as close as possible
+
+ var arrowPosX = Math.min(Math.max(popoverFrame.w / 2 - (position.x - initialPosX), 10), popoverFrame.w - 10);
+ arrow.style('left', ~~arrowPosX + 'px');
+ }
+
+ popoverSelection.style('left', ~~position.x + 'px').style('top', ~~position.y + 'px');
+ } else {
+ popoverSelection.style('left', null).style('top', null);
+ }
+
+ function getFrame(node) {
+ var positionStyle = select(node).style('position');
+
+ if (positionStyle === 'absolute' || positionStyle === 'static') {
+ return {
+ x: node.offsetLeft - scrollLeft,
+ y: node.offsetTop - scrollTop,
+ w: node.offsetWidth,
+ h: node.offsetHeight
+ };
+ } else {
+ return {
+ x: 0,
+ y: 0,
+ w: node.offsetWidth,
+ h: node.offsetHeight
+ };
+ }
+ }
+ }
+
+ function hide() {
+ var anchor = select(this);
+
+ if (_displayType.apply(this, arguments) === 'clickFocus') {
+ anchor.classed('active', false);
+ }
+
+ anchor.selectAll('.popover-' + _id).classed('in', false);
+ }
+
+ function toggle() {
+ if (select(this).select('.popover-' + _id).classed('in')) {
+ hide.apply(this, arguments);
+ } else {
+ show.apply(this, arguments);
+ }
+ }
+
+ return popover;
+ }
+
+ function uiTooltip(klass) {
+ var tooltip = uiPopover((klass || '') + ' tooltip').displayType('hover');
+
+ var _title = function _title() {
+ var title = this.getAttribute('data-original-title');
+
+ if (title) {
+ return title;
+ } else {
+ title = this.getAttribute('title');
+ this.removeAttribute('title');
+ this.setAttribute('data-original-title', title);
+ }
+
+ return title;
+ };
+
+ var _heading = utilFunctor(null);
+
+ var _keys = utilFunctor(null);
+
+ tooltip.title = function (val) {
+ if (!arguments.length) return _title;
+ _title = utilFunctor(val);
+ return tooltip;
+ };
+
+ tooltip.heading = function (val) {
+ if (!arguments.length) return _heading;
+ _heading = utilFunctor(val);
+ return tooltip;
+ };
+
+ tooltip.keys = function (val) {
+ if (!arguments.length) return _keys;
+ _keys = utilFunctor(val);
+ return tooltip;
+ };
+
+ tooltip.content(function () {
+ var heading = _heading.apply(this, arguments);
+
+ var text = _title.apply(this, arguments);
+
+ var keys = _keys.apply(this, arguments);
+
+ return function (selection) {
+ var headingSelect = selection.selectAll('.tooltip-heading').data(heading ? [heading] : []);
+ headingSelect.exit().remove();
+ headingSelect.enter().append('div').attr('class', 'tooltip-heading').merge(headingSelect).html(heading);
+ var textSelect = selection.selectAll('.tooltip-text').data(text ? [text] : []);
+ textSelect.exit().remove();
+ textSelect.enter().append('div').attr('class', 'tooltip-text').merge(textSelect).html(text);
+ var keyhintWrap = selection.selectAll('.keyhint-wrap').data(keys && keys.length ? [0] : []);
+ keyhintWrap.exit().remove();
+ var keyhintWrapEnter = keyhintWrap.enter().append('div').attr('class', 'keyhint-wrap');
+ keyhintWrapEnter.append('span').call(_t.append('tooltip_keyhint'));
+ keyhintWrap = keyhintWrapEnter.merge(keyhintWrap);
+ keyhintWrap.selectAll('kbd.shortcut').data(keys && keys.length ? keys : []).enter().append('kbd').attr('class', 'shortcut').html(function (d) {
+ return d;
+ });
+ };
+ });
+ return tooltip;
+ }
+
+ function uiEditMenu(context) {
+ var dispatch = dispatch$8('toggled');
+
+ var _menu = select(null);
+
+ var _operations = []; // the position the menu should be displayed relative to
+
+ var _anchorLoc = [0, 0];
+ var _anchorLocLonLat = [0, 0]; // a string indicating how the menu was opened
+
+ var _triggerType = '';
+ var _vpTopMargin = 85; // viewport top margin
+
+ var _vpBottomMargin = 45; // viewport bottom margin
+
+ var _vpSideMargin = 35; // viewport side margin
+
+ var _menuTop = false;
+
+ var _menuHeight;
+
+ var _menuWidth; // hardcode these values to make menu positioning easier
+
+
+ var _verticalPadding = 4; // see also `.edit-menu .tooltip` CSS; include margin
+
+ var _tooltipWidth = 210; // offset the menu slightly from the target location
+
+ var _menuSideMargin = 10;
+ var _tooltips = [];
+
+ var editMenu = function editMenu(selection) {
+ var isTouchMenu = _triggerType.includes('touch') || _triggerType.includes('pen');
+
+ var ops = _operations.filter(function (op) {
+ return !isTouchMenu || !op.mouseOnly;
+ });
+
+ if (!ops.length) return;
+ _tooltips = []; // Position the menu above the anchor for stylus and finger input
+ // since the mapper's hand likely obscures the screen below the anchor
+
+ _menuTop = isTouchMenu; // Show labels for touch input since there aren't hover tooltips
+
+ var showLabels = isTouchMenu;
+ var buttonHeight = showLabels ? 32 : 34;
+
+ if (showLabels) {
+ // Get a general idea of the width based on the length of the label
+ _menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function (op) {
+ return op.title.length;
+ })));
+ } else {
+ _menuWidth = 44;
+ }
+
+ _menuHeight = _verticalPadding * 2 + ops.length * buttonHeight;
+ _menu = selection.append('div').attr('class', 'edit-menu').classed('touch-menu', isTouchMenu).style('padding', _verticalPadding + 'px 0');
+
+ var buttons = _menu.selectAll('.edit-menu-item').data(ops); // enter
+
+
+ var buttonsEnter = buttons.enter().append('button').attr('class', function (d) {
+ return 'edit-menu-item edit-menu-item-' + d.id;
+ }).style('height', buttonHeight + 'px').on('click', click) // don't listen for `mouseup` because we only care about non-mouse pointer types
+ .on('pointerup', pointerup).on('pointerdown mousedown', function pointerdown(d3_event) {
+ // don't let button presses also act as map input - #1869
+ d3_event.stopPropagation();
+ }).on('mouseenter.highlight', function (d3_event, d) {
+ if (!d.relatedEntityIds || select(this).classed('disabled')) return;
+ utilHighlightEntities(d.relatedEntityIds(), true, context);
+ }).on('mouseleave.highlight', function (d3_event, d) {
+ if (!d.relatedEntityIds) return;
+ utilHighlightEntities(d.relatedEntityIds(), false, context);
+ });
+ buttonsEnter.each(function (d) {
+ var tooltip = uiTooltip().heading(d.title).title(d.tooltip()).keys([d.keys[0]]);
+
+ _tooltips.push(tooltip);
+
+ select(this).call(tooltip).append('div').attr('class', 'icon-wrap').call(svgIcon(d.icon && d.icon() || '#iD-operation-' + d.id, 'operation'));
+ });
+
+ if (showLabels) {
+ buttonsEnter.append('span').attr('class', 'label').html(function (d) {
+ return d.title;
+ });
+ } // update
+
+
+ buttonsEnter.merge(buttons).classed('disabled', function (d) {
+ return d.disabled();
+ });
+ updatePosition();
+ var initialScale = context.projection.scale();
+ context.map().on('move.edit-menu', function () {
+ if (initialScale !== context.projection.scale()) {
+ editMenu.close();
+ }
+ }).on('drawn.edit-menu', function (info) {
+ if (info.full) updatePosition();
+ });
+ var lastPointerUpType; // `pointerup` is always called before `click`
+
+ function pointerup(d3_event) {
+ lastPointerUpType = d3_event.pointerType;
+ }
+
+ function click(d3_event, operation) {
+ d3_event.stopPropagation();
+
+ if (operation.relatedEntityIds) {
+ utilHighlightEntities(operation.relatedEntityIds(), false, context);
+ }
+
+ if (operation.disabled()) {
+ if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
+ // there are no tooltips for touch interactions so flash feedback instead
+ context.ui().flash.duration(4000).iconName('#iD-operation-' + operation.id).iconClass('operation disabled').label(operation.tooltip)();
+ }
+ } else {
+ if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
+ context.ui().flash.duration(2000).iconName('#iD-operation-' + operation.id).iconClass('operation').label(operation.annotation() || operation.title)();
+ }
+
+ operation();
+ editMenu.close();
+ }
+
+ lastPointerUpType = null;
+ }
+
+ dispatch.call('toggled', this, true);
+ };
+
+ function updatePosition() {
+ if (!_menu || _menu.empty()) return;
+ var anchorLoc = context.projection(_anchorLocLonLat);
+ var viewport = context.surfaceRect();
+
+ if (anchorLoc[0] < 0 || anchorLoc[0] > viewport.width || anchorLoc[1] < 0 || anchorLoc[1] > viewport.height) {
+ // close the menu if it's gone offscreen
+ editMenu.close();
+ return;
+ }
+
+ var menuLeft = displayOnLeft(viewport);
+ var offset = [0, 0];
+ offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin;
+
+ if (_menuTop) {
+ if (anchorLoc[1] - _menuHeight < _vpTopMargin) {
+ // menu is near top viewport edge, shift downward
+ offset[1] = -anchorLoc[1] + _vpTopMargin;
+ } else {
+ offset[1] = -_menuHeight;
+ }
+ } else {
+ if (anchorLoc[1] + _menuHeight > viewport.height - _vpBottomMargin) {
+ // menu is near bottom viewport edge, shift upwards
+ offset[1] = -anchorLoc[1] - _menuHeight + viewport.height - _vpBottomMargin;
+ } else {
+ offset[1] = 0;
+ }
+ }
+
+ var origin = geoVecAdd(anchorLoc, offset);
+
+ _menu.style('left', origin[0] + 'px').style('top', origin[1] + 'px');
+
+ var tooltipSide = tooltipPosition(viewport, menuLeft);
+
+ _tooltips.forEach(function (tooltip) {
+ tooltip.placement(tooltipSide);
+ });
+
+ function displayOnLeft(viewport) {
+ if (_mainLocalizer.textDirection() === 'ltr') {
+ if (anchorLoc[0] + _menuSideMargin + _menuWidth > viewport.width - _vpSideMargin) {
+ // right menu would be too close to the right viewport edge, go left
+ return true;
+ } // prefer right menu
+
+
+ return false;
+ } else {
+ // rtl
+ if (anchorLoc[0] - _menuSideMargin - _menuWidth < _vpSideMargin) {
+ // left menu would be too close to the left viewport edge, go right
+ return false;
+ } // prefer left menu
+
+
+ return true;
+ }
+ }
+
+ function tooltipPosition(viewport, menuLeft) {
+ if (_mainLocalizer.textDirection() === 'ltr') {
+ if (menuLeft) {
+ // if there's not room for a right-side menu then there definitely
+ // isn't room for right-side tooltips
+ return 'left';
+ }
+
+ if (anchorLoc[0] + _menuSideMargin + _menuWidth + _tooltipWidth > viewport.width - _vpSideMargin) {
+ // right tooltips would be too close to the right viewport edge, go left
+ return 'left';
+ } // prefer right tooltips
+
+
+ return 'right';
+ } else {
+ // rtl
+ if (!menuLeft) {
+ return 'right';
+ }
+
+ if (anchorLoc[0] - _menuSideMargin - _menuWidth - _tooltipWidth < _vpSideMargin) {
+ // left tooltips would be too close to the left viewport edge, go right
+ return 'right';
+ } // prefer left tooltips
+
+
+ return 'left';
+ }
+ }
+ }
+
+ editMenu.close = function () {
+ context.map().on('move.edit-menu', null).on('drawn.edit-menu', null);
+
+ _menu.remove();
+
+ _tooltips = [];
+ dispatch.call('toggled', this, false);
+ };
+
+ editMenu.anchorLoc = function (val) {
+ if (!arguments.length) return _anchorLoc;
+ _anchorLoc = val;
+ _anchorLocLonLat = context.projection.invert(_anchorLoc);
+ return editMenu;
+ };
+
+ editMenu.triggerType = function (val) {
+ if (!arguments.length) return _triggerType;
+ _triggerType = val;
+ return editMenu;
+ };
+
+ editMenu.operations = function (val) {
+ if (!arguments.length) return _operations;
+ _operations = val;
+ return editMenu;
+ };
+
+ return utilRebind(editMenu, dispatch, 'on');
+ }
+
+ function uiFeatureInfo(context) {
+ function update(selection) {
+ var features = context.features();
+ var stats = features.stats();
+ var count = 0;
+ var hiddenList = features.hidden().map(function (k) {
+ if (stats[k]) {
+ count += stats[k];
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('feature.' + k + '.description')
+ },
+ count: stats[k]
+ });
+ }
+
+ return null;
+ }).filter(Boolean);
+ selection.html('');
+
+ if (hiddenList.length) {
+ var tooltipBehavior = uiTooltip().placement('top').title(function () {
+ return hiddenList.join('<br/>');
+ });
+ selection.append('a').attr('class', 'chip').attr('href', '#').call(_t.append('feature_info.hidden_warning', {
+ count: count
+ })).call(tooltipBehavior).on('click', function (d3_event) {
+ tooltipBehavior.hide();
+ d3_event.preventDefault(); // open the Map Data pane
+
+ context.ui().togglePanes(context.container().select('.map-panes .map-data-pane'));
+ });
+ }
+
+ selection.classed('hide', !hiddenList.length);
+ }
+
+ return function (selection) {
+ update(selection);
+ context.features().on('change.feature_info', function () {
+ update(selection);
+ });
+ };
+ }
+
+ function uiFlash(context) {
+ var _flashTimer;
+
+ var _duration = 2000;
+ var _iconName = '#iD-icon-no';
+ var _iconClass = 'disabled';
+ var _label = '';
+
+ function flash() {
+ if (_flashTimer) {
+ _flashTimer.stop();
+ }
+
+ context.container().select('.main-footer-wrap').classed('footer-hide', true).classed('footer-show', false);
+ context.container().select('.flash-wrap').classed('footer-hide', false).classed('footer-show', true);
+ var content = context.container().select('.flash-wrap').selectAll('.flash-content').data([0]); // Enter
+
+ var contentEnter = content.enter().append('div').attr('class', 'flash-content');
+ var iconEnter = contentEnter.append('svg').attr('class', 'flash-icon icon').append('g').attr('transform', 'translate(10,10)');
+ iconEnter.append('circle').attr('r', 9);
+ iconEnter.append('use').attr('transform', 'translate(-7,-7)').attr('width', '14').attr('height', '14');
+ contentEnter.append('div').attr('class', 'flash-text'); // Update
+
+ content = content.merge(contentEnter);
+ content.selectAll('.flash-icon').attr('class', 'icon flash-icon ' + (_iconClass || ''));
+ content.selectAll('.flash-icon use').attr('xlink:href', _iconName);
+ content.selectAll('.flash-text').attr('class', 'flash-text').html(_label);
+ _flashTimer = d3_timeout(function () {
+ _flashTimer = null;
+ context.container().select('.main-footer-wrap').classed('footer-hide', false).classed('footer-show', true);
+ context.container().select('.flash-wrap').classed('footer-hide', true).classed('footer-show', false);
+ }, _duration);
+ return content;
+ }
+
+ flash.duration = function (_) {
+ if (!arguments.length) return _duration;
+ _duration = _;
+ return flash;
+ };
+
+ flash.label = function (_) {
+ if (!arguments.length) return _label;
+ _label = _;
+ return flash;
+ };
+
+ flash.iconName = function (_) {
+ if (!arguments.length) return _iconName;
+ _iconName = _;
+ return flash;
+ };
+
+ flash.iconClass = function (_) {
+ if (!arguments.length) return _iconClass;
+ _iconClass = _;
+ return flash;
+ };
+
+ return flash;
+ }
+
+ function uiFullScreen(context) {
+ var element = context.container().node(); // var button = d3_select(null);
+
+ function getFullScreenFn() {
+ if (element.requestFullscreen) {
+ return element.requestFullscreen;
+ } else if (element.msRequestFullscreen) {
+ return element.msRequestFullscreen;
+ } else if (element.mozRequestFullScreen) {
+ return element.mozRequestFullScreen;
+ } else if (element.webkitRequestFullscreen) {
+ return element.webkitRequestFullscreen;
+ }
+ }
+
+ function getExitFullScreenFn() {
+ if (document.exitFullscreen) {
+ return document.exitFullscreen;
+ } else if (document.msExitFullscreen) {
+ return document.msExitFullscreen;
+ } else if (document.mozCancelFullScreen) {
+ return document.mozCancelFullScreen;
+ } else if (document.webkitExitFullscreen) {
+ return document.webkitExitFullscreen;
+ }
+ }
+
+ function isFullScreen() {
+ return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
+ }
+
+ function isSupported() {
+ return !!getFullScreenFn();
+ }
+
+ function fullScreen(d3_event) {
+ d3_event.preventDefault();
+
+ if (!isFullScreen()) {
+ // button.classed('active', true);
+ getFullScreenFn().apply(element);
+ } else {
+ // button.classed('active', false);
+ getExitFullScreenFn().apply(document);
+ }
+ }
+
+ return function () {
+ // selection) {
+ if (!isSupported()) return; // button = selection.append('button')
+ // .attr('title', t('full_screen'))
+ // .on('click', fullScreen)
+ // .call(tooltip);
+ // button.append('span')
+ // .attr('class', 'icon full-screen');
+
+ var detected = utilDetect();
+ var keys = detected.os === 'mac' ? [uiCmd('⌃⌘F'), 'f11'] : ['f11'];
+ context.keybinding().on(keys, fullScreen);
+ };
+ }
+
+ function uiGeolocate(context) {
+ var _geolocationOptions = {
+ // prioritize speed and power usage over precision
+ enableHighAccuracy: false,
+ // don't hang indefinitely getting the location
+ timeout: 6000 // 6sec
+
+ };
+
+ var _locating = uiLoading(context).message(_t.html('geolocate.locating')).blocking(true);
+
+ var _layer = context.layers().layer('geolocate');
+
+ var _position;
+
+ var _extent;
+
+ var _timeoutID;
+
+ var _button = select(null);
+
+ function click() {
+ if (context.inIntro()) return;
+
+ if (!_layer.enabled() && !_locating.isShown()) {
+ // This timeout ensures that we still call finish() even if
+ // the user declines to share their location in Firefox
+ _timeoutID = setTimeout(error, 10000
+ /* 10sec */
+ );
+ context.container().call(_locating); // get the latest position even if we already have one
+
+ navigator.geolocation.getCurrentPosition(success, error, _geolocationOptions);
+ } else {
+ _locating.close();
+
+ _layer.enabled(null, false);
+
+ updateButtonState();
+ }
+ }
+
+ function zoomTo() {
+ context.enter(modeBrowse(context));
+ var map = context.map();
+
+ _layer.enabled(_position, true);
+
+ updateButtonState();
+ map.centerZoomEase(_extent.center(), Math.min(20, map.extentZoom(_extent)));
+ }
+
+ function success(geolocation) {
+ _position = geolocation;
+ var coords = _position.coords;
+ _extent = geoExtent([coords.longitude, coords.latitude]).padByMeters(coords.accuracy);
+ zoomTo();
+ finish();
+ }
+
+ function error() {
+ if (_position) {
+ // use the position from a previous call if we have one
+ zoomTo();
+ } else {
+ context.ui().flash.label(_t.html('geolocate.location_unavailable')).iconName('#iD-icon-geolocate')();
+ }
+
+ finish();
+ }
+
+ function finish() {
+ _locating.close(); // unblock ui
+
+
+ if (_timeoutID) {
+ clearTimeout(_timeoutID);
+ }
+
+ _timeoutID = undefined;
+ }
+
+ function updateButtonState() {
+ _button.classed('active', _layer.enabled());
+
+ _button.attr('aria-pressed', _layer.enabled());
+ }
+
+ return function (selection) {
+ if (!navigator.geolocation || !navigator.geolocation.getCurrentPosition) return;
+ _button = selection.append('button').on('click', click).attr('aria-pressed', false).call(svgIcon('#iD-icon-geolocate', 'light')).call(uiTooltip().placement(_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left').title(_t.html('geolocate.title')).keys([_t('geolocate.key')]));
+ context.keybinding().on(_t('geolocate.key'), click);
+ };
+ }
+
+ function uiPanelBackground(context) {
+ var background = context.background();
+ var _currSourceName = null;
+ var _metadata = {};
+ var _metadataKeys = ['zoom', 'vintage', 'source', 'description', 'resolution', 'accuracy'];
+
+ var debouncedRedraw = debounce(redraw, 250);
+
+ function redraw(selection) {
+ var source = background.baseLayerSource();
+ if (!source) return;
+ var isDG = source.id.match(/^DigitalGlobe/i) !== null;
+ var sourceLabel = source.label();
+
+ if (_currSourceName !== sourceLabel) {
+ _currSourceName = sourceLabel;
+ _metadata = {};
+ }
+
+ selection.html('');
+ var list = selection.append('ul').attr('class', 'background-info');
+ list.append('li').html(_currSourceName);
+
+ _metadataKeys.forEach(function (k) {
+ // DigitalGlobe vintage is available in raster layers for now.
+ if (isDG && k === 'vintage') return;
+ list.append('li').attr('class', 'background-info-list-' + k).classed('hide', !_metadata[k]).call(_t.append('info_panels.background.' + k, {
+ suffix: ':'
+ })).append('span').attr('class', 'background-info-span-' + k).text(_metadata[k]);
+ });
+
+ debouncedGetMetadata(selection);
+ var toggleTiles = context.getDebug('tile') ? 'hide_tiles' : 'show_tiles';
+ selection.append('a').call(_t.append('info_panels.background.' + toggleTiles)).attr('href', '#').attr('class', 'button button-toggle-tiles').on('click', function (d3_event) {
+ d3_event.preventDefault();
+ context.setDebug('tile', !context.getDebug('tile'));
+ selection.call(redraw);
+ });
+
+ if (isDG) {
+ var key = source.id + '-vintage';
+ var sourceVintage = context.background().findSource(key);
+ var showsVintage = context.background().showsLayer(sourceVintage);
+ var toggleVintage = showsVintage ? 'hide_vintage' : 'show_vintage';
+ selection.append('a').call(_t.append('info_panels.background.' + toggleVintage)).attr('href', '#').attr('class', 'button button-toggle-vintage').on('click', function (d3_event) {
+ d3_event.preventDefault();
+ context.background().toggleOverlayLayer(sourceVintage);
+ selection.call(redraw);
+ });
+ } // disable if necessary
+
+
+ ['DigitalGlobe-Premium', 'DigitalGlobe-Standard'].forEach(function (layerId) {
+ if (source.id !== layerId) {
+ var key = layerId + '-vintage';
+ var sourceVintage = context.background().findSource(key);
+
+ if (context.background().showsLayer(sourceVintage)) {
+ context.background().toggleOverlayLayer(sourceVintage);
+ }
+ }
+ });
+ }
+
+ var debouncedGetMetadata = debounce(getMetadata, 250);
+
+ function getMetadata(selection) {
+ var tile = context.container().select('.layer-background img.tile-center'); // tile near viewport center
+
+ if (tile.empty()) return;
+ var sourceName = _currSourceName;
+ var d = tile.datum();
+ var zoom = d && d.length >= 3 && d[2] || Math.floor(context.map().zoom());
+ var center = context.map().center(); // update zoom
+
+ _metadata.zoom = String(zoom);
+ selection.selectAll('.background-info-list-zoom').classed('hide', false).selectAll('.background-info-span-zoom').text(_metadata.zoom);
+ if (!d || !d.length >= 3) return;
+ background.baseLayerSource().getMetadata(center, d, function (err, result) {
+ if (err || _currSourceName !== sourceName) return; // update vintage
+
+ var vintage = result.vintage;
+ _metadata.vintage = vintage && vintage.range || _t('info_panels.background.unknown');
+ selection.selectAll('.background-info-list-vintage').classed('hide', false).selectAll('.background-info-span-vintage').text(_metadata.vintage); // update other _metadata
+
+ _metadataKeys.forEach(function (k) {
+ if (k === 'zoom' || k === 'vintage') return; // done already
+
+ var val = result[k];
+ _metadata[k] = val;
+ selection.selectAll('.background-info-list-' + k).classed('hide', !val).selectAll('.background-info-span-' + k).text(val);
+ });
+ });
+ }
+
+ var panel = function panel(selection) {
+ selection.call(redraw);
+ context.map().on('drawn.info-background', function () {
+ selection.call(debouncedRedraw);
+ }).on('move.info-background', function () {
+ selection.call(debouncedGetMetadata);
+ });
+ };
+
+ panel.off = function () {
+ context.map().on('drawn.info-background', null).on('move.info-background', null);
+ };
+
+ panel.id = 'background';
+ panel.label = _t.html('info_panels.background.title');
+ panel.key = _t('info_panels.background.key');
+ return panel;
+ }
+
+ function uiPanelHistory(context) {
+ var osm;
+
+ function displayTimestamp(timestamp) {
+ if (!timestamp) return _t('info_panels.history.unknown');
+ var options = {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ hour: 'numeric',
+ minute: 'numeric',
+ second: 'numeric'
+ };
+ var d = new Date(timestamp);
+ if (isNaN(d.getTime())) return _t('info_panels.history.unknown');
+ return d.toLocaleString(_mainLocalizer.localeCode(), options);
+ }
+
+ function displayUser(selection, userName) {
+ if (!userName) {
+ selection.append('span').call(_t.append('info_panels.history.unknown'));
+ return;
+ }
+
+ selection.append('span').attr('class', 'user-name').text(userName);
+ var links = selection.append('div').attr('class', 'links');
+
+ if (osm) {
+ links.append('a').attr('class', 'user-osm-link').attr('href', osm.userURL(userName)).attr('target', '_blank').call(_t.append('info_panels.history.profile_link'));
+ }
+
+ links.append('a').attr('class', 'user-hdyc-link').attr('href', 'https://hdyc.neis-one.org/?' + userName).attr('target', '_blank').attr('tabindex', -1).text('HDYC');
+ }
+
+ function displayChangeset(selection, changeset) {
+ if (!changeset) {
+ selection.append('span').call(_t.append('info_panels.history.unknown'));
+ return;
+ }
+
+ selection.append('span').attr('class', 'changeset-id').text(changeset);
+ var links = selection.append('div').attr('class', 'links');
+
+ if (osm) {
+ links.append('a').attr('class', 'changeset-osm-link').attr('href', osm.changesetURL(changeset)).attr('target', '_blank').call(_t.append('info_panels.history.changeset_link'));
+ }
+
+ links.append('a').attr('class', 'changeset-osmcha-link').attr('href', 'https://osmcha.org/changesets/' + changeset).attr('target', '_blank').text('OSMCha');
+ links.append('a').attr('class', 'changeset-achavi-link').attr('href', 'https://overpass-api.de/achavi/?changeset=' + changeset).attr('target', '_blank').text('Achavi');
+ }
+
+ function redraw(selection) {
+ var selectedNoteID = context.selectedNoteID();
+ osm = context.connection();
+ var selected, note, entity;
+
+ if (selectedNoteID && osm) {
+ // selected 1 note
+ selected = [_t.html('note.note') + ' ' + selectedNoteID];
+ note = osm.getNote(selectedNoteID);
+ } else {
+ // selected 1..n entities
+ selected = context.selectedIDs().filter(function (e) {
+ return context.hasEntity(e);
+ });
+
+ if (selected.length) {
+ entity = context.entity(selected[0]);
+ }
+ }
+
+ var singular = selected.length === 1 ? selected[0] : null;
+ selection.html('');
+
+ if (singular) {
+ selection.append('h4').attr('class', 'history-heading').html(singular);
+ } else {
+ selection.append('h4').attr('class', 'history-heading').call(_t.append('info_panels.selected', {
+ n: selected.length
+ }));
+ }
+
+ if (!singular) return;
+
+ if (entity) {
+ selection.call(redrawEntity, entity);
+ } else if (note) {
+ selection.call(redrawNote, note);
+ }
+ }
+
+ function redrawNote(selection, note) {
+ if (!note || note.isNew()) {
+ selection.append('div').call(_t.append('info_panels.history.note_no_history'));
+ return;
+ }
+
+ var list = selection.append('ul');
+ list.append('li').call(_t.append('info_panels.history.note_comments', {
+ suffix: ':'
+ })).append('span').text(note.comments.length);
+
+ if (note.comments.length) {
+ list.append('li').call(_t.append('info_panels.history.note_created_date', {
+ suffix: ':'
+ })).append('span').text(displayTimestamp(note.comments[0].date));
+ list.append('li').call(_t.append('info_panels.history.note_created_user', {
+ suffix: ':'
+ })).call(displayUser, note.comments[0].user);
+ }
+
+ if (osm) {
+ selection.append('a').attr('class', 'view-history-on-osm').attr('target', '_blank').attr('href', osm.noteURL(note)).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').call(_t.append('info_panels.history.note_link_text'));
+ }
+ }
+
+ function redrawEntity(selection, entity) {
+ if (!entity || entity.isNew()) {
+ selection.append('div').call(_t.append('info_panels.history.no_history'));
+ return;
+ }
+
+ var links = selection.append('div').attr('class', 'links');
+
+ if (osm) {
+ links.append('a').attr('class', 'view-history-on-osm').attr('href', osm.historyURL(entity)).attr('target', '_blank').call(_t.append('info_panels.history.history_link'));
+ }
+
+ links.append('a').attr('class', 'pewu-history-viewer-link').attr('href', 'https://pewu.github.io/osm-history/#/' + entity.type + '/' + entity.osmId()).attr('target', '_blank').attr('tabindex', -1).text('PeWu');
+ var list = selection.append('ul');
+ list.append('li').call(_t.append('info_panels.history.version', {
+ suffix: ':'
+ })).append('span').text(entity.version);
+ list.append('li').call(_t.append('info_panels.history.last_edit', {
+ suffix: ':'
+ })).append('span').text(displayTimestamp(entity.timestamp));
+ list.append('li').call(_t.append('info_panels.history.edited_by', {
+ suffix: ':'
+ })).call(displayUser, entity.user);
+ list.append('li').call(_t.append('info_panels.history.changeset', {
+ suffix: ':'
+ })).call(displayChangeset, entity.changeset);
+ }
+
+ var panel = function panel(selection) {
+ selection.call(redraw);
+ context.map().on('drawn.info-history', function () {
+ selection.call(redraw);
+ });
+ context.on('enter.info-history', function () {
+ selection.call(redraw);
+ });
+ };
+
+ panel.off = function () {
+ context.map().on('drawn.info-history', null);
+ context.on('enter.info-history', null);
+ };
+
+ panel.id = 'history';
+ panel.label = _t.html('info_panels.history.title');
+ panel.key = _t('info_panels.history.key');
+ return panel;
+ }
+
+ var OSM_PRECISION = 7;
+ /**
+ * Returns a localized representation of the given length measurement.
+ *
+ * @param {Number} m area in meters
+ * @param {Boolean} isImperial true for U.S. customary units; false for metric
+ */
+
+ function displayLength(m, isImperial) {
+ var d = m * (isImperial ? 3.28084 : 1);
+ var unit;
+
+ if (isImperial) {
+ if (d >= 5280) {
+ d /= 5280;
+ unit = 'miles';
+ } else {
+ unit = 'feet';
+ }
+ } else {
+ if (d >= 1000) {
+ d /= 1000;
+ unit = 'kilometers';
+ } else {
+ unit = 'meters';
+ }
+ }
+
+ return _t('units.' + unit, {
+ quantity: d.toLocaleString(_mainLocalizer.localeCode(), {
+ maximumSignificantDigits: 4
+ })
+ });
+ }
+ /**
+ * Returns a localized representation of the given area measurement.
+ *
+ * @param {Number} m2 area in square meters
+ * @param {Boolean} isImperial true for U.S. customary units; false for metric
+ */
+
+ function displayArea(m2, isImperial) {
+ var locale = _mainLocalizer.localeCode();
+ var d = m2 * (isImperial ? 10.7639111056 : 1);
+ var d1, d2, area;
+ var unit1 = '';
+ var unit2 = '';
+
+ if (isImperial) {
+ if (d >= 6969600) {
+ // > 0.25mi² show mi²
+ d1 = d / 27878400;
+ unit1 = 'square_miles';
+ } else {
+ d1 = d;
+ unit1 = 'square_feet';
+ }
+
+ if (d > 4356 && d < 43560000) {
+ // 0.1 - 1000 acres
+ d2 = d / 43560;
+ unit2 = 'acres';
+ }
+ } else {
+ if (d >= 250000) {
+ // > 0.25km² show km²
+ d1 = d / 1000000;
+ unit1 = 'square_kilometers';
+ } else {
+ d1 = d;
+ unit1 = 'square_meters';
+ }
+
+ if (d > 1000 && d < 10000000) {
+ // 0.1 - 1000 hectares
+ d2 = d / 10000;
+ unit2 = 'hectares';
+ }
+ }
+
+ area = _t('units.' + unit1, {
+ quantity: d1.toLocaleString(locale, {
+ maximumSignificantDigits: 4
+ })
+ });
+
+ if (d2) {
+ return _t('units.area_pair', {
+ area1: area,
+ area2: _t('units.' + unit2, {
+ quantity: d2.toLocaleString(locale, {
+ maximumSignificantDigits: 2
+ })
+ })
+ });
+ } else {
+ return area;
+ }
+ }
+
+ function wrap(x, min, max) {
+ var d = max - min;
+ return ((x - min) % d + d) % d + min;
+ }
+
+ function clamp(x, min, max) {
+ return Math.max(min, Math.min(x, max));
+ }
+
+ function displayCoordinate(deg, pos, neg) {
+ var locale = _mainLocalizer.localeCode();
+ var min = (Math.abs(deg) - Math.floor(Math.abs(deg))) * 60;
+ var sec = (min - Math.floor(min)) * 60;
+ var displayDegrees = _t('units.arcdegrees', {
+ quantity: Math.floor(Math.abs(deg)).toLocaleString(locale)
+ });
+ var displayCoordinate;
+
+ if (Math.floor(sec) > 0) {
+ displayCoordinate = displayDegrees + _t('units.arcminutes', {
+ quantity: Math.floor(min).toLocaleString(locale)
+ }) + _t('units.arcseconds', {
+ quantity: Math.round(sec).toLocaleString(locale)
+ });
+ } else if (Math.floor(min) > 0) {
+ displayCoordinate = displayDegrees + _t('units.arcminutes', {
+ quantity: Math.round(min).toLocaleString(locale)
+ });
+ } else {
+ displayCoordinate = _t('units.arcdegrees', {
+ quantity: Math.round(Math.abs(deg)).toLocaleString(locale)
+ });
+ }
+
+ if (deg === 0) {
+ return displayCoordinate;
+ } else {
+ return _t('units.coordinate', {
+ coordinate: displayCoordinate,
+ direction: _t('units.' + (deg > 0 ? pos : neg))
+ });
+ }
+ }
+ /**
+ * Returns given coordinate pair in degree-minute-second format.
+ *
+ * @param {Array<Number>} coord longitude and latitude
+ */
+
+
+ function dmsCoordinatePair(coord) {
+ return _t('units.coordinate_pair', {
+ latitude: displayCoordinate(clamp(coord[1], -90, 90), 'north', 'south'),
+ longitude: displayCoordinate(wrap(coord[0], -180, 180), 'east', 'west')
+ });
+ }
+ /**
+ * Returns the given coordinate pair in decimal format.
+ * note: unlocalized to avoid comma ambiguity - see #4765
+ *
+ * @param {Array<Number>} coord longitude and latitude
+ */
+
+ function decimalCoordinatePair(coord) {
+ return _t('units.coordinate_pair', {
+ latitude: clamp(coord[1], -90, 90).toFixed(OSM_PRECISION),
+ longitude: wrap(coord[0], -180, 180).toFixed(OSM_PRECISION)
+ });
+ }
+
+ function uiPanelLocation(context) {
+ var currLocation = '';
+
+ function redraw(selection) {
+ selection.html('');
+ var list = selection.append('ul'); // Mouse coordinates
+
+ var coord = context.map().mouseCoordinates();
+
+ if (coord.some(isNaN)) {
+ coord = context.map().center();
+ }
+
+ list.append('li').text(dmsCoordinatePair(coord)).append('li').text(decimalCoordinatePair(coord)); // Location Info
+
+ selection.append('div').attr('class', 'location-info').text(currLocation || ' ');
+ debouncedGetLocation(selection, coord);
+ }
+
+ var debouncedGetLocation = debounce(getLocation, 250);
+
+ function getLocation(selection, coord) {
+ if (!services.geocoder) {
+ currLocation = _t('info_panels.location.unknown_location');
+ selection.selectAll('.location-info').text(currLocation);
+ } else {
+ services.geocoder.reverse(coord, function (err, result) {
+ currLocation = result ? result.display_name : _t('info_panels.location.unknown_location');
+ selection.selectAll('.location-info').text(currLocation);
+ });
+ }
+ }
+
+ var panel = function panel(selection) {
+ selection.call(redraw);
+ context.surface().on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'move.info-location', function () {
+ selection.call(redraw);
+ });
+ };
+
+ panel.off = function () {
+ context.surface().on('.info-location', null);
+ };
+
+ panel.id = 'location';
+ panel.label = _t.html('info_panels.location.title');
+ panel.key = _t('info_panels.location.key');
+ return panel;
+ }
+
+ function uiPanelMeasurement(context) {
+ function radiansToMeters(r) {
+ // using WGS84 authalic radius (6371007.1809 m)
+ return r * 6371007.1809;
+ }
+
+ function steradiansToSqmeters(r) {
+ // http://gis.stackexchange.com/a/124857/40446
+ return r / (4 * Math.PI) * 510065621724000;
+ }
+
+ function toLineString(feature) {
+ if (feature.type === 'LineString') return feature;
+ var result = {
+ type: 'LineString',
+ coordinates: []
+ };
+
+ if (feature.type === 'Polygon') {
+ result.coordinates = feature.coordinates[0];
+ } else if (feature.type === 'MultiPolygon') {
+ result.coordinates = feature.coordinates[0][0];
+ }
+
+ return result;
+ }
+
+ var _isImperial = !_mainLocalizer.usesMetric();
+
+ function redraw(selection) {
+ var graph = context.graph();
+ var selectedNoteID = context.selectedNoteID();
+ var osm = services.osm;
+ var localeCode = _mainLocalizer.localeCode();
+ var heading;
+ var center, location, centroid;
+ var closed, geometry;
+ var totalNodeCount,
+ length = 0,
+ area = 0,
+ distance;
+
+ if (selectedNoteID && osm) {
+ // selected 1 note
+ var note = osm.getNote(selectedNoteID);
+ heading = _t.html('note.note') + ' ' + selectedNoteID;
+ location = note.loc;
+ geometry = 'note';
+ } else {
+ // selected 1..n entities
+ var selectedIDs = context.selectedIDs().filter(function (id) {
+ return context.hasEntity(id);
+ });
+ var selected = selectedIDs.map(function (id) {
+ return context.entity(id);
+ });
+ heading = selected.length === 1 ? selected[0].id : _t.html('info_panels.selected', {
+ n: selected.length
+ });
+
+ if (selected.length) {
+ var extent = geoExtent();
+
+ for (var i in selected) {
+ var entity = selected[i];
+
+ extent._extend(entity.extent(graph));
+
+ geometry = entity.geometry(graph);
+
+ if (geometry === 'line' || geometry === 'area') {
+ closed = entity.type === 'relation' || entity.isClosed() && !entity.isDegenerate();
+ var feature = entity.asGeoJSON(graph);
+ length += radiansToMeters(d3_geoLength(toLineString(feature)));
+ centroid = d3_geoPath(context.projection).centroid(entity.asGeoJSON(graph));
+ centroid = centroid && context.projection.invert(centroid);
+
+ if (!centroid || !isFinite(centroid[0]) || !isFinite(centroid[1])) {
+ centroid = entity.extent(graph).center();
+ }
+
+ if (closed) {
+ area += steradiansToSqmeters(entity.area(graph));
+ }
+ }
+ }
+
+ if (selected.length > 1) {
+ geometry = null;
+ closed = null;
+ centroid = null;
+ }
+
+ if (selected.length === 2 && selected[0].type === 'node' && selected[1].type === 'node') {
+ distance = geoSphericalDistance(selected[0].loc, selected[1].loc);
+ }
+
+ if (selected.length === 1 && selected[0].type === 'node') {
+ location = selected[0].loc;
+ } else {
+ totalNodeCount = utilGetAllNodes(selectedIDs, context.graph()).length;
+ }
+
+ if (!location && !centroid) {
+ center = extent.center();
+ }
+ }
+ }
+
+ selection.html('');
+
+ if (heading) {
+ selection.append('h4').attr('class', 'measurement-heading').html(heading);
+ }
+
+ var list = selection.append('ul');
+ var coordItem;
+
+ if (geometry) {
+ list.append('li').call(_t.append('info_panels.measurement.geometry', {
+ suffix: ':'
+ })).append('span').html(closed ? _t.html('info_panels.measurement.closed_' + geometry) : _t.html('geometry.' + geometry));
+ }
+
+ if (totalNodeCount) {
+ list.append('li').call(_t.append('info_panels.measurement.node_count', {
+ suffix: ':'
+ })).append('span').text(totalNodeCount.toLocaleString(localeCode));
+ }
+
+ if (area) {
+ list.append('li').call(_t.append('info_panels.measurement.area', {
+ suffix: ':'
+ })).append('span').text(displayArea(area, _isImperial));
+ }
+
+ if (length) {
+ list.append('li').call(_t.append('info_panels.measurement.' + (closed ? 'perimeter' : 'length'), {
+ suffix: ':'
+ })).append('span').text(displayLength(length, _isImperial));
+ }
+
+ if (typeof distance === 'number') {
+ list.append('li').call(_t.append('info_panels.measurement.distance', {
+ suffix: ':'
+ })).append('span').text(displayLength(distance, _isImperial));
+ }
+
+ if (location) {
+ coordItem = list.append('li').call(_t.append('info_panels.measurement.location', {
+ suffix: ':'
+ }));
+ coordItem.append('span').text(dmsCoordinatePair(location));
+ coordItem.append('span').text(decimalCoordinatePair(location));
+ }
+
+ if (centroid) {
+ coordItem = list.append('li').call(_t.append('info_panels.measurement.centroid', {
+ suffix: ':'
+ }));
+ coordItem.append('span').text(dmsCoordinatePair(centroid));
+ coordItem.append('span').text(decimalCoordinatePair(centroid));
+ }
+
+ if (center) {
+ coordItem = list.append('li').call(_t.append('info_panels.measurement.center', {
+ suffix: ':'
+ }));
+ coordItem.append('span').text(dmsCoordinatePair(center));
+ coordItem.append('span').text(decimalCoordinatePair(center));
+ }
+
+ if (length || area || typeof distance === 'number') {
+ var toggle = _isImperial ? 'imperial' : 'metric';
+ selection.append('a').call(_t.append('info_panels.measurement.' + toggle)).attr('href', '#').attr('class', 'button button-toggle-units').on('click', function (d3_event) {
+ d3_event.preventDefault();
+ _isImperial = !_isImperial;
+ selection.call(redraw);
+ });
+ }
+ }
+
+ var panel = function panel(selection) {
+ selection.call(redraw);
+ context.map().on('drawn.info-measurement', function () {
+ selection.call(redraw);
+ });
+ context.on('enter.info-measurement', function () {
+ selection.call(redraw);
+ });
+ };
+
+ panel.off = function () {
+ context.map().on('drawn.info-measurement', null);
+ context.on('enter.info-measurement', null);
+ };
+
+ panel.id = 'measurement';
+ panel.label = _t.html('info_panels.measurement.title');
+ panel.key = _t('info_panels.measurement.key');
+ return panel;
+ }
+
+ var uiInfoPanels = {
+ background: uiPanelBackground,
+ history: uiPanelHistory,
+ location: uiPanelLocation,
+ measurement: uiPanelMeasurement
+ };
+
+ function uiInfo(context) {
+ var ids = Object.keys(uiInfoPanels);
+ var wasActive = ['measurement'];
+ var panels = {};
+ var active = {}; // create panels
+
+ ids.forEach(function (k) {
+ if (!panels[k]) {
+ panels[k] = uiInfoPanels[k](context);
+ active[k] = false;
+ }
+ });
+
+ function info(selection) {
+ function redraw() {
+ var activeids = ids.filter(function (k) {
+ return active[k];
+ }).sort();
+ var containers = infoPanels.selectAll('.panel-container').data(activeids, function (k) {
+ return k;
+ });
+ containers.exit().style('opacity', 1).transition().duration(200).style('opacity', 0).on('end', function (d) {
+ select(this).call(panels[d].off).remove();
+ });
+ var enter = containers.enter().append('div').attr('class', function (d) {
+ return 'fillD2 panel-container panel-container-' + d;
+ });
+ enter.style('opacity', 0).transition().duration(200).style('opacity', 1);
+ var title = enter.append('div').attr('class', 'panel-title fillD2');
+ title.append('h3').html(function (d) {
+ return panels[d].label;
+ });
+ title.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function (d3_event, d) {
+ d3_event.stopImmediatePropagation();
+ d3_event.preventDefault();
+ info.toggle(d);
+ }).call(svgIcon('#iD-icon-close'));
+ enter.append('div').attr('class', function (d) {
+ return 'panel-content panel-content-' + d;
+ }); // redraw the panels
+
+ infoPanels.selectAll('.panel-content').each(function (d) {
+ select(this).call(panels[d]);
+ });
+ }
+
+ info.toggle = function (which) {
+ var activeids = ids.filter(function (k) {
+ return active[k];
+ });
+
+ if (which) {
+ // toggle one
+ active[which] = !active[which];
+
+ if (activeids.length === 1 && activeids[0] === which) {
+ // none active anymore
+ wasActive = [which];
+ }
+
+ context.container().select('.' + which + '-panel-toggle-item').classed('active', active[which]).select('input').property('checked', active[which]);
+ } else {
+ // toggle all
+ if (activeids.length) {
+ wasActive = activeids;
+ activeids.forEach(function (k) {
+ active[k] = false;
+ });
+ } else {
+ wasActive.forEach(function (k) {
+ active[k] = true;
+ });
+ }
+ }
+
+ redraw();
+ };
+
+ var infoPanels = selection.selectAll('.info-panels').data([0]);
+ infoPanels = infoPanels.enter().append('div').attr('class', 'info-panels').merge(infoPanels);
+ redraw();
+ context.keybinding().on(uiCmd('⌘' + _t('info_panels.key')), function (d3_event) {
+ d3_event.stopImmediatePropagation();
+ d3_event.preventDefault();
+ info.toggle();
+ });
+ ids.forEach(function (k) {
+ var key = _t('info_panels.' + k + '.key', {
+ "default": null
+ });
+ if (!key) return;
+ context.keybinding().on(uiCmd('⌘⇧' + key), function (d3_event) {
+ d3_event.stopImmediatePropagation();
+ d3_event.preventDefault();
+ info.toggle(k);
+ });
+ });
+ }
+
+ return info;
+ }
+
+ function pointBox(loc, context) {
+ var rect = context.surfaceRect();
+ var point = context.curtainProjection(loc);
+ return {
+ left: point[0] + rect.left - 40,
+ top: point[1] + rect.top - 60,
+ width: 80,
+ height: 90
+ };
+ }
+ function pad(locOrBox, padding, context) {
+ var box;
+
+ if (locOrBox instanceof Array) {
+ var rect = context.surfaceRect();
+ var point = context.curtainProjection(locOrBox);
+ box = {
+ left: point[0] + rect.left,
+ top: point[1] + rect.top
+ };
+ } else {
+ box = locOrBox;
+ }
+
+ return {
+ left: box.left - padding,
+ top: box.top - padding,
+ width: (box.width || 0) + 2 * padding,
+ height: (box.width || 0) + 2 * padding
+ };
+ }
+ function icon(name, svgklass, useklass) {
+ return '<svg class="icon ' + (svgklass || '') + '">' + '<use xlink:href="' + name + '"' + (useklass ? ' class="' + useklass + '"' : '') + '></use></svg>';
+ }
+ var helpStringReplacements; // Returns the localized HTML element for `id` with a standardized set of icon, key, and
+ // label replacements suitable for tutorials and documentation. Optionally supplemented
+ // with custom `replacements`
+
+ function helpHtml(id, replacements) {
+ // only load these the first time
+ if (!helpStringReplacements) {
+ helpStringReplacements = {
+ // insert icons corresponding to various UI elements
+ point_icon: icon('#iD-icon-point', 'inline'),
+ line_icon: icon('#iD-icon-line', 'inline'),
+ area_icon: icon('#iD-icon-area', 'inline'),
+ note_icon: icon('#iD-icon-note', 'inline add-note'),
+ plus: icon('#iD-icon-plus', 'inline'),
+ minus: icon('#iD-icon-minus', 'inline'),
+ layers_icon: icon('#iD-icon-layers', 'inline'),
+ data_icon: icon('#iD-icon-data', 'inline'),
+ inspect: icon('#iD-icon-inspect', 'inline'),
+ help_icon: icon('#iD-icon-help', 'inline'),
+ undo_icon: icon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-redo' : '#iD-icon-undo', 'inline'),
+ redo_icon: icon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-undo' : '#iD-icon-redo', 'inline'),
+ save_icon: icon('#iD-icon-save', 'inline'),
+ // operation icons
+ circularize_icon: icon('#iD-operation-circularize', 'inline operation'),
+ continue_icon: icon('#iD-operation-continue', 'inline operation'),
+ copy_icon: icon('#iD-operation-copy', 'inline operation'),
+ delete_icon: icon('#iD-operation-delete', 'inline operation'),
+ disconnect_icon: icon('#iD-operation-disconnect', 'inline operation'),
+ downgrade_icon: icon('#iD-operation-downgrade', 'inline operation'),
+ extract_icon: icon('#iD-operation-extract', 'inline operation'),
+ merge_icon: icon('#iD-operation-merge', 'inline operation'),
+ move_icon: icon('#iD-operation-move', 'inline operation'),
+ orthogonalize_icon: icon('#iD-operation-orthogonalize', 'inline operation'),
+ paste_icon: icon('#iD-operation-paste', 'inline operation'),
+ reflect_long_icon: icon('#iD-operation-reflect-long', 'inline operation'),
+ reflect_short_icon: icon('#iD-operation-reflect-short', 'inline operation'),
+ reverse_icon: icon('#iD-operation-reverse', 'inline operation'),
+ rotate_icon: icon('#iD-operation-rotate', 'inline operation'),
+ split_icon: icon('#iD-operation-split', 'inline operation'),
+ straighten_icon: icon('#iD-operation-straighten', 'inline operation'),
+ // interaction icons
+ leftclick: icon('#iD-walkthrough-mouse-left', 'inline operation'),
+ rightclick: icon('#iD-walkthrough-mouse-right', 'inline operation'),
+ mousewheel_icon: icon('#iD-walkthrough-mousewheel', 'inline operation'),
+ tap_icon: icon('#iD-walkthrough-tap', 'inline operation'),
+ doubletap_icon: icon('#iD-walkthrough-doubletap', 'inline operation'),
+ longpress_icon: icon('#iD-walkthrough-longpress', 'inline operation'),
+ touchdrag_icon: icon('#iD-walkthrough-touchdrag', 'inline operation'),
+ pinch_icon: icon('#iD-walkthrough-pinch-apart', 'inline operation'),
+ // insert keys; may be localized and platform-dependent
+ shift: uiCmd.display('⇧'),
+ alt: uiCmd.display('⌥'),
+ "return": uiCmd.display('↵'),
+ esc: _t.html('shortcuts.key.esc'),
+ space: _t.html('shortcuts.key.space'),
+ add_note_key: _t.html('modes.add_note.key'),
+ help_key: _t.html('help.key'),
+ shortcuts_key: _t.html('shortcuts.toggle.key'),
+ // reference localized UI labels directly so that they'll always match
+ save: _t.html('save.title'),
+ undo: _t.html('undo.title'),
+ redo: _t.html('redo.title'),
+ upload: _t.html('commit.save'),
+ point: _t.html('modes.add_point.title'),
+ line: _t.html('modes.add_line.title'),
+ area: _t.html('modes.add_area.title'),
+ note: _t.html('modes.add_note.label'),
+ circularize: _t.html('operations.circularize.title'),
+ "continue": _t.html('operations.continue.title'),
+ copy: _t.html('operations.copy.title'),
+ "delete": _t.html('operations.delete.title'),
+ disconnect: _t.html('operations.disconnect.title'),
+ downgrade: _t.html('operations.downgrade.title'),
+ extract: _t.html('operations.extract.title'),
+ merge: _t.html('operations.merge.title'),
+ move: _t.html('operations.move.title'),
+ orthogonalize: _t.html('operations.orthogonalize.title'),
+ paste: _t.html('operations.paste.title'),
+ reflect_long: _t.html('operations.reflect.title.long'),
+ reflect_short: _t.html('operations.reflect.title.short'),
+ reverse: _t.html('operations.reverse.title'),
+ rotate: _t.html('operations.rotate.title'),
+ split: _t.html('operations.split.title'),
+ straighten: _t.html('operations.straighten.title'),
+ map_data: _t.html('map_data.title'),
+ osm_notes: _t.html('map_data.layers.notes.title'),
+ fields: _t.html('inspector.fields'),
+ tags: _t.html('inspector.tags'),
+ relations: _t.html('inspector.relations'),
+ new_relation: _t.html('inspector.new_relation'),
+ turn_restrictions: _t.html('_tagging.presets.fields.restrictions.label'),
+ background_settings: _t.html('background.description'),
+ imagery_offset: _t.html('background.fix_misalignment'),
+ start_the_walkthrough: _t.html('splash.walkthrough'),
+ help: _t.html('help.title'),
+ ok: _t.html('intro.ok')
+ };
+
+ for (var key in helpStringReplacements) {
+ helpStringReplacements[key] = {
+ html: helpStringReplacements[key]
+ };
+ }
+ }
+
+ var reps;
+
+ if (replacements) {
+ reps = Object.assign(replacements, helpStringReplacements);
+ } else {
+ reps = helpStringReplacements;
+ }
+
+ return _t.html(id, reps) // use keyboard key styling for shortcuts
+ .replace(/\`(.*?)\`/g, '<kbd>$1</kbd>');
+ }
+
+ function slugify(text) {
+ return text.toString().toLowerCase().replace(/\s+/g, '-') // Replace spaces with -
+ .replace(/[^\w\-]+/g, '') // Remove all non-word chars
+ .replace(/\-\-+/g, '-') // Replace multiple - with single -
+ .replace(/^-+/, '') // Trim - from start of text
+ .replace(/-+$/, ''); // Trim - from end of text
+ } // console warning for missing walkthrough names
+
+
+ var missingStrings = {};
+
+ function checkKey(key, text) {
+ if (_t(key, {
+ "default": undefined
+ }) === undefined) {
+ if (missingStrings.hasOwnProperty(key)) return; // warn once
+
+ missingStrings[key] = text;
+ var missing = key + ': ' + text;
+ if (typeof console !== 'undefined') console.log(missing); // eslint-disable-line
+ }
+ }
+
+ function localize(obj) {
+ var key; // Assign name if entity has one..
+
+ var name = obj.tags && obj.tags.name;
+
+ if (name) {
+ key = 'intro.graph.name.' + slugify(name);
+ obj.tags.name = _t(key, {
+ "default": name
+ });
+ checkKey(key, name);
+ } // Assign street name if entity has one..
+
+
+ var street = obj.tags && obj.tags['addr:street'];
+
+ if (street) {
+ key = 'intro.graph.name.' + slugify(street);
+ obj.tags['addr:street'] = _t(key, {
+ "default": street
+ });
+ checkKey(key, street); // Add address details common across walkthrough..
+
+ var addrTags = ['block_number', 'city', 'county', 'district', 'hamlet', 'neighbourhood', 'postcode', 'province', 'quarter', 'state', 'subdistrict', 'suburb'];
+ addrTags.forEach(function (k) {
+ var key = 'intro.graph.' + k;
+ var tag = 'addr:' + k;
+ var val = obj.tags && obj.tags[tag];
+ var str = _t(key, {
+ "default": val
+ });
+
+ if (str) {
+ if (str.match(/^<.*>$/) !== null) {
+ delete obj.tags[tag];
+ } else {
+ obj.tags[tag] = str;
+ }
+ }
+ });
+ }
+
+ return obj;
+ } // Used to detect squareness.. some duplicataion of code from actionOrthogonalize.
+
+ function isMostlySquare(points) {
+ // note: uses 15 here instead of the 12 from actionOrthogonalize because
+ // actionOrthogonalize can actually straighten some larger angles as it iterates
+ var threshold = 15; // degrees within right or straight
+
+ var lowerBound = Math.cos((90 - threshold) * Math.PI / 180); // near right
+
+ var upperBound = Math.cos(threshold * Math.PI / 180); // near straight
+
+ for (var i = 0; i < points.length; i++) {
+ var a = points[(i - 1 + points.length) % points.length];
+ var origin = points[i];
+ var b = points[(i + 1) % points.length];
+ var dotp = geoVecNormalizedDot(a, b, origin);
+ var mag = Math.abs(dotp);
+
+ if (mag > lowerBound && mag < upperBound) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ function selectMenuItem(context, operation) {
+ return context.container().select('.edit-menu .edit-menu-item-' + operation);
+ }
+ function transitionTime(point1, point2) {
+ var distance = geoSphericalDistance(point1, point2);
+
+ if (distance === 0) {
+ return 0;
+ } else if (distance < 80) {
+ return 500;
+ } else {
+ return 1000;
+ }
+ }
+
+ // hide class, which sets display=none, and a d3 transition for opacity.
+ // this will cause blinking when called repeatedly, so check that the
+ // value actually changes between calls.
+
+ function uiToggle(show, callback) {
+ return function (selection) {
+ selection.style('opacity', show ? 0 : 1).classed('hide', false).transition().style('opacity', show ? 1 : 0).on('end', function () {
+ select(this).classed('hide', !show).style('opacity', null);
+ if (callback) callback.apply(this);
+ });
+ };
+ }
+
+ function uiCurtain(containerNode) {
+ var surface = select(null),
+ tooltip = select(null),
+ darkness = select(null);
+
+ function curtain(selection) {
+ surface = selection.append('svg').attr('class', 'curtain').style('top', 0).style('left', 0);
+ darkness = surface.append('path').attr('x', 0).attr('y', 0).attr('class', 'curtain-darkness');
+ select(window).on('resize.curtain', resize);
+ tooltip = selection.append('div').attr('class', 'tooltip');
+ tooltip.append('div').attr('class', 'popover-arrow');
+ tooltip.append('div').attr('class', 'popover-inner');
+ resize();
+
+ function resize() {
+ surface.attr('width', containerNode.clientWidth).attr('height', containerNode.clientHeight);
+ curtain.cut(darkness.datum());
+ }
+ }
+ /**
+ * Reveal cuts the curtain to highlight the given box,
+ * and shows a tooltip with instructions next to the box.
+ *
+ * @param {String|ClientRect} [box] box used to cut the curtain
+ * @param {String} [text] text for a tooltip
+ * @param {Object} [options]
+ * @param {string} [options.tooltipClass] optional class to add to the tooltip
+ * @param {integer} [options.duration] transition time in milliseconds
+ * @param {string} [options.buttonText] if set, create a button with this text label
+ * @param {function} [options.buttonCallback] if set, the callback for the button
+ * @param {function} [options.padding] extra margin in px to put around bbox
+ * @param {String|ClientRect} [options.tooltipBox] box for tooltip position, if different from box for the curtain
+ */
+
+
+ curtain.reveal = function (box, html, options) {
+ options = options || {};
+
+ if (typeof box === 'string') {
+ box = select(box).node();
+ }
+
+ if (box && box.getBoundingClientRect) {
+ box = copyBox(box.getBoundingClientRect());
+ var containerRect = containerNode.getBoundingClientRect();
+ box.top -= containerRect.top;
+ box.left -= containerRect.left;
+ }
+
+ if (box && options.padding) {
+ box.top -= options.padding;
+ box.left -= options.padding;
+ box.bottom += options.padding;
+ box.right += options.padding;
+ box.height += options.padding * 2;
+ box.width += options.padding * 2;
+ }
+
+ var tooltipBox;
+
+ if (options.tooltipBox) {
+ tooltipBox = options.tooltipBox;
+
+ if (typeof tooltipBox === 'string') {
+ tooltipBox = select(tooltipBox).node();
+ }
+
+ if (tooltipBox && tooltipBox.getBoundingClientRect) {
+ tooltipBox = copyBox(tooltipBox.getBoundingClientRect());
+ }
+ } else {
+ tooltipBox = box;
+ }
+
+ if (tooltipBox && html) {
+ if (html.indexOf('**') !== -1) {
+ if (html.indexOf('<span') === 0) {
+ html = html.replace(/^(<span.*?>)(.+?)(\*\*)/, '$1<span>$2</span>$3');
+ } else {
+ html = html.replace(/^(.+?)(\*\*)/, '<span>$1</span>$2');
+ } // pseudo markdown bold text for the instruction section..
+
+
+ html = html.replace(/\*\*(.*?)\*\*/g, '<span class="instruction">$1</span>');
+ }
+
+ html = html.replace(/\*(.*?)\*/g, '<em>$1</em>'); // emphasis
+
+ html = html.replace(/\{br\}/g, '<br/><br/>'); // linebreak
+
+ if (options.buttonText && options.buttonCallback) {
+ html += '<div class="button-section">' + '<button href="#" class="button action">' + options.buttonText + '</button></div>';
+ }
+
+ var classes = 'curtain-tooltip popover tooltip arrowed in ' + (options.tooltipClass || '');
+ tooltip.classed(classes, true).selectAll('.popover-inner').html(html);
+
+ if (options.buttonText && options.buttonCallback) {
+ var button = tooltip.selectAll('.button-section .button.action');
+ button.on('click', function (d3_event) {
+ d3_event.preventDefault();
+ options.buttonCallback();
+ });
+ }
+
+ var tip = copyBox(tooltip.node().getBoundingClientRect()),
+ w = containerNode.clientWidth,
+ h = containerNode.clientHeight,
+ tooltipWidth = 200,
+ tooltipArrow = 5,
+ side,
+ pos; // hack: this will have bottom placement,
+ // so need to reserve extra space for the tooltip illustration.
+
+ if (options.tooltipClass === 'intro-mouse') {
+ tip.height += 80;
+ } // trim box dimensions to just the portion that fits in the container..
+
+
+ if (tooltipBox.top + tooltipBox.height > h) {
+ tooltipBox.height -= tooltipBox.top + tooltipBox.height - h;
+ }
+
+ if (tooltipBox.left + tooltipBox.width > w) {
+ tooltipBox.width -= tooltipBox.left + tooltipBox.width - w;
+ } // determine tooltip placement..
+
+
+ if (tooltipBox.top + tooltipBox.height < 100) {
+ // tooltip below box..
+ side = 'bottom';
+ pos = [tooltipBox.left + tooltipBox.width / 2 - tip.width / 2, tooltipBox.top + tooltipBox.height];
+ } else if (tooltipBox.top > h - 140) {
+ // tooltip above box..
+ side = 'top';
+ pos = [tooltipBox.left + tooltipBox.width / 2 - tip.width / 2, tooltipBox.top - tip.height];
+ } else {
+ // tooltip to the side of the tooltipBox..
+ var tipY = tooltipBox.top + tooltipBox.height / 2 - tip.height / 2;
+
+ if (_mainLocalizer.textDirection() === 'rtl') {
+ if (tooltipBox.left - tooltipWidth - tooltipArrow < 70) {
+ side = 'right';
+ pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
+ } else {
+ side = 'left';
+ pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
+ }
+ } else {
+ if (tooltipBox.left + tooltipBox.width + tooltipArrow + tooltipWidth > w - 70) {
+ side = 'left';
+ pos = [tooltipBox.left - tooltipWidth - tooltipArrow, tipY];
+ } else {
+ side = 'right';
+ pos = [tooltipBox.left + tooltipBox.width + tooltipArrow, tipY];
+ }
+ }
+ }
+
+ if (options.duration !== 0 || !tooltip.classed(side)) {
+ tooltip.call(uiToggle(true));
+ }
+
+ tooltip.style('top', pos[1] + 'px').style('left', pos[0] + 'px').attr('class', classes + ' ' + side); // shift popover-inner if it is very close to the top or bottom edge
+ // (doesn't affect the placement of the popover-arrow)
+
+ var shiftY = 0;
+
+ if (side === 'left' || side === 'right') {
+ if (pos[1] < 60) {
+ shiftY = 60 - pos[1];
+ } else if (pos[1] + tip.height > h - 100) {
+ shiftY = h - pos[1] - tip.height - 100;
+ }
+ }
+
+ tooltip.selectAll('.popover-inner').style('top', shiftY + 'px');
+ } else {
+ tooltip.classed('in', false).call(uiToggle(false));
+ }
+
+ curtain.cut(box, options.duration);
+ return tooltip;
+ };
+
+ curtain.cut = function (datum, duration) {
+ darkness.datum(datum).interrupt();
+ var selection;
+
+ if (duration === 0) {
+ selection = darkness;
+ } else {
+ selection = darkness.transition().duration(duration || 600).ease(linear$1);
+ }
+
+ selection.attr('d', function (d) {
+ var containerWidth = containerNode.clientWidth;
+ var containerHeight = containerNode.clientHeight;
+ var string = 'M 0,0 L 0,' + containerHeight + ' L ' + containerWidth + ',' + containerHeight + 'L' + containerWidth + ',0 Z';
+ if (!d) return string;
+ return string + 'M' + d.left + ',' + d.top + 'L' + d.left + ',' + (d.top + d.height) + 'L' + (d.left + d.width) + ',' + (d.top + d.height) + 'L' + (d.left + d.width) + ',' + d.top + 'Z';
+ });
+ };
+
+ curtain.remove = function () {
+ surface.remove();
+ tooltip.remove();
+ select(window).on('resize.curtain', null);
+ }; // ClientRects are immutable, so copy them to an object,
+ // in case we need to trim the height/width.
+
+
+ function copyBox(src) {
+ return {
+ top: src.top,
+ right: src.right,
+ bottom: src.bottom,
+ left: src.left,
+ width: src.width,
+ height: src.height
+ };
+ }
+
+ return curtain;
+ }
+
+ function uiIntroWelcome(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var chapter = {
+ title: 'intro.welcome.title'
+ };
+
+ function welcome() {
+ context.map().centerZoom([-85.63591, 41.94285], 19);
+ reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.welcome'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: practice
+ });
+ }
+
+ function practice() {
+ reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.practice'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: words
+ });
+ }
+
+ function words() {
+ reveal('.intro-nav-wrap .chapter-welcome', helpHtml('intro.welcome.words'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: chapters
+ });
+ }
+
+ function chapters() {
+ dispatch.call('done');
+ reveal('.intro-nav-wrap .chapter-navigation', helpHtml('intro.welcome.chapters', {
+ next: _t('intro.navigation.title')
+ }));
+ }
+
+ chapter.enter = function () {
+ welcome();
+ };
+
+ chapter.exit = function () {
+ context.container().select('.curtain-tooltip.intro-mouse').selectAll('.counter').remove();
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroNavigation(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var timeouts = [];
+ var hallId = 'n2061';
+ var townHall = [-85.63591, 41.94285];
+ var springStreetId = 'w397';
+ var springStreetEndId = 'n1834';
+ var springStreet = [-85.63582, 41.94255];
+ var onewayField = _mainPresetIndex.field('oneway');
+ var maxspeedField = _mainPresetIndex.field('maxspeed');
+ var chapter = {
+ title: 'intro.navigation.title'
+ };
+
+ function timeout(f, t) {
+ timeouts.push(window.setTimeout(f, t));
+ }
+
+ function eventCancel(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ }
+
+ function isTownHallSelected() {
+ var ids = context.selectedIDs();
+ return ids.length === 1 && ids[0] === hallId;
+ }
+
+ function dragMap() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ var msec = transitionTime(townHall, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(townHall, 19, msec);
+ timeout(function () {
+ var centerStart = context.map().center();
+ var textId = context.lastPointerType() === 'mouse' ? 'drag' : 'drag_touch';
+ var dragString = helpHtml('intro.navigation.map_info') + '{br}' + helpHtml('intro.navigation.' + textId);
+ reveal('.surface', dragString);
+ context.map().on('drawn.intro', function () {
+ reveal('.surface', dragString, {
+ duration: 0
+ });
+ });
+ context.map().on('move.intro', function () {
+ var centerNow = context.map().center();
+
+ if (centerStart[0] !== centerNow[0] || centerStart[1] !== centerNow[1]) {
+ context.map().on('move.intro', null);
+ timeout(function () {
+ continueTo(zoomMap);
+ }, 3000);
+ }
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function zoomMap() {
+ var zoomStart = context.map().zoom();
+ var textId = context.lastPointerType() === 'mouse' ? 'zoom' : 'zoom_touch';
+ var zoomString = helpHtml('intro.navigation.' + textId);
+ reveal('.surface', zoomString);
+ context.map().on('drawn.intro', function () {
+ reveal('.surface', zoomString, {
+ duration: 0
+ });
+ });
+ context.map().on('move.intro', function () {
+ if (context.map().zoom() !== zoomStart) {
+ context.map().on('move.intro', null);
+ timeout(function () {
+ continueTo(features);
+ }, 3000);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function features() {
+ var onClick = function onClick() {
+ continueTo(pointsLinesAreas);
+ };
+
+ reveal('.surface', helpHtml('intro.navigation.features'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.map().on('drawn.intro', function () {
+ reveal('.surface', helpHtml('intro.navigation.features'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function pointsLinesAreas() {
+ var onClick = function onClick() {
+ continueTo(nodesWays);
+ };
+
+ reveal('.surface', helpHtml('intro.navigation.points_lines_areas'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.map().on('drawn.intro', function () {
+ reveal('.surface', helpHtml('intro.navigation.points_lines_areas'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function nodesWays() {
+ var onClick = function onClick() {
+ continueTo(clickTownHall);
+ };
+
+ reveal('.surface', helpHtml('intro.navigation.nodes_ways'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.map().on('drawn.intro', function () {
+ reveal('.surface', helpHtml('intro.navigation.nodes_ways'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function clickTownHall() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ var entity = context.hasEntity(hallId);
+ if (!entity) return;
+ reveal(null, null, {
+ duration: 0
+ });
+ context.map().centerZoomEase(entity.loc, 19, 500);
+ timeout(function () {
+ var entity = context.hasEntity(hallId);
+ if (!entity) return;
+ var box = pointBox(entity.loc, context);
+ var textId = context.lastPointerType() === 'mouse' ? 'click_townhall' : 'tap_townhall';
+ reveal(box, helpHtml('intro.navigation.' + textId));
+ context.map().on('move.intro drawn.intro', function () {
+ var entity = context.hasEntity(hallId);
+ if (!entity) return;
+ var box = pointBox(entity.loc, context);
+ reveal(box, helpHtml('intro.navigation.' + textId), {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function () {
+ if (isTownHallSelected()) continueTo(selectedTownHall);
+ });
+ }, 550); // after centerZoomEase
+
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(hallId)) {
+ continueTo(clickTownHall);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function selectedTownHall() {
+ if (!isTownHallSelected()) return clickTownHall();
+ var entity = context.hasEntity(hallId);
+ if (!entity) return clickTownHall();
+ var box = pointBox(entity.loc, context);
+
+ var onClick = function onClick() {
+ continueTo(editorTownHall);
+ };
+
+ reveal(box, helpHtml('intro.navigation.selected_townhall'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var entity = context.hasEntity(hallId);
+ if (!entity) return;
+ var box = pointBox(entity.loc, context);
+ reveal(box, helpHtml('intro.navigation.selected_townhall'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(hallId)) {
+ continueTo(clickTownHall);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function editorTownHall() {
+ if (!isTownHallSelected()) return clickTownHall(); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+
+ var onClick = function onClick() {
+ continueTo(presetTownHall);
+ };
+
+ reveal('.entity-editor-pane', helpHtml('intro.navigation.editor_townhall'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.on('exit.intro', function () {
+ continueTo(clickTownHall);
+ });
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(hallId)) {
+ continueTo(clickTownHall);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ nextStep();
+ }
+ }
+
+ function presetTownHall() {
+ if (!isTownHallSelected()) return clickTownHall(); // reset pane, in case user happened to change it..
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel); // preset match, in case the user happened to change it.
+
+ var entity = context.entity(context.selectedIDs()[0]);
+ var preset = _mainPresetIndex.match(entity, context.graph());
+
+ var onClick = function onClick() {
+ continueTo(fieldsTownHall);
+ };
+
+ reveal('.entity-editor-pane .section-feature-type', helpHtml('intro.navigation.preset_townhall', {
+ preset: preset.name()
+ }), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.on('exit.intro', function () {
+ continueTo(clickTownHall);
+ });
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(hallId)) {
+ continueTo(clickTownHall);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ nextStep();
+ }
+ }
+
+ function fieldsTownHall() {
+ if (!isTownHallSelected()) return clickTownHall(); // reset pane, in case user happened to change it..
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+
+ var onClick = function onClick() {
+ continueTo(closeTownHall);
+ };
+
+ reveal('.entity-editor-pane .section-preset-fields', helpHtml('intro.navigation.fields_townhall'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.on('exit.intro', function () {
+ continueTo(clickTownHall);
+ });
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(hallId)) {
+ continueTo(clickTownHall);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ nextStep();
+ }
+ }
+
+ function closeTownHall() {
+ if (!isTownHallSelected()) return clickTownHall();
+ var selector = '.entity-editor-pane button.close svg use';
+ var href = select(selector).attr('href') || '#iD-icon-close';
+ reveal('.entity-editor-pane', helpHtml('intro.navigation.close_townhall', {
+ button: {
+ html: icon(href, 'inline')
+ }
+ }));
+ context.on('exit.intro', function () {
+ continueTo(searchStreet);
+ });
+ context.history().on('change.intro', function () {
+ // update the close icon in the tooltip if the user edits something.
+ var selector = '.entity-editor-pane button.close svg use';
+ var href = select(selector).attr('href') || '#iD-icon-close';
+ reveal('.entity-editor-pane', helpHtml('intro.navigation.close_townhall', {
+ button: {
+ html: icon(href, 'inline')
+ }
+ }), {
+ duration: 0
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function searchStreet() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial'); // ensure spring street exists
+
+ var msec = transitionTime(springStreet, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(springStreet, 19, msec); // ..and user can see it
+
+ timeout(function () {
+ reveal('.search-header input', helpHtml('intro.navigation.search_street', {
+ name: _t('intro.graph.name.spring-street')
+ }));
+ context.container().select('.search-header input').on('keyup.intro', checkSearchResult);
+ }, msec + 100);
+ }
+
+ function checkSearchResult() {
+ var first = context.container().select('.feature-list-item:nth-child(0n+2)'); // skip "No Results" item
+
+ var firstName = first.select('.entity-name');
+ var name = _t('intro.graph.name.spring-street');
+
+ if (!firstName.empty() && firstName.html() === name) {
+ reveal(first.node(), helpHtml('intro.navigation.choose_street', {
+ name: name
+ }), {
+ duration: 300
+ });
+ context.on('exit.intro', function () {
+ continueTo(selectedStreet);
+ });
+ context.container().select('.search-header input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
+ }
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.container().select('.search-header input').on('keydown.intro', null).on('keyup.intro', null);
+ nextStep();
+ }
+ }
+
+ function selectedStreet() {
+ if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
+ return searchStreet();
+ }
+
+ var onClick = function onClick() {
+ continueTo(editorStreet);
+ };
+
+ var entity = context.entity(springStreetEndId);
+ var box = pointBox(entity.loc, context);
+ box.height = 500;
+ reveal(box, helpHtml('intro.navigation.selected_street', {
+ name: _t('intro.graph.name.spring-street')
+ }), {
+ duration: 600,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ timeout(function () {
+ context.map().on('move.intro drawn.intro', function () {
+ var entity = context.hasEntity(springStreetEndId);
+ if (!entity) return;
+ var box = pointBox(entity.loc, context);
+ box.height = 500;
+ reveal(box, helpHtml('intro.navigation.selected_street', {
+ name: _t('intro.graph.name.spring-street')
+ }), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+ }, 600); // after reveal.
+
+ context.on('enter.intro', function (mode) {
+ if (!context.hasEntity(springStreetId)) {
+ return continueTo(searchStreet);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== springStreetId) {
+ // keep Spring Street selected..
+ context.enter(modeSelect(context, [springStreetId]));
+ }
+ });
+ context.history().on('change.intro', function () {
+ if (!context.hasEntity(springStreetEndId) || !context.hasEntity(springStreetId)) {
+ timeout(function () {
+ continueTo(searchStreet);
+ }, 300); // after any transition (e.g. if user deleted intersection)
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function editorStreet() {
+ var selector = '.entity-editor-pane button.close svg use';
+ var href = select(selector).attr('href') || '#iD-icon-close';
+ reveal('.entity-editor-pane', helpHtml('intro.navigation.street_different_fields') + '{br}' + helpHtml('intro.navigation.editor_street', {
+ button: {
+ html: icon(href, 'inline')
+ },
+ field1: {
+ html: onewayField.label()
+ },
+ field2: {
+ html: maxspeedField.label()
+ }
+ }));
+ context.on('exit.intro', function () {
+ continueTo(play);
+ });
+ context.history().on('change.intro', function () {
+ // update the close icon in the tooltip if the user edits something.
+ var selector = '.entity-editor-pane button.close svg use';
+ var href = select(selector).attr('href') || '#iD-icon-close';
+ reveal('.entity-editor-pane', helpHtml('intro.navigation.street_different_fields') + '{br}' + helpHtml('intro.navigation.editor_street', {
+ button: {
+ html: icon(href, 'inline')
+ },
+ field1: {
+ html: onewayField.label()
+ },
+ field2: {
+ html: maxspeedField.label()
+ }
+ }), {
+ duration: 0
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function play() {
+ dispatch.call('done');
+ reveal('.ideditor', helpHtml('intro.navigation.play', {
+ next: _t('intro.points.title')
+ }), {
+ tooltipBox: '.intro-nav-wrap .chapter-point',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ reveal('.ideditor');
+ }
+ });
+ }
+
+ chapter.enter = function () {
+ dragMap();
+ };
+
+ chapter.exit = function () {
+ timeouts.forEach(window.clearTimeout);
+ context.on('enter.intro exit.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.search-header input').on('keydown.intro keyup.intro', null);
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroPoint(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var timeouts = [];
+ var intersection = [-85.63279, 41.94394];
+ var building = [-85.632422, 41.944045];
+ var cafePreset = _mainPresetIndex.item('amenity/cafe');
+ var _pointID = null;
+ var chapter = {
+ title: 'intro.points.title'
+ };
+
+ function timeout(f, t) {
+ timeouts.push(window.setTimeout(f, t));
+ }
+
+ function eventCancel(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ }
+
+ function addPoint() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ var msec = transitionTime(intersection, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(intersection, 19, msec);
+ timeout(function () {
+ var tooltip = reveal('button.add-point', helpHtml('intro.points.points_info') + '{br}' + helpHtml('intro.points.add_point'));
+ _pointID = null;
+ tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-points');
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'add-point') return;
+ continueTo(placePoint);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function placePoint() {
+ if (context.mode().id !== 'add-point') {
+ return chapter.restart();
+ }
+
+ var pointBox = pad(building, 150, context);
+ var textId = context.lastPointerType() === 'mouse' ? 'place_point' : 'place_point_touch';
+ reveal(pointBox, helpHtml('intro.points.' + textId));
+ context.map().on('move.intro drawn.intro', function () {
+ pointBox = pad(building, 150, context);
+ reveal(pointBox, helpHtml('intro.points.' + textId), {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return chapter.restart();
+ _pointID = context.mode().selectedIDs()[0];
+ continueTo(searchPreset);
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function searchPreset() {
+ if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
+ return addPoint();
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.points.search_cafe', {
+ preset: cafePreset.name()
+ }));
+ context.on('enter.intro', function (mode) {
+ if (!_pointID || !context.hasEntity(_pointID)) {
+ return continueTo(addPoint);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== _pointID) {
+ // keep the user's point selected..
+ context.enter(modeSelect(context, [_pointID])); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.points.search_cafe', {
+ preset: cafePreset.name()
+ }));
+ context.history().on('change.intro', null);
+ }
+ });
+
+ function checkPresetSearch() {
+ var first = context.container().select('.preset-list-item:first-child');
+
+ if (first.classed('preset-amenity-cafe')) {
+ context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
+ reveal(first.select('.preset-list-button').node(), helpHtml('intro.points.choose_cafe', {
+ preset: cafePreset.name()
+ }), {
+ duration: 300
+ });
+ context.history().on('change.intro', function () {
+ continueTo(aboutFeatureEditor);
+ });
+ }
+ }
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ nextStep();
+ }
+ }
+
+ function aboutFeatureEditor() {
+ if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
+ return addPoint();
+ }
+
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.points.feature_editor'), {
+ tooltipClass: 'intro-points-describe',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(addName);
+ }
+ });
+ }, 400);
+ context.on('exit.intro', function () {
+ // if user leaves select mode here, just continue with the tutorial.
+ continueTo(reselectPoint);
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function addName() {
+ if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
+ return addPoint();
+ } // reset pane, in case user happened to change it..
+
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ var addNameString = helpHtml('intro.points.fields_info') + '{br}' + helpHtml('intro.points.add_name');
+ timeout(function () {
+ // It's possible for the user to add a name in a previous step..
+ // If so, don't tell them to add the name in this step.
+ // Give them an OK button instead.
+ var entity = context.entity(_pointID);
+
+ if (entity.tags.name) {
+ var tooltip = reveal('.entity-editor-pane', addNameString, {
+ tooltipClass: 'intro-points-describe',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(addCloseEditor);
+ }
+ });
+ tooltip.select('.instruction').style('display', 'none');
+ } else {
+ reveal('.entity-editor-pane', addNameString, {
+ tooltipClass: 'intro-points-describe'
+ });
+ }
+ }, 400);
+ context.history().on('change.intro', function () {
+ continueTo(addCloseEditor);
+ });
+ context.on('exit.intro', function () {
+ // if user leaves select mode here, just continue with the tutorial.
+ continueTo(reselectPoint);
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function addCloseEditor() {
+ // reset pane, in case user happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ var selector = '.entity-editor-pane button.close svg use';
+ var href = select(selector).attr('href') || '#iD-icon-close';
+ context.on('exit.intro', function () {
+ continueTo(reselectPoint);
+ });
+ reveal('.entity-editor-pane', helpHtml('intro.points.add_close', {
+ button: {
+ html: icon(href, 'inline')
+ }
+ }));
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function reselectPoint() {
+ if (!_pointID) return chapter.restart();
+ var entity = context.hasEntity(_pointID);
+ if (!entity) return chapter.restart(); // make sure it's still a cafe, in case user somehow changed it..
+
+ var oldPreset = _mainPresetIndex.match(entity, context.graph());
+ context.replace(actionChangePreset(_pointID, oldPreset, cafePreset));
+ context.enter(modeBrowse(context));
+ var msec = transitionTime(entity.loc, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerEase(entity.loc, msec);
+ timeout(function () {
+ var box = pointBox(entity.loc, context);
+ reveal(box, helpHtml('intro.points.reselect'), {
+ duration: 600
+ });
+ timeout(function () {
+ context.map().on('move.intro drawn.intro', function () {
+ var entity = context.hasEntity(_pointID);
+ if (!entity) return chapter.restart();
+ var box = pointBox(entity.loc, context);
+ reveal(box, helpHtml('intro.points.reselect'), {
+ duration: 0
+ });
+ });
+ }, 600); // after reveal..
+
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return;
+ continueTo(updatePoint);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function updatePoint() {
+ if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
+ return continueTo(reselectPoint);
+ } // reset pane, in case user happened to untag the point..
+
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ context.on('exit.intro', function () {
+ continueTo(reselectPoint);
+ });
+ context.history().on('change.intro', function () {
+ continueTo(updateCloseEditor);
+ });
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.points.update'), {
+ tooltipClass: 'intro-points-describe'
+ });
+ }, 400);
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function updateCloseEditor() {
+ if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
+ return continueTo(reselectPoint);
+ } // reset pane, in case user happened to change it..
+
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ context.on('exit.intro', function () {
+ continueTo(rightClickPoint);
+ });
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.points.update_close', {
+ button: {
+ html: icon('#iD-icon-close', 'inline')
+ }
+ }));
+ }, 500);
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function rightClickPoint() {
+ if (!_pointID) return chapter.restart();
+ var entity = context.hasEntity(_pointID);
+ if (!entity) return chapter.restart();
+ context.enter(modeBrowse(context));
+ var box = pointBox(entity.loc, context);
+ var textId = context.lastPointerType() === 'mouse' ? 'rightclick' : 'edit_menu_touch';
+ reveal(box, helpHtml('intro.points.' + textId), {
+ duration: 600
+ });
+ timeout(function () {
+ context.map().on('move.intro', function () {
+ var entity = context.hasEntity(_pointID);
+ if (!entity) return chapter.restart();
+ var box = pointBox(entity.loc, context);
+ reveal(box, helpHtml('intro.points.' + textId), {
+ duration: 0
+ });
+ });
+ }, 600); // after reveal
+
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return;
+ var ids = context.selectedIDs();
+ if (ids.length !== 1 || ids[0] !== _pointID) return;
+ timeout(function () {
+ var node = selectMenuItem(context, 'delete').node();
+ if (!node) return;
+ continueTo(enterDelete);
+ }, 50); // after menu visible
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro', null);
+ nextStep();
+ }
+ }
+
+ function enterDelete() {
+ if (!_pointID) return chapter.restart();
+ var entity = context.hasEntity(_pointID);
+ if (!entity) return chapter.restart();
+ var node = selectMenuItem(context, 'delete').node();
+
+ if (!node) {
+ return continueTo(rightClickPoint);
+ }
+
+ reveal('.edit-menu', helpHtml('intro.points.delete'), {
+ padding: 50
+ });
+ timeout(function () {
+ context.map().on('move.intro', function () {
+ reveal('.edit-menu', helpHtml('intro.points.delete'), {
+ duration: 0,
+ padding: 50
+ });
+ });
+ }, 300); // after menu visible
+
+ context.on('exit.intro', function () {
+ if (!_pointID) return chapter.restart();
+ var entity = context.hasEntity(_pointID);
+ if (entity) return continueTo(rightClickPoint); // point still exists
+ });
+ context.history().on('change.intro', function (changed) {
+ if (changed.deleted().length) {
+ continueTo(undo);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro', null);
+ context.history().on('change.intro', null);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function undo() {
+ context.history().on('change.intro', function () {
+ continueTo(play);
+ });
+ reveal('.top-toolbar button.undo-button', helpHtml('intro.points.undo'));
+
+ function continueTo(nextStep) {
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function play() {
+ dispatch.call('done');
+ reveal('.ideditor', helpHtml('intro.points.play', {
+ next: _t('intro.areas.title')
+ }), {
+ tooltipBox: '.intro-nav-wrap .chapter-area',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ reveal('.ideditor');
+ }
+ });
+ }
+
+ chapter.enter = function () {
+ addPoint();
+ };
+
+ chapter.exit = function () {
+ timeouts.forEach(window.clearTimeout);
+ context.on('enter.intro exit.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroArea(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var playground = [-85.63552, 41.94159];
+ var playgroundPreset = _mainPresetIndex.item('leisure/playground');
+ var nameField = _mainPresetIndex.field('name');
+ var descriptionField = _mainPresetIndex.field('description');
+ var timeouts = [];
+
+ var _areaID;
+
+ var chapter = {
+ title: 'intro.areas.title'
+ };
+
+ function timeout(f, t) {
+ timeouts.push(window.setTimeout(f, t));
+ }
+
+ function eventCancel(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ }
+
+ function revealPlayground(center, text, options) {
+ var padding = 180 * Math.pow(2, context.map().zoom() - 19.5);
+ var box = pad(center, padding, context);
+ reveal(box, text, options);
+ }
+
+ function addArea() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ _areaID = null;
+ var msec = transitionTime(playground, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(playground, 19, msec);
+ timeout(function () {
+ var tooltip = reveal('button.add-area', helpHtml('intro.areas.add_playground'));
+ tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-areas');
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'add-area') return;
+ continueTo(startPlayground);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startPlayground() {
+ if (context.mode().id !== 'add-area') {
+ return chapter.restart();
+ }
+
+ _areaID = null;
+ context.map().zoomEase(19.5, 500);
+ timeout(function () {
+ var textId = context.lastPointerType() === 'mouse' ? 'starting_node_click' : 'starting_node_tap';
+ var startDrawString = helpHtml('intro.areas.start_playground') + helpHtml('intro.areas.' + textId);
+ revealPlayground(playground, startDrawString, {
+ duration: 250
+ });
+ timeout(function () {
+ context.map().on('move.intro drawn.intro', function () {
+ revealPlayground(playground, startDrawString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'draw-area') return chapter.restart();
+ continueTo(continuePlayground);
+ });
+ }, 250); // after reveal
+ }, 550); // after easing
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function continuePlayground() {
+ if (context.mode().id !== 'draw-area') {
+ return chapter.restart();
+ }
+
+ _areaID = null;
+ revealPlayground(playground, helpHtml('intro.areas.continue_playground'), {
+ duration: 250
+ });
+ timeout(function () {
+ context.map().on('move.intro drawn.intro', function () {
+ revealPlayground(playground, helpHtml('intro.areas.continue_playground'), {
+ duration: 0
+ });
+ });
+ }, 250); // after reveal
+
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-area') {
+ var entity = context.hasEntity(context.selectedIDs()[0]);
+
+ if (entity && entity.nodes.length >= 6) {
+ return continueTo(finishPlayground);
+ } else {
+ return;
+ }
+ } else if (mode.id === 'select') {
+ _areaID = context.selectedIDs()[0];
+ return continueTo(searchPresets);
+ } else {
+ return chapter.restart();
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function finishPlayground() {
+ if (context.mode().id !== 'draw-area') {
+ return chapter.restart();
+ }
+
+ _areaID = null;
+ var finishString = helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.areas.finish_playground');
+ revealPlayground(playground, finishString, {
+ duration: 250
+ });
+ timeout(function () {
+ context.map().on('move.intro drawn.intro', function () {
+ revealPlayground(playground, finishString, {
+ duration: 0
+ });
+ });
+ }, 250); // after reveal
+
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-area') {
+ return;
+ } else if (mode.id === 'select') {
+ _areaID = context.selectedIDs()[0];
+ return continueTo(searchPresets);
+ } else {
+ return chapter.restart();
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function searchPresets() {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return addArea();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ context.enter(modeSelect(context, [_areaID]));
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.areas.search_playground', {
+ preset: playgroundPreset.name()
+ }));
+ }, 400); // after preset list pane visible..
+
+ context.on('enter.intro', function (mode) {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return continueTo(addArea);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ // keep the user's area selected..
+ context.enter(modeSelect(context, [_areaID])); // reset pane, in case user somehow happened to change it..
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%'); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.areas.search_playground', {
+ preset: playgroundPreset.name()
+ }));
+ context.history().on('change.intro', null);
+ }
+ });
+
+ function checkPresetSearch() {
+ var first = context.container().select('.preset-list-item:first-child');
+
+ if (first.classed('preset-leisure-playground')) {
+ reveal(first.select('.preset-list-button').node(), helpHtml('intro.areas.choose_playground', {
+ preset: playgroundPreset.name()
+ }), {
+ duration: 300
+ });
+ context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
+ context.history().on('change.intro', function () {
+ continueTo(clickAddField);
+ });
+ }
+ }
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ nextStep();
+ }
+ }
+
+ function clickAddField() {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return addArea();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ return searchPresets();
+ }
+
+ if (!context.container().select('.form-field-description').empty()) {
+ return continueTo(describePlayground);
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%'); // It's possible for the user to add a description in a previous step..
+ // If they did this already, just continue to next step.
+
+ var entity = context.entity(_areaID);
+
+ if (entity.tags.description) {
+ return continueTo(play);
+ } // scroll "Add field" into view
+
+
+ var box = context.container().select('.more-fields').node().getBoundingClientRect();
+
+ if (box.top > 300) {
+ var pane = context.container().select('.entity-editor-pane .inspector-body');
+ var start = pane.node().scrollTop;
+ var end = start + (box.top - 300);
+ pane.transition().duration(250).tween('scroll.inspector', function () {
+ var node = this;
+ var i = d3_interpolateNumber(start, end);
+ return function (t) {
+ node.scrollTop = i(t);
+ };
+ });
+ }
+
+ timeout(function () {
+ reveal('.more-fields .combobox-input', helpHtml('intro.areas.add_field', {
+ name: {
+ html: nameField.label()
+ },
+ description: {
+ html: descriptionField.label()
+ }
+ }), {
+ duration: 300
+ });
+ context.container().select('.more-fields .combobox-input').on('click.intro', function () {
+ // Watch for the combobox to appear...
+ var watcher;
+ watcher = window.setInterval(function () {
+ if (!context.container().select('div.combobox').empty()) {
+ window.clearInterval(watcher);
+ continueTo(chooseDescriptionField);
+ }
+ }, 300);
+ });
+ }, 300); // after "Add Field" visible
+ }, 400); // after editor pane visible
+
+ context.on('exit.intro', function () {
+ return continueTo(searchPresets);
+ });
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.more-fields .combobox-input').on('click.intro', null);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function chooseDescriptionField() {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return addArea();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ return searchPresets();
+ }
+
+ if (!context.container().select('.form-field-description').empty()) {
+ return continueTo(describePlayground);
+ } // Make sure combobox is ready..
+
+
+ if (context.container().select('div.combobox').empty()) {
+ return continueTo(clickAddField);
+ } // Watch for the combobox to go away..
+
+
+ var watcher;
+ watcher = window.setInterval(function () {
+ if (context.container().select('div.combobox').empty()) {
+ window.clearInterval(watcher);
+ timeout(function () {
+ if (context.container().select('.form-field-description').empty()) {
+ continueTo(retryChooseDescription);
+ } else {
+ continueTo(describePlayground);
+ }
+ }, 300); // after description field added.
+ }
+ }, 300);
+ reveal('div.combobox', helpHtml('intro.areas.choose_field', {
+ field: {
+ html: descriptionField.label()
+ }
+ }), {
+ duration: 300
+ });
+ context.on('exit.intro', function () {
+ return continueTo(searchPresets);
+ });
+
+ function continueTo(nextStep) {
+ if (watcher) window.clearInterval(watcher);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function describePlayground() {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return addArea();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ return searchPresets();
+ } // reset pane, in case user happened to change it..
+
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+
+ if (context.container().select('.form-field-description').empty()) {
+ return continueTo(retryChooseDescription);
+ }
+
+ context.on('exit.intro', function () {
+ continueTo(play);
+ });
+ reveal('.entity-editor-pane', helpHtml('intro.areas.describe_playground', {
+ button: {
+ html: icon('#iD-icon-close', 'inline')
+ }
+ }), {
+ duration: 300
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function retryChooseDescription() {
+ if (!_areaID || !context.hasEntity(_areaID)) {
+ return addArea();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
+ return searchPresets();
+ } // reset pane, in case user happened to change it..
+
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '0%');
+ reveal('.entity-editor-pane', helpHtml('intro.areas.retry_add_field', {
+ field: {
+ html: descriptionField.label()
+ }
+ }), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(clickAddField);
+ }
+ });
+ context.on('exit.intro', function () {
+ return continueTo(searchPresets);
+ });
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function play() {
+ dispatch.call('done');
+ reveal('.ideditor', helpHtml('intro.areas.play', {
+ next: _t('intro.lines.title')
+ }), {
+ tooltipBox: '.intro-nav-wrap .chapter-line',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ reveal('.ideditor');
+ }
+ });
+ }
+
+ chapter.enter = function () {
+ addArea();
+ };
+
+ chapter.exit = function () {
+ timeouts.forEach(window.clearTimeout);
+ context.on('enter.intro exit.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ context.container().select('.more-fields .combobox-input').on('click.intro', null);
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroLine(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var timeouts = [];
+ var _tulipRoadID = null;
+ var flowerRoadID = 'w646';
+ var tulipRoadStart = [-85.6297754121684, 41.95805253325314];
+ var tulipRoadMidpoint = [-85.62975395449628, 41.95787501510204];
+ var tulipRoadIntersection = [-85.62974496187628, 41.95742515554585];
+ var roadCategory = _mainPresetIndex.item('category-road_minor');
+ var residentialPreset = _mainPresetIndex.item('highway/residential');
+ var woodRoadID = 'w525';
+ var woodRoadEndID = 'n2862';
+ var woodRoadAddNode = [-85.62390110349587, 41.95397111462291];
+ var woodRoadDragEndpoint = [-85.623867390213, 41.95466987786487];
+ var woodRoadDragMidpoint = [-85.62386254803509, 41.95430395953872];
+ var washingtonStreetID = 'w522';
+ var twelfthAvenueID = 'w1';
+ var eleventhAvenueEndID = 'n3550';
+ var twelfthAvenueEndID = 'n5';
+ var _washingtonSegmentID = null;
+ var eleventhAvenueEnd = context.entity(eleventhAvenueEndID).loc;
+ var twelfthAvenueEnd = context.entity(twelfthAvenueEndID).loc;
+ var deleteLinesLoc = [-85.6219395542764, 41.95228033922477];
+ var twelfthAvenue = [-85.62219310052491, 41.952505413152956];
+ var chapter = {
+ title: 'intro.lines.title'
+ };
+
+ function timeout(f, t) {
+ timeouts.push(window.setTimeout(f, t));
+ }
+
+ function eventCancel(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ }
+
+ function addLine() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ var msec = transitionTime(tulipRoadStart, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(tulipRoadStart, 18.5, msec);
+ timeout(function () {
+ var tooltip = reveal('button.add-line', helpHtml('intro.lines.add_line'));
+ tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-lines');
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'add-line') return;
+ continueTo(startLine);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startLine() {
+ if (context.mode().id !== 'add-line') return chapter.restart();
+ _tulipRoadID = null;
+ var padding = 70 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(tulipRoadStart, padding, context);
+ box.height = box.height + 100;
+ var textId = context.lastPointerType() === 'mouse' ? 'start_line' : 'start_line_tap';
+ var startLineString = helpHtml('intro.lines.missing_road') + '{br}' + helpHtml('intro.lines.line_draw_info') + helpHtml('intro.lines.' + textId);
+ reveal(box, startLineString);
+ context.map().on('move.intro drawn.intro', function () {
+ padding = 70 * Math.pow(2, context.map().zoom() - 18);
+ box = pad(tulipRoadStart, padding, context);
+ box.height = box.height + 100;
+ reveal(box, startLineString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'draw-line') return chapter.restart();
+ continueTo(drawLine);
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function drawLine() {
+ if (context.mode().id !== 'draw-line') return chapter.restart();
+ _tulipRoadID = context.mode().selectedIDs()[0];
+ context.map().centerEase(tulipRoadMidpoint, 500);
+ timeout(function () {
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18.5);
+ var box = pad(tulipRoadMidpoint, padding, context);
+ box.height = box.height * 2;
+ reveal(box, helpHtml('intro.lines.intersect', {
+ name: _t('intro.graph.name.flower-street')
+ }));
+ context.map().on('move.intro drawn.intro', function () {
+ padding = 200 * Math.pow(2, context.map().zoom() - 18.5);
+ box = pad(tulipRoadMidpoint, padding, context);
+ box.height = box.height * 2;
+ reveal(box, helpHtml('intro.lines.intersect', {
+ name: _t('intro.graph.name.flower-street')
+ }), {
+ duration: 0
+ });
+ });
+ }, 550); // after easing..
+
+ context.history().on('change.intro', function () {
+ if (isLineConnected()) {
+ continueTo(continueLine);
+ }
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-line') {
+ return;
+ } else if (mode.id === 'select') {
+ continueTo(retryIntersect);
+ return;
+ } else {
+ return chapter.restart();
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function isLineConnected() {
+ var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
+
+ if (!entity) return false;
+ var drawNodes = context.graph().childNodes(entity);
+ return drawNodes.some(function (node) {
+ return context.graph().parentWays(node).some(function (parent) {
+ return parent.id === flowerRoadID;
+ });
+ });
+ }
+
+ function retryIntersect() {
+ select(window).on('pointerdown.intro mousedown.intro', eventCancel, true);
+ var box = pad(tulipRoadIntersection, 80, context);
+ reveal(box, helpHtml('intro.lines.retry_intersect', {
+ name: _t('intro.graph.name.flower-street')
+ }));
+ timeout(chapter.restart, 3000);
+ }
+
+ function continueLine() {
+ if (context.mode().id !== 'draw-line') return chapter.restart();
+
+ var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
+
+ if (!entity) return chapter.restart();
+ context.map().centerEase(tulipRoadIntersection, 500);
+ var continueLineText = helpHtml('intro.lines.continue_line') + '{br}' + helpHtml('intro.lines.finish_line_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.lines.finish_road');
+ reveal('.surface', continueLineText);
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-line') {
+ return;
+ } else if (mode.id === 'select') {
+ return continueTo(chooseCategoryRoad);
+ } else {
+ return chapter.restart();
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function chooseCategoryRoad() {
+ if (context.mode().id !== 'select') return chapter.restart();
+ context.on('exit.intro', function () {
+ return chapter.restart();
+ });
+ var button = context.container().select('.preset-category-road_minor .preset-list-button');
+ if (button.empty()) return chapter.restart(); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
+ reveal(button.node(), helpHtml('intro.lines.choose_category_road', {
+ category: roadCategory.name()
+ }));
+ button.on('click.intro', function () {
+ continueTo(choosePresetResidential);
+ });
+ }, 400); // after editor pane visible
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-list-button').on('click.intro', null);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function choosePresetResidential() {
+ if (context.mode().id !== 'select') return chapter.restart();
+ context.on('exit.intro', function () {
+ return chapter.restart();
+ });
+ var subgrid = context.container().select('.preset-category-road_minor .subgrid');
+ if (subgrid.empty()) return chapter.restart();
+ subgrid.selectAll(':not(.preset-highway-residential) .preset-list-button').on('click.intro', function () {
+ continueTo(retryPresetResidential);
+ });
+ subgrid.selectAll('.preset-highway-residential .preset-list-button').on('click.intro', function () {
+ continueTo(nameRoad);
+ });
+ timeout(function () {
+ reveal(subgrid.node(), helpHtml('intro.lines.choose_preset_residential', {
+ preset: residentialPreset.name()
+ }), {
+ tooltipBox: '.preset-highway-residential .preset-list-button',
+ duration: 300
+ });
+ }, 300);
+
+ function continueTo(nextStep) {
+ context.container().select('.preset-list-button').on('click.intro', null);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ } // selected wrong road type
+
+
+ function retryPresetResidential() {
+ if (context.mode().id !== 'select') return chapter.restart();
+ context.on('exit.intro', function () {
+ return chapter.restart();
+ }); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ var button = context.container().select('.entity-editor-pane .preset-list-button');
+ reveal(button.node(), helpHtml('intro.lines.retry_preset_residential', {
+ preset: residentialPreset.name()
+ }));
+ button.on('click.intro', function () {
+ continueTo(chooseCategoryRoad);
+ });
+ }, 500);
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-list-button').on('click.intro', null);
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function nameRoad() {
+ context.on('exit.intro', function () {
+ continueTo(didNameRoad);
+ });
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.lines.name_road', {
+ button: {
+ html: icon('#iD-icon-close', 'inline')
+ }
+ }), {
+ tooltipClass: 'intro-lines-name_road'
+ });
+ }, 500);
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function didNameRoad() {
+ context.history().checkpoint('doneAddLine');
+ timeout(function () {
+ reveal('.surface', helpHtml('intro.lines.did_name_road'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(updateLine);
+ }
+ });
+ }, 500);
+
+ function continueTo(nextStep) {
+ nextStep();
+ }
+ }
+
+ function updateLine() {
+ context.history().reset('doneAddLine');
+
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return chapter.restart();
+ }
+
+ var msec = transitionTime(woodRoadDragMidpoint, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(woodRoadDragMidpoint, 19, msec);
+ timeout(function () {
+ var padding = 250 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragMidpoint, padding, context);
+
+ var advance = function advance() {
+ continueTo(addNode);
+ };
+
+ reveal(box, helpHtml('intro.lines.update_line'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 250 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragMidpoint, padding, context);
+ reveal(box, helpHtml('intro.lines.update_line'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function addNode() {
+ context.history().reset('doneAddLine');
+
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return chapter.restart();
+ }
+
+ var padding = 40 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadAddNode, padding, context);
+ var addNodeString = helpHtml('intro.lines.add_node' + (context.lastPointerType() === 'mouse' ? '' : '_touch'));
+ reveal(box, addNodeString);
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 40 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadAddNode, padding, context);
+ reveal(box, addNodeString, {
+ duration: 0
+ });
+ });
+ context.history().on('change.intro', function (changed) {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ if (changed.created().length === 1) {
+ timeout(function () {
+ continueTo(startDragEndpoint);
+ }, 500);
+ }
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') {
+ continueTo(updateLine);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startDragEndpoint() {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ var startDragString = helpHtml('intro.lines.start_drag_endpoint' + (context.lastPointerType() === 'mouse' ? '' : '_touch')) + helpHtml('intro.lines.drag_to_intersection');
+ reveal(box, startDragString);
+ context.map().on('move.intro drawn.intro', function () {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ reveal(box, startDragString, {
+ duration: 0
+ });
+ var entity = context.entity(woodRoadEndID);
+
+ if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) <= 4) {
+ continueTo(finishDragEndpoint);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function finishDragEndpoint() {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ var finishDragString = helpHtml('intro.lines.spot_looks_good') + helpHtml('intro.lines.finish_drag_endpoint' + (context.lastPointerType() === 'mouse' ? '' : '_touch'));
+ reveal(box, finishDragString);
+ context.map().on('move.intro drawn.intro', function () {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ reveal(box, finishDragString, {
+ duration: 0
+ });
+ var entity = context.entity(woodRoadEndID);
+
+ if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) > 4) {
+ continueTo(startDragEndpoint);
+ }
+ });
+ context.on('enter.intro', function () {
+ continueTo(startDragMidpoint);
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startDragMidpoint() {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ if (context.selectedIDs().indexOf(woodRoadID) === -1) {
+ context.enter(modeSelect(context, [woodRoadID]));
+ }
+
+ var padding = 80 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragMidpoint, padding, context);
+ reveal(box, helpHtml('intro.lines.start_drag_midpoint'));
+ context.map().on('move.intro drawn.intro', function () {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 80 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragMidpoint, padding, context);
+ reveal(box, helpHtml('intro.lines.start_drag_midpoint'), {
+ duration: 0
+ });
+ });
+ context.history().on('change.intro', function (changed) {
+ if (changed.created().length === 1) {
+ continueTo(continueDragMidpoint);
+ }
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') {
+ // keep Wood Road selected so midpoint triangles are drawn..
+ context.enter(modeSelect(context, [woodRoadID]));
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function continueDragMidpoint() {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ box.height += 400;
+
+ var advance = function advance() {
+ context.history().checkpoint('doneUpdateLine');
+ continueTo(deleteLines);
+ };
+
+ reveal(box, helpHtml('intro.lines.continue_drag_midpoint'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
+ return continueTo(updateLine);
+ }
+
+ var padding = 100 * Math.pow(2, context.map().zoom() - 19);
+ var box = pad(woodRoadDragEndpoint, padding, context);
+ box.height += 400;
+ reveal(box, helpHtml('intro.lines.continue_drag_midpoint'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function deleteLines() {
+ context.history().reset('doneUpdateLine');
+ context.enter(modeBrowse(context));
+
+ if (!context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return chapter.restart();
+ }
+
+ var msec = transitionTime(deleteLinesLoc, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(deleteLinesLoc, 18, msec);
+ timeout(function () {
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(deleteLinesLoc, padding, context);
+ box.top -= 200;
+ box.height += 400;
+
+ var advance = function advance() {
+ continueTo(rightClickIntersection);
+ };
+
+ reveal(box, helpHtml('intro.lines.delete_lines', {
+ street: _t('intro.graph.name.12th-avenue')
+ }), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(deleteLinesLoc, padding, context);
+ box.top -= 200;
+ box.height += 400;
+ reveal(box, helpHtml('intro.lines.delete_lines', {
+ street: _t('intro.graph.name.12th-avenue')
+ }), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ });
+ context.history().on('change.intro', function () {
+ timeout(function () {
+ continueTo(deleteLines);
+ }, 500); // after any transition (e.g. if user deleted intersection)
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function rightClickIntersection() {
+ context.history().reset('doneUpdateLine');
+ context.enter(modeBrowse(context));
+ context.map().centerZoomEase(eleventhAvenueEnd, 18, 500);
+ var rightClickString = helpHtml('intro.lines.split_street', {
+ street1: _t('intro.graph.name.11th-avenue'),
+ street2: _t('intro.graph.name.washington-street')
+ }) + helpHtml('intro.lines.' + (context.lastPointerType() === 'mouse' ? 'rightclick_intersection' : 'edit_menu_intersection_touch'));
+ timeout(function () {
+ var padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(eleventhAvenueEnd, padding, context);
+ reveal(box, rightClickString);
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(eleventhAvenueEnd, padding, context);
+ reveal(box, rightClickString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return;
+ var ids = context.selectedIDs();
+ if (ids.length !== 1 || ids[0] !== eleventhAvenueEndID) return;
+ timeout(function () {
+ var node = selectMenuItem(context, 'split').node();
+ if (!node) return;
+ continueTo(splitIntersection);
+ }, 50); // after menu visible
+ });
+ context.history().on('change.intro', function () {
+ timeout(function () {
+ continueTo(deleteLines);
+ }, 300); // after any transition (e.g. if user deleted intersection)
+ });
+ }, 600);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function splitIntersection() {
+ if (!context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(deleteLines);
+ }
+
+ var node = selectMenuItem(context, 'split').node();
+
+ if (!node) {
+ return continueTo(rightClickIntersection);
+ }
+
+ var wasChanged = false;
+ _washingtonSegmentID = null;
+ reveal('.edit-menu', helpHtml('intro.lines.split_intersection', {
+ street: _t('intro.graph.name.washington-street')
+ }), {
+ padding: 50
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var node = selectMenuItem(context, 'split').node();
+
+ if (!wasChanged && !node) {
+ return continueTo(rightClickIntersection);
+ }
+
+ reveal('.edit-menu', helpHtml('intro.lines.split_intersection', {
+ street: _t('intro.graph.name.washington-street')
+ }), {
+ duration: 0,
+ padding: 50
+ });
+ });
+ context.history().on('change.intro', function (changed) {
+ wasChanged = true;
+ timeout(function () {
+ if (context.history().undoAnnotation() === _t('operations.split.annotation.line', {
+ n: 1
+ })) {
+ _washingtonSegmentID = changed.created()[0].id;
+ continueTo(didSplit);
+ } else {
+ _washingtonSegmentID = null;
+ continueTo(retrySplit);
+ }
+ }, 300); // after any transition (e.g. if user deleted intersection)
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function retrySplit() {
+ context.enter(modeBrowse(context));
+ context.map().centerZoomEase(eleventhAvenueEnd, 18, 500);
+
+ var advance = function advance() {
+ continueTo(rightClickIntersection);
+ };
+
+ var padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(eleventhAvenueEnd, padding, context);
+ reveal(box, helpHtml('intro.lines.retry_split'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(eleventhAvenueEnd, padding, context);
+ reveal(box, helpHtml('intro.lines.retry_split'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: advance
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function didSplit() {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+
+ var ids = context.selectedIDs();
+ var string = 'intro.lines.did_split_' + (ids.length > 1 ? 'multi' : 'single');
+ var street = _t('intro.graph.name.washington-street');
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(twelfthAvenue, padding, context);
+ box.width = box.width / 2;
+ reveal(box, helpHtml(string, {
+ street1: street,
+ street2: street
+ }), {
+ duration: 500
+ });
+ timeout(function () {
+ context.map().centerZoomEase(twelfthAvenue, 18, 500);
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(twelfthAvenue, padding, context);
+ box.width = box.width / 2;
+ reveal(box, helpHtml(string, {
+ street1: street,
+ street2: street
+ }), {
+ duration: 0
+ });
+ });
+ }, 600); // after initial reveal and curtain cut
+
+ context.on('enter.intro', function () {
+ var ids = context.selectedIDs();
+
+ if (ids.length === 1 && ids[0] === _washingtonSegmentID) {
+ continueTo(multiSelect);
+ }
+ });
+ context.history().on('change.intro', function () {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function multiSelect() {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+
+ var ids = context.selectedIDs();
+ var hasWashington = ids.indexOf(_washingtonSegmentID) !== -1;
+ var hasTwelfth = ids.indexOf(twelfthAvenueID) !== -1;
+
+ if (hasWashington && hasTwelfth) {
+ return continueTo(multiRightClick);
+ } else if (!hasWashington && !hasTwelfth) {
+ return continueTo(didSplit);
+ }
+
+ context.map().centerZoomEase(twelfthAvenue, 18, 500);
+ timeout(function () {
+ var selected, other, padding, box;
+
+ if (hasWashington) {
+ selected = _t('intro.graph.name.washington-street');
+ other = _t('intro.graph.name.12th-avenue');
+ padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ box = pad(twelfthAvenueEnd, padding, context);
+ box.width *= 3;
+ } else {
+ selected = _t('intro.graph.name.12th-avenue');
+ other = _t('intro.graph.name.washington-street');
+ padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ box = pad(twelfthAvenue, padding, context);
+ box.width /= 2;
+ }
+
+ reveal(box, helpHtml('intro.lines.multi_select', {
+ selected: selected,
+ other1: other
+ }) + ' ' + helpHtml('intro.lines.add_to_selection_' + (context.lastPointerType() === 'mouse' ? 'click' : 'touch'), {
+ selected: selected,
+ other2: other
+ }));
+ context.map().on('move.intro drawn.intro', function () {
+ if (hasWashington) {
+ selected = _t('intro.graph.name.washington-street');
+ other = _t('intro.graph.name.12th-avenue');
+ padding = 60 * Math.pow(2, context.map().zoom() - 18);
+ box = pad(twelfthAvenueEnd, padding, context);
+ box.width *= 3;
+ } else {
+ selected = _t('intro.graph.name.12th-avenue');
+ other = _t('intro.graph.name.washington-street');
+ padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ box = pad(twelfthAvenue, padding, context);
+ box.width /= 2;
+ }
+
+ reveal(box, helpHtml('intro.lines.multi_select', {
+ selected: selected,
+ other1: other
+ }) + ' ' + helpHtml('intro.lines.add_to_selection_' + (context.lastPointerType() === 'mouse' ? 'click' : 'touch'), {
+ selected: selected,
+ other2: other
+ }), {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function () {
+ continueTo(multiSelect);
+ });
+ context.history().on('change.intro', function () {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+ });
+ }, 600);
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function multiRightClick() {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(twelfthAvenue, padding, context);
+ var rightClickString = helpHtml('intro.lines.multi_select_success') + helpHtml('intro.lines.multi_' + (context.lastPointerType() === 'mouse' ? 'rightclick' : 'edit_menu_touch'));
+ reveal(box, rightClickString);
+ context.map().on('move.intro drawn.intro', function () {
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(twelfthAvenue, padding, context);
+ reveal(box, rightClickString, {
+ duration: 0
+ });
+ });
+ context.ui().editMenu().on('toggled.intro', function (open) {
+ if (!open) return;
+ timeout(function () {
+ var ids = context.selectedIDs();
+
+ if (ids.length === 2 && ids.indexOf(twelfthAvenueID) !== -1 && ids.indexOf(_washingtonSegmentID) !== -1) {
+ var node = selectMenuItem(context, 'delete').node();
+ if (!node) return;
+ continueTo(multiDelete);
+ } else if (ids.length === 1 && ids.indexOf(_washingtonSegmentID) !== -1) {
+ return continueTo(multiSelect);
+ } else {
+ return continueTo(didSplit);
+ }
+ }, 300); // after edit menu visible
+ });
+ context.history().on('change.intro', function () {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.ui().editMenu().on('toggled.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function multiDelete() {
+ if (!_washingtonSegmentID || !context.hasEntity(_washingtonSegmentID) || !context.hasEntity(washingtonStreetID) || !context.hasEntity(twelfthAvenueID) || !context.hasEntity(eleventhAvenueEndID)) {
+ return continueTo(rightClickIntersection);
+ }
+
+ var node = selectMenuItem(context, 'delete').node();
+ if (!node) return continueTo(multiRightClick);
+ reveal('.edit-menu', helpHtml('intro.lines.multi_delete'), {
+ padding: 50
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ reveal('.edit-menu', helpHtml('intro.lines.multi_delete'), {
+ duration: 0,
+ padding: 50
+ });
+ });
+ context.on('exit.intro', function () {
+ if (context.hasEntity(_washingtonSegmentID) || context.hasEntity(twelfthAvenueID)) {
+ return continueTo(multiSelect); // left select mode but roads still exist
+ }
+ });
+ context.history().on('change.intro', function () {
+ if (context.hasEntity(_washingtonSegmentID) || context.hasEntity(twelfthAvenueID)) {
+ continueTo(retryDelete); // changed something but roads still exist
+ } else {
+ continueTo(play);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('exit.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function retryDelete() {
+ context.enter(modeBrowse(context));
+ var padding = 200 * Math.pow(2, context.map().zoom() - 18);
+ var box = pad(twelfthAvenue, padding, context);
+ reveal(box, helpHtml('intro.lines.retry_delete'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(multiSelect);
+ }
+ });
+
+ function continueTo(nextStep) {
+ nextStep();
+ }
+ }
+
+ function play() {
+ dispatch.call('done');
+ reveal('.ideditor', helpHtml('intro.lines.play', {
+ next: _t('intro.buildings.title')
+ }), {
+ tooltipBox: '.intro-nav-wrap .chapter-building',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ reveal('.ideditor');
+ }
+ });
+ }
+
+ chapter.enter = function () {
+ addLine();
+ };
+
+ chapter.exit = function () {
+ timeouts.forEach(window.clearTimeout);
+ select(window).on('pointerdown.intro mousedown.intro', null, true);
+ context.on('enter.intro exit.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-list-button').on('click.intro', null);
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroBuilding(context, reveal) {
+ var dispatch = dispatch$8('done');
+ var house = [-85.62815, 41.95638];
+ var tank = [-85.62732, 41.95347];
+ var buildingCatetory = _mainPresetIndex.item('category-building');
+ var housePreset = _mainPresetIndex.item('building/house');
+ var tankPreset = _mainPresetIndex.item('man_made/storage_tank');
+ var timeouts = [];
+ var _houseID = null;
+ var _tankID = null;
+ var chapter = {
+ title: 'intro.buildings.title'
+ };
+
+ function timeout(f, t) {
+ timeouts.push(window.setTimeout(f, t));
+ }
+
+ function eventCancel(d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ }
+
+ function revealHouse(center, text, options) {
+ var padding = 160 * Math.pow(2, context.map().zoom() - 20);
+ var box = pad(center, padding, context);
+ reveal(box, text, options);
+ }
+
+ function revealTank(center, text, options) {
+ var padding = 190 * Math.pow(2, context.map().zoom() - 19.5);
+ var box = pad(center, padding, context);
+ reveal(box, text, options);
+ }
+
+ function addHouse() {
+ context.enter(modeBrowse(context));
+ context.history().reset('initial');
+ _houseID = null;
+ var msec = transitionTime(house, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(house, 19, msec);
+ timeout(function () {
+ var tooltip = reveal('button.add-area', helpHtml('intro.buildings.add_building'));
+ tooltip.selectAll('.popover-inner').insert('svg', 'span').attr('class', 'tooltip-illustration').append('use').attr('xlink:href', '#iD-graphic-buildings');
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'add-area') return;
+ continueTo(startHouse);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startHouse() {
+ if (context.mode().id !== 'add-area') {
+ return continueTo(addHouse);
+ }
+
+ _houseID = null;
+ context.map().zoomEase(20, 500);
+ timeout(function () {
+ var startString = helpHtml('intro.buildings.start_building') + helpHtml('intro.buildings.building_corner_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap'));
+ revealHouse(house, startString);
+ context.map().on('move.intro drawn.intro', function () {
+ revealHouse(house, startString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'draw-area') return chapter.restart();
+ continueTo(continueHouse);
+ });
+ }, 550); // after easing
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function continueHouse() {
+ if (context.mode().id !== 'draw-area') {
+ return continueTo(addHouse);
+ }
+
+ _houseID = null;
+ var continueString = helpHtml('intro.buildings.continue_building') + '{br}' + helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.buildings.finish_building');
+ revealHouse(house, continueString);
+ context.map().on('move.intro drawn.intro', function () {
+ revealHouse(house, continueString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-area') {
+ return;
+ } else if (mode.id === 'select') {
+ var graph = context.graph();
+ var way = context.entity(context.selectedIDs()[0]);
+ var nodes = graph.childNodes(way);
+ var points = utilArrayUniq(nodes).map(function (n) {
+ return context.projection(n.loc);
+ });
+
+ if (isMostlySquare(points)) {
+ _houseID = way.id;
+ return continueTo(chooseCategoryBuilding);
+ } else {
+ return continueTo(retryHouse);
+ }
+ } else {
+ return chapter.restart();
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function retryHouse() {
+ var onClick = function onClick() {
+ continueTo(addHouse);
+ };
+
+ revealHouse(house, helpHtml('intro.buildings.retry_building'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ revealHouse(house, helpHtml('intro.buildings.retry_building'), {
+ duration: 0,
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: onClick
+ });
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ nextStep();
+ }
+ }
+
+ function chooseCategoryBuilding() {
+ if (!_houseID || !context.hasEntity(_houseID)) {
+ return addHouse();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
+ context.enter(modeSelect(context, [_houseID]));
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
+ var button = context.container().select('.preset-category-building .preset-list-button');
+ reveal(button.node(), helpHtml('intro.buildings.choose_category_building', {
+ category: buildingCatetory.name()
+ }));
+ button.on('click.intro', function () {
+ button.on('click.intro', null);
+ continueTo(choosePresetHouse);
+ });
+ }, 400); // after preset list pane visible..
+
+ context.on('enter.intro', function (mode) {
+ if (!_houseID || !context.hasEntity(_houseID)) {
+ return continueTo(addHouse);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
+ return continueTo(chooseCategoryBuilding);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-list-button').on('click.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function choosePresetHouse() {
+ if (!_houseID || !context.hasEntity(_houseID)) {
+ return addHouse();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
+ context.enter(modeSelect(context, [_houseID]));
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
+ var button = context.container().select('.preset-building-house .preset-list-button');
+ reveal(button.node(), helpHtml('intro.buildings.choose_preset_house', {
+ preset: housePreset.name()
+ }), {
+ duration: 300
+ });
+ button.on('click.intro', function () {
+ button.on('click.intro', null);
+ continueTo(closeEditorHouse);
+ });
+ }, 400); // after preset list pane visible..
+
+ context.on('enter.intro', function (mode) {
+ if (!_houseID || !context.hasEntity(_houseID)) {
+ return continueTo(addHouse);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
+ return continueTo(chooseCategoryBuilding);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-list-button').on('click.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function closeEditorHouse() {
+ if (!_houseID || !context.hasEntity(_houseID)) {
+ return addHouse();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
+ context.enter(modeSelect(context, [_houseID]));
+ }
+
+ context.history().checkpoint('hasHouse');
+ context.on('exit.intro', function () {
+ continueTo(rightClickHouse);
+ });
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.buildings.close', {
+ button: {
+ html: icon('#iD-icon-close', 'inline')
+ }
+ }));
+ }, 500);
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function rightClickHouse() {
+ if (!_houseID) return chapter.restart();
+ context.enter(modeBrowse(context));
+ context.history().reset('hasHouse');
+ var zoom = context.map().zoom();
+
+ if (zoom < 20) {
+ zoom = 20;
+ }
+
+ context.map().centerZoomEase(house, zoom, 500);
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return;
+ var ids = context.selectedIDs();
+ if (ids.length !== 1 || ids[0] !== _houseID) return;
+ timeout(function () {
+ var node = selectMenuItem(context, 'orthogonalize').node();
+ if (!node) return;
+ continueTo(clickSquare);
+ }, 50); // after menu visible
+ });
+ context.map().on('move.intro drawn.intro', function () {
+ var rightclickString = helpHtml('intro.buildings.' + (context.lastPointerType() === 'mouse' ? 'rightclick_building' : 'edit_menu_building_touch'));
+ revealHouse(house, rightclickString, {
+ duration: 0
+ });
+ });
+ context.history().on('change.intro', function () {
+ continueTo(rightClickHouse);
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function clickSquare() {
+ if (!_houseID) return chapter.restart();
+ var entity = context.hasEntity(_houseID);
+ if (!entity) return continueTo(rightClickHouse);
+ var node = selectMenuItem(context, 'orthogonalize').node();
+
+ if (!node) {
+ return continueTo(rightClickHouse);
+ }
+
+ var wasChanged = false;
+ reveal('.edit-menu', helpHtml('intro.buildings.square_building'), {
+ padding: 50
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'browse') {
+ continueTo(rightClickHouse);
+ } else if (mode.id === 'move' || mode.id === 'rotate') {
+ continueTo(retryClickSquare);
+ }
+ });
+ context.map().on('move.intro', function () {
+ var node = selectMenuItem(context, 'orthogonalize').node();
+
+ if (!wasChanged && !node) {
+ return continueTo(rightClickHouse);
+ }
+
+ reveal('.edit-menu', helpHtml('intro.buildings.square_building'), {
+ duration: 0,
+ padding: 50
+ });
+ });
+ context.history().on('change.intro', function () {
+ wasChanged = true;
+ context.history().on('change.intro', null); // Something changed. Wait for transition to complete and check undo annotation.
+
+ timeout(function () {
+ if (context.history().undoAnnotation() === _t('operations.orthogonalize.annotation.feature', {
+ n: 1
+ })) {
+ continueTo(doneSquare);
+ } else {
+ continueTo(retryClickSquare);
+ }
+ }, 500); // after transitioned actions
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function retryClickSquare() {
+ context.enter(modeBrowse(context));
+ revealHouse(house, helpHtml('intro.buildings.retry_square'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(rightClickHouse);
+ }
+ });
+
+ function continueTo(nextStep) {
+ nextStep();
+ }
+ }
+
+ function doneSquare() {
+ context.history().checkpoint('doneSquare');
+ revealHouse(house, helpHtml('intro.buildings.done_square'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(addTank);
+ }
+ });
+
+ function continueTo(nextStep) {
+ nextStep();
+ }
+ }
+
+ function addTank() {
+ context.enter(modeBrowse(context));
+ context.history().reset('doneSquare');
+ _tankID = null;
+ var msec = transitionTime(tank, context.map().center());
+
+ if (msec) {
+ reveal(null, null, {
+ duration: 0
+ });
+ }
+
+ context.map().centerZoomEase(tank, 19.5, msec);
+ timeout(function () {
+ reveal('button.add-area', helpHtml('intro.buildings.add_tank'));
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'add-area') return;
+ continueTo(startTank);
+ });
+ }, msec + 100);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function startTank() {
+ if (context.mode().id !== 'add-area') {
+ return continueTo(addTank);
+ }
+
+ _tankID = null;
+ timeout(function () {
+ var startString = helpHtml('intro.buildings.start_tank') + helpHtml('intro.buildings.tank_edge_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap'));
+ revealTank(tank, startString);
+ context.map().on('move.intro drawn.intro', function () {
+ revealTank(tank, startString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'draw-area') return chapter.restart();
+ continueTo(continueTank);
+ });
+ }, 550); // after easing
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function continueTank() {
+ if (context.mode().id !== 'draw-area') {
+ return continueTo(addTank);
+ }
+
+ _tankID = null;
+ var continueString = helpHtml('intro.buildings.continue_tank') + '{br}' + helpHtml('intro.areas.finish_area_' + (context.lastPointerType() === 'mouse' ? 'click' : 'tap')) + helpHtml('intro.buildings.finish_tank');
+ revealTank(tank, continueString);
+ context.map().on('move.intro drawn.intro', function () {
+ revealTank(tank, continueString, {
+ duration: 0
+ });
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'draw-area') {
+ return;
+ } else if (mode.id === 'select') {
+ _tankID = context.selectedIDs()[0];
+ return continueTo(searchPresetTank);
+ } else {
+ return continueTo(addTank);
+ }
+ });
+
+ function continueTo(nextStep) {
+ context.map().on('move.intro drawn.intro', null);
+ context.on('enter.intro', null);
+ nextStep();
+ }
+ }
+
+ function searchPresetTank() {
+ if (!_tankID || !context.hasEntity(_tankID)) {
+ return addTank();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
+ context.enter(modeSelect(context, [_tankID]));
+ } // disallow scrolling
+
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ timeout(function () {
+ // reset pane, in case user somehow happened to change it..
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%');
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.buildings.search_tank', {
+ preset: tankPreset.name()
+ }));
+ }, 400); // after preset list pane visible..
+
+ context.on('enter.intro', function (mode) {
+ if (!_tankID || !context.hasEntity(_tankID)) {
+ return continueTo(addTank);
+ }
+
+ var ids = context.selectedIDs();
+
+ if (mode.id !== 'select' || !ids.length || ids[0] !== _tankID) {
+ // keep the user's area selected..
+ context.enter(modeSelect(context, [_tankID])); // reset pane, in case user somehow happened to change it..
+
+ context.container().select('.inspector-wrap .panewrap').style('right', '-100%'); // disallow scrolling
+
+ context.container().select('.inspector-wrap').on('wheel.intro', eventCancel);
+ context.container().select('.preset-search-input').on('keydown.intro', null).on('keyup.intro', checkPresetSearch);
+ reveal('.preset-search-input', helpHtml('intro.buildings.search_tank', {
+ preset: tankPreset.name()
+ }));
+ context.history().on('change.intro', null);
+ }
+ });
+
+ function checkPresetSearch() {
+ var first = context.container().select('.preset-list-item:first-child');
+
+ if (first.classed('preset-man_made-storage_tank')) {
+ reveal(first.select('.preset-list-button').node(), helpHtml('intro.buildings.choose_tank', {
+ preset: tankPreset.name()
+ }), {
+ duration: 300
+ });
+ context.container().select('.preset-search-input').on('keydown.intro', eventCancel, true).on('keyup.intro', null);
+ context.history().on('change.intro', function () {
+ continueTo(closeEditorTank);
+ });
+ }
+ }
+
+ function continueTo(nextStep) {
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.on('enter.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ nextStep();
+ }
+ }
+
+ function closeEditorTank() {
+ if (!_tankID || !context.hasEntity(_tankID)) {
+ return addTank();
+ }
+
+ var ids = context.selectedIDs();
+
+ if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
+ context.enter(modeSelect(context, [_tankID]));
+ }
+
+ context.history().checkpoint('hasTank');
+ context.on('exit.intro', function () {
+ continueTo(rightClickTank);
+ });
+ timeout(function () {
+ reveal('.entity-editor-pane', helpHtml('intro.buildings.close', {
+ button: {
+ html: icon('#iD-icon-close', 'inline')
+ }
+ }));
+ }, 500);
+
+ function continueTo(nextStep) {
+ context.on('exit.intro', null);
+ nextStep();
+ }
+ }
+
+ function rightClickTank() {
+ if (!_tankID) return continueTo(addTank);
+ context.enter(modeBrowse(context));
+ context.history().reset('hasTank');
+ context.map().centerEase(tank, 500);
+ timeout(function () {
+ context.on('enter.intro', function (mode) {
+ if (mode.id !== 'select') return;
+ var ids = context.selectedIDs();
+ if (ids.length !== 1 || ids[0] !== _tankID) return;
+ timeout(function () {
+ var node = selectMenuItem(context, 'circularize').node();
+ if (!node) return;
+ continueTo(clickCircle);
+ }, 50); // after menu visible
+ });
+ var rightclickString = helpHtml('intro.buildings.' + (context.lastPointerType() === 'mouse' ? 'rightclick_tank' : 'edit_menu_tank_touch'));
+ revealTank(tank, rightclickString);
+ context.map().on('move.intro drawn.intro', function () {
+ revealTank(tank, rightclickString, {
+ duration: 0
+ });
+ });
+ context.history().on('change.intro', function () {
+ continueTo(rightClickTank);
+ });
+ }, 600);
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function clickCircle() {
+ if (!_tankID) return chapter.restart();
+ var entity = context.hasEntity(_tankID);
+ if (!entity) return continueTo(rightClickTank);
+ var node = selectMenuItem(context, 'circularize').node();
+
+ if (!node) {
+ return continueTo(rightClickTank);
+ }
+
+ var wasChanged = false;
+ reveal('.edit-menu', helpHtml('intro.buildings.circle_tank'), {
+ padding: 50
+ });
+ context.on('enter.intro', function (mode) {
+ if (mode.id === 'browse') {
+ continueTo(rightClickTank);
+ } else if (mode.id === 'move' || mode.id === 'rotate') {
+ continueTo(retryClickCircle);
+ }
+ });
+ context.map().on('move.intro', function () {
+ var node = selectMenuItem(context, 'circularize').node();
+
+ if (!wasChanged && !node) {
+ return continueTo(rightClickTank);
+ }
+
+ reveal('.edit-menu', helpHtml('intro.buildings.circle_tank'), {
+ duration: 0,
+ padding: 50
+ });
+ });
+ context.history().on('change.intro', function () {
+ wasChanged = true;
+ context.history().on('change.intro', null); // Something changed. Wait for transition to complete and check undo annotation.
+
+ timeout(function () {
+ if (context.history().undoAnnotation() === _t('operations.circularize.annotation.feature', {
+ n: 1
+ })) {
+ continueTo(play);
+ } else {
+ continueTo(retryClickCircle);
+ }
+ }, 500); // after transitioned actions
+ });
+
+ function continueTo(nextStep) {
+ context.on('enter.intro', null);
+ context.map().on('move.intro', null);
+ context.history().on('change.intro', null);
+ nextStep();
+ }
+ }
+
+ function retryClickCircle() {
+ context.enter(modeBrowse(context));
+ revealTank(tank, helpHtml('intro.buildings.retry_circle'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ continueTo(rightClickTank);
+ }
+ });
+
+ function continueTo(nextStep) {
+ nextStep();
+ }
+ }
+
+ function play() {
+ dispatch.call('done');
+ reveal('.ideditor', helpHtml('intro.buildings.play', {
+ next: _t('intro.startediting.title')
+ }), {
+ tooltipBox: '.intro-nav-wrap .chapter-startEditing',
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ reveal('.ideditor');
+ }
+ });
+ }
+
+ chapter.enter = function () {
+ addHouse();
+ };
+
+ chapter.exit = function () {
+ timeouts.forEach(window.clearTimeout);
+ context.on('enter.intro exit.intro', null);
+ context.map().on('move.intro drawn.intro', null);
+ context.history().on('change.intro', null);
+ context.container().select('.inspector-wrap').on('wheel.intro', null);
+ context.container().select('.preset-search-input').on('keydown.intro keyup.intro', null);
+ context.container().select('.more-fields .combobox-input').on('click.intro', null);
+ };
+
+ chapter.restart = function () {
+ chapter.exit();
+ chapter.enter();
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ function uiIntroStartEditing(context, reveal) {
+ var dispatch = dispatch$8('done', 'startEditing');
+ var modalSelection = select(null);
+ var chapter = {
+ title: 'intro.startediting.title'
+ };
+
+ function showHelp() {
+ reveal('.map-control.help-control', helpHtml('intro.startediting.help'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ shortcuts();
+ }
+ });
+ }
+
+ function shortcuts() {
+ reveal('.map-control.help-control', helpHtml('intro.startediting.shortcuts'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ showSave();
+ }
+ });
+ }
+
+ function showSave() {
+ context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+
+ reveal('.top-toolbar button.save', helpHtml('intro.startediting.save'), {
+ buttonText: _t.html('intro.ok'),
+ buttonCallback: function buttonCallback() {
+ showStart();
+ }
+ });
+ }
+
+ function showStart() {
+ context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+
+ modalSelection = uiModal(context.container());
+ modalSelection.select('.modal').attr('class', 'modal-splash modal');
+ modalSelection.selectAll('.close').remove();
+ var startbutton = modalSelection.select('.content').attr('class', 'fillL').append('button').attr('class', 'modal-section huge-modal-button').on('click', function () {
+ modalSelection.remove();
+ });
+ startbutton.append('svg').attr('class', 'illustration').append('use').attr('xlink:href', '#iD-logo-walkthrough');
+ startbutton.append('h2').call(_t.append('intro.startediting.start'));
+ dispatch.call('startEditing');
+ }
+
+ chapter.enter = function () {
+ showHelp();
+ };
+
+ chapter.exit = function () {
+ modalSelection.remove();
+ context.container().selectAll('.shaded').remove(); // in case user opened keyboard shortcuts
+ };
+
+ return utilRebind(chapter, dispatch, 'on');
+ }
+
+ var chapterUi = {
+ welcome: uiIntroWelcome,
+ navigation: uiIntroNavigation,
+ point: uiIntroPoint,
+ area: uiIntroArea,
+ line: uiIntroLine,
+ building: uiIntroBuilding,
+ startEditing: uiIntroStartEditing
+ };
+ var chapterFlow = ['welcome', 'navigation', 'point', 'area', 'line', 'building', 'startEditing'];
+ function uiIntro(context) {
+ var INTRO_IMAGERY = 'EsriWorldImageryClarity';
+ var _introGraph = {};
+
+ var _currChapter;
+
+ function intro(selection) {
+ _mainFileFetcher.get('intro_graph').then(function (dataIntroGraph) {
+ // create entities for intro graph and localize names
+ for (var id in dataIntroGraph) {
+ if (!_introGraph[id]) {
+ _introGraph[id] = osmEntity(localize(dataIntroGraph[id]));
+ }
+ }
+
+ selection.call(startIntro);
+ })["catch"](function () {
+ /* ignore */
+ });
+ }
+
+ function startIntro(selection) {
+ context.enter(modeBrowse(context)); // Save current map state
+
+ var osm = context.connection();
+ var history = context.history().toJSON();
+ var hash = window.location.hash;
+ var center = context.map().center();
+ var zoom = context.map().zoom();
+ var background = context.background().baseLayerSource();
+ var overlays = context.background().overlayLayerSources();
+ var opacity = context.container().selectAll('.main-map .layer-background').style('opacity');
+ var caches = osm && osm.caches();
+ var baseEntities = context.history().graph().base().entities; // Show sidebar and disable the sidebar resizing button
+ // (this needs to be before `context.inIntro(true)`)
+
+ context.ui().sidebar.expand();
+ context.container().selectAll('button.sidebar-toggle').classed('disabled', true); // Block saving
+
+ context.inIntro(true); // Load semi-real data used in intro
+
+ if (osm) {
+ osm.toggle(false).reset();
+ }
+
+ context.history().reset();
+ context.history().merge(Object.values(coreGraph().load(_introGraph).entities));
+ context.history().checkpoint('initial'); // Setup imagery
+
+ var imagery = context.background().findSource(INTRO_IMAGERY);
+
+ if (imagery) {
+ context.background().baseLayerSource(imagery);
+ } else {
+ context.background().bing();
+ }
+
+ overlays.forEach(function (d) {
+ return context.background().toggleOverlayLayer(d);
+ }); // Setup data layers (only OSM)
+
+ var layers = context.layers();
+ layers.all().forEach(function (item) {
+ // if the layer has the function `enabled`
+ if (typeof item.layer.enabled === 'function') {
+ item.layer.enabled(item.id === 'osm');
+ }
+ });
+ context.container().selectAll('.main-map .layer-background').style('opacity', 1);
+ var curtain = uiCurtain(context.container().node());
+ selection.call(curtain); // Store that the user started the walkthrough..
+
+ corePreferences('walkthrough_started', 'yes'); // Restore previous walkthrough progress..
+
+ var storedProgress = corePreferences('walkthrough_progress') || '';
+ var progress = storedProgress.split(';').filter(Boolean);
+ var chapters = chapterFlow.map(function (chapter, i) {
+ var s = chapterUi[chapter](context, curtain.reveal).on('done', function () {
+ buttons.filter(function (d) {
+ return d.title === s.title;
+ }).classed('finished', true);
+
+ if (i < chapterFlow.length - 1) {
+ var next = chapterFlow[i + 1];
+ context.container().select("button.chapter-".concat(next)).classed('next', true);
+ } // Store walkthrough progress..
+
+
+ progress.push(chapter);
+ corePreferences('walkthrough_progress', utilArrayUniq(progress).join(';'));
+ });
+ return s;
+ });
+ chapters[chapters.length - 1].on('startEditing', function () {
+ // Store walkthrough progress..
+ progress.push('startEditing');
+ corePreferences('walkthrough_progress', utilArrayUniq(progress).join(';')); // Store if walkthrough is completed..
+
+ var incomplete = utilArrayDifference(chapterFlow, progress);
+
+ if (!incomplete.length) {
+ corePreferences('walkthrough_completed', 'yes');
+ }
+
+ curtain.remove();
+ navwrap.remove();
+ context.container().selectAll('.main-map .layer-background').style('opacity', opacity);
+ context.container().selectAll('button.sidebar-toggle').classed('disabled', false);
+
+ if (osm) {
+ osm.toggle(true).reset().caches(caches);
+ }
+
+ context.history().reset().merge(Object.values(baseEntities));
+ context.background().baseLayerSource(background);
+ overlays.forEach(function (d) {
+ return context.background().toggleOverlayLayer(d);
+ });
+
+ if (history) {
+ context.history().fromJSON(history, false);
+ }
+
+ context.map().centerZoom(center, zoom);
+ window.location.replace(hash);
+ context.inIntro(false);
+ });
+ var navwrap = selection.append('div').attr('class', 'intro-nav-wrap fillD');
+ navwrap.append('svg').attr('class', 'intro-nav-wrap-logo').append('use').attr('xlink:href', '#iD-logo-walkthrough');
+ var buttonwrap = navwrap.append('div').attr('class', 'joined').selectAll('button.chapter');
+ var buttons = buttonwrap.data(chapters).enter().append('button').attr('class', function (d, i) {
+ return "chapter chapter-".concat(chapterFlow[i]);
+ }).on('click', enterChapter);
+ buttons.append('span').html(function (d) {
+ return _t.html(d.title);
+ });
+ buttons.append('span').attr('class', 'status').call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward', 'inline'));
+ enterChapter(null, chapters[0]);
+
+ function enterChapter(d3_event, newChapter) {
+ if (_currChapter) {
+ _currChapter.exit();
+ }
+
+ context.enter(modeBrowse(context));
+ _currChapter = newChapter;
+
+ _currChapter.enter();
+
+ buttons.classed('next', false).classed('active', function (d) {
+ return d.title === _currChapter.title;
+ });
+ }
+ }
+
+ return intro;
+ }
+
+ function uiIssuesInfo(context) {
+ var warningsItem = {
+ id: 'warnings',
+ count: 0,
+ iconID: 'iD-icon-alert',
+ descriptionID: 'issues.warnings_and_errors'
+ };
+ var resolvedItem = {
+ id: 'resolved',
+ count: 0,
+ iconID: 'iD-icon-apply',
+ descriptionID: 'issues.user_resolved_issues'
+ };
+
+ function update(selection) {
+ var shownItems = [];
+ var liveIssues = context.validator().getIssues({
+ what: corePreferences('validate-what') || 'edited',
+ where: corePreferences('validate-where') || 'all'
+ });
+
+ if (liveIssues.length) {
+ warningsItem.count = liveIssues.length;
+ shownItems.push(warningsItem);
+ }
+
+ if (corePreferences('validate-what') === 'all') {
+ var resolvedIssues = context.validator().getResolvedIssues();
+
+ if (resolvedIssues.length) {
+ resolvedItem.count = resolvedIssues.length;
+ shownItems.push(resolvedItem);
+ }
+ }
+
+ var chips = selection.selectAll('.chip').data(shownItems, function (d) {
+ return d.id;
+ });
+ chips.exit().remove();
+ var enter = chips.enter().append('a').attr('class', function (d) {
+ return 'chip ' + d.id + '-count';
+ }).attr('href', '#').each(function (d) {
+ var chipSelection = select(this);
+ var tooltipBehavior = uiTooltip().placement('top').title(_t.html(d.descriptionID));
+ chipSelection.call(tooltipBehavior).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ tooltipBehavior.hide(select(this)); // open the Issues pane
+
+ context.ui().togglePanes(context.container().select('.map-panes .issues-pane'));
+ });
+ chipSelection.call(svgIcon('#' + d.iconID));
+ });
+ enter.append('span').attr('class', 'count');
+ enter.merge(chips).selectAll('span.count').text(function (d) {
+ return d.count.toString();
+ });
+ }
+
+ return function (selection) {
+ update(selection);
+ context.validator().on('validated.infobox', function () {
+ update(selection);
+ });
+ };
+ }
+
+ function uiMapInMap(context) {
+ function mapInMap(selection) {
+ var backgroundLayer = rendererTileLayer(context);
+ var overlayLayers = {};
+ var projection = geoRawMercator();
+ var dataLayer = svgData(projection, context).showLabels(false);
+ var debugLayer = svgDebug(projection, context);
+ var zoom = d3_zoom().scaleExtent([geoZoomToScale(0.5), geoZoomToScale(24)]).on('start', zoomStarted).on('zoom', zoomed).on('end', zoomEnded);
+ var wrap = select(null);
+ var tiles = select(null);
+ var viewport = select(null);
+ var _isTransformed = false;
+ var _isHidden = true;
+ var _skipEvents = false;
+ var _gesture = null;
+ var _zDiff = 6; // by default, minimap renders at (main zoom - 6)
+
+ var _dMini; // dimensions of minimap
+
+
+ var _cMini; // center pixel of minimap
+
+
+ var _tStart; // transform at start of gesture
+
+
+ var _tCurr; // transform at most recent event
+
+
+ var _timeoutID;
+
+ function zoomStarted() {
+ if (_skipEvents) return;
+ _tStart = _tCurr = projection.transform();
+ _gesture = null;
+ }
+
+ function zoomed(d3_event) {
+ if (_skipEvents) return;
+ var x = d3_event.transform.x;
+ var y = d3_event.transform.y;
+ var k = d3_event.transform.k;
+ var isZooming = k !== _tStart.k;
+ var isPanning = x !== _tStart.x || y !== _tStart.y;
+
+ if (!isZooming && !isPanning) {
+ return; // no change
+ } // lock in either zooming or panning, don't allow both in minimap.
+
+
+ if (!_gesture) {
+ _gesture = isZooming ? 'zoom' : 'pan';
+ }
+
+ var tMini = projection.transform();
+ var tX, tY, scale;
+
+ if (_gesture === 'zoom') {
+ scale = k / tMini.k;
+ tX = (_cMini[0] / scale - _cMini[0]) * scale;
+ tY = (_cMini[1] / scale - _cMini[1]) * scale;
+ } else {
+ k = tMini.k;
+ scale = 1;
+ tX = x - tMini.x;
+ tY = y - tMini.y;
+ }
+
+ utilSetTransform(tiles, tX, tY, scale);
+ utilSetTransform(viewport, 0, 0, scale);
+ _isTransformed = true;
+ _tCurr = identity$2.translate(x, y).scale(k);
+ var zMain = geoScaleToZoom(context.projection.scale());
+ var zMini = geoScaleToZoom(k);
+ _zDiff = zMain - zMini;
+ queueRedraw();
+ }
+
+ function zoomEnded() {
+ if (_skipEvents) return;
+ if (_gesture !== 'pan') return;
+ updateProjection();
+ _gesture = null;
+ context.map().center(projection.invert(_cMini)); // recenter main map..
+ }
+
+ function updateProjection() {
+ var loc = context.map().center();
+ var tMain = context.projection.transform();
+ var zMain = geoScaleToZoom(tMain.k);
+ var zMini = Math.max(zMain - _zDiff, 0.5);
+ var kMini = geoZoomToScale(zMini);
+ projection.translate([tMain.x, tMain.y]).scale(kMini);
+ var point = projection(loc);
+ var mouse = _gesture === 'pan' ? geoVecSubtract([_tCurr.x, _tCurr.y], [_tStart.x, _tStart.y]) : [0, 0];
+ var xMini = _cMini[0] - point[0] + tMain.x + mouse[0];
+ var yMini = _cMini[1] - point[1] + tMain.y + mouse[1];
+ projection.translate([xMini, yMini]).clipExtent([[0, 0], _dMini]);
+ _tCurr = projection.transform();
+
+ if (_isTransformed) {
+ utilSetTransform(tiles, 0, 0);
+ utilSetTransform(viewport, 0, 0);
+ _isTransformed = false;
+ }
+
+ zoom.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(zMain - 3)]);
+ _skipEvents = true;
+ wrap.call(zoom.transform, _tCurr);
+ _skipEvents = false;
+ }
+
+ function redraw() {
+ clearTimeout(_timeoutID);
+ if (_isHidden) return;
+ updateProjection();
+ var zMini = geoScaleToZoom(projection.scale()); // setup tile container
+
+ tiles = wrap.selectAll('.map-in-map-tiles').data([0]);
+ tiles = tiles.enter().append('div').attr('class', 'map-in-map-tiles').merge(tiles); // redraw background
+
+ backgroundLayer.source(context.background().baseLayerSource()).projection(projection).dimensions(_dMini);
+ var background = tiles.selectAll('.map-in-map-background').data([0]);
+ background.enter().append('div').attr('class', 'map-in-map-background').merge(background).call(backgroundLayer); // redraw overlay
+
+ var overlaySources = context.background().overlayLayerSources();
+ var activeOverlayLayers = [];
+
+ for (var i = 0; i < overlaySources.length; i++) {
+ if (overlaySources[i].validZoom(zMini)) {
+ if (!overlayLayers[i]) overlayLayers[i] = rendererTileLayer(context);
+ activeOverlayLayers.push(overlayLayers[i].source(overlaySources[i]).projection(projection).dimensions(_dMini));
+ }
+ }
+
+ var overlay = tiles.selectAll('.map-in-map-overlay').data([0]);
+ overlay = overlay.enter().append('div').attr('class', 'map-in-map-overlay').merge(overlay);
+ var overlays = overlay.selectAll('div').data(activeOverlayLayers, function (d) {
+ return d.source().name();
+ });
+ overlays.exit().remove();
+ overlays = overlays.enter().append('div').merge(overlays).each(function (layer) {
+ select(this).call(layer);
+ });
+ var dataLayers = tiles.selectAll('.map-in-map-data').data([0]);
+ dataLayers.exit().remove();
+ dataLayers = dataLayers.enter().append('svg').attr('class', 'map-in-map-data').merge(dataLayers).call(dataLayer).call(debugLayer); // redraw viewport bounding box
+
+ if (_gesture !== 'pan') {
+ var getPath = d3_geoPath(projection);
+ var bbox = {
+ type: 'Polygon',
+ coordinates: [context.map().extent().polygon()]
+ };
+ viewport = wrap.selectAll('.map-in-map-viewport').data([0]);
+ viewport = viewport.enter().append('svg').attr('class', 'map-in-map-viewport').merge(viewport);
+ var path = viewport.selectAll('.map-in-map-bbox').data([bbox]);
+ path.enter().append('path').attr('class', 'map-in-map-bbox').merge(path).attr('d', getPath).classed('thick', function (d) {
+ return getPath.area(d) < 30;
+ });
+ }
+ }
+
+ function queueRedraw() {
+ clearTimeout(_timeoutID);
+ _timeoutID = setTimeout(function () {
+ redraw();
+ }, 750);
+ }
+
+ function toggle(d3_event) {
+ if (d3_event) d3_event.preventDefault();
+ _isHidden = !_isHidden;
+ context.container().select('.minimap-toggle-item').classed('active', !_isHidden).select('input').property('checked', !_isHidden);
+
+ if (_isHidden) {
+ wrap.style('display', 'block').style('opacity', '1').transition().duration(200).style('opacity', '0').on('end', function () {
+ selection.selectAll('.map-in-map').style('display', 'none');
+ });
+ } else {
+ wrap.style('display', 'block').style('opacity', '0').transition().duration(200).style('opacity', '1').on('end', function () {
+ redraw();
+ });
+ }
+ }
+
+ uiMapInMap.toggle = toggle;
+ wrap = selection.selectAll('.map-in-map').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'map-in-map').style('display', _isHidden ? 'none' : 'block').call(zoom).on('dblclick.zoom', null).merge(wrap); // reflow warning: Hardcode dimensions - currently can't resize it anyway..
+
+ _dMini = [200, 150]; //utilGetDimensions(wrap);
+
+ _cMini = geoVecScale(_dMini, 0.5);
+ context.map().on('drawn.map-in-map', function (drawn) {
+ if (drawn.full === true) {
+ redraw();
+ }
+ });
+ redraw();
+ context.keybinding().on(_t('background.minimap.key'), toggle);
+ }
+
+ return mapInMap;
+ }
+
+ function uiNotice(context) {
+ return function (selection) {
+ var div = selection.append('div').attr('class', 'notice');
+ var button = div.append('button').attr('class', 'zoom-to notice fillD').on('click', function () {
+ context.map().zoomEase(context.minEditableZoom());
+ }).on('wheel', function (d3_event) {
+ // let wheel events pass through #4482
+ var e2 = new WheelEvent(d3_event.type, d3_event);
+ context.surface().node().dispatchEvent(e2);
+ });
+ button.call(svgIcon('#iD-icon-plus', 'pre-text')).append('span').attr('class', 'label').call(_t.append('zoom_in_edit'));
+
+ function disableTooHigh() {
+ var canEdit = context.map().zoom() >= context.minEditableZoom();
+ div.style('display', canEdit ? 'none' : 'block');
+ }
+
+ context.map().on('move.notice', debounce(disableTooHigh, 500));
+ disableTooHigh();
+ };
+ }
+
+ function uiPhotoviewer(context) {
+ var dispatch = dispatch$8('resize');
+
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+
+ function photoviewer(selection) {
+ selection.append('button').attr('class', 'thumb-hide').attr('title', _t('icons.close')).on('click', function () {
+ if (services.streetside) {
+ services.streetside.hideViewer(context);
+ }
+
+ if (services.mapillary) {
+ services.mapillary.hideViewer(context);
+ }
+
+ if (services.kartaview) {
+ services.kartaview.hideViewer(context);
+ }
+ }).append('div').call(svgIcon('#iD-icon-close'));
+
+ function preventDefault(d3_event) {
+ d3_event.preventDefault();
+ }
+
+ selection.append('button').attr('class', 'resize-handle-xy').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
+ resizeOnX: true,
+ resizeOnY: true
+ }));
+ selection.append('button').attr('class', 'resize-handle-x').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
+ resizeOnX: true
+ }));
+ selection.append('button').attr('class', 'resize-handle-y').on('touchstart touchdown touchend', preventDefault).on(_pointerPrefix + 'down', buildResizeListener(selection, 'resize', dispatch, {
+ resizeOnY: true
+ }));
+
+ function buildResizeListener(target, eventName, dispatch, options) {
+ var resizeOnX = !!options.resizeOnX;
+ var resizeOnY = !!options.resizeOnY;
+ var minHeight = options.minHeight || 240;
+ var minWidth = options.minWidth || 320;
+ var pointerId;
+ var startX;
+ var startY;
+ var startWidth;
+ var startHeight;
+
+ function startResize(d3_event) {
+ if (pointerId !== (d3_event.pointerId || 'mouse')) return;
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ var mapSize = context.map().dimensions();
+
+ if (resizeOnX) {
+ var maxWidth = mapSize[0];
+ var newWidth = clamp(startWidth + d3_event.clientX - startX, minWidth, maxWidth);
+ target.style('width', newWidth + 'px');
+ }
+
+ if (resizeOnY) {
+ var maxHeight = mapSize[1] - 90; // preserve space at top/bottom of map
+
+ var newHeight = clamp(startHeight + startY - d3_event.clientY, minHeight, maxHeight);
+ target.style('height', newHeight + 'px');
+ }
+
+ dispatch.call(eventName, target, utilGetDimensions(target, true));
+ }
+
+ function clamp(num, min, max) {
+ return Math.max(min, Math.min(num, max));
+ }
+
+ function stopResize(d3_event) {
+ if (pointerId !== (d3_event.pointerId || 'mouse')) return;
+ d3_event.preventDefault();
+ d3_event.stopPropagation(); // remove all the listeners we added
+
+ select(window).on('.' + eventName, null);
+ }
+
+ return function initResize(d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ pointerId = d3_event.pointerId || 'mouse';
+ startX = d3_event.clientX;
+ startY = d3_event.clientY;
+ var targetRect = target.node().getBoundingClientRect();
+ startWidth = targetRect.width;
+ startHeight = targetRect.height;
+ select(window).on(_pointerPrefix + 'move.' + eventName, startResize, false).on(_pointerPrefix + 'up.' + eventName, stopResize, false);
+
+ if (_pointerPrefix === 'pointer') {
+ select(window).on('pointercancel.' + eventName, stopResize, false);
+ }
+ };
+ }
+ }
+
+ photoviewer.onMapResize = function () {
+ var photoviewer = context.container().select('.photoviewer');
+ var content = context.container().select('.main-content');
+ var mapDimensions = utilGetDimensions(content, true); // shrink photo viewer if it is too big
+ // (-90 preserves space at top and bottom of map used by menus)
+
+ var photoDimensions = utilGetDimensions(photoviewer, true);
+
+ if (photoDimensions[0] > mapDimensions[0] || photoDimensions[1] > mapDimensions[1] - 90) {
+ var setPhotoDimensions = [Math.min(photoDimensions[0], mapDimensions[0]), Math.min(photoDimensions[1], mapDimensions[1] - 90)];
+ photoviewer.style('width', setPhotoDimensions[0] + 'px').style('height', setPhotoDimensions[1] + 'px');
+ dispatch.call('resize', photoviewer, setPhotoDimensions);
+ }
+ };
+
+ return utilRebind(photoviewer, dispatch, 'on');
+ }
+
+ function uiRestore(context) {
+ return function (selection) {
+ if (!context.history().hasRestorableChanges()) return;
+ var modalSelection = uiModal(selection, true);
+ modalSelection.select('.modal').attr('class', 'modal fillL');
+ var introModal = modalSelection.select('.content');
+ introModal.append('div').attr('class', 'modal-section').append('h3').call(_t.append('restore.heading'));
+ introModal.append('div').attr('class', 'modal-section').append('p').call(_t.append('restore.description'));
+ var buttonWrap = introModal.append('div').attr('class', 'modal-actions');
+ var restore = buttonWrap.append('button').attr('class', 'restore').on('click', function () {
+ context.history().restore();
+ modalSelection.remove();
+ });
+ restore.append('svg').attr('class', 'logo logo-restore').append('use').attr('xlink:href', '#iD-logo-restore');
+ restore.append('div').call(_t.append('restore.restore'));
+ var reset = buttonWrap.append('button').attr('class', 'reset').on('click', function () {
+ context.history().clearSaved();
+ modalSelection.remove();
+ });
+ reset.append('svg').attr('class', 'logo logo-reset').append('use').attr('xlink:href', '#iD-logo-reset');
+ reset.append('div').call(_t.append('restore.reset'));
+ restore.node().focus();
+ };
+ }
+
+ function uiScale(context) {
+ var projection = context.projection,
+ isImperial = !_mainLocalizer.usesMetric(),
+ maxLength = 180,
+ tickHeight = 8;
+
+ function scaleDefs(loc1, loc2) {
+ var lat = (loc2[1] + loc1[1]) / 2,
+ conversion = isImperial ? 3.28084 : 1,
+ dist = geoLonToMeters(loc2[0] - loc1[0], lat) * conversion,
+ scale = {
+ dist: 0,
+ px: 0,
+ text: ''
+ },
+ buckets,
+ i,
+ val,
+ dLon;
+
+ if (isImperial) {
+ buckets = [5280000, 528000, 52800, 5280, 500, 50, 5, 1];
+ } else {
+ buckets = [5000000, 500000, 50000, 5000, 500, 50, 5, 1];
+ } // determine a user-friendly endpoint for the scale
+
+
+ for (i = 0; i < buckets.length; i++) {
+ val = buckets[i];
+
+ if (dist >= val) {
+ scale.dist = Math.floor(dist / val) * val;
+ break;
+ } else {
+ scale.dist = +dist.toFixed(2);
+ }
+ }
+
+ dLon = geoMetersToLon(scale.dist / conversion, lat);
+ scale.px = Math.round(projection([loc1[0] + dLon, loc1[1]])[0]);
+ scale.text = displayLength(scale.dist / conversion, isImperial);
+ return scale;
+ }
+
+ function update(selection) {
+ // choose loc1, loc2 along bottom of viewport (near where the scale will be drawn)
+ var dims = context.map().dimensions(),
+ loc1 = projection.invert([0, dims[1]]),
+ loc2 = projection.invert([maxLength, dims[1]]),
+ scale = scaleDefs(loc1, loc2);
+ selection.select('.scale-path').attr('d', 'M0.5,0.5v' + tickHeight + 'h' + scale.px + 'v-' + tickHeight);
+ selection.select('.scale-text').style(_mainLocalizer.textDirection() === 'ltr' ? 'left' : 'right', scale.px + 16 + 'px').text(scale.text);
+ }
+
+ return function (selection) {
+ function switchUnits() {
+ isImperial = !isImperial;
+ selection.call(update);
+ }
+
+ var scalegroup = selection.append('svg').attr('class', 'scale').on('click', switchUnits).append('g').attr('transform', 'translate(10,11)');
+ scalegroup.append('path').attr('class', 'scale-path');
+ selection.append('div').attr('class', 'scale-text');
+ selection.call(update);
+ context.map().on('move.scale', function () {
+ update(selection);
+ });
+ };
+ }
+
+ function uiShortcuts(context) {
+ var detected = utilDetect();
+ var _activeTab = 0;
+
+ var _modalSelection;
+
+ var _selection = select(null);
+
+ var _dataShortcuts;
+
+ function shortcutsModal(_modalSelection) {
+ _modalSelection.select('.modal').classed('modal-shortcuts', true);
+
+ var content = _modalSelection.select('.content');
+
+ content.append('div').attr('class', 'modal-section header').append('h2').call(_t.append('shortcuts.title'));
+ _mainFileFetcher.get('shortcuts').then(function (data) {
+ _dataShortcuts = data;
+ content.call(render);
+ })["catch"](function () {
+ /* ignore */
+ });
+ }
+
+ function render(selection) {
+ if (!_dataShortcuts) return;
+ var wrapper = selection.selectAll('.wrapper').data([0]);
+ var wrapperEnter = wrapper.enter().append('div').attr('class', 'wrapper modal-section');
+ var tabsBar = wrapperEnter.append('div').attr('class', 'tabs-bar');
+ var shortcutsList = wrapperEnter.append('div').attr('class', 'shortcuts-list');
+ wrapper = wrapper.merge(wrapperEnter);
+ var tabs = tabsBar.selectAll('.tab').data(_dataShortcuts);
+ var tabsEnter = tabs.enter().append('a').attr('class', 'tab').attr('href', '#').on('click', function (d3_event, d) {
+ d3_event.preventDefault();
+
+ var i = _dataShortcuts.indexOf(d);
+
+ _activeTab = i;
+ render(selection);
+ });
+ tabsEnter.append('span').html(function (d) {
+ return _t.html(d.text);
+ }); // Update
+
+ wrapper.selectAll('.tab').classed('active', function (d, i) {
+ return i === _activeTab;
+ });
+ var shortcuts = shortcutsList.selectAll('.shortcut-tab').data(_dataShortcuts);
+ var shortcutsEnter = shortcuts.enter().append('div').attr('class', function (d) {
+ return 'shortcut-tab shortcut-tab-' + d.tab;
+ });
+ var columnsEnter = shortcutsEnter.selectAll('.shortcut-column').data(function (d) {
+ return d.columns;
+ }).enter().append('table').attr('class', 'shortcut-column');
+ var rowsEnter = columnsEnter.selectAll('.shortcut-row').data(function (d) {
+ return d.rows;
+ }).enter().append('tr').attr('class', 'shortcut-row');
+ var sectionRows = rowsEnter.filter(function (d) {
+ return !d.shortcuts;
+ });
+ sectionRows.append('td');
+ sectionRows.append('td').attr('class', 'shortcut-section').append('h3').html(function (d) {
+ return _t.html(d.text);
+ });
+ var shortcutRows = rowsEnter.filter(function (d) {
+ return d.shortcuts;
+ });
+ var shortcutKeys = shortcutRows.append('td').attr('class', 'shortcut-keys');
+ var modifierKeys = shortcutKeys.filter(function (d) {
+ return d.modifiers;
+ });
+ modifierKeys.selectAll('kbd.modifier').data(function (d) {
+ if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
+ return ['⌘'];
+ } else if (detected.os !== 'mac' && d.text === 'shortcuts.browsing.display_options.fullscreen') {
+ return [];
+ } else {
+ return d.modifiers;
+ }
+ }).enter().each(function () {
+ var selection = select(this);
+ selection.append('kbd').attr('class', 'modifier').text(function (d) {
+ return uiCmd.display(d);
+ });
+ selection.append('span').text('+');
+ });
+ shortcutKeys.selectAll('kbd.shortcut').data(function (d) {
+ var arr = d.shortcuts;
+
+ if (detected.os === 'win' && d.text === 'shortcuts.editing.commands.redo') {
+ arr = ['Y'];
+ } else if (detected.os !== 'mac' && d.text === 'shortcuts.browsing.display_options.fullscreen') {
+ arr = ['F11'];
+ } // replace translations
+
+
+ arr = arr.map(function (s) {
+ return uiCmd.display(s.indexOf('.') !== -1 ? _t(s) : s);
+ });
+ return utilArrayUniq(arr).map(function (s) {
+ return {
+ shortcut: s,
+ separator: d.separator,
+ suffix: d.suffix
+ };
+ });
+ }).enter().each(function (d, i, nodes) {
+ var selection = select(this);
+ var click = d.shortcut.toLowerCase().match(/(.*).click/);
+
+ if (click && click[1]) {
+ // replace "left_click", "right_click" with mouse icon
+ selection.call(svgIcon('#iD-walkthrough-mouse-' + click[1], 'operation'));
+ } else if (d.shortcut.toLowerCase() === 'long-press') {
+ selection.call(svgIcon('#iD-walkthrough-longpress', 'longpress operation'));
+ } else if (d.shortcut.toLowerCase() === 'tap') {
+ selection.call(svgIcon('#iD-walkthrough-tap', 'tap operation'));
+ } else {
+ selection.append('kbd').attr('class', 'shortcut').text(function (d) {
+ return d.shortcut;
+ });
+ }
+
+ if (i < nodes.length - 1) {
+ selection.append('span').html(d.separator || "\xA0" + _t.html('shortcuts.or') + "\xA0");
+ } else if (i === nodes.length - 1 && d.suffix) {
+ selection.append('span').text(d.suffix);
+ }
+ });
+ shortcutKeys.filter(function (d) {
+ return d.gesture;
+ }).each(function () {
+ var selection = select(this);
+ selection.append('span').text('+');
+ selection.append('span').attr('class', 'gesture').html(function (d) {
+ return _t.html(d.gesture);
+ });
+ });
+ shortcutRows.append('td').attr('class', 'shortcut-desc').html(function (d) {
+ return d.text ? _t.html(d.text) : "\xA0";
+ }); // Update
+
+ wrapper.selectAll('.shortcut-tab').style('display', function (d, i) {
+ return i === _activeTab ? 'flex' : 'none';
+ });
+ }
+
+ return function (selection, show) {
+ _selection = selection;
+
+ if (show) {
+ _modalSelection = uiModal(selection);
+
+ _modalSelection.call(shortcutsModal);
+ } else {
+ context.keybinding().on([_t('shortcuts.toggle.key'), '?'], function () {
+ if (context.container().selectAll('.modal-shortcuts').size()) {
+ // already showing
+ if (_modalSelection) {
+ _modalSelection.close();
+
+ _modalSelection = null;
+ }
+ } else {
+ _modalSelection = uiModal(_selection);
+
+ _modalSelection.call(shortcutsModal);
+ }
+ });
+ }
+ };
+ }
+
+ function uiDataHeader() {
+ var _datum;
+
+ function dataHeader(selection) {
+ var header = selection.selectAll('.data-header').data(_datum ? [_datum] : [], function (d) {
+ return d.__featurehash__;
+ });
+ header.exit().remove();
+ var headerEnter = header.enter().append('div').attr('class', 'data-header');
+ var iconEnter = headerEnter.append('div').attr('class', 'data-header-icon');
+ iconEnter.append('div').attr('class', 'preset-icon-28').call(svgIcon('#iD-icon-data', 'note-fill'));
+ headerEnter.append('div').attr('class', 'data-header-label').call(_t.append('map_data.layers.custom.title'));
+ }
+
+ dataHeader.datum = function (val) {
+ if (!arguments.length) return _datum;
+ _datum = val;
+ return this;
+ };
+
+ return dataHeader;
+ }
+
+ // It is keyed on the `value` of the entry. Data should be an array of objects like:
+ // [{
+ // value: 'string value', // required
+ // display: 'label html' // optional
+ // title: 'hover text' // optional
+ // terms: ['search terms'] // optional
+ // }, ...]
+
+ var _comboHideTimerID;
+
+ function uiCombobox(context, klass) {
+ var dispatch = dispatch$8('accept', 'cancel');
+ var container = context.container();
+ var _suggestions = [];
+ var _data = [];
+ var _fetched = {};
+ var _selected = null;
+ var _canAutocomplete = true;
+ var _caseSensitive = false;
+ var _cancelFetch = false;
+ var _minItems = 2;
+ var _tDown = 0;
+
+ var _mouseEnterHandler, _mouseLeaveHandler;
+
+ var _fetcher = function _fetcher(val, cb) {
+ cb(_data.filter(function (d) {
+ var terms = d.terms || [];
+ terms.push(d.value);
+ return terms.some(function (term) {
+ return term.toString().toLowerCase().indexOf(val.toLowerCase()) !== -1;
+ });
+ }));
+ };
+
+ var combobox = function combobox(input, attachTo) {
+ if (!input || input.empty()) return;
+ input.classed('combobox-input', true).on('focus.combo-input', focus).on('blur.combo-input', blur).on('keydown.combo-input', keydown).on('keyup.combo-input', keyup).on('input.combo-input', change).on('mousedown.combo-input', mousedown).each(function () {
+ var parent = this.parentNode;
+ var sibling = this.nextSibling;
+ select(parent).selectAll('.combobox-caret').filter(function (d) {
+ return d === input.node();
+ }).data([input.node()]).enter().insert('div', function () {
+ return sibling;
+ }).attr('class', 'combobox-caret').on('mousedown.combo-caret', function (d3_event) {
+ d3_event.preventDefault(); // don't steal focus from input
+
+ input.node().focus(); // focus the input as if it was clicked
+
+ mousedown(d3_event);
+ }).on('mouseup.combo-caret', function (d3_event) {
+ d3_event.preventDefault(); // don't steal focus from input
+
+ mouseup(d3_event);
+ });
+ });
+
+ function mousedown(d3_event) {
+ if (d3_event.button !== 0) return; // left click only
+
+ if (input.classed('disabled')) return;
+ _tDown = +new Date(); // clear selection
+
+ var start = input.property('selectionStart');
+ var end = input.property('selectionEnd');
+
+ if (start !== end) {
+ var val = utilGetSetValue(input);
+ input.node().setSelectionRange(val.length, val.length);
+ return;
+ }
+
+ input.on('mouseup.combo-input', mouseup);
+ }
+
+ function mouseup(d3_event) {
+ input.on('mouseup.combo-input', null);
+ if (d3_event.button !== 0) return; // left click only
+
+ if (input.classed('disabled')) return;
+ if (input.node() !== document.activeElement) return; // exit if this input is not focused
+
+ var start = input.property('selectionStart');
+ var end = input.property('selectionEnd');
+ if (start !== end) return; // exit if user is selecting
+ // not showing or showing for a different field - try to show it.
+
+ var combo = container.selectAll('.combobox');
+
+ if (combo.empty() || combo.datum() !== input.node()) {
+ var tOrig = _tDown;
+ window.setTimeout(function () {
+ if (tOrig !== _tDown) return; // exit if user double clicked
+
+ fetchComboData('', function () {
+ show();
+ render();
+ });
+ }, 250);
+ } else {
+ hide();
+ }
+ }
+
+ function focus() {
+ fetchComboData(''); // prefetch values (may warm taginfo cache)
+ }
+
+ function blur() {
+ _comboHideTimerID = window.setTimeout(hide, 75);
+ }
+
+ function show() {
+ hide(); // remove any existing
+
+ container.insert('div', ':first-child').datum(input.node()).attr('class', 'combobox' + (klass ? ' combobox-' + klass : '')).style('position', 'absolute').style('display', 'block').style('left', '0px').on('mousedown.combo-container', function (d3_event) {
+ // prevent moving focus out of the input field
+ d3_event.preventDefault();
+ });
+ container.on('scroll.combo-scroll', render, true);
+ }
+
+ function hide() {
+ if (_comboHideTimerID) {
+ window.clearTimeout(_comboHideTimerID);
+ _comboHideTimerID = undefined;
+ }
+
+ container.selectAll('.combobox').remove();
+ container.on('scroll.combo-scroll', null);
+ }
+
+ function keydown(d3_event) {
+ var shown = !container.selectAll('.combobox').empty();
+ var tagName = input.node() ? input.node().tagName.toLowerCase() : '';
+
+ switch (d3_event.keyCode) {
+ case 8: // ⌫ Backspace
+
+ case 46:
+ // ⌦ Delete
+ d3_event.stopPropagation();
+ _selected = null;
+ render();
+ input.on('input.combo-input', function () {
+ var start = input.property('selectionStart');
+ input.node().setSelectionRange(start, start);
+ input.on('input.combo-input', change);
+ });
+ break;
+
+ case 9:
+ // ⇥ Tab
+ accept();
+ break;
+
+ case 13:
+ // ↩ Return
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ break;
+
+ case 38:
+ // ↑ Up arrow
+ if (tagName === 'textarea' && !shown) return;
+ d3_event.preventDefault();
+
+ if (tagName === 'input' && !shown) {
+ show();
+ }
+
+ nav(-1);
+ break;
+
+ case 40:
+ // ↓ Down arrow
+ if (tagName === 'textarea' && !shown) return;
+ d3_event.preventDefault();
+
+ if (tagName === 'input' && !shown) {
+ show();
+ }
+
+ nav(+1);
+ break;
+ }
+ }
+
+ function keyup(d3_event) {
+ switch (d3_event.keyCode) {
+ case 27:
+ // ⎋ Escape
+ cancel();
+ break;
+
+ case 13:
+ // ↩ Return
+ accept();
+ break;
+ }
+ } // Called whenever the input value is changed (e.g. on typing)
+
+
+ function change() {
+ fetchComboData(value(), function () {
+ _selected = null;
+ var val = input.property('value');
+
+ if (_suggestions.length) {
+ if (input.property('selectionEnd') === val.length) {
+ _selected = tryAutocomplete();
+ }
+
+ if (!_selected) {
+ _selected = val;
+ }
+ }
+
+ if (val.length) {
+ var combo = container.selectAll('.combobox');
+
+ if (combo.empty()) {
+ show();
+ }
+ } else {
+ hide();
+ }
+
+ render();
+ });
+ } // Called when the user presses up/down arrows to navigate the list
+
+
+ function nav(dir) {
+ if (_suggestions.length) {
+ // try to determine previously selected index..
+ var index = -1;
+
+ for (var i = 0; i < _suggestions.length; i++) {
+ if (_selected && _suggestions[i].value === _selected) {
+ index = i;
+ break;
+ }
+ } // pick new _selected
+
+
+ index = Math.max(Math.min(index + dir, _suggestions.length - 1), 0);
+ _selected = _suggestions[index].value;
+ input.property('value', _selected);
+ }
+
+ render();
+ ensureVisible();
+ }
+
+ function ensureVisible() {
+ var combo = container.selectAll('.combobox');
+ if (combo.empty()) return;
+ var containerRect = container.node().getBoundingClientRect();
+ var comboRect = combo.node().getBoundingClientRect();
+
+ if (comboRect.bottom > containerRect.bottom) {
+ var node = attachTo ? attachTo.node() : input.node();
+ node.scrollIntoView({
+ behavior: 'instant',
+ block: 'center'
+ });
+ render();
+ } // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move
+
+
+ var selected = combo.selectAll('.combobox-option.selected').node();
+
+ if (selected) {
+ selected.scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest'
+ });
+ }
+ }
+
+ function value() {
+ var value = input.property('value');
+ var start = input.property('selectionStart');
+ var end = input.property('selectionEnd');
+
+ if (start && end) {
+ value = value.substring(0, start);
+ }
+
+ return value;
+ }
+
+ function fetchComboData(v, cb) {
+ _cancelFetch = false;
+
+ _fetcher.call(input, v, function (results) {
+ // already chose a value, don't overwrite or autocomplete it
+ if (_cancelFetch) return;
+ _suggestions = results;
+ results.forEach(function (d) {
+ _fetched[d.value] = d;
+ });
+
+ if (cb) {
+ cb();
+ }
+ });
+ }
+
+ function tryAutocomplete() {
+ if (!_canAutocomplete) return;
+ var val = _caseSensitive ? value() : value().toLowerCase();
+ if (!val) return; // Don't autocomplete if user is typing a number - #4935
+
+ if (!isNaN(parseFloat(val)) && isFinite(val)) return;
+ var bestIndex = -1;
+
+ for (var i = 0; i < _suggestions.length; i++) {
+ var suggestion = _suggestions[i].value;
+ var compare = _caseSensitive ? suggestion : suggestion.toLowerCase(); // if search string matches suggestion exactly, pick it..
+
+ if (compare === val) {
+ bestIndex = i;
+ break; // otherwise lock in the first result that starts with the search string..
+ } else if (bestIndex === -1 && compare.indexOf(val) === 0) {
+ bestIndex = i;
+ }
+ }
+
+ if (bestIndex !== -1) {
+ var bestVal = _suggestions[bestIndex].value;
+ input.property('value', bestVal);
+ input.node().setSelectionRange(val.length, bestVal.length);
+ return bestVal;
+ }
+ }
+
+ function render() {
+ if (_suggestions.length < _minItems || document.activeElement !== input.node()) {
+ hide();
+ return;
+ }
+
+ var shown = !container.selectAll('.combobox').empty();
+ if (!shown) return;
+ var combo = container.selectAll('.combobox');
+ var options = combo.selectAll('.combobox-option').data(_suggestions, function (d) {
+ return d.value;
+ });
+ options.exit().remove(); // enter/update
+
+ options.enter().append('a').attr('class', function (d) {
+ return 'combobox-option ' + (d.klass || '');
+ }).attr('title', function (d) {
+ return d.title;
+ }).html(function (d) {
+ return d.display || d.value;
+ }).on('mouseenter', _mouseEnterHandler).on('mouseleave', _mouseLeaveHandler).merge(options).classed('selected', function (d) {
+ return d.value === _selected;
+ }).on('click.combo-option', accept).order();
+ var node = attachTo ? attachTo.node() : input.node();
+ var containerRect = container.node().getBoundingClientRect();
+ var rect = node.getBoundingClientRect();
+ combo.style('left', rect.left + 5 - containerRect.left + 'px').style('width', rect.width - 10 + 'px').style('top', rect.height + rect.top - containerRect.top + 'px');
+ } // Dispatches an 'accept' event
+ // Then hides the combobox.
+
+
+ function accept(d3_event, d) {
+ _cancelFetch = true;
+ var thiz = input.node();
+
+ if (d) {
+ // user clicked on a suggestion
+ utilGetSetValue(input, d.value); // replace field contents
+
+ utilTriggerEvent(input, 'change');
+ } // clear (and keep) selection
+
+
+ var val = utilGetSetValue(input);
+ thiz.setSelectionRange(val.length, val.length);
+ d = _fetched[val];
+ dispatch.call('accept', thiz, d, val);
+ hide();
+ } // Dispatches an 'cancel' event
+ // Then hides the combobox.
+
+
+ function cancel() {
+ _cancelFetch = true;
+ var thiz = input.node(); // clear (and remove) selection, and replace field contents
+
+ var val = utilGetSetValue(input);
+ var start = input.property('selectionStart');
+ var end = input.property('selectionEnd');
+ val = val.slice(0, start) + val.slice(end);
+ utilGetSetValue(input, val);
+ thiz.setSelectionRange(val.length, val.length);
+ dispatch.call('cancel', thiz);
+ hide();
+ }
+ };
+
+ combobox.canAutocomplete = function (val) {
+ if (!arguments.length) return _canAutocomplete;
+ _canAutocomplete = val;
+ return combobox;
+ };
+
+ combobox.caseSensitive = function (val) {
+ if (!arguments.length) return _caseSensitive;
+ _caseSensitive = val;
+ return combobox;
+ };
+
+ combobox.data = function (val) {
+ if (!arguments.length) return _data;
+ _data = val;
+ return combobox;
+ };
+
+ combobox.fetcher = function (val) {
+ if (!arguments.length) return _fetcher;
+ _fetcher = val;
+ return combobox;
+ };
+
+ combobox.minItems = function (val) {
+ if (!arguments.length) return _minItems;
+ _minItems = val;
+ return combobox;
+ };
+
+ combobox.itemsMouseEnter = function (val) {
+ if (!arguments.length) return _mouseEnterHandler;
+ _mouseEnterHandler = val;
+ return combobox;
+ };
+
+ combobox.itemsMouseLeave = function (val) {
+ if (!arguments.length) return _mouseLeaveHandler;
+ _mouseLeaveHandler = val;
+ return combobox;
+ };
+
+ return utilRebind(combobox, dispatch, 'on');
+ }
+
+ uiCombobox.off = function (input, context) {
+ input.on('focus.combo-input', null).on('blur.combo-input', null).on('keydown.combo-input', null).on('keyup.combo-input', null).on('input.combo-input', null).on('mousedown.combo-input', null).on('mouseup.combo-input', null);
+ context.container().on('scroll.combo-scroll', null);
+ };
+
+ function uiDisclosure(context, key, expandedDefault) {
+ var dispatch = dispatch$8('toggled');
+
+ var _expanded;
+
+ var _label = utilFunctor('');
+
+ var _updatePreference = true;
+
+ var _content = function _content() {};
+
+ var disclosure = function disclosure(selection) {
+ if (_expanded === undefined || _expanded === null) {
+ // loading _expanded here allows it to be reset by calling `disclosure.expanded(null)`
+ var preference = corePreferences('disclosure.' + key + '.expanded');
+ _expanded = preference === null ? !!expandedDefault : preference === 'true';
+ }
+
+ var hideToggle = selection.selectAll('.hide-toggle-' + key).data([0]); // enter
+
+ var hideToggleEnter = hideToggle.enter().append('h3').append('a').attr('role', 'button').attr('href', '#').attr('class', 'hide-toggle hide-toggle-' + key).call(svgIcon('', 'pre-text', 'hide-toggle-icon'));
+ hideToggleEnter.append('span').attr('class', 'hide-toggle-text'); // update
+
+ hideToggle = hideToggleEnter.merge(hideToggle);
+ hideToggle.on('click', toggle).attr('title', _t("icons.".concat(_expanded ? 'collapse' : 'expand'))).attr('aria-expanded', _expanded).classed('expanded', _expanded);
+ hideToggle.selectAll('.hide-toggle-text').html(_label());
+ hideToggle.selectAll('.hide-toggle-icon').attr('xlink:href', _expanded ? '#iD-icon-down' : _mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward');
+ var wrap = selection.selectAll('.disclosure-wrap').data([0]); // enter/update
+
+ wrap = wrap.enter().append('div').attr('class', 'disclosure-wrap disclosure-wrap-' + key).merge(wrap).classed('hide', !_expanded);
+
+ if (_expanded) {
+ wrap.call(_content);
+ }
+
+ function toggle(d3_event) {
+ d3_event.preventDefault();
+ _expanded = !_expanded;
+
+ if (_updatePreference) {
+ corePreferences('disclosure.' + key + '.expanded', _expanded);
+ }
+
+ hideToggle.classed('expanded', _expanded).attr('aria-expanded', _expanded).attr('title', _t("icons.".concat(_expanded ? 'collapse' : 'expand')));
+ hideToggle.selectAll('.hide-toggle-icon').attr('xlink:href', _expanded ? '#iD-icon-down' : _mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward');
+ wrap.call(uiToggle(_expanded));
+
+ if (_expanded) {
+ wrap.call(_content);
+ }
+
+ dispatch.call('toggled', this, _expanded);
+ }
+ };
+
+ disclosure.label = function (val) {
+ if (!arguments.length) return _label;
+ _label = utilFunctor(val);
+ return disclosure;
+ };
+
+ disclosure.expanded = function (val) {
+ if (!arguments.length) return _expanded;
+ _expanded = val;
+ return disclosure;
+ };
+
+ disclosure.updatePreference = function (val) {
+ if (!arguments.length) return _updatePreference;
+ _updatePreference = val;
+ return disclosure;
+ };
+
+ disclosure.content = function (val) {
+ if (!arguments.length) return _content;
+ _content = val;
+ return disclosure;
+ };
+
+ return utilRebind(disclosure, dispatch, 'on');
+ }
+
+ // Can be labeled and collapsible.
+
+ function uiSection(id, context) {
+ var _classes = utilFunctor('');
+
+ var _shouldDisplay;
+
+ var _content;
+
+ var _disclosure;
+
+ var _label;
+
+ var _expandedByDefault = utilFunctor(true);
+
+ var _disclosureContent;
+
+ var _disclosureExpanded;
+
+ var _containerSelection = select(null);
+
+ var section = {
+ id: id
+ };
+
+ section.classes = function (val) {
+ if (!arguments.length) return _classes;
+ _classes = utilFunctor(val);
+ return section;
+ };
+
+ section.label = function (val) {
+ if (!arguments.length) return _label;
+ _label = utilFunctor(val);
+ return section;
+ };
+
+ section.expandedByDefault = function (val) {
+ if (!arguments.length) return _expandedByDefault;
+ _expandedByDefault = utilFunctor(val);
+ return section;
+ };
+
+ section.shouldDisplay = function (val) {
+ if (!arguments.length) return _shouldDisplay;
+ _shouldDisplay = utilFunctor(val);
+ return section;
+ };
+
+ section.content = function (val) {
+ if (!arguments.length) return _content;
+ _content = val;
+ return section;
+ };
+
+ section.disclosureContent = function (val) {
+ if (!arguments.length) return _disclosureContent;
+ _disclosureContent = val;
+ return section;
+ };
+
+ section.disclosureExpanded = function (val) {
+ if (!arguments.length) return _disclosureExpanded;
+ _disclosureExpanded = val;
+ return section;
+ }; // may be called multiple times
+
+
+ section.render = function (selection) {
+ _containerSelection = selection.selectAll('.section-' + id).data([0]);
+
+ var sectionEnter = _containerSelection.enter().append('div').attr('class', 'section section-' + id + ' ' + (_classes && _classes() || ''));
+
+ _containerSelection = sectionEnter.merge(_containerSelection);
+
+ _containerSelection.call(renderContent);
+ };
+
+ section.reRender = function () {
+ _containerSelection.call(renderContent);
+ };
+
+ section.selection = function () {
+ return _containerSelection;
+ };
+
+ section.disclosure = function () {
+ return _disclosure;
+ }; // may be called multiple times
+
+
+ function renderContent(selection) {
+ if (_shouldDisplay) {
+ var shouldDisplay = _shouldDisplay();
+
+ selection.classed('hide', !shouldDisplay);
+
+ if (!shouldDisplay) {
+ selection.html('');
+ return;
+ }
+ }
+
+ if (_disclosureContent) {
+ if (!_disclosure) {
+ _disclosure = uiDisclosure(context, id.replace(/-/g, '_'), _expandedByDefault()).label(_label || '')
+ /*.on('toggled', function(expanded) {
+ if (expanded) { selection.node().parentNode.scrollTop += 200; }
+ })*/
+ .content(_disclosureContent);
+ }
+
+ if (_disclosureExpanded !== undefined) {
+ _disclosure.expanded(_disclosureExpanded);
+
+ _disclosureExpanded = undefined;
+ }
+
+ selection.call(_disclosure);
+ return;
+ }
+
+ if (_content) {
+ selection.call(_content);
+ }
+ }
+
+ return section;
+ }
+
+ // {
+ // key: 'string', // required
+ // value: 'string' // optional
+ // }
+ // -or-
+ // {
+ // qid: 'string' // brand wikidata (e.g. 'Q37158')
+ // }
+ //
+
+ function uiTagReference(what) {
+ var wikibase = what.qid ? services.wikidata : services.osmWikibase;
+ var tagReference = {};
+
+ var _button = select(null);
+
+ var _body = select(null);
+
+ var _loaded;
+
+ var _showing;
+
+ function load() {
+ if (!wikibase) return;
+
+ _button.classed('tag-reference-loading', true);
+
+ wikibase.getDocs(what, gotDocs);
+ }
+
+ function gotDocs(err, docs) {
+ _body.html('');
+
+ if (!docs || !docs.title) {
+ _body.append('p').attr('class', 'tag-reference-description').call(_t.append('inspector.no_documentation_key'));
+
+ done();
+ return;
+ }
+
+ if (docs.imageURL) {
+ _body.append('img').attr('class', 'tag-reference-wiki-image').attr('alt', docs.description).attr('src', docs.imageURL).on('load', function () {
+ done();
+ }).on('error', function () {
+ select(this).remove();
+ done();
+ });
+ } else {
+ done();
+ }
+
+ var tagReferenceDescription = _body.append('p').attr('class', 'tag-reference-description').append('span');
+
+ if (docs.description) {
+ tagReferenceDescription = tagReferenceDescription.attr('class', 'localized-text').attr('lang', docs.descriptionLocaleCode || 'und').text(docs.description);
+ } else {
+ tagReferenceDescription = tagReferenceDescription.call(_t.append('inspector.no_documentation_key'));
+ }
+
+ tagReferenceDescription.append('a').attr('class', 'tag-reference-edit').attr('target', '_blank').attr('title', _t('inspector.edit_reference')).attr('href', docs.editURL).call(svgIcon('#iD-icon-edit', 'inline'));
+
+ if (docs.wiki) {
+ _body.append('a').attr('class', 'tag-reference-link').attr('target', '_blank').attr('href', docs.wiki.url).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').call(_t.append(docs.wiki.text));
+ } // Add link to info about "good changeset comments" - #2923
+
+
+ if (what.key === 'comment') {
+ _body.append('a').attr('class', 'tag-reference-comment-link').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', _t('commit.about_changeset_comments_link')).append('span').call(_t.append('commit.about_changeset_comments'));
+ }
+ }
+
+ function done() {
+ _loaded = true;
+
+ _button.classed('tag-reference-loading', false);
+
+ _body.classed('expanded', true).transition().duration(200).style('max-height', '200px').style('opacity', '1');
+
+ _showing = true;
+
+ _button.selectAll('svg.icon use').each(function () {
+ var iconUse = select(this);
+
+ if (iconUse.attr('href') === '#iD-icon-info') {
+ iconUse.attr('href', '#iD-icon-info-filled');
+ }
+ });
+ }
+
+ function hide() {
+ _body.transition().duration(200).style('max-height', '0px').style('opacity', '0').on('end', function () {
+ _body.classed('expanded', false);
+ });
+
+ _showing = false;
+
+ _button.selectAll('svg.icon use').each(function () {
+ var iconUse = select(this);
+
+ if (iconUse.attr('href') === '#iD-icon-info-filled') {
+ iconUse.attr('href', '#iD-icon-info');
+ }
+ });
+ }
+
+ tagReference.button = function (selection, klass, iconName) {
+ _button = selection.selectAll('.tag-reference-button').data([0]);
+ _button = _button.enter().append('button').attr('class', 'tag-reference-button ' + (klass || '')).attr('title', _t('icons.information')).call(svgIcon('#iD-icon-' + (iconName || 'inspect'))).merge(_button);
+
+ _button.on('click', function (d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ this.blur(); // avoid keeping focus on the button - #4641
+
+ if (_showing) {
+ hide();
+ } else if (_loaded) {
+ done();
+ } else {
+ load();
+ }
+ });
+ };
+
+ tagReference.body = function (selection) {
+ var itemID = what.qid || what.key + '-' + (what.value || '');
+ _body = selection.selectAll('.tag-reference-body').data([itemID], function (d) {
+ return d;
+ });
+
+ _body.exit().remove();
+
+ _body = _body.enter().append('div').attr('class', 'tag-reference-body').style('max-height', '0').style('opacity', '0').merge(_body);
+
+ if (_showing === false) {
+ hide();
+ }
+ };
+
+ tagReference.showing = function (val) {
+ if (!arguments.length) return _showing;
+ _showing = val;
+ return tagReference;
+ };
+
+ return tagReference;
+ }
+
+ // It borrows some code from uiHelp
+
+ function uiFieldHelp(context, fieldName) {
+ var fieldHelp = {};
+
+ var _inspector = select(null);
+
+ var _wrap = select(null);
+
+ var _body = select(null);
+
+ var fieldHelpKeys = {
+ restrictions: [['about', ['about', 'from_via_to', 'maxdist', 'maxvia']], ['inspecting', ['about', 'from_shadow', 'allow_shadow', 'restrict_shadow', 'only_shadow', 'restricted', 'only']], ['modifying', ['about', 'indicators', 'allow_turn', 'restrict_turn', 'only_turn']], ['tips', ['simple', 'simple_example', 'indirect', 'indirect_example', 'indirect_noedit']]]
+ };
+ var fieldHelpHeadings = {};
+ var replacements = {
+ distField: {
+ html: _t.html('restriction.controls.distance')
+ },
+ viaField: {
+ html: _t.html('restriction.controls.via')
+ },
+ fromShadow: {
+ html: icon('#iD-turn-shadow', 'inline shadow from')
+ },
+ allowShadow: {
+ html: icon('#iD-turn-shadow', 'inline shadow allow')
+ },
+ restrictShadow: {
+ html: icon('#iD-turn-shadow', 'inline shadow restrict')
+ },
+ onlyShadow: {
+ html: icon('#iD-turn-shadow', 'inline shadow only')
+ },
+ allowTurn: {
+ html: icon('#iD-turn-yes', 'inline turn')
+ },
+ restrictTurn: {
+ html: icon('#iD-turn-no', 'inline turn')
+ },
+ onlyTurn: {
+ html: icon('#iD-turn-only', 'inline turn')
+ }
+ }; // For each section, squash all the texts into a single markdown document
+
+ var docs = fieldHelpKeys[fieldName].map(function (key) {
+ var helpkey = 'help.field.' + fieldName + '.' + key[0];
+ var text = key[1].reduce(function (all, part) {
+ var subkey = helpkey + '.' + part;
+ var depth = fieldHelpHeadings[subkey]; // is this subkey a heading?
+
+ var hhh = depth ? Array(depth + 1).join('#') + ' ' : ''; // if so, prepend with some ##'s
+
+ return all + hhh + _t.html(subkey, replacements) + '\n\n';
+ }, '');
+ return {
+ key: helpkey,
+ title: _t.html(helpkey + '.title'),
+ html: marked_1(text.trim())
+ };
+ });
+
+ function show() {
+ updatePosition();
+
+ _body.classed('hide', false).style('opacity', '0').transition().duration(200).style('opacity', '1');
+ }
+
+ function hide() {
+ _body.classed('hide', true).transition().duration(200).style('opacity', '0').on('end', function () {
+ _body.classed('hide', true);
+ });
+ }
+
+ function clickHelp(index) {
+ var d = docs[index];
+ var tkeys = fieldHelpKeys[fieldName][index][1];
+
+ _body.selectAll('.field-help-nav-item').classed('active', function (d, i) {
+ return i === index;
+ });
+
+ var content = _body.selectAll('.field-help-content').html(d.html); // class the paragraphs so we can find and style them
+
+
+ content.selectAll('p').attr('class', function (d, i) {
+ return tkeys[i];
+ }); // insert special content for certain help sections
+
+ if (d.key === 'help.field.restrictions.inspecting') {
+ content.insert('img', 'p.from_shadow').attr('class', 'field-help-image cf').attr('src', context.imagePath('tr_inspect.gif'));
+ } else if (d.key === 'help.field.restrictions.modifying') {
+ content.insert('img', 'p.allow_turn').attr('class', 'field-help-image cf').attr('src', context.imagePath('tr_modify.gif'));
+ }
+ }
+
+ fieldHelp.button = function (selection) {
+ if (_body.empty()) return;
+ var button = selection.selectAll('.field-help-button').data([0]); // enter/update
+
+ button.enter().append('button').attr('class', 'field-help-button').call(svgIcon('#iD-icon-help')).merge(button).on('click', function (d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+
+ if (_body.classed('hide')) {
+ show();
+ } else {
+ hide();
+ }
+ });
+ };
+
+ function updatePosition() {
+ var wrap = _wrap.node();
+
+ var inspector = _inspector.node();
+
+ var wRect = wrap.getBoundingClientRect();
+ var iRect = inspector.getBoundingClientRect();
+
+ _body.style('top', wRect.top + inspector.scrollTop - iRect.top + 'px');
+ }
+
+ fieldHelp.body = function (selection) {
+ // This control expects the field to have a form-field-input-wrap div
+ _wrap = selection.selectAll('.form-field-input-wrap');
+ if (_wrap.empty()) return; // absolute position relative to the inspector, so it "floats" above the fields
+
+ _inspector = context.container().select('.sidebar .entity-editor-pane .inspector-body');
+ if (_inspector.empty()) return;
+ _body = _inspector.selectAll('.field-help-body').data([0]);
+
+ var enter = _body.enter().append('div').attr('class', 'field-help-body hide'); // initially hidden
+
+
+ var titleEnter = enter.append('div').attr('class', 'field-help-title cf');
+ titleEnter.append('h2').attr('class', _mainLocalizer.textDirection() === 'rtl' ? 'fr' : 'fl').call(_t.append('help.field.' + fieldName + '.title'));
+ titleEnter.append('button').attr('class', 'fr close').attr('title', _t('icons.close')).on('click', function (d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ hide();
+ }).call(svgIcon('#iD-icon-close'));
+ var navEnter = enter.append('div').attr('class', 'field-help-nav cf');
+ var titles = docs.map(function (d) {
+ return d.title;
+ });
+ navEnter.selectAll('.field-help-nav-item').data(titles).enter().append('div').attr('class', 'field-help-nav-item').html(function (d) {
+ return d;
+ }).on('click', function (d3_event, d) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ clickHelp(titles.indexOf(d));
+ });
+ enter.append('div').attr('class', 'field-help-content');
+ _body = _body.merge(enter);
+ clickHelp(0);
+ };
+
+ return fieldHelp;
+ }
+
+ function uiFieldCheck(field, context) {
+ var dispatch = dispatch$8('change');
+ var options = field.options;
+ var values = [];
+ var texts = [];
+
+ var _tags;
+
+ var input = select(null);
+ var text = select(null);
+ var label = select(null);
+ var reverser = select(null);
+
+ var _impliedYes;
+
+ var _entityIDs = [];
+
+ var _value;
+
+ if (options) {
+ for (var i in options) {
+ var v = options[i];
+ values.push(v === 'undefined' ? undefined : v);
+ texts.push(field.t.html('options.' + v, {
+ 'default': v
+ }));
+ }
+ } else {
+ values = [undefined, 'yes'];
+ texts = [_t.html('inspector.unknown'), _t.html('inspector.check.yes')];
+
+ if (field.type !== 'defaultCheck') {
+ values.push('no');
+ texts.push(_t.html('inspector.check.no'));
+ }
+ } // Checks tags to see whether an undefined value is "Assumed to be Yes"
+
+
+ function checkImpliedYes() {
+ _impliedYes = field.id === 'oneway_yes'; // hack: pretend `oneway` field is a `oneway_yes` field
+ // where implied oneway tag exists (e.g. `junction=roundabout`) #2220, #1841
+
+ if (field.id === 'oneway') {
+ var entity = context.entity(_entityIDs[0]);
+
+ for (var key in entity.tags) {
+ if (key in osmOneWayTags && entity.tags[key] in osmOneWayTags[key]) {
+ _impliedYes = true;
+ texts[0] = _t.html('_tagging.presets.fields.oneway_yes.options.undefined');
+ break;
+ }
+ }
+ }
+ }
+
+ function reverserHidden() {
+ if (!context.container().select('div.inspector-hover').empty()) return true;
+ return !(_value === 'yes' || _impliedYes && !_value);
+ }
+
+ function reverserSetText(selection) {
+ var entity = _entityIDs.length && context.hasEntity(_entityIDs[0]);
+ if (reverserHidden() || !entity) return selection;
+ var first = entity.first();
+ var last = entity.isClosed() ? entity.nodes[entity.nodes.length - 2] : entity.last();
+ var pseudoDirection = first < last;
+ var icon = pseudoDirection ? '#iD-icon-forward' : '#iD-icon-backward';
+ selection.selectAll('.reverser-span').html('').call(_t.append('inspector.check.reverser')).call(svgIcon(icon, 'inline'));
+ return selection;
+ }
+
+ var check = function check(selection) {
+ checkImpliedYes();
+ label = selection.selectAll('.form-field-input-wrap').data([0]);
+ var enter = label.enter().append('label').attr('class', 'form-field-input-wrap form-field-input-check');
+ enter.append('input').property('indeterminate', field.type !== 'defaultCheck').attr('type', 'checkbox').attr('id', field.domId);
+ enter.append('span').html(texts[0]).attr('class', 'value');
+
+ if (field.type === 'onewayCheck') {
+ enter.append('button').attr('class', 'reverser' + (reverserHidden() ? ' hide' : '')).append('span').attr('class', 'reverser-span');
+ }
+
+ label = label.merge(enter);
+ input = label.selectAll('input');
+ text = label.selectAll('span.value');
+ input.on('click', function (d3_event) {
+ d3_event.stopPropagation();
+ var t = {};
+
+ if (Array.isArray(_tags[field.key])) {
+ if (values.indexOf('yes') !== -1) {
+ t[field.key] = 'yes';
+ } else {
+ t[field.key] = values[0];
+ }
+ } else {
+ t[field.key] = values[(values.indexOf(_value) + 1) % values.length];
+ } // Don't cycle through `alternating` or `reversible` states - #4970
+ // (They are supported as translated strings, but should not toggle with clicks)
+
+
+ if (t[field.key] === 'reversible' || t[field.key] === 'alternating') {
+ t[field.key] = values[0];
+ }
+
+ dispatch.call('change', this, t);
+ });
+
+ if (field.type === 'onewayCheck') {
+ reverser = label.selectAll('.reverser');
+ reverser.call(reverserSetText).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ context.perform(function (graph) {
+ for (var i in _entityIDs) {
+ graph = actionReverse(_entityIDs[i])(graph);
+ }
+
+ return graph;
+ }, _t('operations.reverse.annotation.line', {
+ n: 1
+ })); // must manually revalidate since no 'change' event was called
+
+ context.validator().validate();
+ select(this).call(reverserSetText);
+ });
+ }
+ };
+
+ check.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return check;
+ };
+
+ check.tags = function (tags) {
+ _tags = tags;
+
+ function isChecked(val) {
+ return val !== 'no' && val !== '' && val !== undefined && val !== null;
+ }
+
+ function textFor(val) {
+ if (val === '') val = undefined;
+ var index = values.indexOf(val);
+ return index !== -1 ? texts[index] : '"' + val + '"';
+ }
+
+ checkImpliedYes();
+ var isMixed = Array.isArray(tags[field.key]);
+ _value = !isMixed && tags[field.key] && tags[field.key].toLowerCase();
+
+ if (field.type === 'onewayCheck' && (_value === '1' || _value === '-1')) {
+ _value = 'yes';
+ }
+
+ input.property('indeterminate', isMixed || field.type !== 'defaultCheck' && !_value).property('checked', isChecked(_value));
+ text.html(isMixed ? _t.html('inspector.multiple_values') : textFor(_value)).classed('mixed', isMixed);
+ label.classed('set', !!_value);
+
+ if (field.type === 'onewayCheck') {
+ reverser.classed('hide', reverserHidden()).call(reverserSetText);
+ }
+ };
+
+ check.focus = function () {
+ input.node().focus();
+ };
+
+ return utilRebind(check, dispatch, 'on');
+ }
+
+ function uiFieldCombo(field, context) {
+ var dispatch = dispatch$8('change');
+
+ var _isMulti = field.type === 'multiCombo' || field.type === 'manyCombo';
+
+ var _isNetwork = field.type === 'networkCombo';
+
+ var _isSemi = field.type === 'semiCombo';
+
+ var _optarray = field.options;
+
+ var _showTagInfoSuggestions = field.type !== 'manyCombo' && field.autoSuggestions !== false;
+
+ var _allowCustomValues = field.type !== 'manyCombo' && field.customValues !== false;
+
+ var _snake_case = field.snake_case || field.snake_case === undefined;
+
+ var _combobox = uiCombobox(context, 'combo-' + field.safeid).caseSensitive(field.caseSensitive).minItems(_isMulti || _isSemi ? 1 : 2);
+
+ var _container = select(null);
+
+ var _inputWrap = select(null);
+
+ var _input = select(null);
+
+ var _comboData = [];
+ var _multiData = [];
+ var _entityIDs = [];
+
+ var _tags;
+
+ var _countryCode;
+
+ var _staticPlaceholder; // initialize deprecated tags array
+
+
+ var _dataDeprecated = [];
+ _mainFileFetcher.get('deprecated').then(function (d) {
+ _dataDeprecated = d;
+ })["catch"](function () {
+ /* ignore */
+ }); // ensure multiCombo field.key ends with a ':'
+
+ if (_isMulti && field.key && /[^:]$/.test(field.key)) {
+ field.key += ':';
+ }
+
+ function snake(s) {
+ return s.replace(/\s+/g, '_').toLowerCase();
+ }
+
+ function clean(s) {
+ return s.split(';').map(function (s) {
+ return s.trim();
+ }).join(';');
+ } // returns the tag value for a display value
+ // (for multiCombo, dval should be the key suffix, not the entire key)
+
+
+ function tagValue(dval) {
+ dval = clean(dval || '');
+
+ var found = _comboData.find(function (o) {
+ return o.key && clean(o.value) === dval;
+ });
+
+ if (found) return found.key;
+
+ if (field.type === 'typeCombo' && !dval) {
+ return 'yes';
+ }
+
+ return (_snake_case ? snake(dval) : dval) || undefined;
+ } // returns the display value for a tag value
+ // (for multiCombo, tval should be the key suffix, not the entire key)
+
+
+ function displayValue(tval) {
+ tval = tval || '';
+
+ if (field.hasTextForStringId('options.' + tval)) {
+ return field.t('options.' + tval, {
+ "default": tval
+ });
+ }
+
+ if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
+ return '';
+ }
+
+ return tval;
+ } // Compute the difference between arrays of objects by `value` property
+ //
+ // objectDifference([{value:1}, {value:2}, {value:3}], [{value:2}])
+ // > [{value:1}, {value:3}]
+ //
+
+
+ function objectDifference(a, b) {
+ return a.filter(function (d1) {
+ return !b.some(function (d2) {
+ return !d2.isMixed && d1.value === d2.value;
+ });
+ });
+ }
+
+ function initCombo(selection, attachTo) {
+ if (!_allowCustomValues) {
+ selection.attr('readonly', 'readonly');
+ }
+
+ if (_showTagInfoSuggestions && services.taginfo) {
+ selection.call(_combobox.fetcher(setTaginfoValues), attachTo);
+ setTaginfoValues('', setPlaceholder);
+ } else {
+ selection.call(_combobox, attachTo);
+ setStaticValues(setPlaceholder);
+ }
+ }
+
+ function setStaticValues(callback) {
+ if (!_optarray) return;
+ _comboData = _optarray.map(function (v) {
+ return {
+ key: v,
+ value: field.t('options.' + v, {
+ "default": v
+ }),
+ title: v,
+ display: field.t.html('options.' + v, {
+ "default": v
+ }),
+ klass: field.hasTextForStringId('options.' + v) ? '' : 'raw-option'
+ };
+ });
+
+ _combobox.data(objectDifference(_comboData, _multiData));
+
+ if (callback) callback(_comboData);
+ }
+
+ function setTaginfoValues(q, callback) {
+ var fn = _isMulti ? 'multikeys' : 'values';
+ var query = (_isMulti ? field.key : '') + q;
+ var hasCountryPrefix = _isNetwork && _countryCode && _countryCode.indexOf(q.toLowerCase()) === 0;
+
+ if (hasCountryPrefix) {
+ query = _countryCode + ':';
+ }
+
+ var params = {
+ debounce: q !== '',
+ key: field.key,
+ query: query
+ };
+
+ if (_entityIDs.length) {
+ params.geometry = context.graph().geometry(_entityIDs[0]);
+ }
+
+ services.taginfo[fn](params, function (err, data) {
+ if (err) return;
+ data = data.filter(function (d) {
+ if (field.type === 'typeCombo' && d.value === 'yes') {
+ // don't show the fallback value
+ return false;
+ } // don't show values with very low usage
+
+
+ return !d.count || d.count > 10;
+ });
+ var deprecatedValues = osmEntity.deprecatedTagValuesByKey(_dataDeprecated)[field.key];
+
+ if (deprecatedValues) {
+ // don't suggest deprecated tag values
+ data = data.filter(function (d) {
+ return deprecatedValues.indexOf(d.value) === -1;
+ });
+ }
+
+ if (hasCountryPrefix) {
+ data = data.filter(function (d) {
+ return d.value.toLowerCase().indexOf(_countryCode + ':') === 0;
+ });
+ } // hide the caret if there are no suggestions
+
+
+ _container.classed('empty-combobox', data.length === 0);
+
+ _comboData = data.map(function (d) {
+ var k = d.value;
+ if (_isMulti) k = k.replace(field.key, '');
+ var label = field.t('options.' + k, {
+ "default": k
+ });
+ return {
+ key: k,
+ value: label,
+ display: field.t.html('options.' + k, {
+ "default": k
+ }),
+ title: d.title || label,
+ klass: field.hasTextForStringId('options.' + k) ? '' : 'raw-option'
+ };
+ });
+ _comboData = objectDifference(_comboData, _multiData);
+ if (callback) callback(_comboData);
+ });
+ }
+
+ function setPlaceholder(values) {
+ if (_isMulti || _isSemi) {
+ _staticPlaceholder = field.placeholder() || _t('inspector.add');
+ } else {
+ var vals = values.map(function (d) {
+ return d.value;
+ }).filter(function (s) {
+ return s.length < 20;
+ });
+ var placeholders = vals.length > 1 ? vals : values.map(function (d) {
+ return d.key;
+ });
+ _staticPlaceholder = field.placeholder() || placeholders.slice(0, 3).join(', ');
+ }
+
+ if (!/(…|\.\.\.)$/.test(_staticPlaceholder)) {
+ _staticPlaceholder += '…';
+ }
+
+ var ph;
+
+ if (!_isMulti && !_isSemi && _tags && Array.isArray(_tags[field.key])) {
+ ph = _t('inspector.multiple_values');
+ } else {
+ ph = _staticPlaceholder;
+ }
+
+ _container.selectAll('input').attr('placeholder', ph);
+ }
+
+ function change() {
+ var t = {};
+ var val;
+
+ if (_isMulti || _isSemi) {
+ val = tagValue(utilGetSetValue(_input).replace(/,/g, ';')) || '';
+
+ _container.classed('active', false);
+
+ utilGetSetValue(_input, '');
+ var vals = val.split(';').filter(Boolean);
+ if (!vals.length) return;
+
+ if (_isMulti) {
+ utilArrayUniq(vals).forEach(function (v) {
+ var key = (field.key || '') + v;
+
+ if (_tags) {
+ // don't set a multicombo value to 'yes' if it already has a non-'no' value
+ // e.g. `language:de=main`
+ var old = _tags[key];
+ if (typeof old === 'string' && old.toLowerCase() !== 'no') return;
+ }
+
+ key = context.cleanTagKey(key);
+ field.keys.push(key);
+ t[key] = 'yes';
+ });
+ } else if (_isSemi) {
+ var arr = _multiData.map(function (d) {
+ return d.key;
+ });
+
+ arr = arr.concat(vals);
+ t[field.key] = context.cleanTagValue(utilArrayUniq(arr).filter(Boolean).join(';'));
+ }
+
+ window.setTimeout(function () {
+ _input.node().focus();
+ }, 10);
+ } else {
+ var rawValue = utilGetSetValue(_input); // don't override multiple values with blank string
+
+ if (!rawValue && Array.isArray(_tags[field.key])) return;
+ val = context.cleanTagValue(tagValue(rawValue));
+ t[field.key] = val || undefined;
+ }
+
+ dispatch.call('change', this, t);
+ }
+
+ function removeMultikey(d3_event, d) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ var t = {};
+
+ if (_isMulti) {
+ t[d.key] = undefined;
+ } else if (_isSemi) {
+ var arr = _multiData.map(function (md) {
+ return md.key === d.key ? null : md.key;
+ }).filter(Boolean);
+
+ arr = utilArrayUniq(arr);
+ t[field.key] = arr.length ? arr.join(';') : undefined;
+ }
+
+ dispatch.call('change', this, t);
+ }
+
+ function combo(selection) {
+ _container = selection.selectAll('.form-field-input-wrap').data([0]);
+ var type = _isMulti || _isSemi ? 'multicombo' : 'combo';
+ _container = _container.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + type).merge(_container);
+
+ if (_isMulti || _isSemi) {
+ _container = _container.selectAll('.chiplist').data([0]);
+ var listClass = 'chiplist'; // Use a separate line for each value in the Destinations and Via fields
+ // to mimic highway exit signs
+
+ if (field.key === 'destination' || field.key === 'via') {
+ listClass += ' full-line-chips';
+ }
+
+ _container = _container.enter().append('ul').attr('class', listClass).on('click', function () {
+ window.setTimeout(function () {
+ _input.node().focus();
+ }, 10);
+ }).merge(_container);
+ _inputWrap = _container.selectAll('.input-wrap').data([0]);
+ _inputWrap = _inputWrap.enter().append('li').attr('class', 'input-wrap').merge(_inputWrap);
+ _input = _inputWrap.selectAll('input').data([0]);
+ } else {
+ _input = _container.selectAll('input').data([0]);
+ }
+
+ _input = _input.enter().append('input').attr('type', 'text').attr('id', field.domId).call(utilNoAuto).call(initCombo, selection).merge(_input);
+
+ if (_isNetwork) {
+ var extent = combinedEntityExtent();
+ var countryCode = extent && iso1A2Code(extent.center());
+ _countryCode = countryCode && countryCode.toLowerCase();
+ }
+
+ _input.on('change', change).on('blur', change);
+
+ _input.on('keydown.field', function (d3_event) {
+ switch (d3_event.keyCode) {
+ case 13:
+ // ↩ Return
+ _input.node().blur(); // blurring also enters the value
+
+
+ d3_event.stopPropagation();
+ break;
+ }
+ });
+
+ if (_isMulti || _isSemi) {
+ _combobox.on('accept', function () {
+ _input.node().blur();
+
+ _input.node().focus();
+ });
+
+ _input.on('focus', function () {
+ _container.classed('active', true);
+ });
+ }
+ }
+
+ combo.tags = function (tags) {
+ _tags = tags;
+
+ if (_isMulti || _isSemi) {
+ _multiData = [];
+ var maxLength;
+
+ if (_isMulti) {
+ // Build _multiData array containing keys already set..
+ for (var k in tags) {
+ if (field.key && k.indexOf(field.key) !== 0) continue;
+ if (!field.key && field.keys.indexOf(k) === -1) continue;
+ var v = tags[k];
+ if (!v || typeof v === 'string' && v.toLowerCase() === 'no') continue;
+ var suffix = field.key ? k.substr(field.key.length) : k;
+
+ _multiData.push({
+ key: k,
+ value: displayValue(suffix),
+ isMixed: Array.isArray(v)
+ });
+ }
+
+ if (field.key) {
+ // Set keys for form-field modified (needed for undo and reset buttons)..
+ field.keys = _multiData.map(function (d) {
+ return d.key;
+ }); // limit the input length so it fits after prepending the key prefix
+
+ maxLength = context.maxCharsForTagKey() - utilUnicodeCharsCount(field.key);
+ } else {
+ maxLength = context.maxCharsForTagKey();
+ }
+ } else if (_isSemi) {
+ var allValues = [];
+ var commonValues;
+
+ if (Array.isArray(tags[field.key])) {
+ tags[field.key].forEach(function (tagVal) {
+ var thisVals = utilArrayUniq((tagVal || '').split(';')).filter(Boolean);
+ allValues = allValues.concat(thisVals);
+
+ if (!commonValues) {
+ commonValues = thisVals;
+ } else {
+ commonValues = commonValues.filter(function (value) {
+ return thisVals.includes(value);
+ });
+ }
+ });
+ allValues = utilArrayUniq(allValues).filter(Boolean);
+ } else {
+ allValues = utilArrayUniq((tags[field.key] || '').split(';')).filter(Boolean);
+ commonValues = allValues;
+ }
+
+ _multiData = allValues.map(function (v) {
+ return {
+ key: v,
+ value: displayValue(v),
+ isMixed: !commonValues.includes(v)
+ };
+ });
+ var currLength = utilUnicodeCharsCount(commonValues.join(';')); // limit the input length to the remaining available characters
+
+ maxLength = context.maxCharsForTagValue() - currLength;
+
+ if (currLength > 0) {
+ // account for the separator if a new value will be appended to existing
+ maxLength -= 1;
+ }
+ } // a negative maxlength doesn't make sense
+
+
+ maxLength = Math.max(0, maxLength);
+ var allowDragAndDrop = _isSemi // only semiCombo values are ordered
+ && !Array.isArray(tags[field.key]); // Exclude existing multikeys from combo options..
+
+ var available = objectDifference(_comboData, _multiData);
+
+ _combobox.data(available); // Hide 'Add' button if this field uses fixed set of
+ // options and they're all currently used,
+ // or if the field is already at its character limit
+
+
+ var hideAdd = !_allowCustomValues && !available.length || maxLength <= 0;
+
+ _container.selectAll('.chiplist .input-wrap').style('display', hideAdd ? 'none' : null); // Render chips
+
+
+ var chips = _container.selectAll('.chip').data(_multiData);
+
+ chips.exit().remove();
+ var enter = chips.enter().insert('li', '.input-wrap').attr('class', 'chip');
+ enter.append('span');
+ enter.append('a');
+ chips = chips.merge(enter).order().classed('raw-value', function (d) {
+ var k = d.key;
+ if (_isMulti) k = k.replace(field.key, '');
+ return !field.hasTextForStringId('options.' + k);
+ }).classed('draggable', allowDragAndDrop).classed('mixed', function (d) {
+ return d.isMixed;
+ }).attr('title', function (d) {
+ return d.isMixed ? _t('inspector.unshared_value_tooltip') : null;
+ });
+
+ if (allowDragAndDrop) {
+ registerDragAndDrop(chips);
+ }
+
+ chips.select('span').text(function (d) {
+ return d.value;
+ });
+ chips.select('a').attr('href', '#').on('click', removeMultikey).attr('class', 'remove').text('×');
+ } else {
+ var isMixed = Array.isArray(tags[field.key]);
+ var mixedValues = isMixed && tags[field.key].map(function (val) {
+ return displayValue(val);
+ }).filter(Boolean);
+ var showsValue = !isMixed && tags[field.key] && !(field.type === 'typeCombo' && tags[field.key] === 'yes');
+ var isRawValue = showsValue && !field.hasTextForStringId('options.' + tags[field.key]);
+ var isKnownValue = showsValue && !isRawValue;
+ var isReadOnly = !_allowCustomValues || isKnownValue;
+ utilGetSetValue(_input, !isMixed ? displayValue(tags[field.key]) : '').classed('raw-value', isRawValue).classed('known-value', isKnownValue).attr('readonly', isReadOnly ? 'readonly' : undefined).attr('title', isMixed ? mixedValues.join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : _staticPlaceholder || '').classed('mixed', isMixed).on('keydown.deleteCapture', function (d3_event) {
+ if (isReadOnly && isKnownValue && (d3_event.keyCode === utilKeybinding.keyCodes['⌫'] || d3_event.keyCode === utilKeybinding.keyCodes['⌦'])) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
+ var t = {};
+ t[field.key] = undefined;
+ dispatch.call('change', this, t);
+ }
+ });
+ }
+ };
+
+ function registerDragAndDrop(selection) {
+ // allow drag and drop re-ordering of chips
+ var dragOrigin, targetIndex;
+ selection.call(d3_drag().on('start', function (d3_event) {
+ dragOrigin = {
+ x: d3_event.x,
+ y: d3_event.y
+ };
+ targetIndex = null;
+ }).on('drag', function (d3_event) {
+ var x = d3_event.x - dragOrigin.x,
+ y = d3_event.y - dragOrigin.y;
+ if (!select(this).classed('dragging') && // don't display drag until dragging beyond a distance threshold
+ Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;
+ var index = selection.nodes().indexOf(this);
+ select(this).classed('dragging', true);
+ targetIndex = null;
+ var targetIndexOffsetTop = null;
+ var draggedTagWidth = select(this).node().offsetWidth;
+
+ if (field.key === 'destination' || field.key === 'via') {
+ // meaning tags are full width
+ _container.selectAll('.chip').style('transform', function (d2, index2) {
+ var node = select(this).node();
+
+ if (index === index2) {
+ return 'translate(' + x + 'px, ' + y + 'px)'; // move the dragged tag up the order
+ } else if (index2 > index && d3_event.y > node.offsetTop) {
+ if (targetIndex === null || index2 > targetIndex) {
+ targetIndex = index2;
+ }
+
+ return 'translateY(-100%)'; // move the dragged tag down the order
+ } else if (index2 < index && d3_event.y < node.offsetTop + node.offsetHeight) {
+ if (targetIndex === null || index2 < targetIndex) {
+ targetIndex = index2;
+ }
+
+ return 'translateY(100%)';
+ }
+
+ return null;
+ });
+ } else {
+ _container.selectAll('.chip').each(function (d2, index2) {
+ var node = select(this).node(); // check the cursor is in the bounding box
+
+ if (index !== index2 && d3_event.x < node.offsetLeft + node.offsetWidth + 5 && d3_event.x > node.offsetLeft && d3_event.y < node.offsetTop + node.offsetHeight && d3_event.y > node.offsetTop) {
+ targetIndex = index2;
+ targetIndexOffsetTop = node.offsetTop;
+ }
+ }).style('transform', function (d2, index2) {
+ var node = select(this).node();
+
+ if (index === index2) {
+ return 'translate(' + x + 'px, ' + y + 'px)';
+ } // only translate tags in the same row
+
+
+ if (node.offsetTop === targetIndexOffsetTop) {
+ if (index2 < index && index2 >= targetIndex) {
+ return 'translateX(' + draggedTagWidth + 'px)';
+ } else if (index2 > index && index2 <= targetIndex) {
+ return 'translateX(-' + draggedTagWidth + 'px)';
+ }
+ }
+
+ return null;
+ });
+ }
+ }).on('end', function () {
+ if (!select(this).classed('dragging')) {
+ return;
+ }
+
+ var index = selection.nodes().indexOf(this);
+ select(this).classed('dragging', false);
+
+ _container.selectAll('.chip').style('transform', null);
+
+ if (typeof targetIndex === 'number') {
+ var element = _multiData[index];
+
+ _multiData.splice(index, 1);
+
+ _multiData.splice(targetIndex, 0, element);
+
+ var t = {};
+
+ if (_multiData.length) {
+ t[field.key] = _multiData.map(function (element) {
+ return element.key;
+ }).join(';');
+ } else {
+ t[field.key] = undefined;
+ }
+
+ dispatch.call('change', this, t);
+ }
+
+ dragOrigin = undefined;
+ targetIndex = undefined;
+ }));
+ }
+
+ combo.focus = function () {
+ _input.node().focus();
+ };
+
+ combo.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return combo;
+ };
+
+ function combinedEntityExtent() {
+ return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
+ }
+
+ return utilRebind(combo, dispatch, 'on');
+ }
+
+ // based on https://github.com/bestiejs/punycode.js/blob/master/punycode.js
+ var global$2 = global$1m;
+ var uncurryThis$1 = functionUncurryThis;
+
+ var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
+ var base = 36;
+ var tMin = 1;
+ var tMax = 26;
+ var skew = 38;
+ var damp = 700;
+ var initialBias = 72;
+ var initialN = 128; // 0x80
+ var delimiter = '-'; // '\x2D'
+ var regexNonASCII = /[^\0-\u007E]/; // non-ASCII chars
+ var regexSeparators = /[.\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
+ var OVERFLOW_ERROR = 'Overflow: input needs wider integers to process';
+ var baseMinusTMin = base - tMin;
+
+ var RangeError$1 = global$2.RangeError;
+ var exec$1 = uncurryThis$1(regexSeparators.exec);
+ var floor$1 = Math.floor;
+ var fromCharCode = String.fromCharCode;
+ var charCodeAt = uncurryThis$1(''.charCodeAt);
+ var join$1 = uncurryThis$1([].join);
+ var push$1 = uncurryThis$1([].push);
+ var replace$1 = uncurryThis$1(''.replace);
+ var split$1 = uncurryThis$1(''.split);
+ var toLowerCase$1 = uncurryThis$1(''.toLowerCase);
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ */
+ var ucs2decode = function (string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ while (counter < length) {
+ var value = charCodeAt(string, counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // It's a high surrogate, and there is a next character.
+ var extra = charCodeAt(string, counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
+ push$1(output, ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // It's an unmatched surrogate; only append this code unit, in case the
+ // next code unit is the high surrogate of a surrogate pair.
+ push$1(output, value);
+ counter--;
+ }
+ } else {
+ push$1(output, value);
+ }
+ }
+ return output;
+ };
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ */
+ var digitToBasic = function (digit) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26);
+ };
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * https://tools.ietf.org/html/rfc3492#section-3.4
+ */
+ var adapt = function (delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor$1(delta / damp) : delta >> 1;
+ delta += floor$1(delta / numPoints);
+ for (; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor$1(delta / baseMinusTMin);
+ }
+ return floor$1(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ };
+
+ /**
+ * Converts a string of Unicode symbols (e.g. a domain name label) to a
+ * Punycode string of ASCII-only symbols.
+ */
+ // eslint-disable-next-line max-statements -- TODO
+ var encode = function (input) {
+ var output = [];
+
+ // Convert the input in UCS-2 to an array of Unicode code points.
+ input = ucs2decode(input);
+
+ // Cache the length.
+ var inputLength = input.length;
+
+ // Initialize the state.
+ var n = initialN;
+ var delta = 0;
+ var bias = initialBias;
+ var i, currentValue;
+
+ // Handle the basic code points.
+ for (i = 0; i < input.length; i++) {
+ currentValue = input[i];
+ if (currentValue < 0x80) {
+ push$1(output, fromCharCode(currentValue));
+ }
+ }
+
+ var basicLength = output.length; // number of basic code points.
+ var handledCPCount = basicLength; // number of code points that have been handled;
+
+ // Finish the basic string with a delimiter unless it's empty.
+ if (basicLength) {
+ push$1(output, delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+ // All non-basic code points < n have been handled already. Find the next larger one:
+ var m = maxInt;
+ for (i = 0; i < input.length; i++) {
+ currentValue = input[i];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, but guard against overflow.
+ var handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor$1((maxInt - delta) / handledCPCountPlusOne)) {
+ throw RangeError$1(OVERFLOW_ERROR);
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (i = 0; i < input.length; i++) {
+ currentValue = input[i];
+ if (currentValue < n && ++delta > maxInt) {
+ throw RangeError$1(OVERFLOW_ERROR);
+ }
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer.
+ var q = delta;
+ for (var k = base; /* no condition */; k += base) {
+ var t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) break;
+ var qMinusT = q - t;
+ var baseMinusT = base - t;
+ push$1(output, fromCharCode(digitToBasic(t + qMinusT % baseMinusT)));
+ q = floor$1(qMinusT / baseMinusT);
+ }
+
+ push$1(output, fromCharCode(digitToBasic(q)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+ }
+ return join$1(output, '');
+ };
+
+ var stringPunycodeToAscii = function (input) {
+ var encoded = [];
+ var labels = split$1(replace$1(toLowerCase$1(input), regexSeparators, '\u002E'), '.');
+ var i, label;
+ for (i = 0; i < labels.length; i++) {
+ label = labels[i];
+ push$1(encoded, exec$1(regexNonASCII, label) ? 'xn--' + encode(label) : label);
+ }
+ return join$1(encoded, '.');
+ };
+
+ // TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env`
+
+ var $ = _export;
+ var DESCRIPTORS = descriptors;
+ var USE_NATIVE_URL = nativeUrl;
+ var global$1 = global$1m;
+ var bind$2 = functionBindContext;
+ var call = functionCall;
+ var uncurryThis = functionUncurryThis;
+ var defineProperties = objectDefineProperties;
+ var redefine = redefine$h.exports;
+ var anInstance = anInstance$7;
+ var hasOwn = hasOwnProperty_1;
+ var assign$1 = objectAssign;
+ var arrayFrom = arrayFrom$1;
+ var arraySlice = arraySlice$c;
+ var codeAt = stringMultibyte.codeAt;
+ var toASCII = stringPunycodeToAscii;
+ var $toString = toString$k;
+ var setToStringTag = setToStringTag$a;
+ var URLSearchParamsModule = web_urlSearchParams;
+ var InternalStateModule = internalState;
+
+ var setInternalState = InternalStateModule.set;
+ var getInternalURLState = InternalStateModule.getterFor('URL');
+ var URLSearchParams$1 = URLSearchParamsModule.URLSearchParams;
+ var getInternalSearchParamsState = URLSearchParamsModule.getState;
+
+ var NativeURL = global$1.URL;
+ var TypeError$1 = global$1.TypeError;
+ var parseInt$1 = global$1.parseInt;
+ var floor = Math.floor;
+ var pow = Math.pow;
+ var charAt = uncurryThis(''.charAt);
+ var exec = uncurryThis(/./.exec);
+ var join = uncurryThis([].join);
+ var numberToString = uncurryThis(1.0.toString);
+ var pop = uncurryThis([].pop);
+ var push = uncurryThis([].push);
+ var replace = uncurryThis(''.replace);
+ var shift = uncurryThis([].shift);
+ var split = uncurryThis(''.split);
+ var stringSlice = uncurryThis(''.slice);
+ var toLowerCase = uncurryThis(''.toLowerCase);
+ var unshift = uncurryThis([].unshift);
+
+ var INVALID_AUTHORITY = 'Invalid authority';
+ var INVALID_SCHEME = 'Invalid scheme';
+ var INVALID_HOST = 'Invalid host';
+ var INVALID_PORT = 'Invalid port';
+
+ var ALPHA = /[a-z]/i;
+ // eslint-disable-next-line regexp/no-obscure-range -- safe
+ var ALPHANUMERIC = /[\d+-.a-z]/i;
+ var DIGIT = /\d/;
+ var HEX_START = /^0x/i;
+ var OCT = /^[0-7]+$/;
+ var DEC = /^\d+$/;
+ var HEX = /^[\da-f]+$/i;
+ /* eslint-disable regexp/no-control-character -- safe */
+ var FORBIDDEN_HOST_CODE_POINT = /[\0\t\n\r #%/:<>?@[\\\]^|]/;
+ var FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT = /[\0\t\n\r #/:<>?@[\\\]^|]/;
+ var LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g;
+ var TAB_AND_NEW_LINE = /[\t\n\r]/g;
+ /* eslint-enable regexp/no-control-character -- safe */
+ var EOF;
+
+ var parseHost = function (url, input) {
+ var result, codePoints, index;
+ if (charAt(input, 0) == '[') {
+ if (charAt(input, input.length - 1) != ']') return INVALID_HOST;
+ result = parseIPv6(stringSlice(input, 1, -1));
+ if (!result) return INVALID_HOST;
+ url.host = result;
+ // opaque host
+ } else if (!isSpecial(url)) {
+ if (exec(FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT, input)) return INVALID_HOST;
+ result = '';
+ codePoints = arrayFrom(input);
+ for (index = 0; index < codePoints.length; index++) {
+ result += percentEncode(codePoints[index], C0ControlPercentEncodeSet);
+ }
+ url.host = result;
+ } else {
+ input = toASCII(input);
+ if (exec(FORBIDDEN_HOST_CODE_POINT, input)) return INVALID_HOST;
+ result = parseIPv4(input);
+ if (result === null) return INVALID_HOST;
+ url.host = result;
+ }
+ };
+
+ var parseIPv4 = function (input) {
+ var parts = split(input, '.');
+ var partsLength, numbers, index, part, radix, number, ipv4;
+ if (parts.length && parts[parts.length - 1] == '') {
+ parts.length--;
+ }
+ partsLength = parts.length;
+ if (partsLength > 4) return input;
+ numbers = [];
+ for (index = 0; index < partsLength; index++) {
+ part = parts[index];
+ if (part == '') return input;
+ radix = 10;
+ if (part.length > 1 && charAt(part, 0) == '0') {
+ radix = exec(HEX_START, part) ? 16 : 8;
+ part = stringSlice(part, radix == 8 ? 1 : 2);
+ }
+ if (part === '') {
+ number = 0;
+ } else {
+ if (!exec(radix == 10 ? DEC : radix == 8 ? OCT : HEX, part)) return input;
+ number = parseInt$1(part, radix);
+ }
+ push(numbers, number);
+ }
+ for (index = 0; index < partsLength; index++) {
+ number = numbers[index];
+ if (index == partsLength - 1) {
+ if (number >= pow(256, 5 - partsLength)) return null;
+ } else if (number > 255) return null;
+ }
+ ipv4 = pop(numbers);
+ for (index = 0; index < numbers.length; index++) {
+ ipv4 += numbers[index] * pow(256, 3 - index);
+ }
+ return ipv4;
+ };
+
+ // eslint-disable-next-line max-statements -- TODO
+ var parseIPv6 = function (input) {
+ var address = [0, 0, 0, 0, 0, 0, 0, 0];
+ var pieceIndex = 0;
+ var compress = null;
+ var pointer = 0;
+ var value, length, numbersSeen, ipv4Piece, number, swaps, swap;
+
+ var chr = function () {
+ return charAt(input, pointer);
+ };
+
+ if (chr() == ':') {
+ if (charAt(input, 1) != ':') return;
+ pointer += 2;
+ pieceIndex++;
+ compress = pieceIndex;
+ }
+ while (chr()) {
+ if (pieceIndex == 8) return;
+ if (chr() == ':') {
+ if (compress !== null) return;
+ pointer++;
+ pieceIndex++;
+ compress = pieceIndex;
+ continue;
+ }
+ value = length = 0;
+ while (length < 4 && exec(HEX, chr())) {
+ value = value * 16 + parseInt$1(chr(), 16);
+ pointer++;
+ length++;
+ }
+ if (chr() == '.') {
+ if (length == 0) return;
+ pointer -= length;
+ if (pieceIndex > 6) return;
+ numbersSeen = 0;
+ while (chr()) {
+ ipv4Piece = null;
+ if (numbersSeen > 0) {
+ if (chr() == '.' && numbersSeen < 4) pointer++;
+ else return;
+ }
+ if (!exec(DIGIT, chr())) return;
+ while (exec(DIGIT, chr())) {
+ number = parseInt$1(chr(), 10);
+ if (ipv4Piece === null) ipv4Piece = number;
+ else if (ipv4Piece == 0) return;
+ else ipv4Piece = ipv4Piece * 10 + number;
+ if (ipv4Piece > 255) return;
+ pointer++;
+ }
+ address[pieceIndex] = address[pieceIndex] * 256 + ipv4Piece;
+ numbersSeen++;
+ if (numbersSeen == 2 || numbersSeen == 4) pieceIndex++;
+ }
+ if (numbersSeen != 4) return;
+ break;
+ } else if (chr() == ':') {
+ pointer++;
+ if (!chr()) return;
+ } else if (chr()) return;
+ address[pieceIndex++] = value;
+ }
+ if (compress !== null) {
+ swaps = pieceIndex - compress;
+ pieceIndex = 7;
+ while (pieceIndex != 0 && swaps > 0) {
+ swap = address[pieceIndex];
+ address[pieceIndex--] = address[compress + swaps - 1];
+ address[compress + --swaps] = swap;
+ }
+ } else if (pieceIndex != 8) return;
+ return address;
+ };
+
+ var findLongestZeroSequence = function (ipv6) {
+ var maxIndex = null;
+ var maxLength = 1;
+ var currStart = null;
+ var currLength = 0;
+ var index = 0;
+ for (; index < 8; index++) {
+ if (ipv6[index] !== 0) {
+ if (currLength > maxLength) {
+ maxIndex = currStart;
+ maxLength = currLength;
+ }
+ currStart = null;
+ currLength = 0;
+ } else {
+ if (currStart === null) currStart = index;
+ ++currLength;
+ }
+ }
+ if (currLength > maxLength) {
+ maxIndex = currStart;
+ maxLength = currLength;
+ }
+ return maxIndex;
+ };
+
+ var serializeHost = function (host) {
+ var result, index, compress, ignore0;
+ // ipv4
+ if (typeof host == 'number') {
+ result = [];
+ for (index = 0; index < 4; index++) {
+ unshift(result, host % 256);
+ host = floor(host / 256);
+ } return join(result, '.');
+ // ipv6
+ } else if (typeof host == 'object') {
+ result = '';
+ compress = findLongestZeroSequence(host);
+ for (index = 0; index < 8; index++) {
+ if (ignore0 && host[index] === 0) continue;
+ if (ignore0) ignore0 = false;
+ if (compress === index) {
+ result += index ? ':' : '::';
+ ignore0 = true;
+ } else {
+ result += numberToString(host[index], 16);
+ if (index < 7) result += ':';
+ }
+ }
+ return '[' + result + ']';
+ } return host;
+ };
+
+ var C0ControlPercentEncodeSet = {};
+ var fragmentPercentEncodeSet = assign$1({}, C0ControlPercentEncodeSet, {
+ ' ': 1, '"': 1, '<': 1, '>': 1, '`': 1
+ });
+ var pathPercentEncodeSet = assign$1({}, fragmentPercentEncodeSet, {
+ '#': 1, '?': 1, '{': 1, '}': 1
+ });
+ var userinfoPercentEncodeSet = assign$1({}, pathPercentEncodeSet, {
+ '/': 1, ':': 1, ';': 1, '=': 1, '@': 1, '[': 1, '\\': 1, ']': 1, '^': 1, '|': 1
+ });
+
+ var percentEncode = function (chr, set) {
+ var code = codeAt(chr, 0);
+ return code > 0x20 && code < 0x7F && !hasOwn(set, chr) ? chr : encodeURIComponent(chr);
+ };
+
+ var specialSchemes = {
+ ftp: 21,
+ file: null,
+ http: 80,
+ https: 443,
+ ws: 80,
+ wss: 443
+ };
+
+ var isSpecial = function (url) {
+ return hasOwn(specialSchemes, url.scheme);
+ };
+
+ var includesCredentials = function (url) {
+ return url.username != '' || url.password != '';
+ };
+
+ var cannotHaveUsernamePasswordPort = function (url) {
+ return !url.host || url.cannotBeABaseURL || url.scheme == 'file';
+ };
+
+ var isWindowsDriveLetter = function (string, normalized) {
+ var second;
+ return string.length == 2 && exec(ALPHA, charAt(string, 0))
+ && ((second = charAt(string, 1)) == ':' || (!normalized && second == '|'));
+ };
+
+ var startsWithWindowsDriveLetter = function (string) {
+ var third;
+ return string.length > 1 && isWindowsDriveLetter(stringSlice(string, 0, 2)) && (
+ string.length == 2 ||
+ ((third = charAt(string, 2)) === '/' || third === '\\' || third === '?' || third === '#')
+ );
+ };
+
+ var shortenURLsPath = function (url) {
+ var path = url.path;
+ var pathSize = path.length;
+ if (pathSize && (url.scheme != 'file' || pathSize != 1 || !isWindowsDriveLetter(path[0], true))) {
+ path.length--;
+ }
+ };
+
+ var isSingleDot = function (segment) {
+ return segment === '.' || toLowerCase(segment) === '%2e';
+ };
+
+ var isDoubleDot = function (segment) {
+ segment = toLowerCase(segment);
+ return segment === '..' || segment === '%2e.' || segment === '.%2e' || segment === '%2e%2e';
+ };
+
+ // States:
+ var SCHEME_START = {};
+ var SCHEME = {};
+ var NO_SCHEME = {};
+ var SPECIAL_RELATIVE_OR_AUTHORITY = {};
+ var PATH_OR_AUTHORITY = {};
+ var RELATIVE = {};
+ var RELATIVE_SLASH = {};
+ var SPECIAL_AUTHORITY_SLASHES = {};
+ var SPECIAL_AUTHORITY_IGNORE_SLASHES = {};
+ var AUTHORITY = {};
+ var HOST = {};
+ var HOSTNAME = {};
+ var PORT = {};
+ var FILE = {};
+ var FILE_SLASH = {};
+ var FILE_HOST = {};
+ var PATH_START = {};
+ var PATH = {};
+ var CANNOT_BE_A_BASE_URL_PATH = {};
+ var QUERY = {};
+ var FRAGMENT = {};
+
+ // eslint-disable-next-line max-statements -- TODO
+ var parseURL = function (url, input, stateOverride, base) {
+ var state = stateOverride || SCHEME_START;
+ var pointer = 0;
+ var buffer = '';
+ var seenAt = false;
+ var seenBracket = false;
+ var seenPasswordToken = false;
+ var codePoints, chr, bufferCodePoints, failure;
+
+ if (!stateOverride) {
+ url.scheme = '';
+ url.username = '';
+ url.password = '';
+ url.host = null;
+ url.port = null;
+ url.path = [];
+ url.query = null;
+ url.fragment = null;
+ url.cannotBeABaseURL = false;
+ input = replace(input, LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE, '');
+ }
+
+ input = replace(input, TAB_AND_NEW_LINE, '');
+
+ codePoints = arrayFrom(input);
+
+ while (pointer <= codePoints.length) {
+ chr = codePoints[pointer];
+ switch (state) {
+ case SCHEME_START:
+ if (chr && exec(ALPHA, chr)) {
+ buffer += toLowerCase(chr);
+ state = SCHEME;
+ } else if (!stateOverride) {
+ state = NO_SCHEME;
+ continue;
+ } else return INVALID_SCHEME;
+ break;
+
+ case SCHEME:
+ if (chr && (exec(ALPHANUMERIC, chr) || chr == '+' || chr == '-' || chr == '.')) {
+ buffer += toLowerCase(chr);
+ } else if (chr == ':') {
+ if (stateOverride && (
+ (isSpecial(url) != hasOwn(specialSchemes, buffer)) ||
+ (buffer == 'file' && (includesCredentials(url) || url.port !== null)) ||
+ (url.scheme == 'file' && !url.host)
+ )) return;
+ url.scheme = buffer;
+ if (stateOverride) {
+ if (isSpecial(url) && specialSchemes[url.scheme] == url.port) url.port = null;
+ return;
+ }
+ buffer = '';
+ if (url.scheme == 'file') {
+ state = FILE;
+ } else if (isSpecial(url) && base && base.scheme == url.scheme) {
+ state = SPECIAL_RELATIVE_OR_AUTHORITY;
+ } else if (isSpecial(url)) {
+ state = SPECIAL_AUTHORITY_SLASHES;
+ } else if (codePoints[pointer + 1] == '/') {
+ state = PATH_OR_AUTHORITY;
+ pointer++;
+ } else {
+ url.cannotBeABaseURL = true;
+ push(url.path, '');
+ state = CANNOT_BE_A_BASE_URL_PATH;
+ }
+ } else if (!stateOverride) {
+ buffer = '';
+ state = NO_SCHEME;
+ pointer = 0;
+ continue;
+ } else return INVALID_SCHEME;
+ break;
+
+ case NO_SCHEME:
+ if (!base || (base.cannotBeABaseURL && chr != '#')) return INVALID_SCHEME;
+ if (base.cannotBeABaseURL && chr == '#') {
+ url.scheme = base.scheme;
+ url.path = arraySlice(base.path);
+ url.query = base.query;
+ url.fragment = '';
+ url.cannotBeABaseURL = true;
+ state = FRAGMENT;
+ break;
+ }
+ state = base.scheme == 'file' ? FILE : RELATIVE;
+ continue;
+
+ case SPECIAL_RELATIVE_OR_AUTHORITY:
+ if (chr == '/' && codePoints[pointer + 1] == '/') {
+ state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
+ pointer++;
+ } else {
+ state = RELATIVE;
+ continue;
+ } break;
+
+ case PATH_OR_AUTHORITY:
+ if (chr == '/') {
+ state = AUTHORITY;
+ break;
+ } else {
+ state = PATH;
+ continue;
+ }
+
+ case RELATIVE:
+ url.scheme = base.scheme;
+ if (chr == EOF) {
+ url.username = base.username;
+ url.password = base.password;
+ url.host = base.host;
+ url.port = base.port;
+ url.path = arraySlice(base.path);
+ url.query = base.query;
+ } else if (chr == '/' || (chr == '\\' && isSpecial(url))) {
+ state = RELATIVE_SLASH;
+ } else if (chr == '?') {
+ url.username = base.username;
+ url.password = base.password;
+ url.host = base.host;
+ url.port = base.port;
+ url.path = arraySlice(base.path);
+ url.query = '';
+ state = QUERY;
+ } else if (chr == '#') {
+ url.username = base.username;
+ url.password = base.password;
+ url.host = base.host;
+ url.port = base.port;
+ url.path = arraySlice(base.path);
+ url.query = base.query;
+ url.fragment = '';
+ state = FRAGMENT;
+ } else {
+ url.username = base.username;
+ url.password = base.password;
+ url.host = base.host;
+ url.port = base.port;
+ url.path = arraySlice(base.path);
+ url.path.length--;
+ state = PATH;
+ continue;
+ } break;
+
+ case RELATIVE_SLASH:
+ if (isSpecial(url) && (chr == '/' || chr == '\\')) {
+ state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
+ } else if (chr == '/') {
+ state = AUTHORITY;
+ } else {
+ url.username = base.username;
+ url.password = base.password;
+ url.host = base.host;
+ url.port = base.port;
+ state = PATH;
+ continue;
+ } break;
+
+ case SPECIAL_AUTHORITY_SLASHES:
+ state = SPECIAL_AUTHORITY_IGNORE_SLASHES;
+ if (chr != '/' || charAt(buffer, pointer + 1) != '/') continue;
+ pointer++;
+ break;
+
+ case SPECIAL_AUTHORITY_IGNORE_SLASHES:
+ if (chr != '/' && chr != '\\') {
+ state = AUTHORITY;
+ continue;
+ } break;
+
+ case AUTHORITY:
+ if (chr == '@') {
+ if (seenAt) buffer = '%40' + buffer;
+ seenAt = true;
+ bufferCodePoints = arrayFrom(buffer);
+ for (var i = 0; i < bufferCodePoints.length; i++) {
+ var codePoint = bufferCodePoints[i];
+ if (codePoint == ':' && !seenPasswordToken) {
+ seenPasswordToken = true;
+ continue;
+ }
+ var encodedCodePoints = percentEncode(codePoint, userinfoPercentEncodeSet);
+ if (seenPasswordToken) url.password += encodedCodePoints;
+ else url.username += encodedCodePoints;
+ }
+ buffer = '';
+ } else if (
+ chr == EOF || chr == '/' || chr == '?' || chr == '#' ||
+ (chr == '\\' && isSpecial(url))
+ ) {
+ if (seenAt && buffer == '') return INVALID_AUTHORITY;
+ pointer -= arrayFrom(buffer).length + 1;
+ buffer = '';
+ state = HOST;
+ } else buffer += chr;
+ break;
+
+ case HOST:
+ case HOSTNAME:
+ if (stateOverride && url.scheme == 'file') {
+ state = FILE_HOST;
+ continue;
+ } else if (chr == ':' && !seenBracket) {
+ if (buffer == '') return INVALID_HOST;
+ failure = parseHost(url, buffer);
+ if (failure) return failure;
+ buffer = '';
+ state = PORT;
+ if (stateOverride == HOSTNAME) return;
+ } else if (
+ chr == EOF || chr == '/' || chr == '?' || chr == '#' ||
+ (chr == '\\' && isSpecial(url))
+ ) {
+ if (isSpecial(url) && buffer == '') return INVALID_HOST;
+ if (stateOverride && buffer == '' && (includesCredentials(url) || url.port !== null)) return;
+ failure = parseHost(url, buffer);
+ if (failure) return failure;
+ buffer = '';
+ state = PATH_START;
+ if (stateOverride) return;
+ continue;
+ } else {
+ if (chr == '[') seenBracket = true;
+ else if (chr == ']') seenBracket = false;
+ buffer += chr;
+ } break;
+
+ case PORT:
+ if (exec(DIGIT, chr)) {
+ buffer += chr;
+ } else if (
+ chr == EOF || chr == '/' || chr == '?' || chr == '#' ||
+ (chr == '\\' && isSpecial(url)) ||
+ stateOverride
+ ) {
+ if (buffer != '') {
+ var port = parseInt$1(buffer, 10);
+ if (port > 0xFFFF) return INVALID_PORT;
+ url.port = (isSpecial(url) && port === specialSchemes[url.scheme]) ? null : port;
+ buffer = '';
+ }
+ if (stateOverride) return;
+ state = PATH_START;
+ continue;
+ } else return INVALID_PORT;
+ break;
+
+ case FILE:
+ url.scheme = 'file';
+ if (chr == '/' || chr == '\\') state = FILE_SLASH;
+ else if (base && base.scheme == 'file') {
+ if (chr == EOF) {
+ url.host = base.host;
+ url.path = arraySlice(base.path);
+ url.query = base.query;
+ } else if (chr == '?') {
+ url.host = base.host;
+ url.path = arraySlice(base.path);
+ url.query = '';
+ state = QUERY;
+ } else if (chr == '#') {
+ url.host = base.host;
+ url.path = arraySlice(base.path);
+ url.query = base.query;
+ url.fragment = '';
+ state = FRAGMENT;
+ } else {
+ if (!startsWithWindowsDriveLetter(join(arraySlice(codePoints, pointer), ''))) {
+ url.host = base.host;
+ url.path = arraySlice(base.path);
+ shortenURLsPath(url);
+ }
+ state = PATH;
+ continue;
+ }
+ } else {
+ state = PATH;
+ continue;
+ } break;
+
+ case FILE_SLASH:
+ if (chr == '/' || chr == '\\') {
+ state = FILE_HOST;
+ break;
+ }
+ if (base && base.scheme == 'file' && !startsWithWindowsDriveLetter(join(arraySlice(codePoints, pointer), ''))) {
+ if (isWindowsDriveLetter(base.path[0], true)) push(url.path, base.path[0]);
+ else url.host = base.host;
+ }
+ state = PATH;
+ continue;
+
+ case FILE_HOST:
+ if (chr == EOF || chr == '/' || chr == '\\' || chr == '?' || chr == '#') {
+ if (!stateOverride && isWindowsDriveLetter(buffer)) {
+ state = PATH;
+ } else if (buffer == '') {
+ url.host = '';
+ if (stateOverride) return;
+ state = PATH_START;
+ } else {
+ failure = parseHost(url, buffer);
+ if (failure) return failure;
+ if (url.host == 'localhost') url.host = '';
+ if (stateOverride) return;
+ buffer = '';
+ state = PATH_START;
+ } continue;
+ } else buffer += chr;
+ break;
+
+ case PATH_START:
+ if (isSpecial(url)) {
+ state = PATH;
+ if (chr != '/' && chr != '\\') continue;
+ } else if (!stateOverride && chr == '?') {
+ url.query = '';
+ state = QUERY;
+ } else if (!stateOverride && chr == '#') {
+ url.fragment = '';
+ state = FRAGMENT;
+ } else if (chr != EOF) {
+ state = PATH;
+ if (chr != '/') continue;
+ } break;
- if (services.geocoder) {
- list.selectAll('.geocode-item').data([0]).enter().append('button').attr('class', 'geocode-item secondary-action').on('click', geocoderSearch).append('div').attr('class', 'label').append('span').attr('class', 'entity-name').html(_t.html('geocoder.search'));
- }
+ case PATH:
+ if (
+ chr == EOF || chr == '/' ||
+ (chr == '\\' && isSpecial(url)) ||
+ (!stateOverride && (chr == '?' || chr == '#'))
+ ) {
+ if (isDoubleDot(buffer)) {
+ shortenURLsPath(url);
+ if (chr != '/' && !(chr == '\\' && isSpecial(url))) {
+ push(url.path, '');
+ }
+ } else if (isSingleDot(buffer)) {
+ if (chr != '/' && !(chr == '\\' && isSpecial(url))) {
+ push(url.path, '');
+ }
+ } else {
+ if (url.scheme == 'file' && !url.path.length && isWindowsDriveLetter(buffer)) {
+ if (url.host) url.host = '';
+ buffer = charAt(buffer, 0) + ':'; // normalize windows drive letter
+ }
+ push(url.path, buffer);
+ }
+ buffer = '';
+ if (url.scheme == 'file' && (chr == EOF || chr == '?' || chr == '#')) {
+ while (url.path.length > 1 && url.path[0] === '') {
+ shift(url.path);
+ }
+ }
+ if (chr == '?') {
+ url.query = '';
+ state = QUERY;
+ } else if (chr == '#') {
+ url.fragment = '';
+ state = FRAGMENT;
+ }
+ } else {
+ buffer += percentEncode(chr, pathPercentEncodeSet);
+ } break;
- list.selectAll('.no-results-item').style('display', value.length && !results.length ? 'block' : 'none');
- list.selectAll('.geocode-item').style('display', value && _geocodeResults === undefined ? 'block' : 'none');
- list.selectAll('.feature-list-item').data([-1]).remove();
- var items = list.selectAll('.feature-list-item').data(results, function (d) {
- return d.id;
- });
- var enter = items.enter().insert('button', '.geocode-item').attr('class', 'feature-list-item').on('mouseover', mouseover).on('mouseout', mouseout).on('click', click);
- var label = enter.append('div').attr('class', 'label');
- label.each(function (d) {
- select(this).call(svgIcon('#iD-icon-' + d.geometry, 'pre-text'));
- });
- label.append('span').attr('class', 'entity-type').html(function (d) {
- return d.type;
- });
- label.append('span').attr('class', 'entity-name').html(function (d) {
- return d.name;
- });
- enter.style('opacity', 0).transition().style('opacity', 1);
- items.order();
- items.exit().remove();
- }
+ case CANNOT_BE_A_BASE_URL_PATH:
+ if (chr == '?') {
+ url.query = '';
+ state = QUERY;
+ } else if (chr == '#') {
+ url.fragment = '';
+ state = FRAGMENT;
+ } else if (chr != EOF) {
+ url.path[0] += percentEncode(chr, C0ControlPercentEncodeSet);
+ } break;
- function mouseover(d3_event, d) {
- if (d.id === -1) return;
- utilHighlightEntities([d.id], true, context);
- }
+ case QUERY:
+ if (!stateOverride && chr == '#') {
+ url.fragment = '';
+ state = FRAGMENT;
+ } else if (chr != EOF) {
+ if (chr == "'" && isSpecial(url)) url.query += '%27';
+ else if (chr == '#') url.query += '%23';
+ else url.query += percentEncode(chr, C0ControlPercentEncodeSet);
+ } break;
- function mouseout(d3_event, d) {
- if (d.id === -1) return;
- utilHighlightEntities([d.id], false, context);
+ case FRAGMENT:
+ if (chr != EOF) url.fragment += percentEncode(chr, fragmentPercentEncodeSet);
+ break;
}
- function click(d3_event, d) {
- d3_event.preventDefault();
-
- if (d.location) {
- context.map().centerZoomEase([d.location[1], d.location[0]], 19);
- } else if (d.entity) {
- utilHighlightEntities([d.id], false, context);
- context.enter(modeSelect(context, [d.entity.id]));
- context.map().zoomToEase(d.entity);
- } else {
- // download, zoom to, and select the entity with the given ID
- context.zoomToEntity(d.id);
- }
- }
+ pointer++;
+ }
+ };
- function geocoderSearch() {
- services.geocoder.search(search.property('value'), function (err, resp) {
- _geocodeResults = resp || [];
- drawList();
- });
+ // `URL` constructor
+ // https://url.spec.whatwg.org/#url-class
+ var URLConstructor = function URL(url /* , base */) {
+ var that = anInstance(this, URLPrototype);
+ var base = arguments.length > 1 ? arguments[1] : undefined;
+ var urlString = $toString(url);
+ var state = setInternalState(that, { type: 'URL' });
+ var baseState, failure;
+ if (base !== undefined) {
+ try {
+ baseState = getInternalURLState(base);
+ } catch (error) {
+ failure = parseURL(baseState = {}, $toString(base));
+ if (failure) throw TypeError$1(failure);
}
}
+ failure = parseURL(state, urlString, null, baseState);
+ if (failure) throw TypeError$1(failure);
+ var searchParams = state.searchParams = new URLSearchParams$1();
+ var searchParamsState = getInternalSearchParamsState(searchParams);
+ searchParamsState.updateSearchParams(state.query);
+ searchParamsState.updateURL = function () {
+ state.query = $toString(searchParams) || null;
+ };
+ if (!DESCRIPTORS) {
+ that.href = call(serializeURL, that);
+ that.origin = call(getOrigin, that);
+ that.protocol = call(getProtocol, that);
+ that.username = call(getUsername, that);
+ that.password = call(getPassword, that);
+ that.host = call(getHost, that);
+ that.hostname = call(getHostname, that);
+ that.port = call(getPort, that);
+ that.pathname = call(getPathname, that);
+ that.search = call(getSearch, that);
+ that.searchParams = call(getSearchParams, that);
+ that.hash = call(getHash, that);
+ }
+ };
- return featureList;
- }
-
- var $$1 = _export;
- var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f;
- var toLength$1 = toLength$q;
- var notARegExp$1 = notARegexp;
- var requireObjectCoercible$1 = requireObjectCoercible$e;
- var correctIsRegExpLogic$1 = correctIsRegexpLogic;
-
- // eslint-disable-next-line es/no-string-prototype-startswith -- safe
- var $startsWith = ''.startsWith;
- var min$1 = Math.min;
+ var URLPrototype = URLConstructor.prototype;
- var CORRECT_IS_REGEXP_LOGIC$1 = correctIsRegExpLogic$1('startsWith');
- // https://github.com/zloirock/core-js/pull/702
- var MDN_POLYFILL_BUG$1 = !CORRECT_IS_REGEXP_LOGIC$1 && !!function () {
- var descriptor = getOwnPropertyDescriptor$1(String.prototype, 'startsWith');
- return descriptor && !descriptor.writable;
- }();
+ var serializeURL = function () {
+ var url = getInternalURLState(this);
+ var scheme = url.scheme;
+ var username = url.username;
+ var password = url.password;
+ var host = url.host;
+ var port = url.port;
+ var path = url.path;
+ var query = url.query;
+ var fragment = url.fragment;
+ var output = scheme + ':';
+ if (host !== null) {
+ output += '//';
+ if (includesCredentials(url)) {
+ output += username + (password ? ':' + password : '') + '@';
+ }
+ output += serializeHost(host);
+ if (port !== null) output += ':' + port;
+ } else if (scheme == 'file') output += '//';
+ output += url.cannotBeABaseURL ? path[0] : path.length ? '/' + join(path, '/') : '';
+ if (query !== null) output += '?' + query;
+ if (fragment !== null) output += '#' + fragment;
+ return output;
+ };
- // `String.prototype.startsWith` method
- // https://tc39.es/ecma262/#sec-string.prototype.startswith
- $$1({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG$1 && !CORRECT_IS_REGEXP_LOGIC$1 }, {
- startsWith: function startsWith(searchString /* , position = 0 */) {
- var that = String(requireObjectCoercible$1(this));
- notARegExp$1(searchString);
- var index = toLength$1(min$1(arguments.length > 1 ? arguments[1] : undefined, that.length));
- var search = String(searchString);
- return $startsWith
- ? $startsWith.call(that, search, index)
- : that.slice(index, index + search.length) === search;
+ var getOrigin = function () {
+ var url = getInternalURLState(this);
+ var scheme = url.scheme;
+ var port = url.port;
+ if (scheme == 'blob') try {
+ return new URLConstructor(scheme.path[0]).origin;
+ } catch (error) {
+ return 'null';
}
- });
+ if (scheme == 'file' || !isSpecial(url)) return 'null';
+ return scheme + '://' + serializeHost(url.host) + (port !== null ? ':' + port : '');
+ };
- function uiSectionEntityIssues(context) {
- // Does the user prefer to expand the active issue? Useful for viewing tag diff.
- // Expand by default so first timers see it - #6408, #8143
- var preference = corePreferences('entity-issues.reference.expanded');
+ var getProtocol = function () {
+ return getInternalURLState(this).scheme + ':';
+ };
- var _expanded = preference === null ? true : preference === 'true';
+ var getUsername = function () {
+ return getInternalURLState(this).username;
+ };
- var _entityIDs = [];
- var _issues = [];
+ var getPassword = function () {
+ return getInternalURLState(this).password;
+ };
- var _activeIssueID;
+ var getHost = function () {
+ var url = getInternalURLState(this);
+ var host = url.host;
+ var port = url.port;
+ return host === null ? ''
+ : port === null ? serializeHost(host)
+ : serializeHost(host) + ':' + port;
+ };
- var section = uiSection('entity-issues', context).shouldDisplay(function () {
- return _issues.length > 0;
- }).label(function () {
- return _t('inspector.title_count', {
- title: _t.html('issues.list_title'),
- count: _issues.length
- });
- }).disclosureContent(renderDisclosureContent);
- context.validator().on('validated.entity_issues', function () {
- // Refresh on validated events
- reloadIssues();
- section.reRender();
- }).on('focusedIssue.entity_issues', function (issue) {
- makeActiveIssue(issue.id);
- });
+ var getHostname = function () {
+ var host = getInternalURLState(this).host;
+ return host === null ? '' : serializeHost(host);
+ };
- function reloadIssues() {
- _issues = context.validator().getSharedEntityIssues(_entityIDs, {
- includeDisabledRules: true
- });
- }
+ var getPort = function () {
+ var port = getInternalURLState(this).port;
+ return port === null ? '' : $toString(port);
+ };
- function makeActiveIssue(issueID) {
- _activeIssueID = issueID;
- section.selection().selectAll('.issue-container').classed('active', function (d) {
- return d.id === _activeIssueID;
- });
- }
+ var getPathname = function () {
+ var url = getInternalURLState(this);
+ var path = url.path;
+ return url.cannotBeABaseURL ? path[0] : path.length ? '/' + join(path, '/') : '';
+ };
- function renderDisclosureContent(selection) {
- selection.classed('grouped-items-area', true);
- _activeIssueID = _issues.length > 0 ? _issues[0].id : null;
- var containers = selection.selectAll('.issue-container').data(_issues, function (d) {
- return d.key;
- }); // Exit
+ var getSearch = function () {
+ var query = getInternalURLState(this).query;
+ return query ? '?' + query : '';
+ };
- containers.exit().remove(); // Enter
+ var getSearchParams = function () {
+ return getInternalURLState(this).searchParams;
+ };
- var containersEnter = containers.enter().append('div').attr('class', 'issue-container');
- var itemsEnter = containersEnter.append('div').attr('class', function (d) {
- return 'issue severity-' + d.severity;
- }).on('mouseover.highlight', function (d3_event, d) {
- // don't hover-highlight the selected entity
- var ids = d.entityIds.filter(function (e) {
- return _entityIDs.indexOf(e) === -1;
- });
- utilHighlightEntities(ids, true, context);
- }).on('mouseout.highlight', function (d3_event, d) {
- var ids = d.entityIds.filter(function (e) {
- return _entityIDs.indexOf(e) === -1;
- });
- utilHighlightEntities(ids, false, context);
- });
- var labelsEnter = itemsEnter.append('div').attr('class', 'issue-label');
- var textEnter = labelsEnter.append('button').attr('class', 'issue-text').on('click', function (d3_event, d) {
- makeActiveIssue(d.id); // expand only the clicked item
+ var getHash = function () {
+ var fragment = getInternalURLState(this).fragment;
+ return fragment ? '#' + fragment : '';
+ };
- var extent = d.extent(context.graph());
+ var accessorDescriptor = function (getter, setter) {
+ return { get: getter, set: setter, configurable: true, enumerable: true };
+ };
- if (extent) {
- var setZoom = Math.max(context.map().zoom(), 19);
- context.map().unobscuredCenterZoomEase(extent.center(), setZoom);
+ if (DESCRIPTORS) {
+ defineProperties(URLPrototype, {
+ // `URL.prototype.href` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-href
+ href: accessorDescriptor(serializeURL, function (href) {
+ var url = getInternalURLState(this);
+ var urlString = $toString(href);
+ var failure = parseURL(url, urlString);
+ if (failure) throw TypeError$1(failure);
+ getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query);
+ }),
+ // `URL.prototype.origin` getter
+ // https://url.spec.whatwg.org/#dom-url-origin
+ origin: accessorDescriptor(getOrigin),
+ // `URL.prototype.protocol` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-protocol
+ protocol: accessorDescriptor(getProtocol, function (protocol) {
+ var url = getInternalURLState(this);
+ parseURL(url, $toString(protocol) + ':', SCHEME_START);
+ }),
+ // `URL.prototype.username` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-username
+ username: accessorDescriptor(getUsername, function (username) {
+ var url = getInternalURLState(this);
+ var codePoints = arrayFrom($toString(username));
+ if (cannotHaveUsernamePasswordPort(url)) return;
+ url.username = '';
+ for (var i = 0; i < codePoints.length; i++) {
+ url.username += percentEncode(codePoints[i], userinfoPercentEncodeSet);
}
- });
- textEnter.each(function (d) {
- var iconName = '#iD-icon-' + (d.severity === 'warning' ? 'alert' : 'error');
- select(this).call(svgIcon(iconName, 'issue-icon'));
- });
- textEnter.append('span').attr('class', 'issue-message');
- var infoButton = labelsEnter.append('button').attr('class', 'issue-info-button').attr('title', _t('icons.information')).call(svgIcon('#iD-icon-inspect'));
- infoButton.on('click', function (d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- this.blur(); // avoid keeping focus on the button - #4641
-
- var container = select(this.parentNode.parentNode.parentNode);
- var info = container.selectAll('.issue-info');
- var isExpanded = info.classed('expanded');
- _expanded = !isExpanded;
- corePreferences('entity-issues.reference.expanded', _expanded); // update preference
-
- if (isExpanded) {
- info.transition().duration(200).style('max-height', '0px').style('opacity', '0').on('end', function () {
- info.classed('expanded', false);
- });
- } else {
- info.classed('expanded', true).transition().duration(200).style('max-height', '200px').style('opacity', '1').on('end', function () {
- info.style('max-height', null);
- });
+ }),
+ // `URL.prototype.password` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-password
+ password: accessorDescriptor(getPassword, function (password) {
+ var url = getInternalURLState(this);
+ var codePoints = arrayFrom($toString(password));
+ if (cannotHaveUsernamePasswordPort(url)) return;
+ url.password = '';
+ for (var i = 0; i < codePoints.length; i++) {
+ url.password += percentEncode(codePoints[i], userinfoPercentEncodeSet);
}
- });
- itemsEnter.append('ul').attr('class', 'issue-fix-list');
- containersEnter.append('div').attr('class', 'issue-info' + (_expanded ? ' expanded' : '')).style('max-height', _expanded ? null : '0').style('opacity', _expanded ? '1' : '0').each(function (d) {
- if (typeof d.reference === 'function') {
- select(this).call(d.reference);
+ }),
+ // `URL.prototype.host` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-host
+ host: accessorDescriptor(getHost, function (host) {
+ var url = getInternalURLState(this);
+ if (url.cannotBeABaseURL) return;
+ parseURL(url, $toString(host), HOST);
+ }),
+ // `URL.prototype.hostname` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-hostname
+ hostname: accessorDescriptor(getHostname, function (hostname) {
+ var url = getInternalURLState(this);
+ if (url.cannotBeABaseURL) return;
+ parseURL(url, $toString(hostname), HOSTNAME);
+ }),
+ // `URL.prototype.port` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-port
+ port: accessorDescriptor(getPort, function (port) {
+ var url = getInternalURLState(this);
+ if (cannotHaveUsernamePasswordPort(url)) return;
+ port = $toString(port);
+ if (port == '') url.port = null;
+ else parseURL(url, port, PORT);
+ }),
+ // `URL.prototype.pathname` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-pathname
+ pathname: accessorDescriptor(getPathname, function (pathname) {
+ var url = getInternalURLState(this);
+ if (url.cannotBeABaseURL) return;
+ url.path = [];
+ parseURL(url, $toString(pathname), PATH_START);
+ }),
+ // `URL.prototype.search` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-search
+ search: accessorDescriptor(getSearch, function (search) {
+ var url = getInternalURLState(this);
+ search = $toString(search);
+ if (search == '') {
+ url.query = null;
} else {
- select(this).html(_t.html('inspector.no_documentation_key'));
+ if ('?' == charAt(search, 0)) search = stringSlice(search, 1);
+ url.query = '';
+ parseURL(url, search, QUERY);
}
- }); // Update
+ getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query);
+ }),
+ // `URL.prototype.searchParams` getter
+ // https://url.spec.whatwg.org/#dom-url-searchparams
+ searchParams: accessorDescriptor(getSearchParams),
+ // `URL.prototype.hash` accessors pair
+ // https://url.spec.whatwg.org/#dom-url-hash
+ hash: accessorDescriptor(getHash, function (hash) {
+ var url = getInternalURLState(this);
+ hash = $toString(hash);
+ if (hash == '') {
+ url.fragment = null;
+ return;
+ }
+ if ('#' == charAt(hash, 0)) hash = stringSlice(hash, 1);
+ url.fragment = '';
+ parseURL(url, hash, FRAGMENT);
+ })
+ });
+ }
- containers = containers.merge(containersEnter).classed('active', function (d) {
- return d.id === _activeIssueID;
- });
- containers.selectAll('.issue-message').html(function (d) {
- return d.message(context);
- }); // fixes
+ // `URL.prototype.toJSON` method
+ // https://url.spec.whatwg.org/#dom-url-tojson
+ redefine(URLPrototype, 'toJSON', function toJSON() {
+ return call(serializeURL, this);
+ }, { enumerable: true });
- var fixLists = containers.selectAll('.issue-fix-list');
- var fixes = fixLists.selectAll('.issue-fix-item').data(function (d) {
- return d.fixes ? d.fixes(context) : [];
- }, function (fix) {
- return fix.id;
- });
- fixes.exit().remove();
- var fixesEnter = fixes.enter().append('li').attr('class', 'issue-fix-item');
- var buttons = fixesEnter.append('button').on('click', function (d3_event, d) {
- // not all fixes are actionable
- if (select(this).attr('disabled') || !d.onClick) return; // Don't run another fix for this issue within a second of running one
- // (Necessary for "Select a feature type" fix. Most fixes should only ever run once)
+ // `URL.prototype.toString` method
+ // https://url.spec.whatwg.org/#URL-stringification-behavior
+ redefine(URLPrototype, 'toString', function toString() {
+ return call(serializeURL, this);
+ }, { enumerable: true });
- if (d.issue.dateLastRanFix && new Date() - d.issue.dateLastRanFix < 1000) return;
- d.issue.dateLastRanFix = new Date(); // remove hover-highlighting
+ if (NativeURL) {
+ var nativeCreateObjectURL = NativeURL.createObjectURL;
+ var nativeRevokeObjectURL = NativeURL.revokeObjectURL;
+ // `URL.createObjectURL` method
+ // https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
+ if (nativeCreateObjectURL) redefine(URLConstructor, 'createObjectURL', bind$2(nativeCreateObjectURL, NativeURL));
+ // `URL.revokeObjectURL` method
+ // https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL
+ if (nativeRevokeObjectURL) redefine(URLConstructor, 'revokeObjectURL', bind$2(nativeRevokeObjectURL, NativeURL));
+ }
- utilHighlightEntities(d.issue.entityIds.concat(d.entityIds), false, context);
- new Promise(function (resolve, reject) {
- d.onClick(context, resolve, reject);
+ setToStringTag(URLConstructor, 'URL');
- if (d.onClick.length <= 1) {
- // if the fix doesn't take any completion parameters then consider it resolved
- resolve();
- }
- }).then(function () {
- // revalidate whenever the fix has finished running successfully
- context.validator().validate();
- });
- }).on('mouseover.highlight', function (d3_event, d) {
- utilHighlightEntities(d.entityIds, true, context);
- }).on('mouseout.highlight', function (d3_event, d) {
- utilHighlightEntities(d.entityIds, false, context);
- });
- buttons.each(function (d) {
- var iconName = d.icon || 'iD-icon-wrench';
+ $({ global: true, forced: !USE_NATIVE_URL, sham: !DESCRIPTORS }, {
+ URL: URLConstructor
+ });
- if (iconName.startsWith('maki')) {
- iconName += '-15';
- }
+ function uiFieldText(field, context) {
+ var dispatch = dispatch$8('change');
+ var input = select(null);
+ var outlinkButton = select(null);
+ var wrap = select(null);
+ var _entityIDs = [];
- select(this).call(svgIcon('#' + iconName, 'fix-icon'));
- });
- buttons.append('span').attr('class', 'fix-message').html(function (d) {
- return d.title;
- });
- fixesEnter.merge(fixes).selectAll('button').classed('actionable', function (d) {
- return d.onClick;
- }).attr('disabled', function (d) {
- return d.onClick ? null : 'true';
- }).attr('title', function (d) {
- if (d.disabledReason) {
- return d.disabledReason;
- }
+ var _tags;
- return null;
+ var _phoneFormats = {};
+
+ if (field.type === 'tel') {
+ _mainFileFetcher.get('phone_formats').then(function (d) {
+ _phoneFormats = d;
+ updatePhonePlaceholder();
+ })["catch"](function () {
+ /* ignore */
});
}
- section.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
-
- if (!_entityIDs || !val || !utilArrayIdentical(_entityIDs, val)) {
- _entityIDs = val;
- _activeIssueID = null;
- reloadIssues();
- }
-
- return section;
- };
-
- return section;
- }
+ function calcLocked() {
+ // Protect certain fields that have a companion `*:wikidata` value
+ var isLocked = (field.id === 'brand' || field.id === 'network' || field.id === 'operator' || field.id === 'flag') && _entityIDs.length && _entityIDs.some(function (entityID) {
+ var entity = context.graph().hasEntity(entityID);
+ if (!entity) return false; // Features linked to Wikidata are likely important and should be protected
- function uiPresetIcon() {
- var _preset;
+ if (entity.tags.wikidata) return true;
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ var isSuggestion = preset && preset.suggestion; // Lock the field if there is a value and a companion `*:wikidata` value
- var _geometry;
+ var which = field.id; // 'brand', 'network', 'operator', 'flag'
- var _sizeClass = 'medium';
+ return isSuggestion && !!entity.tags[which] && !!entity.tags[which + ':wikidata'];
+ });
- function isSmall() {
- return _sizeClass === 'small';
+ field.locked(isLocked);
}
- function presetIcon(selection) {
- selection.each(render);
- }
+ function i(selection) {
+ calcLocked();
+ var isLocked = field.locked();
+ wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ input = wrap.selectAll('input').data([0]);
+ input = input.enter().append('input').attr('type', field.type === 'identifier' ? 'text' : field.type).attr('id', field.domId).classed(field.type, true).call(utilNoAuto).merge(input);
+ input.classed('disabled', !!isLocked).attr('readonly', isLocked || null).on('input', change(true)).on('blur', change()).on('change', change());
- function getIcon(p, geom) {
- if (isSmall() && p.isFallback && p.isFallback()) return 'iD-icon-' + p.id;
- if (p.icon) return p.icon;
- if (geom === 'line') return 'iD-other-line';
- if (geom === 'vertex') return p.isFallback() ? '' : 'temaki-vertex';
- if (isSmall() && geom === 'point') return '';
- return 'maki-marker-stroked';
- }
+ if (field.type === 'tel') {
+ updatePhonePlaceholder();
+ } else if (field.type === 'number') {
+ var rtl = _mainLocalizer.textDirection() === 'rtl';
+ input.attr('type', 'text');
+ var inc = field.increment;
+ var buttons = wrap.selectAll('.increment, .decrement').data(rtl ? [inc, -inc] : [-inc, inc]);
+ buttons.enter().append('button').attr('class', function (d) {
+ var which = d > 0 ? 'increment' : 'decrement';
+ return 'form-field-button ' + which;
+ }).attr('title', function (d) {
+ var which = d > 0 ? 'increment' : 'decrement';
+ return _t("inspector.".concat(which));
+ }).merge(buttons).on('click', function (d3_event, d) {
+ d3_event.preventDefault();
+ var raw_vals = input.node().value || '0';
+ var vals = raw_vals.split(';');
+ vals = vals.map(function (v) {
+ var num = parseFloat(v.trim(), 10);
+ return isFinite(num) ? clamped(num + d) : v.trim();
+ });
+ input.node().value = vals.join(';');
+ change()();
+ });
+ } else if (field.type === 'identifier' && field.urlFormat && field.pattern) {
+ input.attr('type', 'text');
+ outlinkButton = wrap.selectAll('.foreign-id-permalink').data([0]);
+ outlinkButton.enter().append('button').call(svgIcon('#iD-icon-out-link')).attr('class', 'form-field-button foreign-id-permalink').attr('title', function () {
+ var domainResults = /^https?:\/\/(.{1,}?)\//.exec(field.urlFormat);
- function renderPointBorder(container, drawPoint) {
- var pointBorder = container.selectAll('.preset-icon-point-border').data(drawPoint ? [0] : []);
- pointBorder.exit().remove();
- var pointBorderEnter = pointBorder.enter();
- var w = 40;
- var h = 40;
- pointBorderEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-point-border').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h)).append('path').attr('transform', 'translate(11.5, 8)').attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
- pointBorder = pointBorderEnter.merge(pointBorder);
- }
+ if (domainResults.length >= 2 && domainResults[1]) {
+ var domain = domainResults[1];
+ return _t('icons.view_on', {
+ domain: domain
+ });
+ }
- function renderCategoryBorder(container, category) {
- var categoryBorder = container.selectAll('.preset-icon-category-border').data(category ? [0] : []);
- categoryBorder.exit().remove();
- var categoryBorderEnter = categoryBorder.enter();
- var d = 60;
- var svgEnter = categoryBorderEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-category-border').attr('width', d).attr('height', d).attr('viewBox', "0 0 ".concat(d, " ").concat(d));
- ['fill', 'stroke'].forEach(function (klass) {
- svgEnter.append('path').attr('class', "area ".concat(klass)).attr('d', 'M9.5,7.5 L25.5,7.5 L28.5,12.5 L49.5,12.5 C51.709139,12.5 53.5,14.290861 53.5,16.5 L53.5,43.5 C53.5,45.709139 51.709139,47.5 49.5,47.5 L10.5,47.5 C8.290861,47.5 6.5,45.709139 6.5,43.5 L6.5,12.5 L9.5,7.5 Z');
- });
- categoryBorder = categoryBorderEnter.merge(categoryBorder);
+ return '';
+ }).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ var value = validIdentifierValueForLink();
- if (category) {
- var tagClasses = svgTagClasses().getClassesString(category.members.collection[0].addTags, '');
- categoryBorder.selectAll('path.stroke').attr('class', "area stroke ".concat(tagClasses));
- categoryBorder.selectAll('path.fill').attr('class', "area fill ".concat(tagClasses));
+ if (value) {
+ var url = field.urlFormat.replace(/{value}/, encodeURIComponent(value));
+ window.open(url, '_blank');
+ }
+ }).merge(outlinkButton);
+ } else if (field.type === 'url') {
+ input.attr('type', 'text');
+ outlinkButton = wrap.selectAll('.foreign-id-permalink').data([0]);
+ outlinkButton.enter().append('button').call(svgIcon('#iD-icon-out-link')).attr('class', 'form-field-button foreign-id-permalink').attr('title', function () {
+ return _t('icons.visit_website');
+ }).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ var value = validIdentifierValueForLink();
+ if (value) window.open(value, '_blank');
+ }).merge(outlinkButton);
+ } else if (field.key.split(':').includes('colour')) {
+ input.attr('type', 'text');
+ updateColourPreview();
}
}
- function renderCircleFill(container, drawVertex) {
- var vertexFill = container.selectAll('.preset-icon-fill-vertex').data(drawVertex ? [0] : []);
- vertexFill.exit().remove();
- var vertexFillEnter = vertexFill.enter();
- var w = 60;
- var h = 60;
- var d = 40;
- vertexFillEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-fill-vertex').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h)).append('circle').attr('cx', w / 2).attr('cy', h / 2).attr('r', d / 2);
- vertexFill = vertexFillEnter.merge(vertexFill);
+ function isColourValid(colour) {
+ if (!colour.match(/^(#([0-9a-fA-F]{3}){1,2}|\w+)$/)) {
+ // OSM only supports hex or named colors
+ return false;
+ } else if (!CSS.supports('color', colour) || ['unset', 'inherit', 'initial', 'revert'].includes(colour)) {
+ // see https://stackoverflow.com/a/68217760/1627467
+ return false;
+ }
+
+ return true;
}
- function renderSquareFill(container, drawArea, tagClasses) {
- var fill = container.selectAll('.preset-icon-fill-area').data(drawArea ? [0] : []);
- fill.exit().remove();
- var fillEnter = fill.enter();
- var d = isSmall() ? 40 : 60;
- var w = d;
- var h = d;
- var l = d * 2 / 3;
- var c1 = (w - l) / 2;
- var c2 = c1 + l;
- fillEnter = fillEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-fill-area').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
- ['fill', 'stroke'].forEach(function (klass) {
- fillEnter.append('path').attr('d', "M".concat(c1, " ").concat(c1, " L").concat(c1, " ").concat(c2, " L").concat(c2, " ").concat(c2, " L").concat(c2, " ").concat(c1, " Z")).attr('class', "area ".concat(klass));
- });
- var rVertex = 2.5;
- [[c1, c1], [c1, c2], [c2, c2], [c2, c1]].forEach(function (point) {
- fillEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', rVertex);
- });
+ function updateColourPreview() {
+ wrap.selectAll('.colour-preview').remove();
+ var colour = utilGetSetValue(input);
+ if (!isColourValid(colour) && colour !== '') return;
+ var colourSelector = wrap.selectAll('.colour-selector').data([0]);
+ outlinkButton = wrap.selectAll('.colour-preview').data([colour]);
+ colourSelector.enter().append('input').attr('type', 'color').attr('class', 'form-field-button colour-selector').attr('value', colour).on('input', debounce(function (d3_event) {
+ d3_event.preventDefault();
+ var colour = this.value;
+ if (!isColourValid(colour)) return;
+ utilGetSetValue(input, this.value);
+ change()();
+ updateColourPreview();
+ }, 100));
+ outlinkButton = outlinkButton.enter().append('div').attr('class', 'form-field-button colour-preview').append('div').style('background-color', function (d) {
+ return d;
+ }).attr('class', 'colour-box');
- if (!isSmall()) {
- var rMidpoint = 1.25;
- [[c1, w / 2], [c2, w / 2], [h / 2, c1], [h / 2, c2]].forEach(function (point) {
- fillEnter.append('circle').attr('class', 'midpoint').attr('cx', point[0]).attr('cy', point[1]).attr('r', rMidpoint);
- });
+ if (colour === '') {
+ outlinkButton = outlinkButton.call(svgIcon('#iD-icon-edit'));
}
- fill = fillEnter.merge(fill);
- fill.selectAll('path.stroke').attr('class', "area stroke ".concat(tagClasses));
- fill.selectAll('path.fill').attr('class', "area fill ".concat(tagClasses));
+ outlinkButton.on('click', function () {
+ return wrap.select('.colour-selector').node().click();
+ }).merge(outlinkButton);
}
- function renderLine(container, drawLine, tagClasses) {
- var line = container.selectAll('.preset-icon-line').data(drawLine ? [0] : []);
- line.exit().remove();
- var lineEnter = line.enter();
- var d = isSmall() ? 40 : 60; // draw the line parametrically
-
- var w = d;
- var h = d;
- var y = Math.round(d * 0.72);
- var l = Math.round(d * 0.6);
- var r = 2.5;
- var x1 = (w - l) / 2;
- var x2 = x1 + l;
- lineEnter = lineEnter.append('svg').attr('class', 'preset-icon-line').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
- ['casing', 'stroke'].forEach(function (klass) {
- lineEnter.append('path').attr('d', "M".concat(x1, " ").concat(y, " L").concat(x2, " ").concat(y)).attr('class', "line ".concat(klass));
- });
- [[x1 - 1, y], [x2 + 1, y]].forEach(function (point) {
- lineEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', r);
- });
- line = lineEnter.merge(line);
- line.selectAll('path.stroke').attr('class', "line stroke ".concat(tagClasses));
- line.selectAll('path.casing').attr('class', "line casing ".concat(tagClasses));
- }
+ function updatePhonePlaceholder() {
+ if (input.empty() || !Object.keys(_phoneFormats).length) return;
+ var extent = combinedEntityExtent();
+ var countryCode = extent && iso1A2Code(extent.center());
- function renderRoute(container, drawRoute, p) {
- var route = container.selectAll('.preset-icon-route').data(drawRoute ? [0] : []);
- route.exit().remove();
- var routeEnter = route.enter();
- var d = isSmall() ? 40 : 60; // draw the route parametrically
+ var format = countryCode && _phoneFormats[countryCode.toLowerCase()];
- var w = d;
- var h = d;
- var y1 = Math.round(d * 0.80);
- var y2 = Math.round(d * 0.68);
- var l = Math.round(d * 0.6);
- var r = 2;
- var x1 = (w - l) / 2;
- var x2 = x1 + l / 3;
- var x3 = x2 + l / 3;
- var x4 = x3 + l / 3;
- routeEnter = routeEnter.append('svg').attr('class', 'preset-icon-route').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
- ['casing', 'stroke'].forEach(function (klass) {
- routeEnter.append('path').attr('d', "M".concat(x1, " ").concat(y1, " L").concat(x2, " ").concat(y2)).attr('class', "segment0 line ".concat(klass));
- routeEnter.append('path').attr('d', "M".concat(x2, " ").concat(y2, " L").concat(x3, " ").concat(y1)).attr('class', "segment1 line ".concat(klass));
- routeEnter.append('path').attr('d', "M".concat(x3, " ").concat(y1, " L").concat(x4, " ").concat(y2)).attr('class', "segment2 line ".concat(klass));
- });
- [[x1, y1], [x2, y2], [x3, y1], [x4, y2]].forEach(function (point) {
- routeEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', r);
- });
- route = routeEnter.merge(route);
+ if (format) input.attr('placeholder', format);
+ }
- if (drawRoute) {
- var routeType = p.tags.type === 'waterway' ? 'waterway' : p.tags.route;
- var segmentPresetIDs = routeSegments[routeType];
+ function validIdentifierValueForLink() {
+ var value = utilGetSetValue(input).trim();
- for (var i in segmentPresetIDs) {
- var segmentPreset = _mainPresetIndex.item(segmentPresetIDs[i]);
- var segmentTagClasses = svgTagClasses().getClassesString(segmentPreset.tags, '');
- route.selectAll("path.stroke.segment".concat(i)).attr('class', "segment".concat(i, " line stroke ").concat(segmentTagClasses));
- route.selectAll("path.casing.segment".concat(i)).attr('class', "segment".concat(i, " line casing ").concat(segmentTagClasses));
+ if (field.type === 'url' && value) {
+ try {
+ return new URL(value).href;
+ } catch (e) {
+ return null;
}
}
- }
-
- function renderSvgIcon(container, picon, geom, isFramed, category, tagClasses) {
- var isMaki = picon && /^maki-/.test(picon);
- var isTemaki = picon && /^temaki-/.test(picon);
- var isFa = picon && /^fa[srb]-/.test(picon);
- var isiDIcon = picon && !(isMaki || isTemaki || isFa);
- var icon = container.selectAll('.preset-icon').data(picon ? [0] : []);
- icon.exit().remove();
- icon = icon.enter().append('div').attr('class', 'preset-icon').call(svgIcon('')).merge(icon);
- icon.attr('class', 'preset-icon ' + (geom ? geom + '-geom' : '')).classed('category', category).classed('framed', isFramed).classed('preset-icon-iD', isiDIcon);
- icon.selectAll('svg').attr('class', 'icon ' + picon + ' ' + (!isiDIcon && geom !== 'line' ? '' : tagClasses));
- var suffix = '';
- if (isMaki) {
- suffix = isSmall() && geom === 'point' ? '-11' : '-15';
+ if (field.type === 'identifier' && field.pattern) {
+ return value && value.match(new RegExp(field.pattern))[0];
}
- icon.selectAll('use').attr('href', '#' + picon + suffix);
- }
+ return null;
+ } // clamp number to min/max
- function renderImageIcon(container, imageURL) {
- var imageIcon = container.selectAll('img.image-icon').data(imageURL ? [0] : []);
- imageIcon.exit().remove();
- imageIcon = imageIcon.enter().append('img').attr('class', 'image-icon').on('load', function () {
- return container.classed('showing-img', true);
- }).on('error', function () {
- return container.classed('showing-img', false);
- }).merge(imageIcon);
- imageIcon.attr('src', imageURL);
- } // Route icons are drawn with a zigzag annotation underneath:
- // o o
- // / \ /
- // o o
- // This dataset defines the styles that are used to draw the zigzag segments.
+ function clamped(num) {
+ if (field.minValue !== undefined) {
+ num = Math.max(num, field.minValue);
+ }
- var routeSegments = {
- bicycle: ['highway/cycleway', 'highway/cycleway', 'highway/cycleway'],
- bus: ['highway/unclassified', 'highway/secondary', 'highway/primary'],
- trolleybus: ['highway/unclassified', 'highway/secondary', 'highway/primary'],
- detour: ['highway/tertiary', 'highway/residential', 'highway/unclassified'],
- ferry: ['route/ferry', 'route/ferry', 'route/ferry'],
- foot: ['highway/footway', 'highway/footway', 'highway/footway'],
- hiking: ['highway/path', 'highway/path', 'highway/path'],
- horse: ['highway/bridleway', 'highway/bridleway', 'highway/bridleway'],
- light_rail: ['railway/light_rail', 'railway/light_rail', 'railway/light_rail'],
- monorail: ['railway/monorail', 'railway/monorail', 'railway/monorail'],
- mtb: ['highway/path', 'highway/track', 'highway/bridleway'],
- pipeline: ['man_made/pipeline', 'man_made/pipeline', 'man_made/pipeline'],
- piste: ['piste/downhill', 'piste/hike', 'piste/nordic'],
- power: ['power/line', 'power/line', 'power/line'],
- road: ['highway/secondary', 'highway/primary', 'highway/trunk'],
- subway: ['railway/subway', 'railway/subway', 'railway/subway'],
- train: ['railway/rail', 'railway/rail', 'railway/rail'],
- tram: ['railway/tram', 'railway/tram', 'railway/tram'],
- waterway: ['waterway/stream', 'waterway/stream', 'waterway/stream']
- };
+ if (field.maxValue !== undefined) {
+ num = Math.min(num, field.maxValue);
+ }
- function render() {
- var p = _preset.apply(this, arguments);
+ return num;
+ }
- var geom = _geometry ? _geometry.apply(this, arguments) : null;
+ function change(onInput) {
+ return function () {
+ var t = {};
+ var val = utilGetSetValue(input);
+ if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
- if (geom === 'relation' && p.tags && (p.tags.type === 'route' && p.tags.route && routeSegments[p.tags.route] || p.tags.type === 'waterway')) {
- geom = 'route';
- }
+ if (!val && Array.isArray(_tags[field.key])) return;
- var showThirdPartyIcons = corePreferences('preferences.privacy.thirdpartyicons') || 'true';
- var isFallback = isSmall() && p.isFallback && p.isFallback();
- var imageURL = showThirdPartyIcons === 'true' && p.imageURL;
- var picon = getIcon(p, geom);
- var isCategory = !p.setTags;
- var drawPoint = picon && geom === 'point' && isSmall() && !isFallback;
- var drawVertex = picon !== null && geom === 'vertex' && (!isSmall() || !isFallback);
- var drawLine = picon && geom === 'line' && !isFallback && !isCategory;
- var drawArea = picon && geom === 'area' && !isFallback && !isCategory;
- var drawRoute = picon && geom === 'route';
- var isFramed = drawVertex || drawArea || drawLine || drawRoute || isCategory;
- var tags = !isCategory ? p.setTags({}, geom) : {};
+ if (!onInput) {
+ if (field.type === 'number' && val) {
+ var vals = val.split(';');
+ vals = vals.map(function (v) {
+ var num = parseFloat(v.trim(), 10);
+ return isFinite(num) ? clamped(num) : v.trim();
+ });
+ val = vals.join(';');
+ }
- for (var k in tags) {
- if (tags[k] === '*') {
- tags[k] = 'yes';
+ utilGetSetValue(input, val);
}
- }
- var tagClasses = svgTagClasses().getClassesString(tags, '');
- var selection = select(this);
- var container = selection.selectAll('.preset-icon-container').data([0]);
- container = container.enter().append('div').attr('class', "preset-icon-container ".concat(_sizeClass)).merge(container);
- container.classed('showing-img', !!imageURL).classed('fallback', isFallback);
- renderCategoryBorder(container, isCategory && p);
- renderPointBorder(container, drawPoint);
- renderCircleFill(container, drawVertex);
- renderSquareFill(container, drawArea, tagClasses);
- renderLine(container, drawLine, tagClasses);
- renderRoute(container, drawRoute, p);
- renderSvgIcon(container, picon, geom, isFramed, isCategory, tagClasses);
- renderImageIcon(container, imageURL);
+ t[field.key] = val || undefined;
+ dispatch.call('change', this, t, onInput);
+ };
}
- presetIcon.preset = function (val) {
- if (!arguments.length) return _preset;
- _preset = utilFunctor(val);
- return presetIcon;
+ i.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return i;
};
- presetIcon.geometry = function (val) {
- if (!arguments.length) return _geometry;
- _geometry = utilFunctor(val);
- return presetIcon;
+ i.tags = function (tags) {
+ _tags = tags;
+ var isMixed = Array.isArray(tags[field.key]);
+ utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder() || _t('inspector.unknown')).classed('mixed', isMixed);
+ if (field.key.split(':').includes('colour')) updateColourPreview();
+
+ if (outlinkButton && !outlinkButton.empty()) {
+ var disabled = !validIdentifierValueForLink();
+ outlinkButton.classed('disabled', disabled);
+ }
};
- presetIcon.sizeClass = function (val) {
- if (!arguments.length) return _sizeClass;
- _sizeClass = val;
- return presetIcon;
+ i.focus = function () {
+ var node = input.node();
+ if (node) node.focus();
};
- return presetIcon;
+ function combinedEntityExtent() {
+ return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
+ }
+
+ return utilRebind(i, dispatch, 'on');
}
- function uiSectionFeatureType(context) {
- var dispatch = dispatch$8('choose');
- var _entityIDs = [];
- var _presets = [];
+ function uiFieldAccess(field, context) {
+ var dispatch = dispatch$8('change');
+ var items = select(null);
- var _tagReference;
+ var _tags;
- var section = uiSection('feature-type', context).label(_t.html('inspector.feature_type')).disclosureContent(renderDisclosureContent);
+ function access(selection) {
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ var list = wrap.selectAll('ul').data([0]);
+ list = list.enter().append('ul').attr('class', 'rows').merge(list);
+ items = list.selectAll('li').data(field.keys); // Enter
- function renderDisclosureContent(selection) {
- selection.classed('preset-list-item', true);
- selection.classed('mixed-types', _presets.length > 1);
- var presetButtonWrap = selection.selectAll('.preset-list-button-wrap').data([0]).enter().append('div').attr('class', 'preset-list-button-wrap');
- var presetButton = presetButtonWrap.append('button').attr('class', 'preset-list-button preset-reset').call(uiTooltip().title(_t.html('inspector.back_tooltip')).placement('bottom'));
- presetButton.append('div').attr('class', 'preset-icon-container');
- presetButton.append('div').attr('class', 'label').append('div').attr('class', 'label-inner');
- presetButtonWrap.append('div').attr('class', 'accessory-buttons');
- var tagReferenceBodyWrap = selection.selectAll('.tag-reference-body-wrap').data([0]);
- tagReferenceBodyWrap = tagReferenceBodyWrap.enter().append('div').attr('class', 'tag-reference-body-wrap').merge(tagReferenceBodyWrap); // update header
+ var enter = items.enter().append('li').attr('class', function (d) {
+ return 'labeled-input preset-access-' + d;
+ });
+ enter.append('span').attr('class', 'label preset-label-access').attr('for', function (d) {
+ return 'preset-input-access-' + d;
+ }).html(function (d) {
+ return field.t.html('types.' + d);
+ });
+ enter.append('div').attr('class', 'preset-input-access-wrap').append('input').attr('type', 'text').attr('class', function (d) {
+ return 'preset-input-access preset-input-access-' + d;
+ }).call(utilNoAuto).each(function (d) {
+ select(this).call(uiCombobox(context, 'access-' + d).data(access.options(d)));
+ }); // Update
- if (_tagReference) {
- selection.selectAll('.preset-list-button-wrap .accessory-buttons').style('display', _presets.length === 1 ? null : 'none').call(_tagReference.button);
- tagReferenceBodyWrap.style('display', _presets.length === 1 ? null : 'none').call(_tagReference.body);
+ items = items.merge(enter);
+ wrap.selectAll('.preset-input-access').on('change', change).on('blur', change);
+ }
+
+ function change(d3_event, d) {
+ var tag = {};
+ var value = context.cleanTagValue(utilGetSetValue(select(this))); // don't override multiple values with blank string
+
+ if (!value && typeof _tags[d] !== 'string') return;
+ tag[d] = value || undefined;
+ dispatch.call('change', this, tag);
+ }
+
+ access.options = function (type) {
+ var options = ['no', 'permissive', 'private', 'permit', 'destination', 'customers', 'unknown'];
+
+ if (type !== 'access') {
+ options.unshift('yes');
+ options.push('designated');
+
+ if (type === 'bicycle') {
+ options.push('dismount');
+ }
+ }
+
+ return options.map(function (option) {
+ return {
+ title: field.t('options.' + option + '.description'),
+ value: option
+ };
+ });
+ };
+
+ var placeholdersByHighway = {
+ footway: {
+ foot: 'designated',
+ motor_vehicle: 'no'
+ },
+ steps: {
+ foot: 'yes',
+ motor_vehicle: 'no',
+ bicycle: 'no',
+ horse: 'no'
+ },
+ pedestrian: {
+ foot: 'yes',
+ motor_vehicle: 'no'
+ },
+ cycleway: {
+ motor_vehicle: 'no',
+ bicycle: 'designated'
+ },
+ bridleway: {
+ motor_vehicle: 'no',
+ horse: 'designated'
+ },
+ path: {
+ foot: 'yes',
+ motor_vehicle: 'no',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ motorway: {
+ foot: 'no',
+ motor_vehicle: 'yes',
+ bicycle: 'no',
+ horse: 'no'
+ },
+ trunk: {
+ motor_vehicle: 'yes'
+ },
+ primary: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ secondary: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ tertiary: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ residential: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ unclassified: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ service: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ motorway_link: {
+ foot: 'no',
+ motor_vehicle: 'yes',
+ bicycle: 'no',
+ horse: 'no'
+ },
+ trunk_link: {
+ motor_vehicle: 'yes'
+ },
+ primary_link: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ secondary_link: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
+ },
+ tertiary_link: {
+ foot: 'yes',
+ motor_vehicle: 'yes',
+ bicycle: 'yes',
+ horse: 'yes'
}
-
- selection.selectAll('.preset-reset').on('click', function () {
- dispatch.call('choose', this, _presets);
- }).on('pointerdown pointerup mousedown mouseup', function (d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- });
- var geometries = entityGeometries();
- selection.select('.preset-list-item button').call(uiPresetIcon().geometry(_presets.length === 1 ? geometries.length === 1 && geometries[0] : null).preset(_presets.length === 1 ? _presets[0] : _mainPresetIndex.item('point')));
- var names = _presets.length === 1 ? [_presets[0].nameLabel(), _presets[0].subtitleLabel()].filter(Boolean) : [_t('inspector.multiple_types')];
- var label = selection.select('.label-inner');
- var nameparts = label.selectAll('.namepart').data(names, function (d) {
- return d;
- });
- nameparts.exit().remove();
- nameparts.enter().append('div').attr('class', 'namepart').html(function (d) {
- return d;
- });
- }
-
- section.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return section;
};
- section.presets = function (val) {
- if (!arguments.length) return _presets; // don't reload the same preset
-
- if (!utilArrayIdentical(val, _presets)) {
- _presets = val;
+ access.tags = function (tags) {
+ _tags = tags;
+ utilGetSetValue(items.selectAll('.preset-input-access'), function (d) {
+ return typeof tags[d] === 'string' ? tags[d] : '';
+ }).classed('mixed', function (d) {
+ return tags[d] && Array.isArray(tags[d]);
+ }).attr('title', function (d) {
+ return tags[d] && Array.isArray(tags[d]) && tags[d].filter(Boolean).join('\n');
+ }).attr('placeholder', function (d) {
+ if (tags[d] && Array.isArray(tags[d])) {
+ return _t('inspector.multiple_values');
+ }
- if (_presets.length === 1) {
- _tagReference = uiTagReference(_presets[0].reference()).showing(false);
+ if (d === 'access') {
+ return 'yes';
}
- }
- return section;
- };
+ if (tags.access && typeof tags.access === 'string') {
+ return tags.access;
+ }
- function entityGeometries() {
- var counts = {};
+ if (tags.highway) {
+ if (typeof tags.highway === 'string') {
+ if (placeholdersByHighway[tags.highway] && placeholdersByHighway[tags.highway][d]) {
+ return placeholdersByHighway[tags.highway][d];
+ }
+ } else {
+ var impliedAccesses = tags.highway.filter(Boolean).map(function (highwayVal) {
+ return placeholdersByHighway[highwayVal] && placeholdersByHighway[highwayVal][d];
+ }).filter(Boolean);
- for (var i in _entityIDs) {
- var geometry = context.graph().geometry(_entityIDs[i]);
- if (!counts[geometry]) counts[geometry] = 0;
- counts[geometry] += 1;
- }
+ if (impliedAccesses.length === tags.highway.length && new Set(impliedAccesses).size === 1) {
+ // if all the highway values have the same implied access for this type then use that
+ return impliedAccesses[0];
+ }
+ }
+ }
- return Object.keys(counts).sort(function (geom1, geom2) {
- return counts[geom2] - counts[geom1];
+ return field.placeholder();
});
- }
+ };
- return utilRebind(section, dispatch, 'on');
- }
+ access.focus = function () {
+ items.selectAll('.preset-input-access').node().focus();
+ };
- // It borrows some code from uiHelp
+ return utilRebind(access, dispatch, 'on');
+ }
- function uiFieldHelp(context, fieldName) {
- var fieldHelp = {};
+ function uiFieldAddress(field, context) {
+ var dispatch = dispatch$8('change');
- var _inspector = select(null);
+ var _selection = select(null);
var _wrap = select(null);
- var _body = select(null);
-
- var fieldHelpKeys = {
- restrictions: [['about', ['about', 'from_via_to', 'maxdist', 'maxvia']], ['inspecting', ['about', 'from_shadow', 'allow_shadow', 'restrict_shadow', 'only_shadow', 'restricted', 'only']], ['modifying', ['about', 'indicators', 'allow_turn', 'restrict_turn', 'only_turn']], ['tips', ['simple', 'simple_example', 'indirect', 'indirect_example', 'indirect_noedit']]]
- };
- var fieldHelpHeadings = {};
- var replacements = {
- distField: _t.html('restriction.controls.distance'),
- viaField: _t.html('restriction.controls.via'),
- fromShadow: icon('#iD-turn-shadow', 'inline shadow from'),
- allowShadow: icon('#iD-turn-shadow', 'inline shadow allow'),
- restrictShadow: icon('#iD-turn-shadow', 'inline shadow restrict'),
- onlyShadow: icon('#iD-turn-shadow', 'inline shadow only'),
- allowTurn: icon('#iD-turn-yes', 'inline turn'),
- restrictTurn: icon('#iD-turn-no', 'inline turn'),
- onlyTurn: icon('#iD-turn-only', 'inline turn')
- }; // For each section, squash all the texts into a single markdown document
+ var addrField = _mainPresetIndex.field('address'); // needed for placeholder strings
- var docs = fieldHelpKeys[fieldName].map(function (key) {
- var helpkey = 'help.field.' + fieldName + '.' + key[0];
- var text = key[1].reduce(function (all, part) {
- var subkey = helpkey + '.' + part;
- var depth = fieldHelpHeadings[subkey]; // is this subkey a heading?
+ var _entityIDs = [];
- var hhh = depth ? Array(depth + 1).join('#') + ' ' : ''; // if so, prepend with some ##'s
+ var _tags;
- return all + hhh + _t.html(subkey, replacements) + '\n\n';
- }, '');
- return {
- key: helpkey,
- title: _t.html(helpkey + '.title'),
- html: marked_1(text.trim())
- };
- });
+ var _countryCode;
- function show() {
- updatePosition();
+ var _addressFormats = [{
+ format: [['housenumber', 'street'], ['city', 'postcode']]
+ }];
+ _mainFileFetcher.get('address_formats').then(function (d) {
+ _addressFormats = d;
- _body.classed('hide', false).style('opacity', '0').transition().duration(200).style('opacity', '1');
- }
+ if (!_selection.empty()) {
+ _selection.call(address);
+ }
+ })["catch"](function () {
+ /* ignore */
+ });
- function hide() {
- _body.classed('hide', true).transition().duration(200).style('opacity', '0').on('end', function () {
- _body.classed('hide', true);
+ function getNearStreets() {
+ var extent = combinedEntityExtent();
+ var l = extent.center();
+ var box = geoExtent(l).padByMeters(200);
+ var streets = context.history().intersects(box).filter(isAddressable).map(function (d) {
+ var loc = context.projection([(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]);
+ var choice = geoChooseEdge(context.graph().childNodes(d), loc, context.projection);
+ return {
+ title: d.tags.name,
+ value: d.tags.name,
+ dist: choice.distance
+ };
+ }).sort(function (a, b) {
+ return a.dist - b.dist;
});
- }
+ return utilArrayUniqBy(streets, 'value');
- function clickHelp(index) {
- var d = docs[index];
- var tkeys = fieldHelpKeys[fieldName][index][1];
+ function isAddressable(d) {
+ return d.tags.highway && d.tags.name && d.type === 'way';
+ }
+ }
- _body.selectAll('.field-help-nav-item').classed('active', function (d, i) {
- return i === index;
+ function getNearCities() {
+ var extent = combinedEntityExtent();
+ var l = extent.center();
+ var box = geoExtent(l).padByMeters(200);
+ var cities = context.history().intersects(box).filter(isAddressable).map(function (d) {
+ return {
+ title: d.tags['addr:city'] || d.tags.name,
+ value: d.tags['addr:city'] || d.tags.name,
+ dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
+ };
+ }).sort(function (a, b) {
+ return a.dist - b.dist;
});
+ return utilArrayUniqBy(cities, 'value');
- var content = _body.selectAll('.field-help-content').html(d.html); // class the paragraphs so we can find and style them
-
-
- content.selectAll('p').attr('class', function (d, i) {
- return tkeys[i];
- }); // insert special content for certain help sections
+ function isAddressable(d) {
+ if (d.tags.name) {
+ if (d.tags.admin_level === '8' && d.tags.boundary === 'administrative') return true;
+ if (d.tags.border_type === 'city') return true;
+ if (d.tags.place === 'city' || d.tags.place === 'town' || d.tags.place === 'village') return true;
+ }
- if (d.key === 'help.field.restrictions.inspecting') {
- content.insert('img', 'p.from_shadow').attr('class', 'field-help-image cf').attr('src', context.imagePath('tr_inspect.gif'));
- } else if (d.key === 'help.field.restrictions.modifying') {
- content.insert('img', 'p.allow_turn').attr('class', 'field-help-image cf').attr('src', context.imagePath('tr_modify.gif'));
+ if (d.tags['addr:city']) return true;
+ return false;
}
}
- fieldHelp.button = function (selection) {
- if (_body.empty()) return;
- var button = selection.selectAll('.field-help-button').data([0]); // enter/update
-
- button.enter().append('button').attr('class', 'field-help-button').call(svgIcon('#iD-icon-help')).merge(button).on('click', function (d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
-
- if (_body.classed('hide')) {
- show();
- } else {
- hide();
- }
+ function getNearValues(key) {
+ var extent = combinedEntityExtent();
+ var l = extent.center();
+ var box = geoExtent(l).padByMeters(200);
+ var results = context.history().intersects(box).filter(function hasTag(d) {
+ return _entityIDs.indexOf(d.id) === -1 && d.tags[key];
+ }).map(function (d) {
+ return {
+ title: d.tags[key],
+ value: d.tags[key],
+ dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
+ };
+ }).sort(function (a, b) {
+ return a.dist - b.dist;
});
- };
-
- function updatePosition() {
- var wrap = _wrap.node();
-
- var inspector = _inspector.node();
-
- var wRect = wrap.getBoundingClientRect();
- var iRect = inspector.getBoundingClientRect();
-
- _body.style('top', wRect.top + inspector.scrollTop - iRect.top + 'px');
+ return utilArrayUniqBy(results, 'value');
}
- fieldHelp.body = function (selection) {
- // This control expects the field to have a form-field-input-wrap div
- _wrap = selection.selectAll('.form-field-input-wrap');
- if (_wrap.empty()) return; // absolute position relative to the inspector, so it "floats" above the fields
-
- _inspector = context.container().select('.sidebar .entity-editor-pane .inspector-body');
- if (_inspector.empty()) return;
- _body = _inspector.selectAll('.field-help-body').data([0]);
-
- var enter = _body.enter().append('div').attr('class', 'field-help-body hide'); // initially hidden
-
+ function updateForCountryCode() {
+ if (!_countryCode) return;
+ var addressFormat;
- var titleEnter = enter.append('div').attr('class', 'field-help-title cf');
- titleEnter.append('h2').attr('class', _mainLocalizer.textDirection() === 'rtl' ? 'fr' : 'fl').html(_t.html('help.field.' + fieldName + '.title'));
- titleEnter.append('button').attr('class', 'fr close').on('click', function (d3_event) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- hide();
- }).call(svgIcon('#iD-icon-close'));
- var navEnter = enter.append('div').attr('class', 'field-help-nav cf');
- var titles = docs.map(function (d) {
- return d.title;
- });
- navEnter.selectAll('.field-help-nav-item').data(titles).enter().append('div').attr('class', 'field-help-nav-item').html(function (d) {
- return d;
- }).on('click', function (d3_event, d) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- clickHelp(titles.indexOf(d));
- });
- enter.append('div').attr('class', 'field-help-content');
- _body = _body.merge(enter);
- clickHelp(0);
- };
+ for (var i = 0; i < _addressFormats.length; i++) {
+ var format = _addressFormats[i];
- return fieldHelp;
- }
+ if (!format.countryCodes) {
+ addressFormat = format; // choose the default format, keep going
+ } else if (format.countryCodes.indexOf(_countryCode) !== -1) {
+ addressFormat = format; // choose the country format, stop here
- function uiFieldCheck(field, context) {
- var dispatch = dispatch$8('change');
- var options = field.options;
- var values = [];
- var texts = [];
+ break;
+ }
+ }
- var _tags;
+ var dropdowns = addressFormat.dropdowns || ['city', 'county', 'country', 'district', 'hamlet', 'neighbourhood', 'place', 'postcode', 'province', 'quarter', 'state', 'street', 'subdistrict', 'suburb'];
+ var widths = addressFormat.widths || {
+ housenumber: 1 / 3,
+ street: 2 / 3,
+ city: 2 / 3,
+ state: 1 / 4,
+ postcode: 1 / 3
+ };
- var input = select(null);
- var text = select(null);
- var label = select(null);
- var reverser = select(null);
+ function row(r) {
+ // Normalize widths.
+ var total = r.reduce(function (sum, key) {
+ return sum + (widths[key] || 0.5);
+ }, 0);
+ return r.map(function (key) {
+ return {
+ id: key,
+ width: (widths[key] || 0.5) / total
+ };
+ });
+ }
- var _impliedYes;
+ var rows = _wrap.selectAll('.addr-row').data(addressFormat.format, function (d) {
+ return d.toString();
+ });
- var _entityIDs = [];
+ rows.exit().remove();
+ rows.enter().append('div').attr('class', 'addr-row').selectAll('input').data(row).enter().append('input').property('type', 'text').call(updatePlaceholder).attr('class', function (d) {
+ return 'addr-' + d.id;
+ }).call(utilNoAuto).each(addDropdown).style('width', function (d) {
+ return d.width * 100 + '%';
+ });
- var _value;
+ function addDropdown(d) {
+ if (dropdowns.indexOf(d.id) === -1) return; // not a dropdown
- if (options) {
- for (var i in options) {
- var v = options[i];
- values.push(v === 'undefined' ? undefined : v);
- texts.push(field.t.html('options.' + v, {
- 'default': v
+ var nearValues = d.id === 'street' ? getNearStreets : d.id === 'city' ? getNearCities : getNearValues;
+ select(this).call(uiCombobox(context, 'address-' + d.id).minItems(1).caseSensitive(true).fetcher(function (value, callback) {
+ callback(nearValues('addr:' + d.id));
}));
}
- } else {
- values = [undefined, 'yes'];
- texts = [_t.html('inspector.unknown'), _t.html('inspector.check.yes')];
-
- if (field.type !== 'defaultCheck') {
- values.push('no');
- texts.push(_t.html('inspector.check.no'));
- }
- } // Checks tags to see whether an undefined value is "Assumed to be Yes"
-
-
- function checkImpliedYes() {
- _impliedYes = field.id === 'oneway_yes'; // hack: pretend `oneway` field is a `oneway_yes` field
- // where implied oneway tag exists (e.g. `junction=roundabout`) #2220, #1841
-
- if (field.id === 'oneway') {
- var entity = context.entity(_entityIDs[0]);
- for (var key in entity.tags) {
- if (key in osmOneWayTags && entity.tags[key] in osmOneWayTags[key]) {
- _impliedYes = true;
- texts[0] = _t.html('_tagging.presets.fields.oneway_yes.options.undefined');
- break;
- }
- }
- }
- }
+ _wrap.selectAll('input').on('blur', change()).on('change', change());
- function reverserHidden() {
- if (!context.container().select('div.inspector-hover').empty()) return true;
- return !(_value === 'yes' || _impliedYes && !_value);
- }
+ _wrap.selectAll('input:not(.combobox-input)').on('input', change(true));
- function reverserSetText(selection) {
- var entity = _entityIDs.length && context.hasEntity(_entityIDs[0]);
- if (reverserHidden() || !entity) return selection;
- var first = entity.first();
- var last = entity.isClosed() ? entity.nodes[entity.nodes.length - 2] : entity.last();
- var pseudoDirection = first < last;
- var icon = pseudoDirection ? '#iD-icon-forward' : '#iD-icon-backward';
- selection.selectAll('.reverser-span').html(_t.html('inspector.check.reverser')).call(svgIcon(icon, 'inline'));
- return selection;
+ if (_tags) updateTags(_tags);
}
- var check = function check(selection) {
- checkImpliedYes();
- label = selection.selectAll('.form-field-input-wrap').data([0]);
- var enter = label.enter().append('label').attr('class', 'form-field-input-wrap form-field-input-check');
- enter.append('input').property('indeterminate', field.type !== 'defaultCheck').attr('type', 'checkbox').attr('id', field.domId);
- enter.append('span').html(texts[0]).attr('class', 'value');
-
- if (field.type === 'onewayCheck') {
- enter.append('button').attr('class', 'reverser' + (reverserHidden() ? ' hide' : '')).append('span').attr('class', 'reverser-span');
- }
+ function address(selection) {
+ _selection = selection;
+ _wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ _wrap = _wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(_wrap);
+ var extent = combinedEntityExtent();
- label = label.merge(enter);
- input = label.selectAll('input');
- text = label.selectAll('span.value');
- input.on('click', function (d3_event) {
- d3_event.stopPropagation();
- var t = {};
+ if (extent) {
+ var countryCode;
- if (Array.isArray(_tags[field.key])) {
- if (values.indexOf('yes') !== -1) {
- t[field.key] = 'yes';
- } else {
- t[field.key] = values[0];
- }
+ if (context.inIntro()) {
+ // localize the address format for the walkthrough
+ countryCode = _t('intro.graph.countrycode');
} else {
- t[field.key] = values[(values.indexOf(_value) + 1) % values.length];
- } // Don't cycle through `alternating` or `reversible` states - #4970
- // (They are supported as translated strings, but should not toggle with clicks)
-
-
- if (t[field.key] === 'reversible' || t[field.key] === 'alternating') {
- t[field.key] = values[0];
+ var center = extent.center();
+ countryCode = iso1A2Code(center);
}
- dispatch.call('change', this, t);
- });
+ if (countryCode) {
+ _countryCode = countryCode.toLowerCase();
+ updateForCountryCode();
+ }
+ }
+ }
- if (field.type === 'onewayCheck') {
- reverser = label.selectAll('.reverser');
- reverser.call(reverserSetText).on('click', function (d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- context.perform(function (graph) {
- for (var i in _entityIDs) {
- graph = actionReverse(_entityIDs[i])(graph);
- }
+ function change(onInput) {
+ return function () {
+ var tags = {};
- return graph;
- }, _t('operations.reverse.annotation.line', {
- n: 1
- })); // must manually revalidate since no 'change' event was called
+ _wrap.selectAll('input').each(function (subfield) {
+ var key = field.key + ':' + subfield.id;
+ var value = this.value;
+ if (!onInput) value = context.cleanTagValue(value); // don't override multiple values with blank string
- context.validator().validate();
- select(this).call(reverserSetText);
+ if (Array.isArray(_tags[key]) && !value) return;
+ tags[key] = value || undefined;
});
- }
- };
- check.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return check;
- };
-
- check.tags = function (tags) {
- _tags = tags;
+ dispatch.call('change', this, tags, onInput);
+ };
+ }
- function isChecked(val) {
- return val !== 'no' && val !== '' && val !== undefined && val !== null;
- }
+ function updatePlaceholder(inputSelection) {
+ return inputSelection.attr('placeholder', function (subfield) {
+ if (_tags && Array.isArray(_tags[field.key + ':' + subfield.id])) {
+ return _t('inspector.multiple_values');
+ }
- function textFor(val) {
- if (val === '') val = undefined;
- var index = values.indexOf(val);
- return index !== -1 ? texts[index] : '"' + val + '"';
- }
+ if (_countryCode) {
+ var localkey = subfield.id + '!' + _countryCode;
+ var tkey = addrField.hasTextForStringId('placeholders.' + localkey) ? localkey : subfield.id;
+ return addrField.t('placeholders.' + tkey);
+ }
+ });
+ }
- checkImpliedYes();
- var isMixed = Array.isArray(tags[field.key]);
- _value = !isMixed && tags[field.key] && tags[field.key].toLowerCase();
+ function updateTags(tags) {
+ utilGetSetValue(_wrap.selectAll('input'), function (subfield) {
+ var val = tags[field.key + ':' + subfield.id];
+ return typeof val === 'string' ? val : '';
+ }).attr('title', function (subfield) {
+ var val = tags[field.key + ':' + subfield.id];
+ return val && Array.isArray(val) ? val.filter(Boolean).join('\n') : undefined;
+ }).classed('mixed', function (subfield) {
+ return Array.isArray(tags[field.key + ':' + subfield.id]);
+ }).call(updatePlaceholder);
+ }
- if (field.type === 'onewayCheck' && (_value === '1' || _value === '-1')) {
- _value = 'yes';
- }
+ function combinedEntityExtent() {
+ return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
+ }
- input.property('indeterminate', isMixed || field.type !== 'defaultCheck' && !_value).property('checked', isChecked(_value));
- text.html(isMixed ? _t.html('inspector.multiple_values') : textFor(_value)).classed('mixed', isMixed);
- label.classed('set', !!_value);
+ address.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return address;
+ };
- if (field.type === 'onewayCheck') {
- reverser.classed('hide', reverserHidden()).call(reverserSetText);
- }
+ address.tags = function (tags) {
+ _tags = tags;
+ updateTags(tags);
};
- check.focus = function () {
- input.node().focus();
+ address.focus = function () {
+ var node = _wrap.selectAll('input').node();
+
+ if (node) node.focus();
};
- return utilRebind(check, dispatch, 'on');
+ return utilRebind(address, dispatch, 'on');
}
- function uiFieldCombo(field, context) {
+ function uiFieldCycleway(field, context) {
var dispatch = dispatch$8('change');
+ var items = select(null);
+ var wrap = select(null);
- var _isMulti = field.type === 'multiCombo' || field.type === 'manyCombo';
-
- var _isNetwork = field.type === 'networkCombo';
-
- var _isSemi = field.type === 'semiCombo';
+ var _tags;
- var _optarray = field.options;
+ function cycleway(selection) {
+ function stripcolon(s) {
+ return s.replace(':', '');
+ }
- var _showTagInfoSuggestions = field.type !== 'manyCombo' && field.autoSuggestions !== false;
+ wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ var div = wrap.selectAll('ul').data([0]);
+ div = div.enter().append('ul').attr('class', 'rows').merge(div);
+ var keys = ['cycleway:left', 'cycleway:right'];
+ items = div.selectAll('li').data(keys);
+ var enter = items.enter().append('li').attr('class', function (d) {
+ return 'labeled-input preset-cycleway-' + stripcolon(d);
+ });
+ enter.append('span').attr('class', 'label preset-label-cycleway').attr('for', function (d) {
+ return 'preset-input-cycleway-' + stripcolon(d);
+ }).html(function (d) {
+ return field.t.html('types.' + d);
+ });
+ enter.append('div').attr('class', 'preset-input-cycleway-wrap').append('input').attr('type', 'text').attr('class', function (d) {
+ return 'preset-input-cycleway preset-input-' + stripcolon(d);
+ }).call(utilNoAuto).each(function (d) {
+ select(this).call(uiCombobox(context, 'cycleway-' + stripcolon(d)).data(cycleway.options(d)));
+ });
+ items = items.merge(enter); // Update
- var _allowCustomValues = field.type !== 'manyCombo' && field.customValues !== false;
+ wrap.selectAll('.preset-input-cycleway').on('change', change).on('blur', change);
+ }
- var _snake_case = field.snake_case || field.snake_case === undefined;
+ function change(d3_event, key) {
+ var newValue = context.cleanTagValue(utilGetSetValue(select(this))); // don't override multiple values with blank string
- var _combobox = uiCombobox(context, 'combo-' + field.safeid).caseSensitive(field.caseSensitive).minItems(_isMulti || _isSemi ? 1 : 2);
+ if (!newValue && (Array.isArray(_tags.cycleway) || Array.isArray(_tags[key]))) return;
- var _container = select(null);
+ if (newValue === 'none' || newValue === '') {
+ newValue = undefined;
+ }
- var _inputWrap = select(null);
+ var otherKey = key === 'cycleway:left' ? 'cycleway:right' : 'cycleway:left';
+ var otherValue = typeof _tags.cycleway === 'string' ? _tags.cycleway : _tags[otherKey];
- var _input = select(null);
+ if (otherValue && Array.isArray(otherValue)) {
+ // we must always have an explicit value for comparison
+ otherValue = otherValue[0];
+ }
- var _comboData = [];
- var _multiData = [];
- var _entityIDs = [];
+ if (otherValue === 'none' || otherValue === '') {
+ otherValue = undefined;
+ }
- var _tags;
+ var tag = {}; // If the left and right tags match, use the cycleway tag to tag both
+ // sides the same way
- var _countryCode;
+ if (newValue === otherValue) {
+ tag = {
+ cycleway: newValue,
+ 'cycleway:left': undefined,
+ 'cycleway:right': undefined
+ };
+ } else {
+ // Always set both left and right as changing one can affect the other
+ tag = {
+ cycleway: undefined
+ };
+ tag[key] = newValue;
+ tag[otherKey] = otherValue;
+ }
- var _staticPlaceholder; // initialize deprecated tags array
+ dispatch.call('change', this, tag);
+ }
+ cycleway.options = function () {
+ return field.options.map(function (option) {
+ return {
+ title: field.t('options.' + option + '.description'),
+ value: option
+ };
+ });
+ };
- var _dataDeprecated = [];
- _mainFileFetcher.get('deprecated').then(function (d) {
- _dataDeprecated = d;
- })["catch"](function () {
- /* ignore */
- }); // ensure multiCombo field.key ends with a ':'
+ cycleway.tags = function (tags) {
+ _tags = tags; // If cycleway is set, use that instead of individual values
- if (_isMulti && field.key && /[^:]$/.test(field.key)) {
- field.key += ':';
- }
+ var commonValue = typeof tags.cycleway === 'string' && tags.cycleway;
+ utilGetSetValue(items.selectAll('.preset-input-cycleway'), function (d) {
+ if (commonValue) return commonValue;
+ return !tags.cycleway && typeof tags[d] === 'string' ? tags[d] : '';
+ }).attr('title', function (d) {
+ if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
+ var vals = [];
- function snake(s) {
- return s.replace(/\s+/g, '_').toLowerCase();
- }
+ if (Array.isArray(tags.cycleway)) {
+ vals = vals.concat(tags.cycleway);
+ }
- function clean(s) {
- return s.split(';').map(function (s) {
- return s.trim();
- }).join(';');
- } // returns the tag value for a display value
- // (for multiCombo, dval should be the key suffix, not the entire key)
+ if (Array.isArray(tags[d])) {
+ vals = vals.concat(tags[d]);
+ }
+ return vals.filter(Boolean).join('\n');
+ }
- function tagValue(dval) {
- dval = clean(dval || '');
+ return null;
+ }).attr('placeholder', function (d) {
+ if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
+ return _t('inspector.multiple_values');
+ }
- var found = _comboData.find(function (o) {
- return o.key && clean(o.value) === dval;
+ return field.placeholder();
+ }).classed('mixed', function (d) {
+ return Array.isArray(tags.cycleway) || Array.isArray(tags[d]);
});
+ };
- if (found) return found.key;
-
- if (field.type === 'typeCombo' && !dval) {
- return 'yes';
- }
+ cycleway.focus = function () {
+ var node = wrap.selectAll('input').node();
+ if (node) node.focus();
+ };
- return (_snake_case ? snake(dval) : dval) || undefined;
- } // returns the display value for a tag value
- // (for multiCombo, tval should be the key suffix, not the entire key)
+ return utilRebind(cycleway, dispatch, 'on');
+ }
+ function uiFieldLanes(field, context) {
+ var dispatch = dispatch$8('change');
+ var LANE_WIDTH = 40;
+ var LANE_HEIGHT = 200;
+ var _entityIDs = [];
- function displayValue(tval) {
- tval = tval || '';
+ function lanes(selection) {
+ var lanesData = context.entity(_entityIDs[0]).lanes();
- if (field.hasTextForStringId('options.' + tval)) {
- return field.t('options.' + tval, {
- "default": tval
- });
+ if (!context.container().select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode) {
+ selection.call(lanes.off);
+ return;
}
- if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
- return '';
- }
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ var surface = wrap.selectAll('.surface').data([0]);
+ var d = utilGetDimensions(wrap);
+ var freeSpace = d[0] - lanesData.lanes.length * LANE_WIDTH * 1.5 + LANE_WIDTH * 0.5;
+ surface = surface.enter().append('svg').attr('width', d[0]).attr('height', 300).attr('class', 'surface').merge(surface);
+ var lanesSelection = surface.selectAll('.lanes').data([0]);
+ lanesSelection = lanesSelection.enter().append('g').attr('class', 'lanes').merge(lanesSelection);
+ lanesSelection.attr('transform', function () {
+ return 'translate(' + freeSpace / 2 + ', 0)';
+ });
+ var lane = lanesSelection.selectAll('.lane').data(lanesData.lanes);
+ lane.exit().remove();
+ var enter = lane.enter().append('g').attr('class', 'lane');
+ enter.append('g').append('rect').attr('y', 50).attr('width', LANE_WIDTH).attr('height', LANE_HEIGHT);
+ enter.append('g').attr('class', 'forward').append('text').attr('y', 40).attr('x', 14).text('▲');
+ enter.append('g').attr('class', 'bothways').append('text').attr('y', 40).attr('x', 14).text('▲▼');
+ enter.append('g').attr('class', 'backward').append('text').attr('y', 40).attr('x', 14).text('▼');
+ lane = lane.merge(enter);
+ lane.attr('transform', function (d) {
+ return 'translate(' + LANE_WIDTH * d.index * 1.5 + ', 0)';
+ });
+ lane.select('.forward').style('visibility', function (d) {
+ return d.direction === 'forward' ? 'visible' : 'hidden';
+ });
+ lane.select('.bothways').style('visibility', function (d) {
+ return d.direction === 'bothways' ? 'visible' : 'hidden';
+ });
+ lane.select('.backward').style('visibility', function (d) {
+ return d.direction === 'backward' ? 'visible' : 'hidden';
+ });
+ }
- return tval;
- } // Compute the difference between arrays of objects by `value` property
- //
- // objectDifference([{value:1}, {value:2}, {value:3}], [{value:2}])
- // > [{value:1}, {value:3}]
- //
+ lanes.entityIDs = function (val) {
+ _entityIDs = val;
+ };
+ lanes.tags = function () {};
- function objectDifference(a, b) {
- return a.filter(function (d1) {
- return !b.some(function (d2) {
- return !d2.isMixed && d1.value === d2.value;
- });
- });
- }
+ lanes.focus = function () {};
- function initCombo(selection, attachTo) {
- if (!_allowCustomValues) {
- selection.attr('readonly', 'readonly');
- }
+ lanes.off = function () {};
- if (_showTagInfoSuggestions && services.taginfo) {
- selection.call(_combobox.fetcher(setTaginfoValues), attachTo);
- setTaginfoValues('', setPlaceholder);
- } else {
- selection.call(_combobox, attachTo);
- setStaticValues(setPlaceholder);
- }
- }
+ return utilRebind(lanes, dispatch, 'on');
+ }
+ uiFieldLanes.supportsMultiselection = false;
- function setStaticValues(callback) {
- if (!_optarray) return;
- _comboData = _optarray.map(function (v) {
- return {
- key: v,
- value: field.t('options.' + v, {
- "default": v
- }),
- title: v,
- display: field.t.html('options.' + v, {
- "default": v
- }),
- klass: field.hasTextForStringId('options.' + v) ? '' : 'raw-option'
- };
- });
+ var _languagesArray = [];
+ function uiFieldLocalized(field, context) {
+ var dispatch = dispatch$8('change', 'input');
+ var wikipedia = services.wikipedia;
+ var input = select(null);
+ var localizedInputs = select(null);
- _combobox.data(objectDifference(_comboData, _multiData));
+ var _countryCode;
- if (callback) callback(_comboData);
- }
+ var _tags; // A concern here in switching to async data means that _languagesArray will not
+ // be available the first time through, so things like the fetchers and
+ // the language() function will not work immediately.
- function setTaginfoValues(q, callback) {
- var fn = _isMulti ? 'multikeys' : 'values';
- var query = (_isMulti ? field.key : '') + q;
- var hasCountryPrefix = _isNetwork && _countryCode && _countryCode.indexOf(q.toLowerCase()) === 0;
- if (hasCountryPrefix) {
- query = _countryCode + ':';
- }
+ _mainFileFetcher.get('languages').then(loadLanguagesArray)["catch"](function () {
+ /* ignore */
+ });
+ var _territoryLanguages = {};
+ _mainFileFetcher.get('territory_languages').then(function (d) {
+ _territoryLanguages = d;
+ })["catch"](function () {
+ /* ignore */
+ }); // reuse these combos
- var params = {
- debounce: q !== '',
- key: field.key,
- query: query
- };
+ var langCombo = uiCombobox(context, 'localized-lang').fetcher(fetchLanguages).minItems(0);
- if (_entityIDs.length) {
- params.geometry = context.graph().geometry(_entityIDs[0]);
- }
+ var _selection = select(null);
- services.taginfo[fn](params, function (err, data) {
- if (err) return;
- data = data.filter(function (d) {
- if (field.type === 'typeCombo' && d.value === 'yes') {
- // don't show the fallback value
- return false;
- } // don't show values with very low usage
+ var _multilingual = [];
+ var _buttonTip = uiTooltip().title(_t.html('translate.translate')).placement('left');
- return !d.count || d.count > 10;
- });
- var deprecatedValues = osmEntity.deprecatedTagValuesByKey(_dataDeprecated)[field.key];
+ var _wikiTitles;
- if (deprecatedValues) {
- // don't suggest deprecated tag values
- data = data.filter(function (d) {
- return deprecatedValues.indexOf(d.value) === -1;
- });
- }
+ var _entityIDs = [];
- if (hasCountryPrefix) {
- data = data.filter(function (d) {
- return d.value.toLowerCase().indexOf(_countryCode + ':') === 0;
- });
- } // hide the caret if there are no suggestions
+ function loadLanguagesArray(dataLanguages) {
+ if (_languagesArray.length !== 0) return; // some conversion is needed to ensure correct OSM tags are used
+ var replacements = {
+ sr: 'sr-Cyrl',
+ // in OSM, `sr` implies Cyrillic
+ 'sr-Cyrl': false // `sr-Cyrl` isn't used in OSM
- _container.classed('empty-combobox', data.length === 0);
+ };
- _comboData = data.map(function (d) {
- var k = d.value;
- if (_isMulti) k = k.replace(field.key, '');
- var label = field.t('options.' + k, {
- "default": k
- });
- return {
- key: k,
- value: label,
- display: field.t.html('options.' + k, {
- "default": k
- }),
- title: d.title || label,
- klass: field.hasTextForStringId('options.' + k) ? '' : 'raw-option'
- };
- });
- _comboData = objectDifference(_comboData, _multiData);
- if (callback) callback(_comboData);
- });
- }
+ for (var code in dataLanguages) {
+ if (replacements[code] === false) continue;
+ var metaCode = code;
+ if (replacements[code]) metaCode = replacements[code];
- function setPlaceholder(values) {
- if (_isMulti || _isSemi) {
- _staticPlaceholder = field.placeholder() || _t('inspector.add');
- } else {
- var vals = values.map(function (d) {
- return d.value;
- }).filter(function (s) {
- return s.length < 20;
- });
- var placeholders = vals.length > 1 ? vals : values.map(function (d) {
- return d.key;
+ _languagesArray.push({
+ localName: _mainLocalizer.languageName(metaCode, {
+ localOnly: true
+ }),
+ nativeName: dataLanguages[metaCode].nativeName,
+ code: code,
+ label: _mainLocalizer.languageName(metaCode)
});
- _staticPlaceholder = field.placeholder() || placeholders.slice(0, 3).join(', ');
}
+ }
- if (!/(…|\.\.\.)$/.test(_staticPlaceholder)) {
- _staticPlaceholder += '…';
- }
+ function calcLocked() {
+ // Protect name field for suggestion presets that don't display a brand/operator field
+ var isLocked = field.id === 'name' && _entityIDs.length && _entityIDs.some(function (entityID) {
+ var entity = context.graph().hasEntity(entityID);
+ if (!entity) return false; // Features linked to Wikidata are likely important and should be protected
- var ph;
+ if (entity.tags.wikidata) return true; // Assume the name has already been confirmed if its source has been researched
- if (!_isMulti && !_isSemi && _tags && Array.isArray(_tags[field.key])) {
- ph = _t('inspector.multiple_values');
- } else {
- ph = _staticPlaceholder;
- }
+ if (entity.tags['name:etymology:wikidata']) return true; // Lock the `name` if this is a suggestion preset that assigns the name,
+ // and the preset does not display a `brand` or `operator` field.
+ // (For presets like hotels, car dealerships, post offices, the `name` should remain editable)
+ // see also similar logic in `outdated_tags.js`
- _container.selectAll('input').attr('placeholder', ph);
- }
+ var preset = _mainPresetIndex.match(entity, context.graph());
- function change() {
- var t = {};
- var val;
+ if (preset) {
+ var isSuggestion = preset.suggestion;
+ var fields = preset.fields();
+ var showsBrandField = fields.some(function (d) {
+ return d.id === 'brand';
+ });
+ var showsOperatorField = fields.some(function (d) {
+ return d.id === 'operator';
+ });
+ var setsName = preset.addTags.name;
+ var setsBrandWikidata = preset.addTags['brand:wikidata'];
+ var setsOperatorWikidata = preset.addTags['operator:wikidata'];
+ return isSuggestion && setsName && (setsBrandWikidata && !showsBrandField || setsOperatorWikidata && !showsOperatorField);
+ }
- if (_isMulti || _isSemi) {
- val = tagValue(utilGetSetValue(_input).replace(/,/g, ';')) || '';
+ return false;
+ });
- _container.classed('active', false);
+ field.locked(isLocked);
+ } // update _multilingual, maintaining the existing order
- utilGetSetValue(_input, '');
- var vals = val.split(';').filter(Boolean);
- if (!vals.length) return;
- if (_isMulti) {
- utilArrayUniq(vals).forEach(function (v) {
- var key = (field.key || '') + v;
+ function calcMultilingual(tags) {
+ var existingLangsOrdered = _multilingual.map(function (item) {
+ return item.lang;
+ });
- if (_tags) {
- // don't set a multicombo value to 'yes' if it already has a non-'no' value
- // e.g. `language:de=main`
- var old = _tags[key];
- if (typeof old === 'string' && old.toLowerCase() !== 'no') return;
- }
+ var existingLangs = new Set(existingLangsOrdered.filter(Boolean));
- key = context.cleanTagKey(key);
- field.keys.push(key);
- t[key] = 'yes';
- });
- } else if (_isSemi) {
- var arr = _multiData.map(function (d) {
- return d.key;
- });
+ for (var k in tags) {
+ var m = k.match(/^(.*):(.*)$/);
- arr = arr.concat(vals);
- t[field.key] = context.cleanTagValue(utilArrayUniq(arr).filter(Boolean).join(';'));
- }
+ if (m && m[1] === field.key && m[2]) {
+ var item = {
+ lang: m[2],
+ value: tags[k]
+ };
- window.setTimeout(function () {
- _input.node().focus();
- }, 10);
- } else {
- var rawValue = utilGetSetValue(_input); // don't override multiple values with blank string
+ if (existingLangs.has(item.lang)) {
+ // update the value
+ _multilingual[existingLangsOrdered.indexOf(item.lang)].value = item.value;
+ existingLangs["delete"](item.lang);
+ } else {
+ _multilingual.push(item);
+ }
+ }
+ } // Don't remove items based on deleted tags, since this makes the UI
+ // disappear unexpectedly when clearing values - #8164
- if (!rawValue && Array.isArray(_tags[field.key])) return;
- val = context.cleanTagValue(tagValue(rawValue));
- t[field.key] = val || undefined;
- }
- dispatch.call('change', this, t);
+ _multilingual.forEach(function (item) {
+ if (item.lang && existingLangs.has(item.lang)) {
+ item.value = '';
+ }
+ });
}
- function removeMultikey(d3_event, d) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- var t = {};
-
- if (_isMulti) {
- t[d.key] = undefined;
- } else if (_isSemi) {
- var arr = _multiData.map(function (md) {
- return md.key === d.key ? null : md.key;
- }).filter(Boolean);
+ function localized(selection) {
+ _selection = selection;
+ calcLocked();
+ var isLocked = field.locked();
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]); // enter/update
- arr = utilArrayUniq(arr);
- t[field.key] = arr.length ? arr.join(';') : undefined;
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ input = wrap.selectAll('.localized-main').data([0]); // enter/update
+
+ input = input.enter().append('input').attr('type', 'text').attr('id', field.domId).attr('class', 'localized-main').call(utilNoAuto).merge(input);
+ input.classed('disabled', !!isLocked).attr('readonly', isLocked || null).on('input', change(true)).on('blur', change()).on('change', change());
+ var translateButton = wrap.selectAll('.localized-add').data([0]);
+ translateButton = translateButton.enter().append('button').attr('class', 'localized-add form-field-button').attr('aria-label', _t('icons.plus')).call(svgIcon('#iD-icon-plus')).merge(translateButton);
+ translateButton.classed('disabled', !!isLocked).call(isLocked ? _buttonTip.destroy : _buttonTip).on('click', addNew);
+
+ if (_tags && !_multilingual.length) {
+ calcMultilingual(_tags);
}
- dispatch.call('change', this, t);
- }
+ localizedInputs = selection.selectAll('.localized-multilingual').data([0]);
+ localizedInputs = localizedInputs.enter().append('div').attr('class', 'localized-multilingual').merge(localizedInputs);
+ localizedInputs.call(renderMultilingual);
+ localizedInputs.selectAll('button, input').classed('disabled', !!isLocked).attr('readonly', isLocked || null);
+ selection.selectAll('.combobox-caret').classed('nope', true);
- function combo(selection) {
- _container = selection.selectAll('.form-field-input-wrap').data([0]);
- var type = _isMulti || _isSemi ? 'multicombo' : 'combo';
- _container = _container.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + type).merge(_container);
+ function addNew(d3_event) {
+ d3_event.preventDefault();
+ if (field.locked()) return;
+ var defaultLang = _mainLocalizer.languageCode().toLowerCase();
- if (_isMulti || _isSemi) {
- _container = _container.selectAll('.chiplist').data([0]);
- var listClass = 'chiplist'; // Use a separate line for each value in the Destinations and Via fields
- // to mimic highway exit signs
+ var langExists = _multilingual.find(function (datum) {
+ return datum.lang === defaultLang;
+ });
- if (field.key === 'destination' || field.key === 'via') {
- listClass += ' full-line-chips';
+ var isLangEn = defaultLang.indexOf('en') > -1;
+
+ if (isLangEn || langExists) {
+ defaultLang = '';
+ langExists = _multilingual.find(function (datum) {
+ return datum.lang === defaultLang;
+ });
}
- _container = _container.enter().append('ul').attr('class', listClass).on('click', function () {
- window.setTimeout(function () {
- _input.node().focus();
- }, 10);
- }).merge(_container);
- _inputWrap = _container.selectAll('.input-wrap').data([0]);
- _inputWrap = _inputWrap.enter().append('li').attr('class', 'input-wrap').merge(_inputWrap);
- _input = _inputWrap.selectAll('input').data([0]);
- } else {
- _input = _container.selectAll('input').data([0]);
+ if (!langExists) {
+ // prepend the value so it appears at the top
+ _multilingual.unshift({
+ lang: defaultLang,
+ value: ''
+ });
+
+ localizedInputs.call(renderMultilingual);
+ }
}
- _input = _input.enter().append('input').attr('type', 'text').attr('id', field.domId).call(utilNoAuto).call(initCombo, selection).merge(_input);
+ function change(onInput) {
+ return function (d3_event) {
+ if (field.locked()) {
+ d3_event.preventDefault();
+ return;
+ }
- if (_isNetwork) {
- var extent = combinedEntityExtent();
- var countryCode = extent && iso1A2Code(extent.center());
- _countryCode = countryCode && countryCode.toLowerCase();
+ var val = utilGetSetValue(select(this));
+ if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
+
+ if (!val && Array.isArray(_tags[field.key])) return;
+ var t = {};
+ t[field.key] = val || undefined;
+ dispatch.call('change', this, t, onInput);
+ };
}
+ }
- _input.on('change', change).on('blur', change);
+ function key(lang) {
+ return field.key + ':' + lang;
+ }
- _input.on('keydown.field', function (d3_event) {
- switch (d3_event.keyCode) {
- case 13:
- // ↩ Return
- _input.node().blur(); // blurring also enters the value
+ function changeLang(d3_event, d) {
+ var tags = {}; // make sure unrecognized suffixes are lowercase - #7156
+ var lang = utilGetSetValue(select(this)).toLowerCase();
- d3_event.stopPropagation();
- break;
- }
+ var language = _languagesArray.find(function (d) {
+ return d.label.toLowerCase() === lang || d.localName && d.localName.toLowerCase() === lang || d.nativeName && d.nativeName.toLowerCase() === lang;
});
- if (_isMulti || _isSemi) {
- _combobox.on('accept', function () {
- _input.node().blur();
+ if (language) lang = language.code;
- _input.node().focus();
- });
+ if (d.lang && d.lang !== lang) {
+ tags[key(d.lang)] = undefined;
+ }
- _input.on('focus', function () {
- _container.classed('active', true);
- });
+ var newKey = lang && context.cleanTagKey(key(lang));
+ var value = utilGetSetValue(select(this.parentNode).selectAll('.localized-value'));
+
+ if (newKey && value) {
+ tags[newKey] = value;
+ } else if (newKey && _wikiTitles && _wikiTitles[d.lang]) {
+ tags[newKey] = _wikiTitles[d.lang];
}
+
+ d.lang = lang;
+ dispatch.call('change', this, tags);
}
- combo.tags = function (tags) {
- _tags = tags;
+ function changeValue(d3_event, d) {
+ if (!d.lang) return;
+ var value = context.cleanTagValue(utilGetSetValue(select(this))) || undefined; // don't override multiple values with blank string
- if (_isMulti || _isSemi) {
- _multiData = [];
- var maxLength;
+ if (!value && Array.isArray(d.value)) return;
+ var t = {};
+ t[key(d.lang)] = value;
+ d.value = value;
+ dispatch.call('change', this, t);
+ }
- if (_isMulti) {
- // Build _multiData array containing keys already set..
- for (var k in tags) {
- if (field.key && k.indexOf(field.key) !== 0) continue;
- if (!field.key && field.keys.indexOf(k) === -1) continue;
- var v = tags[k];
- if (!v || typeof v === 'string' && v.toLowerCase() === 'no') continue;
- var suffix = field.key ? k.substr(field.key.length) : k;
+ function fetchLanguages(value, cb) {
+ var v = value.toLowerCase(); // show the user's language first
- _multiData.push({
- key: k,
- value: displayValue(suffix),
- isMixed: Array.isArray(v)
- });
- }
+ var langCodes = [_mainLocalizer.localeCode(), _mainLocalizer.languageCode()];
- if (field.key) {
- // Set keys for form-field modified (needed for undo and reset buttons)..
- field.keys = _multiData.map(function (d) {
- return d.key;
- }); // limit the input length so it fits after prepending the key prefix
+ if (_countryCode && _territoryLanguages[_countryCode]) {
+ langCodes = langCodes.concat(_territoryLanguages[_countryCode]);
+ }
- maxLength = context.maxCharsForTagKey() - utilUnicodeCharsCount(field.key);
- } else {
- maxLength = context.maxCharsForTagKey();
- }
- } else if (_isSemi) {
- var allValues = [];
- var commonValues;
+ var langItems = [];
+ langCodes.forEach(function (code) {
+ var langItem = _languagesArray.find(function (item) {
+ return item.code === code;
+ });
- if (Array.isArray(tags[field.key])) {
- tags[field.key].forEach(function (tagVal) {
- var thisVals = utilArrayUniq((tagVal || '').split(';')).filter(Boolean);
- allValues = allValues.concat(thisVals);
+ if (langItem) langItems.push(langItem);
+ });
+ langItems = utilArrayUniq(langItems.concat(_languagesArray));
+ cb(langItems.filter(function (d) {
+ return d.label.toLowerCase().indexOf(v) >= 0 || d.localName && d.localName.toLowerCase().indexOf(v) >= 0 || d.nativeName && d.nativeName.toLowerCase().indexOf(v) >= 0 || d.code.toLowerCase().indexOf(v) >= 0;
+ }).map(function (d) {
+ return {
+ value: d.label
+ };
+ }));
+ }
- if (!commonValues) {
- commonValues = thisVals;
- } else {
- commonValues = commonValues.filter(function (value) {
- return thisVals.includes(value);
- });
- }
- });
- allValues = utilArrayUniq(allValues).filter(Boolean);
- } else {
- allValues = utilArrayUniq((tags[field.key] || '').split(';')).filter(Boolean);
- commonValues = allValues;
- }
+ function renderMultilingual(selection) {
+ var entries = selection.selectAll('div.entry').data(_multilingual, function (d) {
+ return d.lang;
+ });
+ entries.exit().style('top', '0').style('max-height', '240px').transition().duration(200).style('opacity', '0').style('max-height', '0px').remove();
+ var entriesEnter = entries.enter().append('div').attr('class', 'entry').each(function (_, index) {
+ var wrap = select(this);
+ var domId = utilUniqueDomId(index);
+ var label = wrap.append('label').attr('class', 'field-label').attr('for', domId);
+ var text = label.append('span').attr('class', 'label-text');
+ text.append('span').attr('class', 'label-textvalue').call(_t.append('translate.localized_translation_label'));
+ text.append('span').attr('class', 'label-textannotation');
+ label.append('button').attr('class', 'remove-icon-multilingual').attr('title', _t('icons.remove')).on('click', function (d3_event, d) {
+ if (field.locked()) return;
+ d3_event.preventDefault(); // remove the UI item manually
- _multiData = allValues.map(function (v) {
- return {
- key: v,
- value: displayValue(v),
- isMixed: !commonValues.includes(v)
- };
- });
- var currLength = utilUnicodeCharsCount(commonValues.join(';')); // limit the input length to the remaining available characters
+ _multilingual.splice(_multilingual.indexOf(d), 1);
- maxLength = context.maxCharsForTagValue() - currLength;
+ var langKey = d.lang && key(d.lang);
- if (currLength > 0) {
- // account for the separator if a new value will be appended to existing
- maxLength -= 1;
+ if (langKey && langKey in _tags) {
+ delete _tags[langKey]; // remove from entity tags
+
+ var t = {};
+ t[langKey] = undefined;
+ dispatch.call('change', this, t);
+ return;
}
- } // a negative maxlength doesn't make sense
+ renderMultilingual(selection);
+ }).call(svgIcon('#iD-operation-delete'));
+ wrap.append('input').attr('class', 'localized-lang').attr('id', domId).attr('type', 'text').attr('placeholder', _t('translate.localized_translation_language')).on('blur', changeLang).on('change', changeLang).call(langCombo);
+ wrap.append('input').attr('type', 'text').attr('class', 'localized-value').on('blur', changeValue).on('change', changeValue);
+ });
+ entriesEnter.style('margin-top', '0px').style('max-height', '0px').style('opacity', '0').transition().duration(200).style('margin-top', '10px').style('max-height', '240px').style('opacity', '1').on('end', function () {
+ select(this).style('max-height', '').style('overflow', 'visible');
+ });
+ entries = entries.merge(entriesEnter);
+ entries.order(); // allow removing the entry UIs even if there isn't a tag to remove
- maxLength = Math.max(0, maxLength);
- var allowDragAndDrop = _isSemi // only semiCombo values are ordered
- && !Array.isArray(tags[field.key]); // Exclude existing multikeys from combo options..
+ entries.classed('present', true);
+ utilGetSetValue(entries.select('.localized-lang'), function (d) {
+ var langItem = _languagesArray.find(function (item) {
+ return item.code === d.lang;
+ });
- var available = objectDifference(_comboData, _multiData);
+ if (langItem) return langItem.label;
+ return d.lang;
+ });
+ utilGetSetValue(entries.select('.localized-value'), function (d) {
+ return typeof d.value === 'string' ? d.value : '';
+ }).attr('title', function (d) {
+ return Array.isArray(d.value) ? d.value.filter(Boolean).join('\n') : null;
+ }).attr('placeholder', function (d) {
+ return Array.isArray(d.value) ? _t('inspector.multiple_values') : _t('translate.localized_translation_name');
+ }).classed('mixed', function (d) {
+ return Array.isArray(d.value);
+ });
+ }
- _combobox.data(available); // Hide 'Add' button if this field uses fixed set of
- // options and they're all currently used,
- // or if the field is already at its character limit
+ localized.tags = function (tags) {
+ _tags = tags; // Fetch translations from wikipedia
+ if (typeof tags.wikipedia === 'string' && !_wikiTitles) {
+ _wikiTitles = {};
+ var wm = tags.wikipedia.match(/([^:]+):(.+)/);
- var hideAdd = !_allowCustomValues && !available.length || maxLength <= 0;
+ if (wm && wm[0] && wm[1]) {
+ wikipedia.translations(wm[1], wm[2], function (err, d) {
+ if (err || !d) return;
+ _wikiTitles = d;
+ });
+ }
+ }
- _container.selectAll('.chiplist .input-wrap').style('display', hideAdd ? 'none' : null); // Render chips
+ var isMixed = Array.isArray(tags[field.key]);
+ utilGetSetValue(input, typeof tags[field.key] === 'string' ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder()).classed('mixed', isMixed);
+ calcMultilingual(tags);
+ _selection.call(localized);
+ };
- var chips = _container.selectAll('.chip').data(_multiData);
+ localized.focus = function () {
+ input.node().focus();
+ };
- chips.exit().remove();
- var enter = chips.enter().insert('li', '.input-wrap').attr('class', 'chip');
- enter.append('span');
- enter.append('a');
- chips = chips.merge(enter).order().classed('raw-value', function (d) {
- var k = d.key;
- if (_isMulti) k = k.replace(field.key, '');
- return !field.hasTextForStringId('options.' + k);
- }).classed('draggable', allowDragAndDrop).classed('mixed', function (d) {
- return d.isMixed;
- }).attr('title', function (d) {
- return d.isMixed ? _t('inspector.unshared_value_tooltip') : null;
- });
+ localized.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ _multilingual = [];
+ loadCountryCode();
+ return localized;
+ };
- if (allowDragAndDrop) {
- registerDragAndDrop(chips);
- }
+ function loadCountryCode() {
+ var extent = combinedEntityExtent();
+ var countryCode = extent && iso1A2Code(extent.center());
+ _countryCode = countryCode && countryCode.toLowerCase();
+ }
- chips.select('span').html(function (d) {
- return d.value;
- });
- chips.select('a').attr('href', '#').on('click', removeMultikey).attr('class', 'remove').html('×');
- } else {
- var isMixed = Array.isArray(tags[field.key]);
- var mixedValues = isMixed && tags[field.key].map(function (val) {
- return displayValue(val);
- }).filter(Boolean);
- var showsValue = !isMixed && tags[field.key] && !(field.type === 'typeCombo' && tags[field.key] === 'yes');
- var isRawValue = showsValue && !field.hasTextForStringId('options.' + tags[field.key]);
- var isKnownValue = showsValue && !isRawValue;
- var isReadOnly = !_allowCustomValues || isKnownValue;
- utilGetSetValue(_input, !isMixed ? displayValue(tags[field.key]) : '').classed('raw-value', isRawValue).classed('known-value', isKnownValue).attr('readonly', isReadOnly ? 'readonly' : undefined).attr('title', isMixed ? mixedValues.join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : _staticPlaceholder || '').classed('mixed', isMixed).on('keydown.deleteCapture', function (d3_event) {
- if (isReadOnly && isKnownValue && (d3_event.keyCode === utilKeybinding.keyCodes['⌫'] || d3_event.keyCode === utilKeybinding.keyCodes['⌦'])) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- var t = {};
- t[field.key] = undefined;
- dispatch.call('change', this, t);
- }
- });
- }
- };
+ function combinedEntityExtent() {
+ return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
+ }
- function registerDragAndDrop(selection) {
- // allow drag and drop re-ordering of chips
- var dragOrigin, targetIndex;
- selection.call(d3_drag().on('start', function (d3_event) {
- dragOrigin = {
- x: d3_event.x,
- y: d3_event.y
- };
- targetIndex = null;
- }).on('drag', function (d3_event) {
- var x = d3_event.x - dragOrigin.x,
- y = d3_event.y - dragOrigin.y;
- if (!select(this).classed('dragging') && // don't display drag until dragging beyond a distance threshold
- Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;
- var index = selection.nodes().indexOf(this);
- select(this).classed('dragging', true);
- targetIndex = null;
- var targetIndexOffsetTop = null;
- var draggedTagWidth = select(this).node().offsetWidth;
+ return utilRebind(localized, dispatch, 'on');
+ }
- if (field.key === 'destination' || field.key === 'via') {
- // meaning tags are full width
- _container.selectAll('.chip').style('transform', function (d2, index2) {
- var node = select(this).node();
+ function uiFieldRoadheight(field, context) {
+ var dispatch = dispatch$8('change');
+ var primaryUnitInput = select(null);
+ var primaryInput = select(null);
+ var secondaryInput = select(null);
+ var secondaryUnitInput = select(null);
+ var _entityIDs = [];
- if (index === index2) {
- return 'translate(' + x + 'px, ' + y + 'px)'; // move the dragged tag up the order
- } else if (index2 > index && d3_event.y > node.offsetTop) {
- if (targetIndex === null || index2 > targetIndex) {
- targetIndex = index2;
- }
+ var _tags;
- return 'translateY(-100%)'; // move the dragged tag down the order
- } else if (index2 < index && d3_event.y < node.offsetTop + node.offsetHeight) {
- if (targetIndex === null || index2 < targetIndex) {
- targetIndex = index2;
- }
+ var _isImperial;
- return 'translateY(100%)';
- }
+ var primaryUnits = [{
+ value: 'm',
+ title: _t('inspector.roadheight.meter')
+ }, {
+ value: 'ft',
+ title: _t('inspector.roadheight.foot')
+ }];
+ var unitCombo = uiCombobox(context, 'roadheight-unit').data(primaryUnits);
- return null;
- });
- } else {
- _container.selectAll('.chip').each(function (d2, index2) {
- var node = select(this).node(); // check the cursor is in the bounding box
+ function roadheight(selection) {
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ primaryInput = wrap.selectAll('input.roadheight-number').data([0]);
+ primaryInput = primaryInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-number').attr('id', field.domId).call(utilNoAuto).merge(primaryInput);
+ primaryInput.on('change', change).on('blur', change);
+ var loc = combinedEntityExtent().center();
+ _isImperial = roadHeightUnit(loc) === 'ft';
+ primaryUnitInput = wrap.selectAll('input.roadheight-unit').data([0]);
+ primaryUnitInput = primaryUnitInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-unit').call(unitCombo).merge(primaryUnitInput);
+ primaryUnitInput.on('blur', changeUnits).on('change', changeUnits);
+ secondaryInput = wrap.selectAll('input.roadheight-secondary-number').data([0]);
+ secondaryInput = secondaryInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-secondary-number').call(utilNoAuto).merge(secondaryInput);
+ secondaryInput.on('change', change).on('blur', change);
+ secondaryUnitInput = wrap.selectAll('input.roadheight-secondary-unit').data([0]);
+ secondaryUnitInput = secondaryUnitInput.enter().append('input').attr('type', 'text').call(utilNoAuto).classed('disabled', true).classed('roadheight-secondary-unit', true).attr('readonly', 'readonly').merge(secondaryUnitInput);
- if (index !== index2 && d3_event.x < node.offsetLeft + node.offsetWidth + 5 && d3_event.x > node.offsetLeft && d3_event.y < node.offsetTop + node.offsetHeight && d3_event.y > node.offsetTop) {
- targetIndex = index2;
- targetIndexOffsetTop = node.offsetTop;
- }
- }).style('transform', function (d2, index2) {
- var node = select(this).node();
+ function changeUnits() {
+ _isImperial = utilGetSetValue(primaryUnitInput) === 'ft';
+ utilGetSetValue(primaryUnitInput, _isImperial ? 'ft' : 'm');
+ setUnitSuggestions();
+ change();
+ }
+ }
- if (index === index2) {
- return 'translate(' + x + 'px, ' + y + 'px)';
- } // only translate tags in the same row
+ function setUnitSuggestions() {
+ utilGetSetValue(primaryUnitInput, _isImperial ? 'ft' : 'm');
+ }
+ function change() {
+ var tag = {};
+ var primaryValue = utilGetSetValue(primaryInput).trim();
+ var secondaryValue = utilGetSetValue(secondaryInput).trim(); // don't override multiple values with blank string
- if (node.offsetTop === targetIndexOffsetTop) {
- if (index2 < index && index2 >= targetIndex) {
- return 'translateX(' + draggedTagWidth + 'px)';
- } else if (index2 > index && index2 <= targetIndex) {
- return 'translateX(-' + draggedTagWidth + 'px)';
- }
- }
+ if (!primaryValue && !secondaryValue && Array.isArray(_tags[field.key])) return;
- return null;
- });
+ if (!primaryValue && !secondaryValue) {
+ tag[field.key] = undefined;
+ } else if (isNaN(primaryValue) || isNaN(secondaryValue) || !_isImperial) {
+ tag[field.key] = context.cleanTagValue(primaryValue);
+ } else {
+ if (primaryValue !== '') {
+ primaryValue = context.cleanTagValue(primaryValue + '\'');
}
- }).on('end', function () {
- if (!select(this).classed('dragging')) {
- return;
+
+ if (secondaryValue !== '') {
+ secondaryValue = context.cleanTagValue(secondaryValue + '"');
}
- var index = selection.nodes().indexOf(this);
- select(this).classed('dragging', false);
+ tag[field.key] = primaryValue + secondaryValue;
+ }
- _container.selectAll('.chip').style('transform', null);
+ dispatch.call('change', this, tag);
+ }
- if (typeof targetIndex === 'number') {
- var element = _multiData[index];
+ roadheight.tags = function (tags) {
+ _tags = tags;
+ var primaryValue = tags[field.key];
+ var secondaryValue;
+ var isMixed = Array.isArray(primaryValue);
- _multiData.splice(index, 1);
+ if (!isMixed) {
+ if (primaryValue && (primaryValue.indexOf('\'') >= 0 || primaryValue.indexOf('"') >= 0)) {
+ secondaryValue = primaryValue.match(/(-?[\d.]+)"/);
- _multiData.splice(targetIndex, 0, element);
+ if (secondaryValue !== null) {
+ secondaryValue = secondaryValue[1];
+ }
- var t = {};
+ primaryValue = primaryValue.match(/(-?[\d.]+)'/);
- if (_multiData.length) {
- t[field.key] = _multiData.map(function (element) {
- return element.key;
- }).join(';');
- } else {
- t[field.key] = undefined;
+ if (primaryValue !== null) {
+ primaryValue = primaryValue[1];
}
- dispatch.call('change', this, t);
+ _isImperial = true;
+ } else if (primaryValue) {
+ _isImperial = false;
}
+ }
- dragOrigin = undefined;
- targetIndex = undefined;
- }));
- }
+ setUnitSuggestions();
+ utilGetSetValue(primaryInput, typeof primaryValue === 'string' ? primaryValue : '').attr('title', isMixed ? primaryValue.filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : _t('inspector.unknown')).classed('mixed', isMixed);
+ utilGetSetValue(secondaryInput, typeof secondaryValue === 'string' ? secondaryValue : '').attr('placeholder', isMixed ? _t('inspector.multiple_values') : _isImperial ? '0' : null).classed('mixed', isMixed).classed('disabled', !_isImperial).attr('readonly', _isImperial ? null : 'readonly');
+ secondaryUnitInput.attr('value', _isImperial ? _t('inspector.roadheight.inch') : null);
+ };
- combo.focus = function () {
- _input.node().focus();
+ roadheight.focus = function () {
+ primaryInput.node().focus();
};
- combo.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
+ roadheight.entityIDs = function (val) {
_entityIDs = val;
- return combo;
};
function combinedEntityExtent() {
return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
}
- return utilRebind(combo, dispatch, 'on');
+ return utilRebind(roadheight, dispatch, 'on');
}
- function uiFieldText(field, context) {
+ function uiFieldRoadspeed(field, context) {
var dispatch = dispatch$8('change');
+ var unitInput = select(null);
var input = select(null);
- var outlinkButton = select(null);
var _entityIDs = [];
var _tags;
- var _phoneFormats = {};
+ var _isImperial;
- if (field.type === 'tel') {
- _mainFileFetcher.get('phone_formats').then(function (d) {
- _phoneFormats = d;
- updatePhonePlaceholder();
- })["catch"](function () {
- /* ignore */
- });
+ var speedCombo = uiCombobox(context, 'roadspeed');
+ var unitCombo = uiCombobox(context, 'roadspeed-unit').data(['km/h', 'mph'].map(comboValues));
+ var metricValues = [20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
+ var imperialValues = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80];
+
+ function roadspeed(selection) {
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ input = wrap.selectAll('input.roadspeed-number').data([0]);
+ input = input.enter().append('input').attr('type', 'text').attr('class', 'roadspeed-number').attr('id', field.domId).call(utilNoAuto).call(speedCombo).merge(input);
+ input.on('change', change).on('blur', change);
+ var loc = combinedEntityExtent().center();
+ _isImperial = roadSpeedUnit(loc) === 'mph';
+ unitInput = wrap.selectAll('input.roadspeed-unit').data([0]);
+ unitInput = unitInput.enter().append('input').attr('type', 'text').attr('class', 'roadspeed-unit').attr('aria-label', _t('inspector.speed_unit')).call(unitCombo).merge(unitInput);
+ unitInput.on('blur', changeUnits).on('change', changeUnits);
+
+ function changeUnits() {
+ _isImperial = utilGetSetValue(unitInput) === 'mph';
+ utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
+ setUnitSuggestions();
+ change();
+ }
}
- function calcLocked() {
- // Protect certain fields that have a companion `*:wikidata` value
- var isLocked = (field.id === 'brand' || field.id === 'network' || field.id === 'operator' || field.id === 'flag') && _entityIDs.length && _entityIDs.some(function (entityID) {
- var entity = context.graph().hasEntity(entityID);
- if (!entity) return false; // Features linked to Wikidata are likely important and should be protected
+ function setUnitSuggestions() {
+ speedCombo.data((_isImperial ? imperialValues : metricValues).map(comboValues));
+ utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
+ }
- if (entity.tags.wikidata) return true;
- var preset = _mainPresetIndex.match(entity, context.graph());
- var isSuggestion = preset && preset.suggestion; // Lock the field if there is a value and a companion `*:wikidata` value
+ function comboValues(d) {
+ return {
+ value: d.toString(),
+ title: d.toString()
+ };
+ }
- var which = field.id; // 'brand', 'network', 'operator', 'flag'
+ function change() {
+ var tag = {};
+ var value = utilGetSetValue(input).trim(); // don't override multiple values with blank string
- return isSuggestion && !!entity.tags[which] && !!entity.tags[which + ':wikidata'];
- });
+ if (!value && Array.isArray(_tags[field.key])) return;
- field.locked(isLocked);
+ if (!value) {
+ tag[field.key] = undefined;
+ } else if (isNaN(value) || !_isImperial) {
+ tag[field.key] = context.cleanTagValue(value);
+ } else {
+ tag[field.key] = context.cleanTagValue(value + ' mph');
+ }
+
+ dispatch.call('change', this, tag);
}
- function i(selection) {
- calcLocked();
- var isLocked = field.locked();
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- input = wrap.selectAll('input').data([0]);
- input = input.enter().append('input').attr('type', field.type === 'identifier' ? 'text' : field.type).attr('id', field.domId).classed(field.type, true).call(utilNoAuto).merge(input);
- input.classed('disabled', !!isLocked).attr('readonly', isLocked || null).on('input', change(true)).on('blur', change()).on('change', change());
+ roadspeed.tags = function (tags) {
+ _tags = tags;
+ var value = tags[field.key];
+ var isMixed = Array.isArray(value);
- if (field.type === 'tel') {
- updatePhonePlaceholder();
- } else if (field.type === 'number') {
- var rtl = _mainLocalizer.textDirection() === 'rtl';
- input.attr('type', 'text');
- var inc = field.increment;
- var buttons = wrap.selectAll('.increment, .decrement').data(rtl ? [inc, -inc] : [-inc, inc]);
- buttons.enter().append('button').attr('class', function (d) {
- var which = d > 0 ? 'increment' : 'decrement';
- return 'form-field-button ' + which;
- }).merge(buttons).on('click', function (d3_event, d) {
- d3_event.preventDefault();
- var raw_vals = input.node().value || '0';
- var vals = raw_vals.split(';');
- vals = vals.map(function (v) {
- var num = parseFloat(v.trim(), 10);
- return isFinite(num) ? clamped(num + d) : v.trim();
- });
- input.node().value = vals.join(';');
- change()();
- });
- } else if (field.type === 'identifier' && field.urlFormat && field.pattern) {
- input.attr('type', 'text');
- outlinkButton = wrap.selectAll('.foreign-id-permalink').data([0]);
- outlinkButton.enter().append('button').call(svgIcon('#iD-icon-out-link')).attr('class', 'form-field-button foreign-id-permalink').attr('title', function () {
- var domainResults = /^https?:\/\/(.{1,}?)\//.exec(field.urlFormat);
+ if (!isMixed) {
+ if (value && value.indexOf('mph') >= 0) {
+ value = parseInt(value, 10).toString();
+ _isImperial = true;
+ } else if (value) {
+ _isImperial = false;
+ }
+ }
- if (domainResults.length >= 2 && domainResults[1]) {
- var domain = domainResults[1];
- return _t('icons.view_on', {
- domain: domain
- });
- }
+ setUnitSuggestions();
+ utilGetSetValue(input, typeof value === 'string' ? value : '').attr('title', isMixed ? value.filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder()).classed('mixed', isMixed);
+ };
- return '';
- }).on('click', function (d3_event) {
- d3_event.preventDefault();
- var value = validIdentifierValueForLink();
+ roadspeed.focus = function () {
+ input.node().focus();
+ };
- if (value) {
- var url = field.urlFormat.replace(/{value}/, encodeURIComponent(value));
- window.open(url, '_blank');
- }
- }).merge(outlinkButton);
- } else if (field.type === 'url') {
- input.attr('type', 'text');
- outlinkButton = wrap.selectAll('.foreign-id-permalink').data([0]);
- outlinkButton.enter().append('button').call(svgIcon('#iD-icon-out-link')).attr('class', 'form-field-button foreign-id-permalink').attr('title', function () {
- return _t('icons.visit_website');
- }).on('click', function (d3_event) {
- d3_event.preventDefault();
- var value = validIdentifierValueForLink();
- if (value) window.open(value, '_blank');
- }).merge(outlinkButton);
- }
+ roadspeed.entityIDs = function (val) {
+ _entityIDs = val;
+ };
+
+ function combinedEntityExtent() {
+ return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
}
- function updatePhonePlaceholder() {
- if (input.empty() || !Object.keys(_phoneFormats).length) return;
- var extent = combinedEntityExtent();
- var countryCode = extent && iso1A2Code(extent.center());
+ return utilRebind(roadspeed, dispatch, 'on');
+ }
- var format = countryCode && _phoneFormats[countryCode.toLowerCase()];
+ function uiFieldRadio(field, context) {
+ var dispatch = dispatch$8('change');
+ var placeholder = select(null);
+ var wrap = select(null);
+ var labels = select(null);
+ var radios = select(null);
+ var radioData = (field.options || field.keys).slice(); // shallow copy
- if (format) input.attr('placeholder', format);
- }
+ var typeField;
+ var layerField;
+ var _oldType = {};
+ var _entityIDs = [];
- function validIdentifierValueForLink() {
- var value = utilGetSetValue(input).trim().split(';')[0];
- if (field.type === 'url' && value) return value;
+ function selectedKey() {
+ var node = wrap.selectAll('.form-field-input-radio label.active input');
+ return !node.empty() && node.datum();
+ }
- if (field.type === 'identifier' && field.pattern) {
- return value && value.match(new RegExp(field.pattern));
- }
+ function radio(selection) {
+ selection.classed('preset-radio', true);
+ wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ var enter = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-radio');
+ enter.append('span').attr('class', 'placeholder');
+ wrap = wrap.merge(enter);
+ placeholder = wrap.selectAll('.placeholder');
+ labels = wrap.selectAll('label').data(radioData);
+ enter = labels.enter().append('label');
+ enter.append('input').attr('type', 'radio').attr('name', field.id).attr('value', function (d) {
+ return field.t('options.' + d, {
+ 'default': d
+ });
+ }).attr('checked', false);
+ enter.append('span').html(function (d) {
+ return field.t.html('options.' + d, {
+ 'default': d
+ });
+ });
+ labels = labels.merge(enter);
+ radios = labels.selectAll('input').on('change', changeRadio);
+ }
- return null;
- } // clamp number to min/max
+ function structureExtras(selection, tags) {
+ var selected = selectedKey() || tags.layer !== undefined;
+ var type = _mainPresetIndex.field(selected);
+ var layer = _mainPresetIndex.field('layer');
+ var showLayer = selected === 'bridge' || selected === 'tunnel' || tags.layer !== undefined;
+ var extrasWrap = selection.selectAll('.structure-extras-wrap').data(selected ? [0] : []);
+ extrasWrap.exit().remove();
+ extrasWrap = extrasWrap.enter().append('div').attr('class', 'structure-extras-wrap').merge(extrasWrap);
+ var list = extrasWrap.selectAll('ul').data([0]);
+ list = list.enter().append('ul').attr('class', 'rows').merge(list); // Type
+ if (type) {
+ if (!typeField || typeField.id !== selected) {
+ typeField = uiField(context, type, _entityIDs, {
+ wrap: false
+ }).on('change', changeType);
+ }
- function clamped(num) {
- if (field.minValue !== undefined) {
- num = Math.max(num, field.minValue);
+ typeField.tags(tags);
+ } else {
+ typeField = null;
}
- if (field.maxValue !== undefined) {
- num = Math.min(num, field.maxValue);
- }
+ var typeItem = list.selectAll('.structure-type-item').data(typeField ? [typeField] : [], function (d) {
+ return d.id;
+ }); // Exit
- return num;
- }
+ typeItem.exit().remove(); // Enter
- function change(onInput) {
- return function () {
- var t = {};
- var val = utilGetSetValue(input);
- if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
+ var typeEnter = typeItem.enter().insert('li', ':first-child').attr('class', 'labeled-input structure-type-item');
+ typeEnter.append('span').attr('class', 'label structure-label-type').attr('for', 'preset-input-' + selected).call(_t.append('inspector.radio.structure.type'));
+ typeEnter.append('div').attr('class', 'structure-input-type-wrap'); // Update
- if (!val && Array.isArray(_tags[field.key])) return;
+ typeItem = typeItem.merge(typeEnter);
- if (!onInput) {
- if (field.type === 'number' && val) {
- var vals = val.split(';');
- vals = vals.map(function (v) {
- var num = parseFloat(v.trim(), 10);
- return isFinite(num) ? clamped(num) : v.trim();
- });
- val = vals.join(';');
- }
+ if (typeField) {
+ typeItem.selectAll('.structure-input-type-wrap').call(typeField.render);
+ } // Layer
- utilGetSetValue(input, val);
+
+ if (layer && showLayer) {
+ if (!layerField) {
+ layerField = uiField(context, layer, _entityIDs, {
+ wrap: false
+ }).on('change', changeLayer);
}
- t[field.key] = val || undefined;
- dispatch.call('change', this, t, onInput);
- };
- }
+ layerField.tags(tags);
+ field.keys = utilArrayUnion(field.keys, ['layer']);
+ } else {
+ layerField = null;
+ field.keys = field.keys.filter(function (k) {
+ return k !== 'layer';
+ });
+ }
- i.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return i;
- };
+ var layerItem = list.selectAll('.structure-layer-item').data(layerField ? [layerField] : []); // Exit
- i.tags = function (tags) {
- _tags = tags;
- var isMixed = Array.isArray(tags[field.key]);
- utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder() || _t('inspector.unknown')).classed('mixed', isMixed);
+ layerItem.exit().remove(); // Enter
- if (outlinkButton && !outlinkButton.empty()) {
- var disabled = !validIdentifierValueForLink();
- outlinkButton.classed('disabled', disabled);
- }
- };
+ var layerEnter = layerItem.enter().append('li').attr('class', 'labeled-input structure-layer-item');
+ layerEnter.append('span').attr('class', 'label structure-label-layer').attr('for', 'preset-input-layer').call(_t.append('inspector.radio.structure.layer'));
+ layerEnter.append('div').attr('class', 'structure-input-layer-wrap'); // Update
- i.focus = function () {
- var node = input.node();
- if (node) node.focus();
- };
+ layerItem = layerItem.merge(layerEnter);
- function combinedEntityExtent() {
- return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
+ if (layerField) {
+ layerItem.selectAll('.structure-input-layer-wrap').call(layerField.render);
+ }
}
- return utilRebind(i, dispatch, 'on');
- }
+ function changeType(t, onInput) {
+ var key = selectedKey();
+ if (!key) return;
+ var val = t[key];
- function uiFieldAccess(field, context) {
- var dispatch = dispatch$8('change');
- var items = select(null);
+ if (val !== 'no') {
+ _oldType[key] = val;
+ }
- var _tags;
+ if (field.type === 'structureRadio') {
+ // remove layer if it should not be set
+ if (val === 'no' || key !== 'bridge' && key !== 'tunnel' || key === 'tunnel' && val === 'building_passage') {
+ t.layer = undefined;
+ } // add layer if it should be set
- function access(selection) {
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- var list = wrap.selectAll('ul').data([0]);
- list = list.enter().append('ul').attr('class', 'rows').merge(list);
- items = list.selectAll('li').data(field.keys); // Enter
- var enter = items.enter().append('li').attr('class', function (d) {
- return 'labeled-input preset-access-' + d;
- });
- enter.append('span').attr('class', 'label preset-label-access').attr('for', function (d) {
- return 'preset-input-access-' + d;
- }).html(function (d) {
- return field.t.html('types.' + d);
- });
- enter.append('div').attr('class', 'preset-input-access-wrap').append('input').attr('type', 'text').attr('class', function (d) {
- return 'preset-input-access preset-input-access-' + d;
- }).call(utilNoAuto).each(function (d) {
- select(this).call(uiCombobox(context, 'access-' + d).data(access.options(d)));
- }); // Update
+ if (t.layer === undefined) {
+ if (key === 'bridge' && val !== 'no') {
+ t.layer = '1';
+ }
- items = items.merge(enter);
- wrap.selectAll('.preset-input-access').on('change', change).on('blur', change);
+ if (key === 'tunnel' && val !== 'no' && val !== 'building_passage') {
+ t.layer = '-1';
+ }
+ }
+ }
+
+ dispatch.call('change', this, t, onInput);
}
- function change(d3_event, d) {
- var tag = {};
- var value = context.cleanTagValue(utilGetSetValue(select(this))); // don't override multiple values with blank string
+ function changeLayer(t, onInput) {
+ if (t.layer === '0') {
+ t.layer = undefined;
+ }
- if (!value && typeof _tags[d] !== 'string') return;
- tag[d] = value || undefined;
- dispatch.call('change', this, tag);
+ dispatch.call('change', this, t, onInput);
}
- access.options = function (type) {
- var options = ['no', 'permissive', 'private', 'permit', 'destination'];
-
- if (type !== 'access') {
- options.unshift('yes');
- options.push('designated');
+ function changeRadio() {
+ var t = {};
+ var activeKey;
- if (type === 'bicycle') {
- options.push('dismount');
- }
+ if (field.key) {
+ t[field.key] = undefined;
}
- return options.map(function (option) {
- return {
- title: field.t('options.' + option + '.description'),
- value: option
- };
+ radios.each(function (d) {
+ var active = select(this).property('checked');
+ if (active) activeKey = d;
+
+ if (field.key) {
+ if (active) t[field.key] = d;
+ } else {
+ var val = _oldType[activeKey] || 'yes';
+ t[d] = active ? val : undefined;
+ }
});
- };
- var placeholdersByHighway = {
- footway: {
- foot: 'designated',
- motor_vehicle: 'no'
- },
- steps: {
- foot: 'yes',
- motor_vehicle: 'no',
- bicycle: 'no',
- horse: 'no'
- },
- pedestrian: {
- foot: 'yes',
- motor_vehicle: 'no'
- },
- cycleway: {
- motor_vehicle: 'no',
- bicycle: 'designated'
- },
- bridleway: {
- motor_vehicle: 'no',
- horse: 'designated'
- },
- path: {
- foot: 'yes',
- motor_vehicle: 'no',
- bicycle: 'yes',
- horse: 'yes'
- },
- motorway: {
- foot: 'no',
- motor_vehicle: 'yes',
- bicycle: 'no',
- horse: 'no'
- },
- trunk: {
- motor_vehicle: 'yes'
- },
- primary: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- secondary: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- tertiary: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- residential: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- unclassified: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- service: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- motorway_link: {
- foot: 'no',
- motor_vehicle: 'yes',
- bicycle: 'no',
- horse: 'no'
- },
- trunk_link: {
- motor_vehicle: 'yes'
- },
- primary_link: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- secondary_link: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
- },
- tertiary_link: {
- foot: 'yes',
- motor_vehicle: 'yes',
- bicycle: 'yes',
- horse: 'yes'
+ if (field.type === 'structureRadio') {
+ if (activeKey === 'bridge') {
+ t.layer = '1';
+ } else if (activeKey === 'tunnel' && t.tunnel !== 'building_passage') {
+ t.layer = '-1';
+ } else {
+ t.layer = undefined;
+ }
}
- };
- access.tags = function (tags) {
- _tags = tags;
- utilGetSetValue(items.selectAll('.preset-input-access'), function (d) {
- return typeof tags[d] === 'string' ? tags[d] : '';
- }).classed('mixed', function (d) {
- return tags[d] && Array.isArray(tags[d]);
- }).attr('title', function (d) {
- return tags[d] && Array.isArray(tags[d]) && tags[d].filter(Boolean).join('\n');
- }).attr('placeholder', function (d) {
- if (tags[d] && Array.isArray(tags[d])) {
- return _t('inspector.multiple_values');
- }
+ dispatch.call('change', this, t);
+ }
- if (d === 'access') {
- return 'yes';
+ radio.tags = function (tags) {
+ function isOptionChecked(d) {
+ if (field.key) {
+ return tags[field.key] === d;
}
- if (tags.access && typeof tags.access === 'string') {
- return tags.access;
+ return !!(typeof tags[d] === 'string' && tags[d].toLowerCase() !== 'no');
+ }
+
+ function isMixed(d) {
+ if (field.key) {
+ return Array.isArray(tags[field.key]) && tags[field.key].includes(d);
}
- if (tags.highway) {
- if (typeof tags.highway === 'string') {
- if (placeholdersByHighway[tags.highway] && placeholdersByHighway[tags.highway][d]) {
- return placeholdersByHighway[tags.highway][d];
- }
- } else {
- var impliedAccesses = tags.highway.filter(Boolean).map(function (highwayVal) {
- return placeholdersByHighway[highwayVal] && placeholdersByHighway[highwayVal][d];
- }).filter(Boolean);
+ return Array.isArray(tags[d]);
+ }
- if (impliedAccesses.length === tags.highway.length && new Set(impliedAccesses).size === 1) {
- // if all the highway values have the same implied access for this type then use that
- return impliedAccesses[0];
- }
- }
+ radios.property('checked', function (d) {
+ return isOptionChecked(d) && (field.key || field.options.filter(isOptionChecked).length === 1);
+ });
+ labels.classed('active', function (d) {
+ if (field.key) {
+ return Array.isArray(tags[field.key]) && tags[field.key].includes(d) || tags[field.key] === d;
}
- return field.placeholder();
+ return Array.isArray(tags[d]) && tags[d].some(function (v) {
+ return typeof v === 'string' && v.toLowerCase() !== 'no';
+ }) || !!(typeof tags[d] === 'string' && tags[d].toLowerCase() !== 'no');
+ }).classed('mixed', isMixed).attr('title', function (d) {
+ return isMixed(d) ? _t('inspector.unshared_value_tooltip') : null;
+ });
+ var selection = radios.filter(function () {
+ return this.checked;
});
+
+ if (selection.empty()) {
+ placeholder.call(_t.append('inspector.none'));
+ } else {
+ placeholder.text(selection.attr('value'));
+ _oldType[selection.datum()] = tags[selection.datum()];
+ }
+
+ if (field.type === 'structureRadio') {
+ // For waterways without a tunnel tag, set 'culvert' as
+ // the _oldType to default to if the user picks 'tunnel'
+ if (!!tags.waterway && !_oldType.tunnel) {
+ _oldType.tunnel = 'culvert';
+ }
+
+ wrap.call(structureExtras, tags);
+ }
};
- access.focus = function () {
- items.selectAll('.preset-input-access').node().focus();
+ radio.focus = function () {
+ radios.node().focus();
};
- return utilRebind(access, dispatch, 'on');
+ radio.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ _oldType = {};
+ return radio;
+ };
+
+ radio.isAllowed = function () {
+ return _entityIDs.length === 1;
+ };
+
+ return utilRebind(radio, dispatch, 'on');
}
- function uiFieldAddress(field, context) {
+ function uiFieldRestrictions(field, context) {
var dispatch = dispatch$8('change');
+ var breathe = behaviorBreathe();
+ corePreferences('turn-restriction-via-way', null); // remove old key
- var _selection = select(null);
+ var storedViaWay = corePreferences('turn-restriction-via-way0'); // use new key #6922
- var _wrap = select(null);
+ var storedDistance = corePreferences('turn-restriction-distance');
- var addrField = _mainPresetIndex.field('address'); // needed for placeholder strings
+ var _maxViaWay = storedViaWay !== null ? +storedViaWay : 0;
- var _entityIDs = [];
+ var _maxDistance = storedDistance ? +storedDistance : 30;
- var _tags;
+ var _initialized = false;
- var _countryCode;
+ var _parent = select(null); // the entire field
- var _addressFormats = [{
- format: [['housenumber', 'street'], ['city', 'postcode']]
- }];
- _mainFileFetcher.get('address_formats').then(function (d) {
- _addressFormats = d;
- if (!_selection.empty()) {
- _selection.call(address);
- }
- })["catch"](function () {
- /* ignore */
- });
+ var _container = select(null); // just the map
- function getNearStreets() {
- var extent = combinedEntityExtent();
- var l = extent.center();
- var box = geoExtent(l).padByMeters(200);
- var streets = context.history().intersects(box).filter(isAddressable).map(function (d) {
- var loc = context.projection([(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]);
- var choice = geoChooseEdge(context.graph().childNodes(d), loc, context.projection);
- return {
- title: d.tags.name,
- value: d.tags.name,
- dist: choice.distance
- };
- }).sort(function (a, b) {
- return a.dist - b.dist;
- });
- return utilArrayUniqBy(streets, 'value');
- function isAddressable(d) {
- return d.tags.highway && d.tags.name && d.type === 'way';
- }
- }
+ var _oldTurns;
- function getNearCities() {
- var extent = combinedEntityExtent();
- var l = extent.center();
- var box = geoExtent(l).padByMeters(200);
- var cities = context.history().intersects(box).filter(isAddressable).map(function (d) {
- return {
- title: d.tags['addr:city'] || d.tags.name,
- value: d.tags['addr:city'] || d.tags.name,
- dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
- };
- }).sort(function (a, b) {
- return a.dist - b.dist;
- });
- return utilArrayUniqBy(cities, 'value');
+ var _graph;
- function isAddressable(d) {
- if (d.tags.name) {
- if (d.tags.admin_level === '8' && d.tags.boundary === 'administrative') return true;
- if (d.tags.border_type === 'city') return true;
- if (d.tags.place === 'city' || d.tags.place === 'town' || d.tags.place === 'village') return true;
- }
+ var _vertexID;
- if (d.tags['addr:city']) return true;
- return false;
- }
- }
+ var _intersection;
- function getNearValues(key) {
- var extent = combinedEntityExtent();
- var l = extent.center();
- var box = geoExtent(l).padByMeters(200);
- var results = context.history().intersects(box).filter(function hasTag(d) {
- return _entityIDs.indexOf(d.id) === -1 && d.tags[key];
- }).map(function (d) {
- return {
- title: d.tags[key],
- value: d.tags[key],
- dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
- };
- }).sort(function (a, b) {
- return a.dist - b.dist;
- });
- return utilArrayUniqBy(results, 'value');
- }
+ var _fromWayID;
- function updateForCountryCode() {
- if (!_countryCode) return;
- var addressFormat;
+ var _lastXPos;
- for (var i = 0; i < _addressFormats.length; i++) {
- var format = _addressFormats[i];
+ function restrictions(selection) {
+ _parent = selection; // try to reuse the intersection, but always rebuild it if the graph has changed
- if (!format.countryCodes) {
- addressFormat = format; // choose the default format, keep going
- } else if (format.countryCodes.indexOf(_countryCode) !== -1) {
- addressFormat = format; // choose the country format, stop here
+ if (_vertexID && (context.graph() !== _graph || !_intersection)) {
+ _graph = context.graph();
+ _intersection = osmIntersection(_graph, _vertexID, _maxDistance);
+ } // It's possible for there to be no actual intersection here.
+ // for example, a vertex of two `highway=path`
+ // In this case, hide the field.
- break;
- }
- }
- var dropdowns = addressFormat.dropdowns || ['city', 'county', 'country', 'district', 'hamlet', 'neighbourhood', 'place', 'postcode', 'province', 'quarter', 'state', 'street', 'subdistrict', 'suburb'];
- var widths = addressFormat.widths || {
- housenumber: 1 / 3,
- street: 2 / 3,
- city: 2 / 3,
- state: 1 / 4,
- postcode: 1 / 3
- };
+ var isOK = _intersection && _intersection.vertices.length && // has vertices
+ _intersection.vertices // has the vertex that the user selected
+ .filter(function (vertex) {
+ return vertex.id === _vertexID;
+ }).length && _intersection.ways.length > 2 && // has more than 2 ways
+ _intersection.ways // has more than 1 TO way
+ .filter(function (way) {
+ return way.__to;
+ }).length > 1; // Also hide in the case where
- function row(r) {
- // Normalize widths.
- var total = r.reduce(function (sum, key) {
- return sum + (widths[key] || 0.5);
- }, 0);
- return r.map(function (key) {
- return {
- id: key,
- width: (widths[key] || 0.5) / total
- };
- });
+ select(selection.node().parentNode).classed('hide', !isOK); // if form field is hidden or has detached from dom, clean up.
+
+ if (!isOK || !context.container().select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode || !selection.node().parentNode.parentNode) {
+ selection.call(restrictions.off);
+ return;
}
- var rows = _wrap.selectAll('.addr-row').data(addressFormat.format, function (d) {
- return d.toString();
- });
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ var container = wrap.selectAll('.restriction-container').data([0]); // enter
- rows.exit().remove();
- rows.enter().append('div').attr('class', 'addr-row').selectAll('input').data(row).enter().append('input').property('type', 'text').call(updatePlaceholder).attr('class', function (d) {
- return 'addr-' + d.id;
- }).call(utilNoAuto).each(addDropdown).style('width', function (d) {
- return d.width * 100 + '%';
- });
+ var containerEnter = container.enter().append('div').attr('class', 'restriction-container');
+ containerEnter.append('div').attr('class', 'restriction-help'); // update
- function addDropdown(d) {
- if (dropdowns.indexOf(d.id) === -1) return; // not a dropdown
+ _container = containerEnter.merge(container).call(renderViewer);
+ var controls = wrap.selectAll('.restriction-controls').data([0]); // enter/update
- var nearValues = d.id === 'street' ? getNearStreets : d.id === 'city' ? getNearCities : getNearValues;
- select(this).call(uiCombobox(context, 'address-' + d.id).minItems(1).caseSensitive(true).fetcher(function (value, callback) {
- callback(nearValues('addr:' + d.id));
- }));
- }
+ controls.enter().append('div').attr('class', 'restriction-controls-container').append('div').attr('class', 'restriction-controls').merge(controls).call(renderControls);
+ }
- _wrap.selectAll('input').on('blur', change()).on('change', change());
+ function renderControls(selection) {
+ var distControl = selection.selectAll('.restriction-distance').data([0]);
+ distControl.exit().remove();
+ var distControlEnter = distControl.enter().append('div').attr('class', 'restriction-control restriction-distance');
+ distControlEnter.append('span').attr('class', 'restriction-control-label restriction-distance-label').call(_t.append('restriction.controls.distance', {
+ suffix: ':'
+ }));
+ distControlEnter.append('input').attr('class', 'restriction-distance-input').attr('type', 'range').attr('min', '20').attr('max', '50').attr('step', '5');
+ distControlEnter.append('span').attr('class', 'restriction-distance-text'); // update
- _wrap.selectAll('input:not(.combobox-input)').on('input', change(true));
+ selection.selectAll('.restriction-distance-input').property('value', _maxDistance).on('input', function () {
+ var val = select(this).property('value');
+ _maxDistance = +val;
+ _intersection = null;
- if (_tags) updateTags(_tags);
+ _container.selectAll('.layer-osm .layer-turns *').remove();
+
+ corePreferences('turn-restriction-distance', _maxDistance);
+
+ _parent.call(restrictions);
+ });
+ selection.selectAll('.restriction-distance-text').call(displayMaxDistance(_maxDistance));
+ var viaControl = selection.selectAll('.restriction-via-way').data([0]);
+ viaControl.exit().remove();
+ var viaControlEnter = viaControl.enter().append('div').attr('class', 'restriction-control restriction-via-way');
+ viaControlEnter.append('span').attr('class', 'restriction-control-label restriction-via-way-label').call(_t.append('restriction.controls.via', {
+ suffix: ':'
+ }));
+ viaControlEnter.append('input').attr('class', 'restriction-via-way-input').attr('type', 'range').attr('min', '0').attr('max', '2').attr('step', '1');
+ viaControlEnter.append('span').attr('class', 'restriction-via-way-text'); // update
+
+ selection.selectAll('.restriction-via-way-input').property('value', _maxViaWay).on('input', function () {
+ var val = select(this).property('value');
+ _maxViaWay = +val;
+
+ _container.selectAll('.layer-osm .layer-turns *').remove();
+
+ corePreferences('turn-restriction-via-way0', _maxViaWay);
+
+ _parent.call(restrictions);
+ });
+ selection.selectAll('.restriction-via-way-text').call(displayMaxVia(_maxViaWay));
}
- function address(selection) {
- _selection = selection;
- _wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- _wrap = _wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(_wrap);
- var extent = combinedEntityExtent();
+ function renderViewer(selection) {
+ if (!_intersection) return;
+ var vgraph = _intersection.graph;
+ var filter = utilFunctor(true);
+ var projection = geoRawMercator(); // Reflow warning: `utilGetDimensions` calls `getBoundingClientRect`
+ // Instead of asking the restriction-container for its dimensions,
+ // we can ask the .sidebar, which can have its dimensions cached.
+ // width: calc as sidebar - padding
+ // height: hardcoded (from `80_app.css`)
+ // var d = utilGetDimensions(selection);
- if (extent) {
- var countryCode;
+ var sdims = utilGetDimensions(context.container().select('.sidebar'));
+ var d = [sdims[0] - 50, 370];
+ var c = geoVecScale(d, 0.5);
+ var z = 22;
+ projection.scale(geoZoomToScale(z)); // Calculate extent of all key vertices
- if (context.inIntro()) {
- // localize the address format for the walkthrough
- countryCode = _t('intro.graph.countrycode');
- } else {
- var center = extent.center();
- countryCode = iso1A2Code(center);
- }
+ var extent = geoExtent();
- if (countryCode) {
- _countryCode = countryCode.toLowerCase();
- updateForCountryCode();
- }
+ for (var i = 0; i < _intersection.vertices.length; i++) {
+ extent._extend(_intersection.vertices[i].extent());
}
- }
- function change(onInput) {
- return function () {
- var tags = {};
+ var padTop = 35; // reserve top space for hint text
+ // If this is a large intersection, adjust zoom to fit extent
- _wrap.selectAll('input').each(function (subfield) {
- var key = field.key + ':' + subfield.id;
- var value = this.value;
- if (!onInput) value = context.cleanTagValue(value); // don't override multiple values with blank string
+ if (_intersection.vertices.length > 1) {
+ var hPadding = Math.min(160, Math.max(110, d[0] * 0.4));
+ var vPadding = 160;
+ var tl = projection([extent[0][0], extent[1][1]]);
+ var br = projection([extent[1][0], extent[0][1]]);
+ var hFactor = (br[0] - tl[0]) / (d[0] - hPadding);
+ var vFactor = (br[1] - tl[1]) / (d[1] - vPadding - padTop);
+ var hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2;
+ var vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2;
+ z = z - Math.max(hZoomDiff, vZoomDiff);
+ projection.scale(geoZoomToScale(z));
+ }
- if (Array.isArray(_tags[key]) && !value) return;
- tags[key] = value || undefined;
- });
+ var extentCenter = projection(extent.center());
+ extentCenter[1] = extentCenter[1] - padTop / 2;
+ projection.translate(geoVecSubtract(c, extentCenter)).clipExtent([[0, 0], d]);
+ var drawLayers = svgLayers(projection, context).only(['osm', 'touch']).dimensions(d);
+ var drawVertices = svgVertices(projection, context);
+ var drawLines = svgLines(projection, context);
+ var drawTurns = svgTurns(projection, context);
+ var firstTime = selection.selectAll('.surface').empty();
+ selection.call(drawLayers);
+ var surface = selection.selectAll('.surface').classed('tr', true);
- dispatch.call('change', this, tags, onInput);
- };
- }
+ if (firstTime) {
+ _initialized = true;
+ surface.call(breathe);
+ } // This can happen if we've lowered the detail while a FROM way
+ // is selected, and that way is no longer part of the intersection.
- function updatePlaceholder(inputSelection) {
- return inputSelection.attr('placeholder', function (subfield) {
- if (_tags && Array.isArray(_tags[field.key + ':' + subfield.id])) {
- return _t('inspector.multiple_values');
- }
- if (_countryCode) {
- var localkey = subfield.id + '!' + _countryCode;
- var tkey = addrField.hasTextForStringId('placeholders.' + localkey) ? localkey : subfield.id;
- return addrField.t('placeholders.' + tkey);
+ if (_fromWayID && !vgraph.hasEntity(_fromWayID)) {
+ _fromWayID = null;
+ _oldTurns = null;
+ }
+
+ surface.call(utilSetDimensions, d).call(drawVertices, vgraph, _intersection.vertices, filter, extent, z).call(drawLines, vgraph, _intersection.ways, filter).call(drawTurns, vgraph, _intersection.turns(_fromWayID, _maxViaWay));
+ surface.on('click.restrictions', click).on('mouseover.restrictions', mouseover);
+ surface.selectAll('.selected').classed('selected', false);
+ surface.selectAll('.related').classed('related', false);
+ var way;
+
+ if (_fromWayID) {
+ way = vgraph.entity(_fromWayID);
+ surface.selectAll('.' + _fromWayID).classed('selected', true).classed('related', true);
+ }
+
+ document.addEventListener('resizeWindow', function () {
+ utilSetDimensions(_container, null);
+ redraw(1);
+ }, false);
+ updateHints(null);
+
+ function click(d3_event) {
+ surface.call(breathe.off).call(breathe);
+ var datum = d3_event.target.__data__;
+ var entity = datum && datum.properties && datum.properties.entity;
+
+ if (entity) {
+ datum = entity;
}
- });
- }
- function updateTags(tags) {
- utilGetSetValue(_wrap.selectAll('input'), function (subfield) {
- var val = tags[field.key + ':' + subfield.id];
- return typeof val === 'string' ? val : '';
- }).attr('title', function (subfield) {
- var val = tags[field.key + ':' + subfield.id];
- return val && Array.isArray(val) && val.filter(Boolean).join('\n');
- }).classed('mixed', function (subfield) {
- return Array.isArray(tags[field.key + ':' + subfield.id]);
- }).call(updatePlaceholder);
- }
+ if (datum instanceof osmWay && (datum.__from || datum.__via)) {
+ _fromWayID = datum.id;
+ _oldTurns = null;
+ redraw();
+ } else if (datum instanceof osmTurn) {
+ var actions, extraActions, turns, i;
+ var restrictionType = osmInferRestriction(vgraph, datum, projection);
- function combinedEntityExtent() {
- return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
- }
+ if (datum.restrictionID && !datum.direct) {
+ return;
+ } else if (datum.restrictionID && !datum.only) {
+ // NO -> ONLY
+ var seen = {};
+ var datumOnly = JSON.parse(JSON.stringify(datum)); // deep clone the datum
- address.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return address;
- };
+ datumOnly.only = true; // but change this property
- address.tags = function (tags) {
- _tags = tags;
- updateTags(tags);
- };
+ restrictionType = restrictionType.replace(/^no/, 'only'); // Adding an ONLY restriction should destroy all other direct restrictions from the FROM towards the VIA.
+ // We will remember them in _oldTurns, and restore them if the user clicks again.
- address.focus = function () {
- var node = _wrap.selectAll('input').node();
+ turns = _intersection.turns(_fromWayID, 2);
+ extraActions = [];
+ _oldTurns = [];
- if (node) node.focus();
- };
+ for (i = 0; i < turns.length; i++) {
+ var turn = turns[i];
+ if (seen[turn.restrictionID]) continue; // avoid deleting the turn twice (#4968, #4928)
- return utilRebind(address, dispatch, 'on');
- }
+ if (turn.direct && turn.path[1] === datum.path[1]) {
+ seen[turns[i].restrictionID] = true;
+ turn.restrictionType = osmInferRestriction(vgraph, turn, projection);
- function uiFieldCycleway(field, context) {
- var dispatch = dispatch$8('change');
- var items = select(null);
- var wrap = select(null);
+ _oldTurns.push(turn);
- var _tags;
+ extraActions.push(actionUnrestrictTurn(turn));
+ }
+ }
- function cycleway(selection) {
- function stripcolon(s) {
- return s.replace(':', '');
- }
+ actions = _intersection.actions.concat(extraActions, [actionRestrictTurn(datumOnly, restrictionType), _t('operations.restriction.annotation.create')]);
+ } else if (datum.restrictionID) {
+ // ONLY -> Allowed
+ // Restore whatever restrictions we might have destroyed by cycling thru the ONLY state.
+ // This relies on the assumption that the intersection was already split up when we
+ // performed the previous action (NO -> ONLY), so the IDs in _oldTurns shouldn't have changed.
+ turns = _oldTurns || [];
+ extraActions = [];
- wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- var div = wrap.selectAll('ul').data([0]);
- div = div.enter().append('ul').attr('class', 'rows').merge(div);
- var keys = ['cycleway:left', 'cycleway:right'];
- items = div.selectAll('li').data(keys);
- var enter = items.enter().append('li').attr('class', function (d) {
- return 'labeled-input preset-cycleway-' + stripcolon(d);
- });
- enter.append('span').attr('class', 'label preset-label-cycleway').attr('for', function (d) {
- return 'preset-input-cycleway-' + stripcolon(d);
- }).html(function (d) {
- return field.t.html('types.' + d);
- });
- enter.append('div').attr('class', 'preset-input-cycleway-wrap').append('input').attr('type', 'text').attr('class', function (d) {
- return 'preset-input-cycleway preset-input-' + stripcolon(d);
- }).call(utilNoAuto).each(function (d) {
- select(this).call(uiCombobox(context, 'cycleway-' + stripcolon(d)).data(cycleway.options(d)));
- });
- items = items.merge(enter); // Update
+ for (i = 0; i < turns.length; i++) {
+ if (turns[i].key !== datum.key) {
+ extraActions.push(actionRestrictTurn(turns[i], turns[i].restrictionType));
+ }
+ }
- wrap.selectAll('.preset-input-cycleway').on('change', change).on('blur', change);
- }
+ _oldTurns = null;
+ actions = _intersection.actions.concat(extraActions, [actionUnrestrictTurn(datum), _t('operations.restriction.annotation.delete')]);
+ } else {
+ // Allowed -> NO
+ actions = _intersection.actions.concat([actionRestrictTurn(datum, restrictionType), _t('operations.restriction.annotation.create')]);
+ }
- function change(d3_event, key) {
- var newValue = context.cleanTagValue(utilGetSetValue(select(this))); // don't override multiple values with blank string
+ context.perform.apply(context, actions); // At this point the datum will be changed, but will have same key..
+ // Refresh it and update the help..
- if (!newValue && (Array.isArray(_tags.cycleway) || Array.isArray(_tags[key]))) return;
+ var s = surface.selectAll('.' + datum.key);
+ datum = s.empty() ? null : s.datum();
+ updateHints(datum);
+ } else {
+ _fromWayID = null;
+ _oldTurns = null;
+ redraw();
+ }
+ }
- if (newValue === 'none' || newValue === '') {
- newValue = undefined;
+ function mouseover(d3_event) {
+ var datum = d3_event.target.__data__;
+ updateHints(datum);
}
- var otherKey = key === 'cycleway:left' ? 'cycleway:right' : 'cycleway:left';
- var otherValue = typeof _tags.cycleway === 'string' ? _tags.cycleway : _tags[otherKey];
+ _lastXPos = _lastXPos || sdims[0];
- if (otherValue && Array.isArray(otherValue)) {
- // we must always have an explicit value for comparison
- otherValue = otherValue[0];
- }
+ function redraw(minChange) {
+ var xPos = -1;
- if (otherValue === 'none' || otherValue === '') {
- otherValue = undefined;
- }
+ if (minChange) {
+ xPos = utilGetDimensions(context.container().select('.sidebar'))[0];
+ }
- var tag = {}; // If the left and right tags match, use the cycleway tag to tag both
- // sides the same way
+ if (!minChange || minChange && Math.abs(xPos - _lastXPos) >= minChange) {
+ if (context.hasEntity(_vertexID)) {
+ _lastXPos = xPos;
- if (newValue === otherValue) {
- tag = {
- cycleway: newValue,
- 'cycleway:left': undefined,
- 'cycleway:right': undefined
- };
- } else {
- // Always set both left and right as changing one can affect the other
- tag = {
- cycleway: undefined
- };
- tag[key] = newValue;
- tag[otherKey] = otherValue;
+ _container.call(renderViewer);
+ }
+ }
}
- dispatch.call('change', this, tag);
- }
+ function highlightPathsFrom(wayID) {
+ surface.selectAll('.related').classed('related', false).classed('allow', false).classed('restrict', false).classed('only', false);
+ surface.selectAll('.' + wayID).classed('related', true);
- cycleway.options = function () {
- return field.options.map(function (option) {
- return {
- title: field.t('options.' + option + '.description'),
- value: option
- };
- });
- };
+ if (wayID) {
+ var turns = _intersection.turns(wayID, _maxViaWay);
- cycleway.tags = function (tags) {
- _tags = tags; // If cycleway is set, use that instead of individual values
+ for (var i = 0; i < turns.length; i++) {
+ var turn = turns[i];
+ var ids = [turn.to.way];
+ var klass = turn.no ? 'restrict' : turn.only ? 'only' : 'allow';
- var commonValue = typeof tags.cycleway === 'string' && tags.cycleway;
- utilGetSetValue(items.selectAll('.preset-input-cycleway'), function (d) {
- if (commonValue) return commonValue;
- return !tags.cycleway && typeof tags[d] === 'string' ? tags[d] : '';
- }).attr('title', function (d) {
- if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
- var vals = [];
+ if (turn.only || turns.length === 1) {
+ if (turn.via.ways) {
+ ids = ids.concat(turn.via.ways);
+ }
+ } else if (turn.to.way === wayID) {
+ continue;
+ }
- if (Array.isArray(tags.cycleway)) {
- vals = vals.concat(tags.cycleway);
+ surface.selectAll(utilEntitySelector(ids)).classed('related', true).classed('allow', klass === 'allow').classed('restrict', klass === 'restrict').classed('only', klass === 'only');
}
+ }
+ }
- if (Array.isArray(tags[d])) {
- vals = vals.concat(tags[d]);
- }
+ function updateHints(datum) {
+ var help = _container.selectAll('.restriction-help').html('');
- return vals.filter(Boolean).join('\n');
- }
+ var placeholders = {};
+ ['from', 'via', 'to'].forEach(function (k) {
+ placeholders[k] = {
+ html: '<span class="qualifier">' + _t('restriction.help.' + k) + '</span>'
+ };
+ });
+ var entity = datum && datum.properties && datum.properties.entity;
- return null;
- }).attr('placeholder', function (d) {
- if (Array.isArray(tags.cycleway) || Array.isArray(tags[d])) {
- return _t('inspector.multiple_values');
+ if (entity) {
+ datum = entity;
}
- return field.placeholder();
- }).classed('mixed', function (d) {
- return Array.isArray(tags.cycleway) || Array.isArray(tags[d]);
- });
- };
+ if (_fromWayID) {
+ way = vgraph.entity(_fromWayID);
+ surface.selectAll('.' + _fromWayID).classed('selected', true).classed('related', true);
+ } // Hovering a way
- cycleway.focus = function () {
- var node = wrap.selectAll('input').node();
- if (node) node.focus();
- };
- return utilRebind(cycleway, dispatch, 'on');
- }
+ if (datum instanceof osmWay && datum.__from) {
+ way = datum;
+ highlightPathsFrom(_fromWayID ? null : way.id);
+ surface.selectAll('.' + way.id).classed('related', true);
+ var clickSelect = !_fromWayID || _fromWayID !== way.id;
+ help.append('div') // "Click to select FROM {fromName}." / "FROM {fromName}"
+ .html(_t.html('restriction.help.' + (clickSelect ? 'select_from_name' : 'from_name'), {
+ from: placeholders.from,
+ fromName: displayName(way.id, vgraph)
+ })); // Hovering a turn arrow
+ } else if (datum instanceof osmTurn) {
+ var restrictionType = osmInferRestriction(vgraph, datum, projection);
+ var turnType = restrictionType.replace(/^(only|no)\_/, '');
+ var indirect = datum.direct === false ? _t.html('restriction.help.indirect') : '';
+ var klass, turnText, nextText;
- function uiFieldLanes(field, context) {
- var dispatch = dispatch$8('change');
- var LANE_WIDTH = 40;
- var LANE_HEIGHT = 200;
- var _entityIDs = [];
+ if (datum.no) {
+ klass = 'restrict';
+ turnText = _t.html('restriction.help.turn.no_' + turnType, {
+ indirect: indirect
+ });
+ nextText = _t.html('restriction.help.turn.only_' + turnType, {
+ indirect: ''
+ });
+ } else if (datum.only) {
+ klass = 'only';
+ turnText = _t.html('restriction.help.turn.only_' + turnType, {
+ indirect: indirect
+ });
+ nextText = _t.html('restriction.help.turn.allowed_' + turnType, {
+ indirect: ''
+ });
+ } else {
+ klass = 'allow';
+ turnText = _t.html('restriction.help.turn.allowed_' + turnType, {
+ indirect: indirect
+ });
+ nextText = _t.html('restriction.help.turn.no_' + turnType, {
+ indirect: ''
+ });
+ }
- function lanes(selection) {
- var lanesData = context.entity(_entityIDs[0]).lanes();
+ help.append('div') // "NO Right Turn (indirect)"
+ .attr('class', 'qualifier ' + klass).html(turnText);
+ help.append('div') // "FROM {fromName} TO {toName}"
+ .html(_t.html('restriction.help.from_name_to_name', {
+ from: placeholders.from,
+ fromName: displayName(datum.from.way, vgraph),
+ to: placeholders.to,
+ toName: displayName(datum.to.way, vgraph)
+ }));
- if (!context.container().select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode) {
- selection.call(lanes.off);
- return;
- }
+ if (datum.via.ways && datum.via.ways.length) {
+ var names = [];
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- var surface = wrap.selectAll('.surface').data([0]);
- var d = utilGetDimensions(wrap);
- var freeSpace = d[0] - lanesData.lanes.length * LANE_WIDTH * 1.5 + LANE_WIDTH * 0.5;
- surface = surface.enter().append('svg').attr('width', d[0]).attr('height', 300).attr('class', 'surface').merge(surface);
- var lanesSelection = surface.selectAll('.lanes').data([0]);
- lanesSelection = lanesSelection.enter().append('g').attr('class', 'lanes').merge(lanesSelection);
- lanesSelection.attr('transform', function () {
- return 'translate(' + freeSpace / 2 + ', 0)';
- });
- var lane = lanesSelection.selectAll('.lane').data(lanesData.lanes);
- lane.exit().remove();
- var enter = lane.enter().append('g').attr('class', 'lane');
- enter.append('g').append('rect').attr('y', 50).attr('width', LANE_WIDTH).attr('height', LANE_HEIGHT);
- enter.append('g').attr('class', 'forward').append('text').attr('y', 40).attr('x', 14).html('▲');
- enter.append('g').attr('class', 'bothways').append('text').attr('y', 40).attr('x', 14).html('▲▼');
- enter.append('g').attr('class', 'backward').append('text').attr('y', 40).attr('x', 14).html('▼');
- lane = lane.merge(enter);
- lane.attr('transform', function (d) {
- return 'translate(' + LANE_WIDTH * d.index * 1.5 + ', 0)';
- });
- lane.select('.forward').style('visibility', function (d) {
- return d.direction === 'forward' ? 'visible' : 'hidden';
- });
- lane.select('.bothways').style('visibility', function (d) {
- return d.direction === 'bothways' ? 'visible' : 'hidden';
- });
- lane.select('.backward').style('visibility', function (d) {
- return d.direction === 'backward' ? 'visible' : 'hidden';
- });
- }
+ for (var i = 0; i < datum.via.ways.length; i++) {
+ var prev = names[names.length - 1];
+ var curr = displayName(datum.via.ways[i], vgraph);
- lanes.entityIDs = function (val) {
- _entityIDs = val;
- };
+ if (!prev || curr !== prev) {
+ // collapse identical names
+ names.push(curr);
+ }
+ }
- lanes.tags = function () {};
+ help.append('div') // "VIA {viaNames}"
+ .html(_t.html('restriction.help.via_names', {
+ via: placeholders.via,
+ viaNames: names.join(', ')
+ }));
+ }
- lanes.focus = function () {};
+ if (!indirect) {
+ help.append('div') // Click for "No Right Turn"
+ .html(_t.html('restriction.help.toggle', {
+ turn: {
+ html: nextText.trim()
+ }
+ }));
+ }
- lanes.off = function () {};
+ highlightPathsFrom(null);
+ var alongIDs = datum.path.slice();
+ surface.selectAll(utilEntitySelector(alongIDs)).classed('related', true).classed('allow', klass === 'allow').classed('restrict', klass === 'restrict').classed('only', klass === 'only'); // Hovering empty surface
+ } else {
+ highlightPathsFrom(null);
- return utilRebind(lanes, dispatch, 'on');
- }
- uiFieldLanes.supportsMultiselection = false;
+ if (_fromWayID) {
+ help.append('div') // "FROM {fromName}"
+ .html(_t.html('restriction.help.from_name', {
+ from: placeholders.from,
+ fromName: displayName(_fromWayID, vgraph)
+ }));
+ } else {
+ help.append('div') // "Click to select a FROM segment."
+ .html(_t.html('restriction.help.select_from', {
+ from: placeholders.from
+ }));
+ }
+ }
+ }
+ }
- var _languagesArray = [];
- function uiFieldLocalized(field, context) {
- var dispatch = dispatch$8('change', 'input');
- var wikipedia = services.wikipedia;
- var input = select(null);
- var localizedInputs = select(null);
+ function displayMaxDistance(maxDist) {
+ return function (selection) {
+ var isImperial = !_mainLocalizer.usesMetric();
+ var opts;
+
+ if (isImperial) {
+ var distToFeet = {
+ // imprecise conversion for prettier display
+ 20: 70,
+ 25: 85,
+ 30: 100,
+ 35: 115,
+ 40: 130,
+ 45: 145,
+ 50: 160
+ }[maxDist];
+ opts = {
+ distance: _t('units.feet', {
+ quantity: distToFeet
+ })
+ };
+ } else {
+ opts = {
+ distance: _t('units.meters', {
+ quantity: maxDist
+ })
+ };
+ }
- var _countryCode;
+ return selection.html('').call(_t.append('restriction.controls.distance_up_to', opts));
+ };
+ }
- var _tags; // A concern here in switching to async data means that _languagesArray will not
- // be available the first time through, so things like the fetchers and
- // the language() function will not work immediately.
+ function displayMaxVia(maxVia) {
+ return function (selection) {
+ selection = selection.html('');
+ return maxVia === 0 ? selection.call(_t.append('restriction.controls.via_node_only')) : maxVia === 1 ? selection.call(_t.append('restriction.controls.via_up_to_one')) : selection.call(_t.append('restriction.controls.via_up_to_two'));
+ };
+ }
+ function displayName(entityID, graph) {
+ var entity = graph.entity(entityID);
+ var name = utilDisplayName(entity) || '';
+ var matched = _mainPresetIndex.match(entity, graph);
+ var type = matched && matched.name() || utilDisplayType(entity.id);
+ return name || type;
+ }
- _mainFileFetcher.get('languages').then(loadLanguagesArray)["catch"](function () {
- /* ignore */
- });
- var _territoryLanguages = {};
- _mainFileFetcher.get('territory_languages').then(function (d) {
- _territoryLanguages = d;
- })["catch"](function () {
- /* ignore */
- }); // reuse these combos
+ restrictions.entityIDs = function (val) {
+ _intersection = null;
+ _fromWayID = null;
+ _oldTurns = null;
+ _vertexID = val[0];
+ };
- var langCombo = uiCombobox(context, 'localized-lang').fetcher(fetchLanguages).minItems(0);
+ restrictions.tags = function () {};
- var _selection = select(null);
+ restrictions.focus = function () {};
- var _multilingual = [];
+ restrictions.off = function (selection) {
+ if (!_initialized) return;
+ selection.selectAll('.surface').call(breathe.off).on('click.restrictions', null).on('mouseover.restrictions', null);
+ select(window).on('resize.restrictions', null);
+ };
- var _buttonTip = uiTooltip().title(_t.html('translate.translate')).placement('left');
+ return utilRebind(restrictions, dispatch, 'on');
+ }
+ uiFieldRestrictions.supportsMultiselection = false;
- var _wikiTitles;
+ function uiFieldTextarea(field, context) {
+ var dispatch = dispatch$8('change');
+ var input = select(null);
- var _entityIDs = [];
+ var _tags;
- function loadLanguagesArray(dataLanguages) {
- if (_languagesArray.length !== 0) return; // some conversion is needed to ensure correct OSM tags are used
+ function textarea(selection) {
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ input = wrap.selectAll('textarea').data([0]);
+ input = input.enter().append('textarea').attr('id', field.domId).call(utilNoAuto).on('input', change(true)).on('blur', change()).on('change', change()).merge(input);
+ }
- var replacements = {
- sr: 'sr-Cyrl',
- // in OSM, `sr` implies Cyrillic
- 'sr-Cyrl': false // `sr-Cyrl` isn't used in OSM
+ function change(onInput) {
+ return function () {
+ var val = utilGetSetValue(input);
+ if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
+ if (!val && Array.isArray(_tags[field.key])) return;
+ var t = {};
+ t[field.key] = val || undefined;
+ dispatch.call('change', this, t, onInput);
};
-
- for (var code in dataLanguages) {
- if (replacements[code] === false) continue;
- var metaCode = code;
- if (replacements[code]) metaCode = replacements[code];
-
- _languagesArray.push({
- localName: _mainLocalizer.languageName(metaCode, {
- localOnly: true
- }),
- nativeName: dataLanguages[metaCode].nativeName,
- code: code,
- label: _mainLocalizer.languageName(metaCode)
- });
- }
}
- function calcLocked() {
- // Protect name field for suggestion presets that don't display a brand/operator field
- var isLocked = field.id === 'name' && _entityIDs.length && _entityIDs.some(function (entityID) {
- var entity = context.graph().hasEntity(entityID);
- if (!entity) return false; // Features linked to Wikidata are likely important and should be protected
+ textarea.tags = function (tags) {
+ _tags = tags;
+ var isMixed = Array.isArray(tags[field.key]);
+ utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder() || _t('inspector.unknown')).classed('mixed', isMixed);
+ };
- if (entity.tags.wikidata) return true; // Assume the name has already been confirmed if its source has been researched
+ textarea.focus = function () {
+ input.node().focus();
+ };
- if (entity.tags['name:etymology:wikidata']) return true; // Lock the `name` if this is a suggestion preset that assigns the name,
- // and the preset does not display a `brand` or `operator` field.
- // (For presets like hotels, car dealerships, post offices, the `name` should remain editable)
- // see also similar logic in `outdated_tags.js`
+ return utilRebind(textarea, dispatch, 'on');
+ }
- var preset = _mainPresetIndex.match(entity, context.graph());
+ function uiFieldWikidata(field, context) {
+ var wikidata = services.wikidata;
+ var dispatch = dispatch$8('change');
- if (preset) {
- var isSuggestion = preset.suggestion;
- var fields = preset.fields();
- var showsBrandField = fields.some(function (d) {
- return d.id === 'brand';
- });
- var showsOperatorField = fields.some(function (d) {
- return d.id === 'operator';
- });
- var setsName = preset.addTags.name;
- var setsBrandWikidata = preset.addTags['brand:wikidata'];
- var setsOperatorWikidata = preset.addTags['operator:wikidata'];
- return isSuggestion && setsName && (setsBrandWikidata && !showsBrandField || setsOperatorWikidata && !showsOperatorField);
- }
+ var _selection = select(null);
- return false;
- });
+ var _searchInput = select(null);
- field.locked(isLocked);
- } // update _multilingual, maintaining the existing order
+ var _qid = null;
+ var _wikidataEntity = null;
+ var _wikiURL = '';
+ var _entityIDs = [];
+ var _wikipediaKey = field.keys && field.keys.find(function (key) {
+ return key.includes('wikipedia');
+ }),
+ _hintKey = field.key === 'wikidata' ? 'name' : field.key.split(':')[0];
- function calcMultilingual(tags) {
- var existingLangsOrdered = _multilingual.map(function (item) {
- return item.lang;
- });
+ var combobox = uiCombobox(context, 'combo-' + field.safeid).caseSensitive(true).minItems(1);
- var existingLangs = new Set(existingLangsOrdered.filter(Boolean));
+ function wiki(selection) {
+ _selection = selection;
+ var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
+ wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
+ var list = wrap.selectAll('ul').data([0]);
+ list = list.enter().append('ul').attr('class', 'rows').merge(list);
+ var searchRow = list.selectAll('li.wikidata-search').data([0]);
+ var searchRowEnter = searchRow.enter().append('li').attr('class', 'wikidata-search');
+ searchRowEnter.append('input').attr('type', 'text').attr('id', field.domId).style('flex', '1').call(utilNoAuto).on('focus', function () {
+ var node = select(this).node();
+ node.setSelectionRange(0, node.value.length);
+ }).on('blur', function () {
+ setLabelForEntity();
+ }).call(combobox.fetcher(fetchWikidataItems));
+ combobox.on('accept', function (d) {
+ if (d) {
+ _qid = d.id;
+ change();
+ }
+ }).on('cancel', function () {
+ setLabelForEntity();
+ });
+ searchRowEnter.append('button').attr('class', 'form-field-button wiki-link').attr('title', _t('icons.view_on', {
+ domain: 'wikidata.org'
+ })).call(svgIcon('#iD-icon-out-link')).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ if (_wikiURL) window.open(_wikiURL, '_blank');
+ });
+ searchRow = searchRow.merge(searchRowEnter);
+ _searchInput = searchRow.select('input');
+ var wikidataProperties = ['description', 'identifier'];
+ var items = list.selectAll('li.labeled-input').data(wikidataProperties); // Enter
- for (var k in tags) {
- var m = k.match(/^(.*):(.*)$/);
+ var enter = items.enter().append('li').attr('class', function (d) {
+ return 'labeled-input preset-wikidata-' + d;
+ });
+ enter.append('span').attr('class', 'label').html(function (d) {
+ return _t.html('wikidata.' + d);
+ });
+ enter.append('input').attr('type', 'text').call(utilNoAuto).classed('disabled', 'true').attr('readonly', 'true');
+ enter.append('button').attr('class', 'form-field-button').attr('title', _t('icons.copy')).call(svgIcon('#iD-operation-copy')).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ select(this.parentNode).select('input').node().select();
+ document.execCommand('copy');
+ });
+ }
- if (m && m[1] === field.key && m[2]) {
- var item = {
- lang: m[2],
- value: tags[k]
- };
+ function fetchWikidataItems(q, callback) {
+ if (!q && _hintKey) {
+ // other tags may be good search terms
+ for (var i in _entityIDs) {
+ var entity = context.hasEntity(_entityIDs[i]);
- if (existingLangs.has(item.lang)) {
- // update the value
- _multilingual[existingLangsOrdered.indexOf(item.lang)].value = item.value;
- existingLangs["delete"](item.lang);
- } else {
- _multilingual.push(item);
+ if (entity.tags[_hintKey]) {
+ q = entity.tags[_hintKey];
+ break;
}
}
- } // Don't remove items based on deleted tags, since this makes the UI
- // disappear unexpectedly when clearing values - #8164
+ }
+ wikidata.itemsForSearchQuery(q, function (err, data) {
+ if (err) return;
- _multilingual.forEach(function (item) {
- if (item.lang && existingLangs.has(item.lang)) {
- item.value = '';
+ for (var i in data) {
+ data[i].value = data[i].label + ' (' + data[i].id + ')';
+ data[i].title = data[i].description;
}
+
+ if (callback) callback(data);
});
}
- function localized(selection) {
- _selection = selection;
- calcLocked();
- var isLocked = field.locked();
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]); // enter/update
-
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- input = wrap.selectAll('.localized-main').data([0]); // enter/update
-
- input = input.enter().append('input').attr('type', 'text').attr('id', field.domId).attr('class', 'localized-main').call(utilNoAuto).merge(input);
- input.classed('disabled', !!isLocked).attr('readonly', isLocked || null).on('input', change(true)).on('blur', change()).on('change', change());
- var translateButton = wrap.selectAll('.localized-add').data([0]);
- translateButton = translateButton.enter().append('button').attr('class', 'localized-add form-field-button').call(svgIcon('#iD-icon-plus')).merge(translateButton);
- translateButton.classed('disabled', !!isLocked).call(isLocked ? _buttonTip.destroy : _buttonTip).on('click', addNew);
+ function change() {
+ var syncTags = {};
+ syncTags[field.key] = _qid;
+ dispatch.call('change', this, syncTags); // attempt asynchronous update of wikidata tag..
- if (_tags && !_multilingual.length) {
- calcMultilingual(_tags);
- }
+ var initGraph = context.graph();
+ var initEntityIDs = _entityIDs;
+ wikidata.entityByQID(_qid, function (err, entity) {
+ if (err) return; // If graph has changed, we can't apply this update.
- localizedInputs = selection.selectAll('.localized-multilingual').data([0]);
- localizedInputs = localizedInputs.enter().append('div').attr('class', 'localized-multilingual').merge(localizedInputs);
- localizedInputs.call(renderMultilingual);
- localizedInputs.selectAll('button, input').classed('disabled', !!isLocked).attr('readonly', isLocked || null);
+ if (context.graph() !== initGraph) return;
+ if (!entity.sitelinks) return;
+ var langs = wikidata.languagesToQuery(); // use the label and description languages as fallbacks
- function addNew(d3_event) {
- d3_event.preventDefault();
- if (field.locked()) return;
- var defaultLang = _mainLocalizer.languageCode().toLowerCase();
+ ['labels', 'descriptions'].forEach(function (key) {
+ if (!entity[key]) return;
+ var valueLangs = Object.keys(entity[key]);
+ if (valueLangs.length === 0) return;
+ var valueLang = valueLangs[0];
- var langExists = _multilingual.find(function (datum) {
- return datum.lang === defaultLang;
+ if (langs.indexOf(valueLang) === -1) {
+ langs.push(valueLang);
+ }
});
+ var newWikipediaValue;
- var isLangEn = defaultLang.indexOf('en') > -1;
-
- if (isLangEn || langExists) {
- defaultLang = '';
- langExists = _multilingual.find(function (datum) {
- return datum.lang === defaultLang;
- });
- }
+ if (_wikipediaKey) {
+ var foundPreferred;
- if (!langExists) {
- // prepend the value so it appears at the top
- _multilingual.unshift({
- lang: defaultLang,
- value: ''
- });
+ for (var i in langs) {
+ var lang = langs[i];
+ var siteID = lang.replace('-', '_') + 'wiki';
- localizedInputs.call(renderMultilingual);
- }
- }
+ if (entity.sitelinks[siteID]) {
+ foundPreferred = true;
+ newWikipediaValue = lang + ':' + entity.sitelinks[siteID].title; // use the first match
- function change(onInput) {
- return function (d3_event) {
- if (field.locked()) {
- d3_event.preventDefault();
- return;
+ break;
+ }
}
- var val = utilGetSetValue(select(this));
- if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
+ if (!foundPreferred) {
+ // No wikipedia sites available in the user's language or the fallback languages,
+ // default to any wikipedia sitelink
+ var wikiSiteKeys = Object.keys(entity.sitelinks).filter(function (site) {
+ return site.endsWith('wiki');
+ });
- if (!val && Array.isArray(_tags[field.key])) return;
- var t = {};
- t[field.key] = val || undefined;
- dispatch.call('change', this, t, onInput);
- };
- }
- }
+ if (wikiSiteKeys.length === 0) {
+ // if no wikipedia pages are linked to this wikidata entity, delete that tag
+ newWikipediaValue = null;
+ } else {
+ var wikiLang = wikiSiteKeys[0].slice(0, -4).replace('_', '-');
+ var wikiTitle = entity.sitelinks[wikiSiteKeys[0]].title;
+ newWikipediaValue = wikiLang + ':' + wikiTitle;
+ }
+ }
+ }
- function key(lang) {
- return field.key + ':' + lang;
- }
+ if (newWikipediaValue) {
+ newWikipediaValue = context.cleanTagValue(newWikipediaValue);
+ }
- function changeLang(d3_event, d) {
- var tags = {}; // make sure unrecognized suffixes are lowercase - #7156
+ if (typeof newWikipediaValue === 'undefined') return;
+ var actions = initEntityIDs.map(function (entityID) {
+ var entity = context.hasEntity(entityID);
+ if (!entity) return null;
+ var currTags = Object.assign({}, entity.tags); // shallow copy
- var lang = utilGetSetValue(select(this)).toLowerCase();
+ if (newWikipediaValue === null) {
+ if (!currTags[_wikipediaKey]) return null;
+ delete currTags[_wikipediaKey];
+ } else {
+ currTags[_wikipediaKey] = newWikipediaValue;
+ }
- var language = _languagesArray.find(function (d) {
- return d.label.toLowerCase() === lang || d.localName && d.localName.toLowerCase() === lang || d.nativeName && d.nativeName.toLowerCase() === lang;
- });
+ return actionChangeTags(entityID, currTags);
+ }).filter(Boolean);
+ if (!actions.length) return; // Coalesce the update of wikidata tag into the previous tag change
- if (language) lang = language.code;
+ context.overwrite(function actionUpdateWikipediaTags(graph) {
+ actions.forEach(function (action) {
+ graph = action(graph);
+ });
+ return graph;
+ }, context.history().undoAnnotation()); // do not dispatch.call('change') here, because entity_editor
+ // changeTags() is not intended to be called asynchronously
+ });
+ }
- if (d.lang && d.lang !== lang) {
- tags[key(d.lang)] = undefined;
- }
+ function setLabelForEntity() {
+ var label = '';
- var newKey = lang && context.cleanTagKey(key(lang));
- var value = utilGetSetValue(select(this.parentNode).selectAll('.localized-value'));
+ if (_wikidataEntity) {
+ label = entityPropertyForDisplay(_wikidataEntity, 'labels');
- if (newKey && value) {
- tags[newKey] = value;
- } else if (newKey && _wikiTitles && _wikiTitles[d.lang]) {
- tags[newKey] = _wikiTitles[d.lang];
+ if (label.length === 0) {
+ label = _wikidataEntity.id.toString();
+ }
}
- d.lang = lang;
- dispatch.call('change', this, tags);
- }
-
- function changeValue(d3_event, d) {
- if (!d.lang) return;
- var value = context.cleanTagValue(utilGetSetValue(select(this))) || undefined; // don't override multiple values with blank string
-
- if (!value && Array.isArray(d.value)) return;
- var t = {};
- t[key(d.lang)] = value;
- d.value = value;
- dispatch.call('change', this, t);
+ utilGetSetValue(_searchInput, label);
}
- function fetchLanguages(value, cb) {
- var v = value.toLowerCase(); // show the user's language first
-
- var langCodes = [_mainLocalizer.localeCode(), _mainLocalizer.languageCode()];
+ wiki.tags = function (tags) {
+ var isMixed = Array.isArray(tags[field.key]);
- if (_countryCode && _territoryLanguages[_countryCode]) {
- langCodes = langCodes.concat(_territoryLanguages[_countryCode]);
- }
+ _searchInput.attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : '').classed('mixed', isMixed);
- var langItems = [];
- langCodes.forEach(function (code) {
- var langItem = _languagesArray.find(function (item) {
- return item.code === code;
- });
+ _qid = typeof tags[field.key] === 'string' && tags[field.key] || '';
- if (langItem) langItems.push(langItem);
- });
- langItems = utilArrayUniq(langItems.concat(_languagesArray));
- cb(langItems.filter(function (d) {
- return d.label.toLowerCase().indexOf(v) >= 0 || d.localName && d.localName.toLowerCase().indexOf(v) >= 0 || d.nativeName && d.nativeName.toLowerCase().indexOf(v) >= 0 || d.code.toLowerCase().indexOf(v) >= 0;
- }).map(function (d) {
- return {
- value: d.label
- };
- }));
- }
+ if (!/^Q[0-9]*$/.test(_qid)) {
+ // not a proper QID
+ unrecognized();
+ return;
+ } // QID value in correct format
- function renderMultilingual(selection) {
- var entries = selection.selectAll('div.entry').data(_multilingual, function (d) {
- return d.lang;
- });
- entries.exit().style('top', '0').style('max-height', '240px').transition().duration(200).style('opacity', '0').style('max-height', '0px').remove();
- var entriesEnter = entries.enter().append('div').attr('class', 'entry').each(function (_, index) {
- var wrap = select(this);
- var domId = utilUniqueDomId(index);
- var label = wrap.append('label').attr('class', 'field-label').attr('for', domId);
- var text = label.append('span').attr('class', 'label-text');
- text.append('span').attr('class', 'label-textvalue').html(_t.html('translate.localized_translation_label'));
- text.append('span').attr('class', 'label-textannotation');
- label.append('button').attr('class', 'remove-icon-multilingual').on('click', function (d3_event, d) {
- if (field.locked()) return;
- d3_event.preventDefault(); // remove the UI item manually
- _multilingual.splice(_multilingual.indexOf(d), 1);
+ _wikiURL = 'https://wikidata.org/wiki/' + _qid;
+ wikidata.entityByQID(_qid, function (err, entity) {
+ if (err) {
+ unrecognized();
+ return;
+ }
- var langKey = d.lang && key(d.lang);
+ _wikidataEntity = entity;
+ setLabelForEntity();
+ var description = entityPropertyForDisplay(entity, 'descriptions');
- if (langKey && langKey in _tags) {
- delete _tags[langKey]; // remove from entity tags
+ _selection.select('button.wiki-link').classed('disabled', false);
- var t = {};
- t[langKey] = undefined;
- dispatch.call('change', this, t);
- return;
- }
+ _selection.select('.preset-wikidata-description').style('display', function () {
+ return description.length > 0 ? 'flex' : 'none';
+ }).select('input').attr('value', description);
- renderMultilingual(selection);
- }).call(svgIcon('#iD-operation-delete'));
- wrap.append('input').attr('class', 'localized-lang').attr('id', domId).attr('type', 'text').attr('placeholder', _t('translate.localized_translation_language')).on('blur', changeLang).on('change', changeLang).call(langCombo);
- wrap.append('input').attr('type', 'text').attr('class', 'localized-value').on('blur', changeValue).on('change', changeValue);
- });
- entriesEnter.style('margin-top', '0px').style('max-height', '0px').style('opacity', '0').transition().duration(200).style('margin-top', '10px').style('max-height', '240px').style('opacity', '1').on('end', function () {
- select(this).style('max-height', '').style('overflow', 'visible');
- });
- entries = entries.merge(entriesEnter);
- entries.order(); // allow removing the entry UIs even if there isn't a tag to remove
+ _selection.select('.preset-wikidata-identifier').style('display', function () {
+ return entity.id ? 'flex' : 'none';
+ }).select('input').attr('value', entity.id);
+ }); // not a proper QID
- entries.classed('present', true);
- utilGetSetValue(entries.select('.localized-lang'), function (d) {
- var langItem = _languagesArray.find(function (item) {
- return item.code === d.lang;
- });
+ function unrecognized() {
+ _wikidataEntity = null;
+ setLabelForEntity();
- if (langItem) return langItem.label;
- return d.lang;
- });
- utilGetSetValue(entries.select('.localized-value'), function (d) {
- return typeof d.value === 'string' ? d.value : '';
- }).attr('title', function (d) {
- return Array.isArray(d.value) ? d.value.filter(Boolean).join('\n') : null;
- }).attr('placeholder', function (d) {
- return Array.isArray(d.value) ? _t('inspector.multiple_values') : _t('translate.localized_translation_name');
- }).classed('mixed', function (d) {
- return Array.isArray(d.value);
- });
- }
+ _selection.select('.preset-wikidata-description').style('display', 'none');
- localized.tags = function (tags) {
- _tags = tags; // Fetch translations from wikipedia
+ _selection.select('.preset-wikidata-identifier').style('display', 'none');
- if (typeof tags.wikipedia === 'string' && !_wikiTitles) {
- _wikiTitles = {};
- var wm = tags.wikipedia.match(/([^:]+):(.+)/);
+ _selection.select('button.wiki-link').classed('disabled', true);
- if (wm && wm[0] && wm[1]) {
- wikipedia.translations(wm[1], wm[2], function (err, d) {
- if (err || !d) return;
- _wikiTitles = d;
- });
+ if (_qid && _qid !== '') {
+ _wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + _qid;
+ } else {
+ _wikiURL = '';
}
}
+ };
- var isMixed = Array.isArray(tags[field.key]);
- utilGetSetValue(input, typeof tags[field.key] === 'string' ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder()).classed('mixed', isMixed);
- calcMultilingual(tags);
+ function entityPropertyForDisplay(wikidataEntity, propKey) {
+ if (!wikidataEntity[propKey]) return '';
+ var propObj = wikidataEntity[propKey];
+ var langKeys = Object.keys(propObj);
+ if (langKeys.length === 0) return ''; // sorted by priority, since we want to show the user's language first if possible
- _selection.call(localized);
- };
+ var langs = wikidata.languagesToQuery();
- localized.focus = function () {
- input.node().focus();
- };
+ for (var i in langs) {
+ var lang = langs[i];
+ var valueObj = propObj[lang];
+ if (valueObj && valueObj.value && valueObj.value.length > 0) return valueObj.value;
+ } // default to any available value
- localized.entityIDs = function (val) {
+
+ return propObj[langKeys[0]].value;
+ }
+
+ wiki.entityIDs = function (val) {
if (!arguments.length) return _entityIDs;
_entityIDs = val;
- _multilingual = [];
- loadCountryCode();
- return localized;
+ return wiki;
};
- function loadCountryCode() {
- var extent = combinedEntityExtent();
- var countryCode = extent && iso1A2Code(extent.center());
- _countryCode = countryCode && countryCode.toLowerCase();
- }
-
- function combinedEntityExtent() {
- return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
- }
+ wiki.focus = function () {
+ _searchInput.node().focus();
+ };
- return utilRebind(localized, dispatch, 'on');
+ return utilRebind(wiki, dispatch, 'on');
}
- function uiFieldRoadheight(field, context) {
+ function uiFieldWikipedia(field, context) {
+ var _arguments = arguments;
var dispatch = dispatch$8('change');
- var primaryUnitInput = select(null);
- var primaryInput = select(null);
- var secondaryInput = select(null);
- var secondaryUnitInput = select(null);
- var _entityIDs = [];
+ var wikipedia = services.wikipedia;
+ var wikidata = services.wikidata;
+
+ var _langInput = select(null);
+
+ var _titleInput = select(null);
+
+ var _wikiURL = '';
+
+ var _entityIDs;
var _tags;
- var _isImperial;
+ var _dataWikipedia = [];
+ _mainFileFetcher.get('wmf_sitematrix').then(function (d) {
+ _dataWikipedia = d;
+ if (_tags) updateForTags(_tags);
+ })["catch"](function () {
+ /* ignore */
+ });
+ var langCombo = uiCombobox(context, 'wikipedia-lang').fetcher(function (value, callback) {
+ var v = value.toLowerCase();
+ callback(_dataWikipedia.filter(function (d) {
+ return d[0].toLowerCase().indexOf(v) >= 0 || d[1].toLowerCase().indexOf(v) >= 0 || d[2].toLowerCase().indexOf(v) >= 0;
+ }).map(function (d) {
+ return {
+ value: d[1]
+ };
+ }));
+ });
+ var titleCombo = uiCombobox(context, 'wikipedia-title').fetcher(function (value, callback) {
+ if (!value) {
+ value = '';
- var primaryUnits = [{
- value: 'm',
- title: _t('inspector.roadheight.meter')
- }, {
- value: 'ft',
- title: _t('inspector.roadheight.foot')
- }];
- var unitCombo = uiCombobox(context, 'roadheight-unit').data(primaryUnits);
+ for (var i in _entityIDs) {
+ var entity = context.hasEntity(_entityIDs[i]);
- function roadheight(selection) {
+ if (entity.tags.name) {
+ value = entity.tags.name;
+ break;
+ }
+ }
+ }
+
+ var searchfn = value.length > 7 ? wikipedia.search : wikipedia.suggestions;
+ searchfn(language()[2], value, function (query, data) {
+ callback(data.map(function (d) {
+ return {
+ value: d
+ };
+ }));
+ });
+ });
+
+ function wiki(selection) {
var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- primaryInput = wrap.selectAll('input.roadheight-number').data([0]);
- primaryInput = primaryInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-number').attr('id', field.domId).call(utilNoAuto).merge(primaryInput);
- primaryInput.on('change', change).on('blur', change);
- var loc = combinedEntityExtent().center();
- _isImperial = roadHeightUnit(loc) === 'ft';
- primaryUnitInput = wrap.selectAll('input.roadheight-unit').data([0]);
- primaryUnitInput = primaryUnitInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-unit').call(unitCombo).merge(primaryUnitInput);
- primaryUnitInput.on('blur', changeUnits).on('change', changeUnits);
- secondaryInput = wrap.selectAll('input.roadheight-secondary-number').data([0]);
- secondaryInput = secondaryInput.enter().append('input').attr('type', 'text').attr('class', 'roadheight-secondary-number').call(utilNoAuto).merge(secondaryInput);
- secondaryInput.on('change', change).on('blur', change);
- secondaryUnitInput = wrap.selectAll('input.roadheight-secondary-unit').data([0]);
- secondaryUnitInput = secondaryUnitInput.enter().append('input').attr('type', 'text').call(utilNoAuto).classed('disabled', true).classed('roadheight-secondary-unit', true).attr('readonly', 'readonly').merge(secondaryUnitInput);
+ wrap = wrap.enter().append('div').attr('class', "form-field-input-wrap form-field-input-".concat(field.type)).merge(wrap);
+ var langContainer = wrap.selectAll('.wiki-lang-container').data([0]);
+ langContainer = langContainer.enter().append('div').attr('class', 'wiki-lang-container').merge(langContainer);
+ _langInput = langContainer.selectAll('input.wiki-lang').data([0]);
+ _langInput = _langInput.enter().append('input').attr('type', 'text').attr('class', 'wiki-lang').attr('placeholder', _t('translate.localized_translation_language')).call(utilNoAuto).call(langCombo).merge(_langInput);
- function changeUnits() {
- _isImperial = utilGetSetValue(primaryUnitInput) === 'ft';
- utilGetSetValue(primaryUnitInput, _isImperial ? 'ft' : 'm');
- setUnitSuggestions();
- change();
- }
- }
+ _langInput.on('blur', changeLang).on('change', changeLang);
- function setUnitSuggestions() {
- utilGetSetValue(primaryUnitInput, _isImperial ? 'ft' : 'm');
- }
+ var titleContainer = wrap.selectAll('.wiki-title-container').data([0]);
+ titleContainer = titleContainer.enter().append('div').attr('class', 'wiki-title-container').merge(titleContainer);
+ _titleInput = titleContainer.selectAll('input.wiki-title').data([0]);
+ _titleInput = _titleInput.enter().append('input').attr('type', 'text').attr('class', 'wiki-title').attr('id', field.domId).call(utilNoAuto).call(titleCombo).merge(_titleInput);
- function change() {
- var tag = {};
- var primaryValue = utilGetSetValue(primaryInput).trim();
- var secondaryValue = utilGetSetValue(secondaryInput).trim(); // don't override multiple values with blank string
+ _titleInput.on('blur', function () {
+ change(true);
+ }).on('change', function () {
+ change(false);
+ });
- if (!primaryValue && !secondaryValue && Array.isArray(_tags[field.key])) return;
+ var link = titleContainer.selectAll('.wiki-link').data([0]);
+ link = link.enter().append('button').attr('class', 'form-field-button wiki-link').attr('title', _t('icons.view_on', {
+ domain: 'wikipedia.org'
+ })).call(svgIcon('#iD-icon-out-link')).merge(link);
+ link.on('click', function (d3_event) {
+ d3_event.preventDefault();
+ if (_wikiURL) window.open(_wikiURL, '_blank');
+ });
+ }
- if (!primaryValue && !secondaryValue) {
- tag[field.key] = undefined;
- } else if (isNaN(primaryValue) || isNaN(secondaryValue) || !_isImperial) {
- tag[field.key] = context.cleanTagValue(primaryValue);
- } else {
- if (primaryValue !== '') {
- primaryValue = context.cleanTagValue(primaryValue + '\'');
- }
+ function defaultLanguageInfo(skipEnglishFallback) {
+ var langCode = _mainLocalizer.languageCode().toLowerCase();
- if (secondaryValue !== '') {
- secondaryValue = context.cleanTagValue(secondaryValue + '"');
- }
+ for (var i in _dataWikipedia) {
+ var d = _dataWikipedia[i]; // default to the language of iD's current locale
- tag[field.key] = primaryValue + secondaryValue;
- }
+ if (d[2] === langCode) return d;
+ } // fallback to English
- dispatch.call('change', this, tag);
- }
- roadheight.tags = function (tags) {
- _tags = tags;
- var primaryValue = tags[field.key];
- var secondaryValue;
- var isMixed = Array.isArray(primaryValue);
+ return skipEnglishFallback ? ['', '', ''] : ['English', 'English', 'en'];
+ }
- if (!isMixed) {
- if (primaryValue && (primaryValue.indexOf('\'') >= 0 || primaryValue.indexOf('"') >= 0)) {
- secondaryValue = primaryValue.match(/(-?[\d.]+)"/);
+ function language(skipEnglishFallback) {
+ var value = utilGetSetValue(_langInput).toLowerCase();
- if (secondaryValue !== null) {
- secondaryValue = secondaryValue[1];
- }
+ for (var i in _dataWikipedia) {
+ var d = _dataWikipedia[i]; // return the language already set in the UI, if supported
- primaryValue = primaryValue.match(/(-?[\d.]+)'/);
+ if (d[0].toLowerCase() === value || d[1].toLowerCase() === value || d[2] === value) return d;
+ } // fallback to English
- if (primaryValue !== null) {
- primaryValue = primaryValue[1];
- }
- _isImperial = true;
- } else if (primaryValue) {
- _isImperial = false;
- }
- }
+ return defaultLanguageInfo(skipEnglishFallback);
+ }
- setUnitSuggestions();
- utilGetSetValue(primaryInput, typeof primaryValue === 'string' ? primaryValue : '').attr('title', isMixed ? primaryValue.filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : _t('inspector.unknown')).classed('mixed', isMixed);
- utilGetSetValue(secondaryInput, typeof secondaryValue === 'string' ? secondaryValue : '').attr('placeholder', isMixed ? _t('inspector.multiple_values') : _isImperial ? '0' : null).classed('mixed', isMixed).classed('disabled', !_isImperial).attr('readonly', _isImperial ? null : 'readonly');
- secondaryUnitInput.attr('value', _isImperial ? _t('inspector.roadheight.inch') : null);
- };
+ function changeLang() {
+ utilGetSetValue(_langInput, language()[1]);
+ change(true);
+ }
- roadheight.focus = function () {
- primaryInput.node().focus();
- };
+ function change(skipWikidata) {
+ var value = utilGetSetValue(_titleInput);
+ var m = value.match(/https?:\/\/([-a-z]+)\.wikipedia\.org\/(?:wiki|\1-[-a-z]+)\/([^#]+)(?:#(.+))?/);
- roadheight.entityIDs = function (val) {
- _entityIDs = val;
- };
+ var langInfo = m && _dataWikipedia.find(function (d) {
+ return m[1] === d[2];
+ });
- function combinedEntityExtent() {
- return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
- }
+ var syncTags = {};
- return utilRebind(roadheight, dispatch, 'on');
- }
+ if (langInfo) {
+ var nativeLangName = langInfo[1]; // Normalize title http://www.mediawiki.org/wiki/API:Query#Title_normalization
- function uiFieldRoadspeed(field, context) {
- var dispatch = dispatch$8('change');
- var unitInput = select(null);
- var input = select(null);
- var _entityIDs = [];
+ value = decodeURIComponent(m[2]).replace(/_/g, ' ');
- var _tags;
+ if (m[3]) {
+ var anchor; // try {
+ // leave this out for now - #6232
+ // Best-effort `anchordecode:` implementation
+ // anchor = decodeURIComponent(m[3].replace(/\.([0-9A-F]{2})/g, '%$1'));
+ // } catch (e) {
- var _isImperial;
+ anchor = decodeURIComponent(m[3]); // }
- var speedCombo = uiCombobox(context, 'roadspeed');
- var unitCombo = uiCombobox(context, 'roadspeed-unit').data(['km/h', 'mph'].map(comboValues));
- var metricValues = [20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
- var imperialValues = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80];
+ value += '#' + anchor.replace(/_/g, ' ');
+ }
- function roadspeed(selection) {
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- input = wrap.selectAll('input.roadspeed-number').data([0]);
- input = input.enter().append('input').attr('type', 'text').attr('class', 'roadspeed-number').attr('id', field.domId).call(utilNoAuto).call(speedCombo).merge(input);
- input.on('change', change).on('blur', change);
- var loc = combinedEntityExtent().center();
- _isImperial = roadSpeedUnit(loc) === 'mph';
- unitInput = wrap.selectAll('input.roadspeed-unit').data([0]);
- unitInput = unitInput.enter().append('input').attr('type', 'text').attr('class', 'roadspeed-unit').call(unitCombo).merge(unitInput);
- unitInput.on('blur', changeUnits).on('change', changeUnits);
+ value = value.slice(0, 1).toUpperCase() + value.slice(1);
+ utilGetSetValue(_langInput, nativeLangName);
+ utilGetSetValue(_titleInput, value);
+ }
- function changeUnits() {
- _isImperial = utilGetSetValue(unitInput) === 'mph';
- utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
- setUnitSuggestions();
- change();
+ if (value) {
+ syncTags.wikipedia = context.cleanTagValue(language()[2] + ':' + value);
+ } else {
+ syncTags.wikipedia = undefined;
}
- }
- function setUnitSuggestions() {
- speedCombo.data((_isImperial ? imperialValues : metricValues).map(comboValues));
- utilGetSetValue(unitInput, _isImperial ? 'mph' : 'km/h');
- }
+ dispatch.call('change', this, syncTags);
+ if (skipWikidata || !value || !language()[2]) return; // attempt asynchronous update of wikidata tag..
- function comboValues(d) {
- return {
- value: d.toString(),
- title: d.toString()
- };
- }
+ var initGraph = context.graph();
+ var initEntityIDs = _entityIDs;
+ wikidata.itemsByTitle(language()[2], value, function (err, data) {
+ if (err || !data || !Object.keys(data).length) return; // If graph has changed, we can't apply this update.
- function change() {
- var tag = {};
- var value = utilGetSetValue(input).trim(); // don't override multiple values with blank string
+ if (context.graph() !== initGraph) return;
+ var qids = Object.keys(data);
+ var value = qids && qids.find(function (id) {
+ return id.match(/^Q\d+$/);
+ });
+ var actions = initEntityIDs.map(function (entityID) {
+ var entity = context.entity(entityID).tags;
+ var currTags = Object.assign({}, entity); // shallow copy
- if (!value && Array.isArray(_tags[field.key])) return;
+ if (currTags.wikidata !== value) {
+ currTags.wikidata = value;
+ return actionChangeTags(entityID, currTags);
+ }
- if (!value) {
- tag[field.key] = undefined;
- } else if (isNaN(value) || !_isImperial) {
- tag[field.key] = context.cleanTagValue(value);
- } else {
- tag[field.key] = context.cleanTagValue(value + ' mph');
- }
+ return null;
+ }).filter(Boolean);
+ if (!actions.length) return; // Coalesce the update of wikidata tag into the previous tag change
- dispatch.call('change', this, tag);
+ context.overwrite(function actionUpdateWikidataTags(graph) {
+ actions.forEach(function (action) {
+ graph = action(graph);
+ });
+ return graph;
+ }, context.history().undoAnnotation()); // do not dispatch.call('change') here, because entity_editor
+ // changeTags() is not intended to be called asynchronously
+ });
}
- roadspeed.tags = function (tags) {
+ wiki.tags = function (tags) {
_tags = tags;
- var value = tags[field.key];
- var isMixed = Array.isArray(value);
+ updateForTags(tags);
+ };
- if (!isMixed) {
- if (value && value.indexOf('mph') >= 0) {
- value = parseInt(value, 10).toString();
- _isImperial = true;
- } else if (value) {
- _isImperial = false;
+ function updateForTags(tags) {
+ var value = typeof tags[field.key] === 'string' ? tags[field.key] : ''; // Expect tag format of `tagLang:tagArticleTitle`, e.g. `fr:Paris`, with
+ // optional suffix of `#anchor`
+
+ var m = value.match(/([^:]+):([^#]+)(?:#(.+))?/);
+ var tagLang = m && m[1];
+ var tagArticleTitle = m && m[2];
+ var anchor = m && m[3];
+
+ var tagLangInfo = tagLang && _dataWikipedia.find(function (d) {
+ return tagLang === d[2];
+ }); // value in correct format
+
+
+ if (tagLangInfo) {
+ var nativeLangName = tagLangInfo[1];
+ utilGetSetValue(_langInput, nativeLangName);
+ utilGetSetValue(_titleInput, tagArticleTitle + (anchor ? '#' + anchor : ''));
+
+ if (anchor) {
+ try {
+ // Best-effort `anchorencode:` implementation
+ anchor = encodeURIComponent(anchor.replace(/ /g, '_')).replace(/%/g, '.');
+ } catch (e) {
+ anchor = anchor.replace(/ /g, '_');
+ }
}
- }
- setUnitSuggestions();
- utilGetSetValue(input, typeof value === 'string' ? value : '').attr('title', isMixed ? value.filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder()).classed('mixed', isMixed);
- };
+ _wikiURL = 'https://' + tagLang + '.wikipedia.org/wiki/' + tagArticleTitle.replace(/ /g, '_') + (anchor ? '#' + anchor : ''); // unrecognized value format
+ } else {
+ utilGetSetValue(_titleInput, value);
- roadspeed.focus = function () {
- input.node().focus();
- };
+ if (value && value !== '') {
+ utilGetSetValue(_langInput, '');
+ var defaultLangInfo = defaultLanguageInfo();
+ _wikiURL = "https://".concat(defaultLangInfo[2], ".wikipedia.org/w/index.php?fulltext=1&search=").concat(value);
+ } else {
+ var shownOrDefaultLangInfo = language(true
+ /* skipEnglishFallback */
+ );
+ utilGetSetValue(_langInput, shownOrDefaultLangInfo[1]);
+ _wikiURL = '';
+ }
+ }
+ }
- roadspeed.entityIDs = function (val) {
+ wiki.entityIDs = function (val) {
+ if (!_arguments.length) return _entityIDs;
_entityIDs = val;
+ return wiki;
};
- function combinedEntityExtent() {
- return _entityIDs && _entityIDs.length && utilTotalExtent(_entityIDs, context.graph());
- }
+ wiki.focus = function () {
+ _titleInput.node().focus();
+ };
- return utilRebind(roadspeed, dispatch, 'on');
+ return utilRebind(wiki, dispatch, 'on');
}
+ uiFieldWikipedia.supportsMultiselection = false;
- function uiFieldRadio(field, context) {
- var dispatch = dispatch$8('change');
- var placeholder = select(null);
- var wrap = select(null);
- var labels = select(null);
- var radios = select(null);
- var radioData = (field.options || field.keys).slice(); // shallow copy
-
- var typeField;
- var layerField;
- var _oldType = {};
- var _entityIDs = [];
+ var uiFields = {
+ access: uiFieldAccess,
+ address: uiFieldAddress,
+ check: uiFieldCheck,
+ combo: uiFieldCombo,
+ cycleway: uiFieldCycleway,
+ defaultCheck: uiFieldCheck,
+ email: uiFieldText,
+ identifier: uiFieldText,
+ lanes: uiFieldLanes,
+ localized: uiFieldLocalized,
+ roadheight: uiFieldRoadheight,
+ roadspeed: uiFieldRoadspeed,
+ manyCombo: uiFieldCombo,
+ multiCombo: uiFieldCombo,
+ networkCombo: uiFieldCombo,
+ number: uiFieldText,
+ onewayCheck: uiFieldCheck,
+ radio: uiFieldRadio,
+ restrictions: uiFieldRestrictions,
+ semiCombo: uiFieldCombo,
+ structureRadio: uiFieldRadio,
+ tel: uiFieldText,
+ text: uiFieldText,
+ textarea: uiFieldTextarea,
+ typeCombo: uiFieldCombo,
+ url: uiFieldText,
+ wikidata: uiFieldWikidata,
+ wikipedia: uiFieldWikipedia
+ };
- function selectedKey() {
- var node = wrap.selectAll('.form-field-input-radio label.active input');
- return !node.empty() && node.datum();
- }
+ function uiField(context, presetField, entityIDs, options) {
+ options = Object.assign({
+ show: true,
+ wrap: true,
+ remove: true,
+ revert: true,
+ info: true
+ }, options);
+ var dispatch = dispatch$8('change', 'revert');
+ var field = Object.assign({}, presetField); // shallow copy
- function radio(selection) {
- selection.classed('preset-radio', true);
- wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- var enter = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-radio');
- enter.append('span').attr('class', 'placeholder');
- wrap = wrap.merge(enter);
- placeholder = wrap.selectAll('.placeholder');
- labels = wrap.selectAll('label').data(radioData);
- enter = labels.enter().append('label');
- enter.append('input').attr('type', 'radio').attr('name', field.id).attr('value', function (d) {
- return field.t('options.' + d, {
- 'default': d
- });
- }).attr('checked', false);
- enter.append('span').html(function (d) {
- return field.t.html('options.' + d, {
- 'default': d
- });
- });
- labels = labels.merge(enter);
- radios = labels.selectAll('input').on('change', changeRadio);
- }
+ field.domId = utilUniqueDomId('form-field-' + field.safeid);
+ var _show = options.show;
+ var _state = '';
+ var _tags = {};
- function structureExtras(selection, tags) {
- var selected = selectedKey() || tags.layer !== undefined;
- var type = _mainPresetIndex.field(selected);
- var layer = _mainPresetIndex.field('layer');
- var showLayer = selected === 'bridge' || selected === 'tunnel' || tags.layer !== undefined;
- var extrasWrap = selection.selectAll('.structure-extras-wrap').data(selected ? [0] : []);
- extrasWrap.exit().remove();
- extrasWrap = extrasWrap.enter().append('div').attr('class', 'structure-extras-wrap').merge(extrasWrap);
- var list = extrasWrap.selectAll('ul').data([0]);
- list = list.enter().append('ul').attr('class', 'rows').merge(list); // Type
+ var _entityExtent;
- if (type) {
- if (!typeField || typeField.id !== selected) {
- typeField = uiField(context, type, _entityIDs, {
- wrap: false
- }).on('change', changeType);
- }
+ if (entityIDs && entityIDs.length) {
+ _entityExtent = entityIDs.reduce(function (extent, entityID) {
+ var entity = context.graph().entity(entityID);
+ return extent.extend(entity.extent(context.graph()));
+ }, geoExtent());
+ }
- typeField.tags(tags);
- } else {
- typeField = null;
- }
+ var _locked = false;
- var typeItem = list.selectAll('.structure-type-item').data(typeField ? [typeField] : [], function (d) {
- return d.id;
- }); // Exit
+ var _lockedTip = uiTooltip().title(_t.html('inspector.lock.suggestion', {
+ label: field.label
+ })).placement('bottom');
- typeItem.exit().remove(); // Enter
+ field.keys = field.keys || [field.key]; // only create the fields that are actually being shown
- var typeEnter = typeItem.enter().insert('li', ':first-child').attr('class', 'labeled-input structure-type-item');
- typeEnter.append('span').attr('class', 'label structure-label-type').attr('for', 'preset-input-' + selected).html(_t.html('inspector.radio.structure.type'));
- typeEnter.append('div').attr('class', 'structure-input-type-wrap'); // Update
+ if (_show && !field.impl) {
+ createField();
+ } // Creates the field.. This is done lazily,
+ // once we know that the field will be shown.
- typeItem = typeItem.merge(typeEnter);
- if (typeField) {
- typeItem.selectAll('.structure-input-type-wrap').call(typeField.render);
- } // Layer
+ function createField() {
+ field.impl = uiFields[field.type](field, context).on('change', function (t, onInput) {
+ dispatch.call('change', field, t, onInput);
+ });
+ if (entityIDs) {
+ field.entityIDs = entityIDs; // if this field cares about the entities, pass them along
- if (layer && showLayer) {
- if (!layerField) {
- layerField = uiField(context, layer, _entityIDs, {
- wrap: false
- }).on('change', changeLayer);
+ if (field.impl.entityIDs) {
+ field.impl.entityIDs(entityIDs);
}
+ }
+ }
- layerField.tags(tags);
- field.keys = utilArrayUnion(field.keys, ['layer']);
- } else {
- layerField = null;
- field.keys = field.keys.filter(function (k) {
- return k !== 'layer';
+ function isModified() {
+ if (!entityIDs || !entityIDs.length) return false;
+ return entityIDs.some(function (entityID) {
+ var original = context.graph().base().entities[entityID];
+ var latest = context.graph().entity(entityID);
+ return field.keys.some(function (key) {
+ return original ? latest.tags[key] !== original.tags[key] : latest.tags[key];
});
- }
+ });
+ }
- var layerItem = list.selectAll('.structure-layer-item').data(layerField ? [layerField] : []); // Exit
+ function tagsContainFieldKey() {
+ return field.keys.some(function (key) {
+ if (field.type === 'multiCombo') {
+ for (var tagKey in _tags) {
+ if (tagKey.indexOf(key) === 0) {
+ return true;
+ }
+ }
- layerItem.exit().remove(); // Enter
+ return false;
+ }
- var layerEnter = layerItem.enter().append('li').attr('class', 'labeled-input structure-layer-item');
- layerEnter.append('span').attr('class', 'label structure-label-layer').attr('for', 'preset-input-layer').html(_t.html('inspector.radio.structure.layer'));
- layerEnter.append('div').attr('class', 'structure-input-layer-wrap'); // Update
+ return _tags[key] !== undefined;
+ });
+ }
- layerItem = layerItem.merge(layerEnter);
+ function revert(d3_event, d) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ if (!entityIDs || _locked) return;
+ dispatch.call('revert', d, d.keys);
+ }
- if (layerField) {
- layerItem.selectAll('.structure-input-layer-wrap').call(layerField.render);
- }
+ function remove(d3_event, d) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ if (_locked) return;
+ var t = {};
+ d.keys.forEach(function (key) {
+ t[key] = undefined;
+ });
+ dispatch.call('change', d, t);
}
- function changeType(t, onInput) {
- var key = selectedKey();
- if (!key) return;
- var val = t[key];
+ field.render = function (selection) {
+ var container = selection.selectAll('.form-field').data([field]); // Enter
- if (val !== 'no') {
- _oldType[key] = val;
- }
+ var enter = container.enter().append('div').attr('class', function (d) {
+ return 'form-field form-field-' + d.safeid;
+ }).classed('nowrap', !options.wrap);
- if (field.type === 'structureRadio') {
- // remove layer if it should not be set
- if (val === 'no' || key !== 'bridge' && key !== 'tunnel' || key === 'tunnel' && val === 'building_passage') {
- t.layer = undefined;
- } // add layer if it should be set
+ if (options.wrap) {
+ var labelEnter = enter.append('label').attr('class', 'field-label').attr('for', function (d) {
+ return d.domId;
+ });
+ var textEnter = labelEnter.append('span').attr('class', 'label-text');
+ textEnter.append('span').attr('class', 'label-textvalue').html(function (d) {
+ return d.label();
+ });
+ textEnter.append('span').attr('class', 'label-textannotation');
+ if (options.remove) {
+ labelEnter.append('button').attr('class', 'remove-icon').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete'));
+ }
- if (t.layer === undefined) {
- if (key === 'bridge' && val !== 'no') {
- t.layer = '1';
- }
+ if (options.revert) {
+ labelEnter.append('button').attr('class', 'modified-icon').attr('title', _t('icons.undo')).call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-redo' : '#iD-icon-undo'));
+ }
+ } // Update
- if (key === 'tunnel' && val !== 'no' && val !== 'building_passage') {
- t.layer = '-1';
- }
+
+ container = container.merge(enter);
+ container.select('.field-label > .remove-icon') // propagate bound data
+ .on('click', remove);
+ container.select('.field-label > .modified-icon') // propagate bound data
+ .on('click', revert);
+ container.each(function (d) {
+ var selection = select(this);
+
+ if (!d.impl) {
+ createField();
}
- }
- dispatch.call('change', this, t, onInput);
- }
+ var reference, help; // instantiate field help
- function changeLayer(t, onInput) {
- if (t.layer === '0') {
- t.layer = undefined;
- }
+ if (options.wrap && field.type === 'restrictions') {
+ help = uiFieldHelp(context, 'restrictions');
+ } // instantiate tag reference
- dispatch.call('change', this, t, onInput);
- }
- function changeRadio() {
- var t = {};
- var activeKey;
+ if (options.wrap && options.info) {
+ var referenceKey = d.key || '';
- if (field.key) {
- t[field.key] = undefined;
- }
+ if (d.type === 'multiCombo') {
+ // lookup key without the trailing ':'
+ referenceKey = referenceKey.replace(/:$/, '');
+ }
- radios.each(function (d) {
- var active = select(this).property('checked');
- if (active) activeKey = d;
+ reference = uiTagReference(d.reference || {
+ key: referenceKey
+ });
- if (field.key) {
- if (active) t[field.key] = d;
- } else {
- var val = _oldType[activeKey] || 'yes';
- t[d] = active ? val : undefined;
+ if (_state === 'hover') {
+ reference.showing(false);
+ }
}
- });
- if (field.type === 'structureRadio') {
- if (activeKey === 'bridge') {
- t.layer = '1';
- } else if (activeKey === 'tunnel' && t.tunnel !== 'building_passage') {
- t.layer = '-1';
- } else {
- t.layer = undefined;
- }
- }
+ selection.call(d.impl); // add field help components
- dispatch.call('change', this, t);
- }
+ if (help) {
+ selection.call(help.body).select('.field-label').call(help.button);
+ } // add tag reference components
- radio.tags = function (tags) {
- radios.property('checked', function (d) {
- if (field.key) {
- return tags[field.key] === d;
+
+ if (reference) {
+ selection.call(reference.body).select('.field-label').call(reference.button);
}
- return !!(typeof tags[d] === 'string' && tags[d].toLowerCase() !== 'no');
+ d.impl.tags(_tags);
});
+ container.classed('locked', _locked).classed('modified', isModified()).classed('present', tagsContainFieldKey()); // show a tip and lock icon if the field is locked
- function isMixed(d) {
- if (field.key) {
- return Array.isArray(tags[field.key]) && tags[field.key].includes(d);
- }
-
- return Array.isArray(tags[d]);
- }
+ var annotation = container.selectAll('.field-label .label-textannotation');
+ var icon = annotation.selectAll('.icon').data(_locked ? [0] : []);
+ icon.exit().remove();
+ icon.enter().append('svg').attr('class', 'icon').append('use').attr('xlink:href', '#fas-lock');
+ container.call(_locked ? _lockedTip : _lockedTip.destroy);
+ };
- labels.classed('active', function (d) {
- if (field.key) {
- return Array.isArray(tags[field.key]) && tags[field.key].includes(d) || tags[field.key] === d;
- }
+ field.state = function (val) {
+ if (!arguments.length) return _state;
+ _state = val;
+ return field;
+ };
- return Array.isArray(tags[d]) || !!(tags[d] && tags[d].toLowerCase() !== 'no');
- }).classed('mixed', isMixed).attr('title', function (d) {
- return isMixed(d) ? _t('inspector.unshared_value_tooltip') : null;
- });
- var selection = radios.filter(function () {
- return this.checked;
- });
+ field.tags = function (val) {
+ if (!arguments.length) return _tags;
+ _tags = val;
- if (selection.empty()) {
- placeholder.html(_t.html('inspector.none'));
- } else {
- placeholder.html(selection.attr('value'));
- _oldType[selection.datum()] = tags[selection.datum()];
- }
+ if (tagsContainFieldKey() && !_show) {
+ // always show a field if it has a value to display
+ _show = true;
- if (field.type === 'structureRadio') {
- // For waterways without a tunnel tag, set 'culvert' as
- // the _oldType to default to if the user picks 'tunnel'
- if (!!tags.waterway && !_oldType.tunnel) {
- _oldType.tunnel = 'culvert';
+ if (!field.impl) {
+ createField();
}
-
- wrap.call(structureExtras, tags);
}
- };
- radio.focus = function () {
- radios.node().focus();
+ return field;
};
- radio.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- _oldType = {};
- return radio;
+ field.locked = function (val) {
+ if (!arguments.length) return _locked;
+ _locked = val;
+ return field;
};
- radio.isAllowed = function () {
- return _entityIDs.length === 1;
- };
+ field.show = function () {
+ _show = true;
- return utilRebind(radio, dispatch, 'on');
- }
+ if (!field.impl) {
+ createField();
+ }
- function uiFieldRestrictions(field, context) {
- var dispatch = dispatch$8('change');
- var breathe = behaviorBreathe();
- corePreferences('turn-restriction-via-way', null); // remove old key
+ if (field["default"] && field.key && _tags[field.key] !== field["default"]) {
+ var t = {};
+ t[field.key] = field["default"];
+ dispatch.call('change', this, t);
+ }
+ }; // A shown field has a visible UI, a non-shown field is in the 'Add field' dropdown
- var storedViaWay = corePreferences('turn-restriction-via-way0'); // use new key #6922
- var storedDistance = corePreferences('turn-restriction-distance');
+ field.isShown = function () {
+ return _show;
+ }; // An allowed field can appear in the UI or in the 'Add field' dropdown.
+ // A non-allowed field is hidden from the user altogether
- var _maxViaWay = storedViaWay !== null ? +storedViaWay : 0;
- var _maxDistance = storedDistance ? +storedDistance : 30;
+ field.isAllowed = function () {
+ if (entityIDs && entityIDs.length > 1 && uiFields[field.type].supportsMultiselection === false) return false;
+ if (field.geometry && !entityIDs.every(function (entityID) {
+ return field.matchGeometry(context.graph().geometry(entityID));
+ })) return false;
- var _initialized = false;
+ if (entityIDs && _entityExtent && field.locationSetID) {
+ // is field allowed in this location?
+ var validLocations = _mainLocations.locationsAt(_entityExtent.center());
+ if (!validLocations[field.locationSetID]) return false;
+ }
- var _parent = select(null); // the entire field
+ var prerequisiteTag = field.prerequisiteTag;
+ if (entityIDs && !tagsContainFieldKey() && // ignore tagging prerequisites if a value is already present
+ prerequisiteTag) {
+ if (!entityIDs.every(function (entityID) {
+ var entity = context.graph().entity(entityID);
- var _container = select(null); // just the map
+ if (prerequisiteTag.key) {
+ var value = entity.tags[prerequisiteTag.key];
+ if (!value) return false;
+ if (prerequisiteTag.valueNot) {
+ return prerequisiteTag.valueNot !== value;
+ }
- var _oldTurns;
+ if (prerequisiteTag.value) {
+ return prerequisiteTag.value === value;
+ }
+ } else if (prerequisiteTag.keyNot) {
+ if (entity.tags[prerequisiteTag.keyNot]) return false;
+ }
- var _graph;
+ return true;
+ })) return false;
+ }
- var _vertexID;
+ return true;
+ };
- var _intersection;
+ field.focus = function () {
+ if (field.impl) {
+ field.impl.focus();
+ }
+ };
- var _fromWayID;
+ return utilRebind(field, dispatch, 'on');
+ }
- var _lastXPos;
+ function uiFormFields(context) {
+ var moreCombo = uiCombobox(context, 'more-fields').minItems(1);
+ var _fieldsArr = [];
+ var _lastPlaceholder = '';
+ var _state = '';
+ var _klass = '';
- function restrictions(selection) {
- _parent = selection; // try to reuse the intersection, but always rebuild it if the graph has changed
+ function formFields(selection) {
+ var allowedFields = _fieldsArr.filter(function (field) {
+ return field.isAllowed();
+ });
- if (_vertexID && (context.graph() !== _graph || !_intersection)) {
- _graph = context.graph();
- _intersection = osmIntersection(_graph, _vertexID, _maxDistance);
- } // It's possible for there to be no actual intersection here.
- // for example, a vertex of two `highway=path`
- // In this case, hide the field.
+ var shown = allowedFields.filter(function (field) {
+ return field.isShown();
+ });
+ var notShown = allowedFields.filter(function (field) {
+ return !field.isShown();
+ });
+ var container = selection.selectAll('.form-fields-container').data([0]);
+ container = container.enter().append('div').attr('class', 'form-fields-container ' + (_klass || '')).merge(container);
+ var fields = container.selectAll('.wrap-form-field').data(shown, function (d) {
+ return d.id + (d.entityIDs ? d.entityIDs.join() : '');
+ });
+ fields.exit().remove(); // Enter
+ var enter = fields.enter().append('div').attr('class', function (d) {
+ return 'wrap-form-field wrap-form-field-' + d.safeid;
+ }); // Update
- var isOK = _intersection && _intersection.vertices.length && // has vertices
- _intersection.vertices // has the vertex that the user selected
- .filter(function (vertex) {
- return vertex.id === _vertexID;
- }).length && _intersection.ways.length > 2 && // has more than 2 ways
- _intersection.ways // has more than 1 TO way
- .filter(function (way) {
- return way.__to;
- }).length > 1; // Also hide in the case where
+ fields = fields.merge(enter);
+ fields.order().each(function (d) {
+ select(this).call(d.render);
+ });
+ var titles = [];
+ var moreFields = notShown.map(function (field) {
+ var title = field.title();
+ titles.push(title);
+ var terms = field.terms();
+ if (field.key) terms.push(field.key);
+ if (field.keys) terms = terms.concat(field.keys);
+ return {
+ display: field.label(),
+ value: title,
+ title: title,
+ field: field,
+ terms: terms
+ };
+ });
+ var placeholder = titles.slice(0, 3).join(', ') + (titles.length > 3 ? '…' : '');
+ var more = selection.selectAll('.more-fields').data(_state === 'hover' || moreFields.length === 0 ? [] : [0]);
+ more.exit().remove();
+ var moreEnter = more.enter().append('div').attr('class', 'more-fields').append('label');
+ moreEnter.append('span').call(_t.append('inspector.add_fields'));
+ more = moreEnter.merge(more);
+ var input = more.selectAll('.value').data([0]);
+ input.exit().remove();
+ input = input.enter().append('input').attr('class', 'value').attr('type', 'text').attr('placeholder', placeholder).call(utilNoAuto).merge(input);
+ input.call(utilGetSetValue, '').call(moreCombo.data(moreFields).on('accept', function (d) {
+ if (!d) return; // user entered something that was not matched
- select(selection.node().parentNode).classed('hide', !isOK); // if form field is hidden or has detached from dom, clean up.
+ var field = d.field;
+ field.show();
+ selection.call(formFields); // rerender
- if (!isOK || !context.container().select('.inspector-wrap.inspector-hidden').empty() || !selection.node().parentNode || !selection.node().parentNode.parentNode) {
- selection.call(restrictions.off);
- return;
+ field.focus();
+ })); // avoid updating placeholder excessively (triggers style recalc)
+
+ if (_lastPlaceholder !== placeholder) {
+ input.attr('placeholder', placeholder);
+ _lastPlaceholder = placeholder;
}
+ }
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- var container = wrap.selectAll('.restriction-container').data([0]); // enter
+ formFields.fieldsArr = function (val) {
+ if (!arguments.length) return _fieldsArr;
+ _fieldsArr = val || [];
+ return formFields;
+ };
- var containerEnter = container.enter().append('div').attr('class', 'restriction-container');
- containerEnter.append('div').attr('class', 'restriction-help'); // update
+ formFields.state = function (val) {
+ if (!arguments.length) return _state;
+ _state = val;
+ return formFields;
+ };
- _container = containerEnter.merge(container).call(renderViewer);
- var controls = wrap.selectAll('.restriction-controls').data([0]); // enter/update
+ formFields.klass = function (val) {
+ if (!arguments.length) return _klass;
+ _klass = val;
+ return formFields;
+ };
- controls.enter().append('div').attr('class', 'restriction-controls-container').append('div').attr('class', 'restriction-controls').merge(controls).call(renderControls);
- }
+ return formFields;
+ }
- function renderControls(selection) {
- var distControl = selection.selectAll('.restriction-distance').data([0]);
- distControl.exit().remove();
- var distControlEnter = distControl.enter().append('div').attr('class', 'restriction-control restriction-distance');
- distControlEnter.append('span').attr('class', 'restriction-control-label restriction-distance-label').html(_t.html('restriction.controls.distance') + ':');
- distControlEnter.append('input').attr('class', 'restriction-distance-input').attr('type', 'range').attr('min', '20').attr('max', '50').attr('step', '5');
- distControlEnter.append('span').attr('class', 'restriction-distance-text'); // update
+ function uiChangesetEditor(context) {
+ var dispatch = dispatch$8('change');
+ var formFields = uiFormFields(context);
+ var commentCombo = uiCombobox(context, 'comment').caseSensitive(true);
- selection.selectAll('.restriction-distance-input').property('value', _maxDistance).on('input', function () {
- var val = select(this).property('value');
- _maxDistance = +val;
- _intersection = null;
+ var _fieldsArr;
- _container.selectAll('.layer-osm .layer-turns *').remove();
+ var _tags;
- corePreferences('turn-restriction-distance', _maxDistance);
+ var _changesetID;
- _parent.call(restrictions);
- });
- selection.selectAll('.restriction-distance-text').html(displayMaxDistance(_maxDistance));
- var viaControl = selection.selectAll('.restriction-via-way').data([0]);
- viaControl.exit().remove();
- var viaControlEnter = viaControl.enter().append('div').attr('class', 'restriction-control restriction-via-way');
- viaControlEnter.append('span').attr('class', 'restriction-control-label restriction-via-way-label').html(_t.html('restriction.controls.via') + ':');
- viaControlEnter.append('input').attr('class', 'restriction-via-way-input').attr('type', 'range').attr('min', '0').attr('max', '2').attr('step', '1');
- viaControlEnter.append('span').attr('class', 'restriction-via-way-text'); // update
+ function changesetEditor(selection) {
+ render(selection);
+ }
- selection.selectAll('.restriction-via-way-input').property('value', _maxViaWay).on('input', function () {
- var val = select(this).property('value');
- _maxViaWay = +val;
+ function render(selection) {
+ var initial = false;
- _container.selectAll('.layer-osm .layer-turns *').remove();
+ if (!_fieldsArr) {
+ initial = true;
+ var presets = _mainPresetIndex;
+ _fieldsArr = [uiField(context, presets.field('comment'), null, {
+ show: true,
+ revert: false
+ }), uiField(context, presets.field('source'), null, {
+ show: false,
+ revert: false
+ }), uiField(context, presets.field('hashtags'), null, {
+ show: false,
+ revert: false
+ })];
- corePreferences('turn-restriction-via-way0', _maxViaWay);
+ _fieldsArr.forEach(function (field) {
+ field.on('change', function (t, onInput) {
+ dispatch.call('change', field, undefined, t, onInput);
+ });
+ });
+ }
- _parent.call(restrictions);
+ _fieldsArr.forEach(function (field) {
+ field.tags(_tags);
});
- selection.selectAll('.restriction-via-way-text').html(displayMaxVia(_maxViaWay));
- }
-
- function renderViewer(selection) {
- if (!_intersection) return;
- var vgraph = _intersection.graph;
- var filter = utilFunctor(true);
- var projection = geoRawMercator(); // Reflow warning: `utilGetDimensions` calls `getBoundingClientRect`
- // Instead of asking the restriction-container for its dimensions,
- // we can ask the .sidebar, which can have its dimensions cached.
- // width: calc as sidebar - padding
- // height: hardcoded (from `80_app.css`)
- // var d = utilGetDimensions(selection);
-
- var sdims = utilGetDimensions(context.container().select('.sidebar'));
- var d = [sdims[0] - 50, 370];
- var c = geoVecScale(d, 0.5);
- var z = 22;
- projection.scale(geoZoomToScale(z)); // Calculate extent of all key vertices
- var extent = geoExtent();
+ selection.call(formFields.fieldsArr(_fieldsArr));
- for (var i = 0; i < _intersection.vertices.length; i++) {
- extent._extend(_intersection.vertices[i].extent());
- } // If this is a large intersection, adjust zoom to fit extent
+ if (initial) {
+ var commentField = selection.select('.form-field-comment textarea');
+ var commentNode = commentField.node();
+ if (commentNode) {
+ commentNode.focus();
+ commentNode.select();
+ } // trigger a 'blur' event so that comment field can be cleaned
+ // and checked for hashtags, even if retrieved from localstorage
- if (_intersection.vertices.length > 1) {
- var padding = 180; // in z22 pixels
- var tl = projection([extent[0][0], extent[1][1]]);
- var br = projection([extent[1][0], extent[0][1]]);
- var hFactor = (br[0] - tl[0]) / (d[0] - padding);
- var vFactor = (br[1] - tl[1]) / (d[1] - padding);
- var hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2;
- var vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2;
- z = z - Math.max(hZoomDiff, vZoomDiff);
- projection.scale(geoZoomToScale(z));
- }
+ utilTriggerEvent(commentField, 'blur');
+ var osm = context.connection();
- var padTop = 35; // reserve top space for hint text
+ if (osm) {
+ osm.userChangesets(function (err, changesets) {
+ if (err) return;
+ var comments = changesets.map(function (changeset) {
+ var comment = changeset.tags.comment;
+ return comment ? {
+ title: comment,
+ value: comment
+ } : null;
+ }).filter(Boolean);
+ commentField.call(commentCombo.data(utilArrayUniqBy(comments, 'title')));
+ });
+ }
+ } // Add warning if comment mentions Google
- var extentCenter = projection(extent.center());
- extentCenter[1] = extentCenter[1] - padTop;
- projection.translate(geoVecSubtract(c, extentCenter)).clipExtent([[0, 0], d]);
- var drawLayers = svgLayers(projection, context).only(['osm', 'touch']).dimensions(d);
- var drawVertices = svgVertices(projection, context);
- var drawLines = svgLines(projection, context);
- var drawTurns = svgTurns(projection, context);
- var firstTime = selection.selectAll('.surface').empty();
- selection.call(drawLayers);
- var surface = selection.selectAll('.surface').classed('tr', true);
- if (firstTime) {
- _initialized = true;
- surface.call(breathe);
- } // This can happen if we've lowered the detail while a FROM way
- // is selected, and that way is no longer part of the intersection.
+ var hasGoogle = _tags.comment.match(/google/i);
+ var commentWarning = selection.select('.form-field-comment').selectAll('.comment-warning').data(hasGoogle ? [0] : []);
+ commentWarning.exit().transition().duration(200).style('opacity', 0).remove();
+ var commentEnter = commentWarning.enter().insert('div', '.tag-reference-body').attr('class', 'field-warning comment-warning').style('opacity', 0);
+ commentEnter.append('a').attr('target', '_blank').call(svgIcon('#iD-icon-alert', 'inline')).attr('href', _t('commit.google_warning_link')).append('span').call(_t.append('commit.google_warning'));
+ commentEnter.transition().duration(200).style('opacity', 1);
+ }
- if (_fromWayID && !vgraph.hasEntity(_fromWayID)) {
- _fromWayID = null;
- _oldTurns = null;
- }
+ changesetEditor.tags = function (_) {
+ if (!arguments.length) return _tags;
+ _tags = _; // Don't reset _fieldsArr here.
- surface.call(utilSetDimensions, d).call(drawVertices, vgraph, _intersection.vertices, filter, extent, z).call(drawLines, vgraph, _intersection.ways, filter).call(drawTurns, vgraph, _intersection.turns(_fromWayID, _maxViaWay));
- surface.on('click.restrictions', click).on('mouseover.restrictions', mouseover);
- surface.selectAll('.selected').classed('selected', false);
- surface.selectAll('.related').classed('related', false);
- var way;
+ return changesetEditor;
+ };
- if (_fromWayID) {
- way = vgraph.entity(_fromWayID);
- surface.selectAll('.' + _fromWayID).classed('selected', true).classed('related', true);
- }
+ changesetEditor.changesetID = function (_) {
+ if (!arguments.length) return _changesetID;
+ if (_changesetID === _) return changesetEditor;
+ _changesetID = _;
+ _fieldsArr = null;
+ return changesetEditor;
+ };
- document.addEventListener('resizeWindow', function () {
- utilSetDimensions(_container, null);
- redraw(1);
- }, false);
- updateHints(null);
+ return utilRebind(changesetEditor, dispatch, 'on');
+ }
- function click(d3_event) {
- surface.call(breathe.off).call(breathe);
- var datum = d3_event.target.__data__;
- var entity = datum && datum.properties && datum.properties.entity;
+ var JXON = new function () {
+ var sValueProp = 'keyValue',
+ sAttributesProp = 'keyAttributes',
+ sAttrPref = '@',
- if (entity) {
- datum = entity;
- }
+ /* you can customize these values */
+ aCache = [],
+ rIsNull = /^\s*$/,
+ rIsBool = /^(?:true|false)$/i;
- if (datum instanceof osmWay && (datum.__from || datum.__via)) {
- _fromWayID = datum.id;
- _oldTurns = null;
- redraw();
- } else if (datum instanceof osmTurn) {
- var actions, extraActions, turns, i;
- var restrictionType = osmInferRestriction(vgraph, datum, projection);
+ function parseText(sValue) {
+ if (rIsNull.test(sValue)) {
+ return null;
+ }
- if (datum.restrictionID && !datum.direct) {
- return;
- } else if (datum.restrictionID && !datum.only) {
- // NO -> ONLY
- var seen = {};
- var datumOnly = JSON.parse(JSON.stringify(datum)); // deep clone the datum
+ if (rIsBool.test(sValue)) {
+ return sValue.toLowerCase() === 'true';
+ }
- datumOnly.only = true; // but change this property
+ if (isFinite(sValue)) {
+ return parseFloat(sValue);
+ }
- restrictionType = restrictionType.replace(/^no/, 'only'); // Adding an ONLY restriction should destroy all other direct restrictions from the FROM towards the VIA.
- // We will remember them in _oldTurns, and restore them if the user clicks again.
+ if (isFinite(Date.parse(sValue))) {
+ return new Date(sValue);
+ }
- turns = _intersection.turns(_fromWayID, 2);
- extraActions = [];
- _oldTurns = [];
+ return sValue;
+ }
- for (i = 0; i < turns.length; i++) {
- var turn = turns[i];
- if (seen[turn.restrictionID]) continue; // avoid deleting the turn twice (#4968, #4928)
+ function EmptyTree() {}
- if (turn.direct && turn.path[1] === datum.path[1]) {
- seen[turns[i].restrictionID] = true;
- turn.restrictionType = osmInferRestriction(vgraph, turn, projection);
+ EmptyTree.prototype.toString = function () {
+ return 'null';
+ };
- _oldTurns.push(turn);
+ EmptyTree.prototype.valueOf = function () {
+ return null;
+ };
- extraActions.push(actionUnrestrictTurn(turn));
- }
- }
+ function objectify(vValue) {
+ return vValue === null ? new EmptyTree() : vValue instanceof Object ? vValue : new vValue.constructor(vValue);
+ }
- actions = _intersection.actions.concat(extraActions, [actionRestrictTurn(datumOnly, restrictionType), _t('operations.restriction.annotation.create')]);
- } else if (datum.restrictionID) {
- // ONLY -> Allowed
- // Restore whatever restrictions we might have destroyed by cycling thru the ONLY state.
- // This relies on the assumption that the intersection was already split up when we
- // performed the previous action (NO -> ONLY), so the IDs in _oldTurns shouldn't have changed.
- turns = _oldTurns || [];
- extraActions = [];
+ function createObjTree(oParentNode, nVerb, bFreeze, bNesteAttr) {
+ var nLevelStart = aCache.length,
+ bChildren = oParentNode.hasChildNodes(),
+ bAttributes = oParentNode.hasAttributes(),
+ bHighVerb = Boolean(nVerb & 2);
+ var sProp,
+ vContent,
+ nLength = 0,
+ sCollectedTxt = '',
+ vResult = bHighVerb ? {} :
+ /* put here the default value for empty nodes: */
+ true;
- for (i = 0; i < turns.length; i++) {
- if (turns[i].key !== datum.key) {
- extraActions.push(actionRestrictTurn(turns[i], turns[i].restrictionType));
- }
- }
+ if (bChildren) {
+ for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
+ oNode = oParentNode.childNodes.item(nItem);
- _oldTurns = null;
- actions = _intersection.actions.concat(extraActions, [actionUnrestrictTurn(datum), _t('operations.restriction.annotation.delete')]);
- } else {
- // Allowed -> NO
- actions = _intersection.actions.concat([actionRestrictTurn(datum, restrictionType), _t('operations.restriction.annotation.create')]);
+ if (oNode.nodeType === 4) {
+ /* nodeType is 'CDATASection' (4) */
+ sCollectedTxt += oNode.nodeValue;
+ } else if (oNode.nodeType === 3) {
+ /* nodeType is 'Text' (3) */
+ sCollectedTxt += oNode.nodeValue.trim();
+ } else if (oNode.nodeType === 1 && !oNode.prefix) {
+ /* nodeType is 'Element' (1) */
+ aCache.push(oNode);
}
-
- context.perform.apply(context, actions); // At this point the datum will be changed, but will have same key..
- // Refresh it and update the help..
-
- var s = surface.selectAll('.' + datum.key);
- datum = s.empty() ? null : s.datum();
- updateHints(datum);
- } else {
- _fromWayID = null;
- _oldTurns = null;
- redraw();
}
}
- function mouseover(d3_event) {
- var datum = d3_event.target.__data__;
- updateHints(datum);
+ var nLevelEnd = aCache.length,
+ vBuiltVal = parseText(sCollectedTxt);
+
+ if (!bHighVerb && (bChildren || bAttributes)) {
+ vResult = nVerb === 0 ? objectify(vBuiltVal) : {};
}
- _lastXPos = _lastXPos || sdims[0];
+ for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
+ sProp = aCache[nElId].nodeName.toLowerCase();
+ vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
- function redraw(minChange) {
- var xPos = -1;
+ if (vResult.hasOwnProperty(sProp)) {
+ if (vResult[sProp].constructor !== Array) {
+ vResult[sProp] = [vResult[sProp]];
+ }
- if (minChange) {
- xPos = utilGetDimensions(context.container().select('.sidebar'))[0];
+ vResult[sProp].push(vContent);
+ } else {
+ vResult[sProp] = vContent;
+ nLength++;
}
+ }
- if (!minChange || minChange && Math.abs(xPos - _lastXPos) >= minChange) {
- if (context.hasEntity(_vertexID)) {
- _lastXPos = xPos;
+ if (bAttributes) {
+ var nAttrLen = oParentNode.attributes.length,
+ sAPrefix = bNesteAttr ? '' : sAttrPref,
+ oAttrParent = bNesteAttr ? {} : vResult;
- _container.call(renderViewer);
+ for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
+ oAttrib = oParentNode.attributes.item(nAttrib);
+ oAttrParent[sAPrefix + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
+ }
+
+ if (bNesteAttr) {
+ if (bFreeze) {
+ Object.freeze(oAttrParent);
}
+
+ vResult[sAttributesProp] = oAttrParent;
+ nLength -= nAttrLen - 1;
}
}
- function highlightPathsFrom(wayID) {
- surface.selectAll('.related').classed('related', false).classed('allow', false).classed('restrict', false).classed('only', false);
- surface.selectAll('.' + wayID).classed('related', true);
+ if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) {
+ vResult[sValueProp] = vBuiltVal;
+ } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
+ vResult = vBuiltVal;
+ }
- if (wayID) {
- var turns = _intersection.turns(wayID, _maxViaWay);
+ if (bFreeze && (bHighVerb || nLength > 0)) {
+ Object.freeze(vResult);
+ }
- for (var i = 0; i < turns.length; i++) {
- var turn = turns[i];
- var ids = [turn.to.way];
- var klass = turn.no ? 'restrict' : turn.only ? 'only' : 'allow';
+ aCache.length = nLevelStart;
+ return vResult;
+ }
- if (turn.only || turns.length === 1) {
- if (turn.via.ways) {
- ids = ids.concat(turn.via.ways);
- }
- } else if (turn.to.way === wayID) {
- continue;
- }
+ function loadObjTree(oXMLDoc, oParentEl, oParentObj) {
+ var vValue, oChild;
- surface.selectAll(utilEntitySelector(ids)).classed('related', true).classed('allow', klass === 'allow').classed('restrict', klass === 'restrict').classed('only', klass === 'only');
- }
- }
+ if (oParentObj instanceof String || oParentObj instanceof Number || oParentObj instanceof Boolean) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString()));
+ /* verbosity level is 0 */
+ } else if (oParentObj.constructor === Date) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));
}
- function updateHints(datum) {
- var help = _container.selectAll('.restriction-help').html('');
-
- var placeholders = {};
- ['from', 'via', 'to'].forEach(function (k) {
- placeholders[k] = '<span class="qualifier">' + _t('restriction.help.' + k) + '</span>';
- });
- var entity = datum && datum.properties && datum.properties.entity;
+ for (var sName in oParentObj) {
+ vValue = oParentObj[sName];
- if (entity) {
- datum = entity;
+ if (isFinite(sName) || vValue instanceof Function) {
+ continue;
}
-
- if (_fromWayID) {
- way = vgraph.entity(_fromWayID);
- surface.selectAll('.' + _fromWayID).classed('selected', true).classed('related', true);
- } // Hovering a way
+ /* verbosity level is 0 */
- if (datum instanceof osmWay && datum.__from) {
- way = datum;
- highlightPathsFrom(_fromWayID ? null : way.id);
- surface.selectAll('.' + way.id).classed('related', true);
- var clickSelect = !_fromWayID || _fromWayID !== way.id;
- help.append('div') // "Click to select FROM {fromName}." / "FROM {fromName}"
- .html(_t.html('restriction.help.' + (clickSelect ? 'select_from_name' : 'from_name'), {
- from: placeholders.from,
- fromName: displayName(way.id, vgraph)
- })); // Hovering a turn arrow
- } else if (datum instanceof osmTurn) {
- var restrictionType = osmInferRestriction(vgraph, datum, projection);
- var turnType = restrictionType.replace(/^(only|no)\_/, '');
- var indirect = datum.direct === false ? _t.html('restriction.help.indirect') : '';
- var klass, turnText, nextText;
+ if (sName === sValueProp) {
+ if (vValue !== null && vValue !== true) {
+ oParentEl.appendChild(oXMLDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue)));
+ }
+ } else if (sName === sAttributesProp) {
+ /* verbosity level is 3 */
+ for (var sAttrib in vValue) {
+ oParentEl.setAttribute(sAttrib, vValue[sAttrib]);
+ }
+ } else if (sName.charAt(0) === sAttrPref) {
+ oParentEl.setAttribute(sName.slice(1), vValue);
+ } else if (vValue.constructor === Array) {
+ for (var nItem = 0; nItem < vValue.length; nItem++) {
+ oChild = oXMLDoc.createElement(sName);
+ loadObjTree(oXMLDoc, oChild, vValue[nItem]);
+ oParentEl.appendChild(oChild);
+ }
+ } else {
+ oChild = oXMLDoc.createElement(sName);
- if (datum.no) {
- klass = 'restrict';
- turnText = _t.html('restriction.help.turn.no_' + turnType, {
- indirect: indirect
- });
- nextText = _t.html('restriction.help.turn.only_' + turnType, {
- indirect: ''
- });
- } else if (datum.only) {
- klass = 'only';
- turnText = _t.html('restriction.help.turn.only_' + turnType, {
- indirect: indirect
- });
- nextText = _t.html('restriction.help.turn.allowed_' + turnType, {
- indirect: ''
- });
- } else {
- klass = 'allow';
- turnText = _t.html('restriction.help.turn.allowed_' + turnType, {
- indirect: indirect
- });
- nextText = _t.html('restriction.help.turn.no_' + turnType, {
- indirect: ''
- });
+ if (vValue instanceof Object) {
+ loadObjTree(oXMLDoc, oChild, vValue);
+ } else if (vValue !== null && vValue !== true) {
+ oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
}
- help.append('div') // "NO Right Turn (indirect)"
- .attr('class', 'qualifier ' + klass).html(turnText);
- help.append('div') // "FROM {fromName} TO {toName}"
- .html(_t.html('restriction.help.from_name_to_name', {
- from: placeholders.from,
- fromName: displayName(datum.from.way, vgraph),
- to: placeholders.to,
- toName: displayName(datum.to.way, vgraph)
- }));
+ oParentEl.appendChild(oChild);
+ }
+ }
+ }
- if (datum.via.ways && datum.via.ways.length) {
- var names = [];
+ this.build = function (oXMLParent, nVerbosity
+ /* optional */
+ , bFreeze
+ /* optional */
+ , bNesteAttributes
+ /* optional */
+ ) {
+ var _nVerb = arguments.length > 1 && typeof nVerbosity === 'number' ? nVerbosity & 3 :
+ /* put here the default verbosity level: */
+ 1;
- for (var i = 0; i < datum.via.ways.length; i++) {
- var prev = names[names.length - 1];
- var curr = displayName(datum.via.ways[i], vgraph);
+ return createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
+ };
- if (!prev || curr !== prev) {
- // collapse identical names
- names.push(curr);
- }
- }
+ this.unbuild = function (oObjTree) {
+ var oNewDoc = document.implementation.createDocument('', '', null);
+ loadObjTree(oNewDoc, oNewDoc, oObjTree);
+ return oNewDoc;
+ };
- help.append('div') // "VIA {viaNames}"
- .html(_t.html('restriction.help.via_names', {
- via: placeholders.via,
- viaNames: names.join(', ')
- }));
- }
+ this.stringify = function (oObjTree) {
+ return new XMLSerializer().serializeToString(JXON.unbuild(oObjTree));
+ };
+ }(); // var myObject = JXON.build(doc);
+ // we got our javascript object! try: alert(JSON.stringify(myObject));
+ // var newDoc = JXON.unbuild(myObject);
+ // we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));
- if (!indirect) {
- help.append('div') // Click for "No Right Turn"
- .html(_t.html('restriction.help.toggle', {
- turn: nextText.trim()
- }));
- }
+ function uiSectionChanges(context) {
+ var detected = utilDetect();
+ var _discardTags = {};
+ _mainFileFetcher.get('discarded').then(function (d) {
+ _discardTags = d;
+ })["catch"](function () {
+ /* ignore */
+ });
+ var section = uiSection('changes-list', context).label(function () {
+ var history = context.history();
+ var summary = history.difference().summary();
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('commit.changes')
+ },
+ count: summary.length
+ });
+ }).disclosureContent(renderDisclosureContent);
- highlightPathsFrom(null);
- var alongIDs = datum.path.slice();
- surface.selectAll(utilEntitySelector(alongIDs)).classed('related', true).classed('allow', klass === 'allow').classed('restrict', klass === 'restrict').classed('only', klass === 'only'); // Hovering empty surface
- } else {
- highlightPathsFrom(null);
+ function renderDisclosureContent(selection) {
+ var history = context.history();
+ var summary = history.difference().summary();
+ var container = selection.selectAll('.commit-section').data([0]);
+ var containerEnter = container.enter().append('div').attr('class', 'commit-section');
+ containerEnter.append('ul').attr('class', 'changeset-list');
+ container = containerEnter.merge(container);
+ var items = container.select('ul').selectAll('li').data(summary);
+ var itemsEnter = items.enter().append('li').attr('class', 'change-item');
+ var buttons = itemsEnter.append('button').on('mouseover', mouseover).on('mouseout', mouseout).on('click', click);
+ buttons.each(function (d) {
+ select(this).call(svgIcon('#iD-icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
+ });
+ buttons.append('span').attr('class', 'change-type').html(function (d) {
+ return _t.html('commit.' + d.changeType) + ' ';
+ });
+ buttons.append('strong').attr('class', 'entity-type').text(function (d) {
+ var matched = _mainPresetIndex.match(d.entity, d.graph);
+ return matched && matched.name() || utilDisplayType(d.entity.id);
+ });
+ buttons.append('span').attr('class', 'entity-name').text(function (d) {
+ var name = utilDisplayName(d.entity) || '',
+ string = '';
- if (_fromWayID) {
- help.append('div') // "FROM {fromName}"
- .html(_t.html('restriction.help.from_name', {
- from: placeholders.from,
- fromName: displayName(_fromWayID, vgraph)
- }));
- } else {
- help.append('div') // "Click to select a FROM segment."
- .html(_t.html('restriction.help.select_from', {
- from: placeholders.from
- }));
- }
+ if (name !== '') {
+ string += ':';
}
- }
- }
- function displayMaxDistance(maxDist) {
- var isImperial = !_mainLocalizer.usesMetric();
- var opts;
+ return string += ' ' + name;
+ });
+ items = itemsEnter.merge(items); // Download changeset link
- if (isImperial) {
- var distToFeet = {
- // imprecise conversion for prettier display
- 20: 70,
- 25: 85,
- 30: 100,
- 35: 115,
- 40: 130,
- 45: 145,
- 50: 160
- }[maxDist];
- opts = {
- distance: _t('units.feet', {
- quantity: distToFeet
- })
- };
+ var changeset = new osmChangeset().update({
+ id: undefined
+ });
+ var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
+ delete changeset.id; // Export without chnageset_id
+
+ var data = JXON.stringify(changeset.osmChangeJXON(changes));
+ var blob = new Blob([data], {
+ type: 'text/xml;charset=utf-8;'
+ });
+ var fileName = 'changes.osc';
+ var linkEnter = container.selectAll('.download-changes').data([0]).enter().append('a').attr('class', 'download-changes');
+
+ if (detected.download) {
+ // All except IE11 and Edge
+ linkEnter // download the data as a file
+ .attr('href', window.URL.createObjectURL(blob)).attr('download', fileName);
} else {
- opts = {
- distance: _t('units.meters', {
- quantity: maxDist
- })
- };
+ // IE11 and Edge
+ linkEnter // open data uri in a new tab
+ .attr('target', '_blank').on('click.download', function () {
+ navigator.msSaveBlob(blob, fileName);
+ });
}
- return _t.html('restriction.controls.distance_up_to', opts);
- }
+ linkEnter.call(svgIcon('#iD-icon-load', 'inline')).append('span').call(_t.append('commit.download_changes'));
- function displayMaxVia(maxVia) {
- return maxVia === 0 ? _t.html('restriction.controls.via_node_only') : maxVia === 1 ? _t.html('restriction.controls.via_up_to_one') : _t.html('restriction.controls.via_up_to_two');
- }
+ function mouseover(d) {
+ if (d.entity) {
+ context.surface().selectAll(utilEntityOrMemberSelector([d.entity.id], context.graph())).classed('hover', true);
+ }
+ }
- function displayName(entityID, graph) {
- var entity = graph.entity(entityID);
- var name = utilDisplayName(entity) || '';
- var matched = _mainPresetIndex.match(entity, graph);
- var type = matched && matched.name() || utilDisplayType(entity.id);
- return name || type;
+ function mouseout() {
+ context.surface().selectAll('.hover').classed('hover', false);
+ }
+
+ function click(d3_event, change) {
+ if (change.changeType !== 'deleted') {
+ var entity = change.entity;
+ context.map().zoomToEase(entity);
+ context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph())).classed('hover', true);
+ }
+ }
}
- restrictions.entityIDs = function (val) {
- _intersection = null;
- _fromWayID = null;
- _oldTurns = null;
- _vertexID = val[0];
- };
+ return section;
+ }
- restrictions.tags = function () {};
+ function uiCommitWarnings(context) {
+ function commitWarnings(selection) {
+ var issuesBySeverity = context.validator().getIssuesBySeverity({
+ what: 'edited',
+ where: 'all',
+ includeDisabledRules: true
+ });
- restrictions.focus = function () {};
+ for (var severity in issuesBySeverity) {
+ var issues = issuesBySeverity[severity];
- restrictions.off = function (selection) {
- if (!_initialized) return;
- selection.selectAll('.surface').call(breathe.off).on('click.restrictions', null).on('mouseover.restrictions', null);
- select(window).on('resize.restrictions', null);
- };
+ if (severity !== 'error') {
+ // exclude 'fixme' and similar - #8603
+ issues = issues.filter(function (issue) {
+ return issue.type !== 'help_request';
+ });
+ }
- return utilRebind(restrictions, dispatch, 'on');
+ var section = severity + '-section';
+ var issueItem = severity + '-item';
+ var container = selection.selectAll('.' + section).data(issues.length ? [0] : []);
+ container.exit().remove();
+ var containerEnter = container.enter().append('div').attr('class', 'modal-section ' + section + ' fillL2');
+ containerEnter.append('h3').html(severity === 'warning' ? _t.html('commit.warnings') : _t.html('commit.errors'));
+ containerEnter.append('ul').attr('class', 'changeset-list');
+ container = containerEnter.merge(container);
+ var items = container.select('ul').selectAll('li').data(issues, function (d) {
+ return d.key;
+ });
+ items.exit().remove();
+ var itemsEnter = items.enter().append('li').attr('class', issueItem);
+ var buttons = itemsEnter.append('button').on('mouseover', function (d3_event, d) {
+ if (d.entityIds) {
+ context.surface().selectAll(utilEntityOrMemberSelector(d.entityIds, context.graph())).classed('hover', true);
+ }
+ }).on('mouseout', function () {
+ context.surface().selectAll('.hover').classed('hover', false);
+ }).on('click', function (d3_event, d) {
+ context.validator().focusIssue(d);
+ });
+ buttons.call(svgIcon('#iD-icon-alert', 'pre-text'));
+ buttons.append('strong').attr('class', 'issue-message');
+ buttons.filter(function (d) {
+ return d.tooltip;
+ }).call(uiTooltip().title(function (d) {
+ return d.tooltip;
+ }).placement('top'));
+ items = itemsEnter.merge(items);
+ items.selectAll('.issue-message').html(function (d) {
+ return d.message(context);
+ });
+ }
+ }
+
+ return commitWarnings;
}
- uiFieldRestrictions.supportsMultiselection = false;
- function uiFieldTextarea(field, context) {
- var dispatch = dispatch$8('change');
- var input = select(null);
+ var readOnlyTags = [/^changesets_count$/, /^created_by$/, /^ideditor:/, /^imagery_used$/, /^host$/, /^locale$/, /^warnings:/, /^resolved:/, /^closed:note$/, /^closed:keepright$/, /^closed:improveosm:/, /^closed:osmose:/]; // treat most punctuation (except -, _, +, &) as hashtag delimiters - #4398
+ // from https://stackoverflow.com/a/25575009
- var _tags;
+ var hashtagRegex = /(#[^\u2000-\u206F\u2E00-\u2E7F\s\\'!"#$%()*,.\/:;<=>?@\[\]^`{|}~]+)/g;
+ function uiCommit(context) {
+ var dispatch = dispatch$8('cancel');
- function textarea(selection) {
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- input = wrap.selectAll('textarea').data([0]);
- input = input.enter().append('textarea').attr('id', field.domId).call(utilNoAuto).on('input', change(true)).on('blur', change()).on('change', change()).merge(input);
- }
+ var _userDetails;
- function change(onInput) {
- return function () {
- var val = utilGetSetValue(input);
- if (!onInput) val = context.cleanTagValue(val); // don't override multiple values with blank string
+ var _selection;
- if (!val && Array.isArray(_tags[field.key])) return;
- var t = {};
- t[field.key] = val || undefined;
- dispatch.call('change', this, t, onInput);
- };
- }
+ var changesetEditor = uiChangesetEditor(context).on('change', changeTags);
+ var rawTagEditor = uiSectionRawTagEditor('changeset-tag-editor', context).on('change', changeTags).readOnlyTags(readOnlyTags);
+ var commitChanges = uiSectionChanges(context);
+ var commitWarnings = uiCommitWarnings(context);
- textarea.tags = function (tags) {
- _tags = tags;
- var isMixed = Array.isArray(tags[field.key]);
- utilGetSetValue(input, !isMixed && tags[field.key] ? tags[field.key] : '').attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : undefined).attr('placeholder', isMixed ? _t('inspector.multiple_values') : field.placeholder() || _t('inspector.unknown')).classed('mixed', isMixed);
- };
+ function commit(selection) {
+ _selection = selection; // Initialize changeset if one does not exist yet.
- textarea.focus = function () {
- input.node().focus();
- };
+ if (!context.changeset) initChangeset();
+ loadDerivedChangesetTags();
+ selection.call(render);
+ }
- return utilRebind(textarea, dispatch, 'on');
- }
+ function initChangeset() {
+ // expire stored comment, hashtags, source after cutoff datetime - #3947 #4899
+ var commentDate = +corePreferences('commentDate') || 0;
+ var currDate = Date.now();
+ var cutoff = 2 * 86400 * 1000; // 2 days
- var $ = _export;
- var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
- var toLength = toLength$q;
- var notARegExp = notARegexp;
- var requireObjectCoercible = requireObjectCoercible$e;
- var correctIsRegExpLogic = correctIsRegexpLogic;
+ if (commentDate > currDate || currDate - commentDate > cutoff) {
+ corePreferences('comment', null);
+ corePreferences('hashtags', null);
+ corePreferences('source', null);
+ } // load in explicitly-set values, if any
- // eslint-disable-next-line es/no-string-prototype-endswith -- safe
- var $endsWith = ''.endsWith;
- var min = Math.min;
- var CORRECT_IS_REGEXP_LOGIC = correctIsRegExpLogic('endsWith');
- // https://github.com/zloirock/core-js/pull/702
- var MDN_POLYFILL_BUG = !CORRECT_IS_REGEXP_LOGIC && !!function () {
- var descriptor = getOwnPropertyDescriptor(String.prototype, 'endsWith');
- return descriptor && !descriptor.writable;
- }();
+ if (context.defaultChangesetComment()) {
+ corePreferences('comment', context.defaultChangesetComment());
+ corePreferences('commentDate', Date.now());
+ }
- // `String.prototype.endsWith` method
- // https://tc39.es/ecma262/#sec-string.prototype.endswith
- $({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG && !CORRECT_IS_REGEXP_LOGIC }, {
- endsWith: function endsWith(searchString /* , endPosition = @length */) {
- var that = String(requireObjectCoercible(this));
- notARegExp(searchString);
- var endPosition = arguments.length > 1 ? arguments[1] : undefined;
- var len = toLength(that.length);
- var end = endPosition === undefined ? len : min(toLength(endPosition), len);
- var search = String(searchString);
- return $endsWith
- ? $endsWith.call(that, search, end)
- : that.slice(end - search.length, end) === search;
- }
- });
+ if (context.defaultChangesetSource()) {
+ corePreferences('source', context.defaultChangesetSource());
+ corePreferences('commentDate', Date.now());
+ }
- function uiFieldWikidata(field, context) {
- var wikidata = services.wikidata;
- var dispatch = dispatch$8('change');
+ if (context.defaultChangesetHashtags()) {
+ corePreferences('hashtags', context.defaultChangesetHashtags());
+ corePreferences('commentDate', Date.now());
+ }
+
+ var detected = utilDetect();
+ var tags = {
+ comment: corePreferences('comment') || '',
+ created_by: context.cleanTagValue('iD ' + context.version),
+ host: context.cleanTagValue(detected.host),
+ locale: context.cleanTagValue(_mainLocalizer.localeCode())
+ }; // call findHashtags initially - this will remove stored
+ // hashtags if any hashtags are found in the comment - #4304
- var _selection = select(null);
+ findHashtags(tags, true);
+ var hashtags = corePreferences('hashtags');
- var _searchInput = select(null);
+ if (hashtags) {
+ tags.hashtags = hashtags;
+ }
- var _qid = null;
- var _wikidataEntity = null;
- var _wikiURL = '';
- var _entityIDs = [];
+ var source = corePreferences('source');
- var _wikipediaKey = field.keys && field.keys.find(function (key) {
- return key.includes('wikipedia');
- }),
- _hintKey = field.key === 'wikidata' ? 'name' : field.key.split(':')[0];
+ if (source) {
+ tags.source = source;
+ }
- var combobox = uiCombobox(context, 'combo-' + field.safeid).caseSensitive(true).minItems(1);
+ var photoOverlaysUsed = context.history().photoOverlaysUsed();
- function wiki(selection) {
- _selection = selection;
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', 'form-field-input-wrap form-field-input-' + field.type).merge(wrap);
- var list = wrap.selectAll('ul').data([0]);
- list = list.enter().append('ul').attr('class', 'rows').merge(list);
- var searchRow = list.selectAll('li.wikidata-search').data([0]);
- var searchRowEnter = searchRow.enter().append('li').attr('class', 'wikidata-search');
- searchRowEnter.append('input').attr('type', 'text').attr('id', field.domId).style('flex', '1').call(utilNoAuto).on('focus', function () {
- var node = select(this).node();
- node.setSelectionRange(0, node.value.length);
- }).on('blur', function () {
- setLabelForEntity();
- }).call(combobox.fetcher(fetchWikidataItems));
- combobox.on('accept', function (d) {
- if (d) {
- _qid = d.id;
- change();
- }
- }).on('cancel', function () {
- setLabelForEntity();
- });
- searchRowEnter.append('button').attr('class', 'form-field-button wiki-link').attr('title', _t('icons.view_on', {
- domain: 'wikidata.org'
- })).call(svgIcon('#iD-icon-out-link')).on('click', function (d3_event) {
- d3_event.preventDefault();
- if (_wikiURL) window.open(_wikiURL, '_blank');
- });
- searchRow = searchRow.merge(searchRowEnter);
- _searchInput = searchRow.select('input');
- var wikidataProperties = ['description', 'identifier'];
- var items = list.selectAll('li.labeled-input').data(wikidataProperties); // Enter
+ if (photoOverlaysUsed.length) {
+ var sources = (tags.source || '').split(';'); // include this tag for any photo layer
- var enter = items.enter().append('li').attr('class', function (d) {
- return 'labeled-input preset-wikidata-' + d;
- });
- enter.append('span').attr('class', 'label').html(function (d) {
- return _t.html('wikidata.' + d);
- });
- enter.append('input').attr('type', 'text').call(utilNoAuto).classed('disabled', 'true').attr('readonly', 'true');
- enter.append('button').attr('class', 'form-field-button').attr('title', _t('icons.copy')).call(svgIcon('#iD-operation-copy')).on('click', function (d3_event) {
- d3_event.preventDefault();
- select(this.parentNode).select('input').node().select();
- document.execCommand('copy');
- });
- }
+ if (sources.indexOf('streetlevel imagery') === -1) {
+ sources.push('streetlevel imagery');
+ } // add the photo overlays used during editing as sources
- function fetchWikidataItems(q, callback) {
- if (!q && _hintKey) {
- // other tags may be good search terms
- for (var i in _entityIDs) {
- var entity = context.hasEntity(_entityIDs[i]);
- if (entity.tags[_hintKey]) {
- q = entity.tags[_hintKey];
- break;
+ photoOverlaysUsed.forEach(function (photoOverlay) {
+ if (sources.indexOf(photoOverlay) === -1) {
+ sources.push(photoOverlay);
}
- }
+ });
+ tags.source = context.cleanTagValue(sources.join(';'));
}
- wikidata.itemsForSearchQuery(q, function (err, data) {
- if (err) return;
-
- for (var i in data) {
- data[i].value = data[i].label + ' (' + data[i].id + ')';
- data[i].title = data[i].description;
- }
-
- if (callback) callback(data);
+ context.changeset = new osmChangeset({
+ tags: tags
});
- }
+ } // Calculates read-only metadata tags based on the user's editing session and applies
+ // them to the changeset.
- function change() {
- var syncTags = {};
- syncTags[field.key] = _qid;
- dispatch.call('change', this, syncTags); // attempt asynchronous update of wikidata tag..
- var initGraph = context.graph();
- var initEntityIDs = _entityIDs;
- wikidata.entityByQID(_qid, function (err, entity) {
- if (err) return; // If graph has changed, we can't apply this update.
+ function loadDerivedChangesetTags() {
+ var osm = context.connection();
+ if (!osm) return;
+ var tags = Object.assign({}, context.changeset.tags); // shallow copy
+ // assign tags for imagery used
- if (context.graph() !== initGraph) return;
- if (!entity.sitelinks) return;
- var langs = wikidata.languagesToQuery(); // use the label and description languages as fallbacks
+ var imageryUsed = context.cleanTagValue(context.history().imageryUsed().join(';'));
+ tags.imagery_used = imageryUsed || 'None'; // assign tags for closed issues and notes
- ['labels', 'descriptions'].forEach(function (key) {
- if (!entity[key]) return;
- var valueLangs = Object.keys(entity[key]);
- if (valueLangs.length === 0) return;
- var valueLang = valueLangs[0];
+ var osmClosed = osm.getClosedIDs();
+ var itemType;
- if (langs.indexOf(valueLang) === -1) {
- langs.push(valueLang);
- }
- });
- var newWikipediaValue;
+ if (osmClosed.length) {
+ tags['closed:note'] = context.cleanTagValue(osmClosed.join(';'));
+ }
- if (_wikipediaKey) {
- var foundPreferred;
+ if (services.keepRight) {
+ var krClosed = services.keepRight.getClosedIDs();
- for (var i in langs) {
- var lang = langs[i];
- var siteID = lang.replace('-', '_') + 'wiki';
+ if (krClosed.length) {
+ tags['closed:keepright'] = context.cleanTagValue(krClosed.join(';'));
+ }
+ }
- if (entity.sitelinks[siteID]) {
- foundPreferred = true;
- newWikipediaValue = lang + ':' + entity.sitelinks[siteID].title; // use the first match
+ if (services.improveOSM) {
+ var iOsmClosed = services.improveOSM.getClosedCounts();
- break;
- }
- }
+ for (itemType in iOsmClosed) {
+ tags['closed:improveosm:' + itemType] = context.cleanTagValue(iOsmClosed[itemType].toString());
+ }
+ }
- if (!foundPreferred) {
- // No wikipedia sites available in the user's language or the fallback languages,
- // default to any wikipedia sitelink
- var wikiSiteKeys = Object.keys(entity.sitelinks).filter(function (site) {
- return site.endsWith('wiki');
- });
+ if (services.osmose) {
+ var osmoseClosed = services.osmose.getClosedCounts();
- if (wikiSiteKeys.length === 0) {
- // if no wikipedia pages are linked to this wikidata entity, delete that tag
- newWikipediaValue = null;
- } else {
- var wikiLang = wikiSiteKeys[0].slice(0, -4).replace('_', '-');
- var wikiTitle = entity.sitelinks[wikiSiteKeys[0]].title;
- newWikipediaValue = wikiLang + ':' + wikiTitle;
- }
- }
+ for (itemType in osmoseClosed) {
+ tags['closed:osmose:' + itemType] = context.cleanTagValue(osmoseClosed[itemType].toString());
}
+ } // remove existing issue counts
- if (newWikipediaValue) {
- newWikipediaValue = context.cleanTagValue(newWikipediaValue);
+
+ for (var key in tags) {
+ if (key.match(/(^warnings:)|(^resolved:)/)) {
+ delete tags[key];
}
+ }
- if (typeof newWikipediaValue === 'undefined') return;
- var actions = initEntityIDs.map(function (entityID) {
- var entity = context.hasEntity(entityID);
- if (!entity) return null;
- var currTags = Object.assign({}, entity.tags); // shallow copy
+ function addIssueCounts(issues, prefix) {
+ var issuesByType = utilArrayGroupBy(issues, 'type');
- if (newWikipediaValue === null) {
- if (!currTags[_wikipediaKey]) return null;
- delete currTags[_wikipediaKey];
+ for (var issueType in issuesByType) {
+ var issuesOfType = issuesByType[issueType];
+
+ if (issuesOfType[0].subtype) {
+ var issuesBySubtype = utilArrayGroupBy(issuesOfType, 'subtype');
+
+ for (var issueSubtype in issuesBySubtype) {
+ var issuesOfSubtype = issuesBySubtype[issueSubtype];
+ tags[prefix + ':' + issueType + ':' + issueSubtype] = context.cleanTagValue(issuesOfSubtype.length.toString());
+ }
} else {
- currTags[_wikipediaKey] = newWikipediaValue;
+ tags[prefix + ':' + issueType] = context.cleanTagValue(issuesOfType.length.toString());
}
+ }
+ } // add counts of warnings generated by the user's edits
- return actionChangeTags(entityID, currTags);
- }).filter(Boolean);
- if (!actions.length) return; // Coalesce the update of wikidata tag into the previous tag change
- context.overwrite(function actionUpdateWikipediaTags(graph) {
- actions.forEach(function (action) {
- graph = action(graph);
- });
- return graph;
- }, context.history().undoAnnotation()); // do not dispatch.call('change') here, because entity_editor
- // changeTags() is not intended to be called asynchronously
+ var warnings = context.validator().getIssuesBySeverity({
+ what: 'edited',
+ where: 'all',
+ includeIgnored: true,
+ includeDisabledRules: true
+ }).warning.filter(function (issue) {
+ return issue.type !== 'help_request';
+ }); // exclude 'fixme' and similar - #8603
+
+ addIssueCounts(warnings, 'warnings'); // add counts of issues resolved by the user's edits
+
+ var resolvedIssues = context.validator().getResolvedIssues();
+ addIssueCounts(resolvedIssues, 'resolved');
+ context.changeset = context.changeset.update({
+ tags: tags
});
}
- function setLabelForEntity() {
- var label = '';
+ function render(selection) {
+ var osm = context.connection();
+ if (!osm) return;
+ var header = selection.selectAll('.header').data([0]);
+ var headerTitle = header.enter().append('div').attr('class', 'header fillL');
+ headerTitle.append('div').append('h2').call(_t.append('commit.title'));
+ headerTitle.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ dispatch.call('cancel', this);
+ }).call(svgIcon('#iD-icon-close'));
+ var body = selection.selectAll('.body').data([0]);
+ body = body.enter().append('div').attr('class', 'body').merge(body); // Changeset Section
- if (_wikidataEntity) {
- label = entityPropertyForDisplay(_wikidataEntity, 'labels');
+ var changesetSection = body.selectAll('.changeset-editor').data([0]);
+ changesetSection = changesetSection.enter().append('div').attr('class', 'modal-section changeset-editor').merge(changesetSection);
+ changesetSection.call(changesetEditor.changesetID(context.changeset.id).tags(context.changeset.tags)); // Warnings
- if (label.length === 0) {
- label = _wikidataEntity.id.toString();
+ body.call(commitWarnings); // Upload Explanation
+
+ var saveSection = body.selectAll('.save-section').data([0]);
+ saveSection = saveSection.enter().append('div').attr('class', 'modal-section save-section fillL').merge(saveSection);
+ var prose = saveSection.selectAll('.commit-info').data([0]);
+
+ if (prose.enter().size()) {
+ // first time, make sure to update user details in prose
+ _userDetails = null;
+ }
+
+ prose = prose.enter().append('p').attr('class', 'commit-info').call(_t.append('commit.upload_explanation')).merge(prose); // always check if this has changed, but only update prose.html()
+ // if needed, because it can trigger a style recalculation
+
+ osm.userDetails(function (err, user) {
+ if (err) return;
+ if (_userDetails === user) return; // no change
+
+ _userDetails = user;
+ var userLink = select(document.createElement('div'));
+
+ if (user.image_url) {
+ userLink.append('img').attr('src', user.image_url).attr('class', 'icon pre-text user-icon');
}
+
+ userLink.append('a').attr('class', 'user-info').text(user.display_name).attr('href', osm.userURL(user.display_name)).attr('target', '_blank');
+ prose.html(_t.html('commit.upload_explanation_with_user', {
+ user: {
+ html: userLink.html()
+ }
+ }));
+ }); // Request Review
+
+ var requestReview = saveSection.selectAll('.request-review').data([0]); // Enter
+
+ var requestReviewEnter = requestReview.enter().append('div').attr('class', 'request-review');
+ var requestReviewDomId = utilUniqueDomId('commit-input-request-review');
+ var labelEnter = requestReviewEnter.append('label').attr('for', requestReviewDomId);
+
+ if (!labelEnter.empty()) {
+ labelEnter.call(uiTooltip().title(_t.html('commit.request_review_info')).placement('top'));
}
- utilGetSetValue(_searchInput, label);
- }
+ labelEnter.append('input').attr('type', 'checkbox').attr('id', requestReviewDomId);
+ labelEnter.append('span').call(_t.append('commit.request_review')); // Update
- wiki.tags = function (tags) {
- var isMixed = Array.isArray(tags[field.key]);
+ requestReview = requestReview.merge(requestReviewEnter);
+ var requestReviewInput = requestReview.selectAll('input').property('checked', isReviewRequested(context.changeset.tags)).on('change', toggleRequestReview); // Buttons
- _searchInput.attr('title', isMixed ? tags[field.key].filter(Boolean).join('\n') : null).attr('placeholder', isMixed ? _t('inspector.multiple_values') : '').classed('mixed', isMixed);
+ var buttonSection = saveSection.selectAll('.buttons').data([0]); // enter
- _qid = typeof tags[field.key] === 'string' && tags[field.key] || '';
+ var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons fillL');
+ buttonEnter.append('button').attr('class', 'secondary-action button cancel-button').append('span').attr('class', 'label').call(_t.append('commit.cancel'));
+ var uploadButton = buttonEnter.append('button').attr('class', 'action button save-button');
+ uploadButton.append('span').attr('class', 'label').call(_t.append('commit.save'));
+ var uploadBlockerTooltipText = getUploadBlockerMessage(); // update
- if (!/^Q[0-9]*$/.test(_qid)) {
- // not a proper QID
- unrecognized();
- return;
- } // QID value in correct format
+ buttonSection = buttonSection.merge(buttonEnter);
+ buttonSection.selectAll('.cancel-button').on('click.cancel', function () {
+ dispatch.call('cancel', this);
+ });
+ buttonSection.selectAll('.save-button').classed('disabled', uploadBlockerTooltipText !== null).on('click.save', function () {
+ if (!select(this).classed('disabled')) {
+ this.blur(); // avoid keeping focus on the button - #4641
+ for (var key in context.changeset.tags) {
+ // remove any empty keys before upload
+ if (!key) delete context.changeset.tags[key];
+ }
- _wikiURL = 'https://wikidata.org/wiki/' + _qid;
- wikidata.entityByQID(_qid, function (err, entity) {
- if (err) {
- unrecognized();
- return;
+ context.uploader().save(context.changeset);
}
+ }); // remove any existing tooltip
- _wikidataEntity = entity;
- setLabelForEntity();
- var description = entityPropertyForDisplay(entity, 'descriptions');
+ uiTooltip().destroyAny(buttonSection.selectAll('.save-button'));
- _selection.select('button.wiki-link').classed('disabled', false);
+ if (uploadBlockerTooltipText) {
+ buttonSection.selectAll('.save-button').call(uiTooltip().title(uploadBlockerTooltipText).placement('top'));
+ } // Raw Tag Editor
- _selection.select('.preset-wikidata-description').style('display', function () {
- return description.length > 0 ? 'flex' : 'none';
- }).select('input').attr('value', description);
- _selection.select('.preset-wikidata-identifier').style('display', function () {
- return entity.id ? 'flex' : 'none';
- }).select('input').attr('value', entity.id);
- }); // not a proper QID
+ var tagSection = body.selectAll('.tag-section.raw-tag-editor').data([0]);
+ tagSection = tagSection.enter().append('div').attr('class', 'modal-section tag-section raw-tag-editor').merge(tagSection);
+ tagSection.call(rawTagEditor.tags(Object.assign({}, context.changeset.tags)) // shallow copy
+ .render);
+ var changesSection = body.selectAll('.commit-changes-section').data([0]);
+ changesSection = changesSection.enter().append('div').attr('class', 'modal-section commit-changes-section').merge(changesSection); // Change summary
- function unrecognized() {
- _wikidataEntity = null;
- setLabelForEntity();
+ changesSection.call(commitChanges.render);
- _selection.select('.preset-wikidata-description').style('display', 'none');
+ function toggleRequestReview() {
+ var rr = requestReviewInput.property('checked');
+ updateChangeset({
+ review_requested: rr ? 'yes' : undefined
+ });
+ tagSection.call(rawTagEditor.tags(Object.assign({}, context.changeset.tags)) // shallow copy
+ .render);
+ }
+ }
- _selection.select('.preset-wikidata-identifier').style('display', 'none');
+ function getUploadBlockerMessage() {
+ var errors = context.validator().getIssuesBySeverity({
+ what: 'edited',
+ where: 'all'
+ }).error;
- _selection.select('button.wiki-link').classed('disabled', true);
+ if (errors.length) {
+ return _t('commit.outstanding_errors_message', {
+ count: errors.length
+ });
+ } else {
+ var hasChangesetComment = context.changeset && context.changeset.tags.comment && context.changeset.tags.comment.trim().length;
- if (_qid && _qid !== '') {
- _wikiURL = 'https://wikidata.org/wiki/Special:Search?search=' + _qid;
- } else {
- _wikiURL = '';
+ if (!hasChangesetComment) {
+ return _t('commit.comment_needed_message');
}
}
- };
-
- function entityPropertyForDisplay(wikidataEntity, propKey) {
- if (!wikidataEntity[propKey]) return '';
- var propObj = wikidataEntity[propKey];
- var langKeys = Object.keys(propObj);
- if (langKeys.length === 0) return ''; // sorted by priority, since we want to show the user's language first if possible
- var langs = wikidata.languagesToQuery();
+ return null;
+ }
- for (var i in langs) {
- var lang = langs[i];
- var valueObj = propObj[lang];
- if (valueObj && valueObj.value && valueObj.value.length > 0) return valueObj.value;
- } // default to any available value
+ function changeTags(_, changed, onInput) {
+ if (changed.hasOwnProperty('comment')) {
+ if (changed.comment === undefined) {
+ changed.comment = '';
+ }
+ if (!onInput) {
+ corePreferences('comment', changed.comment);
+ corePreferences('commentDate', Date.now());
+ }
+ }
- return propObj[langKeys[0]].value;
- }
+ if (changed.hasOwnProperty('source')) {
+ if (changed.source === undefined) {
+ corePreferences('source', null);
+ } else if (!onInput) {
+ corePreferences('source', changed.source);
+ corePreferences('commentDate', Date.now());
+ }
+ } // no need to update `prefs` for `hashtags` here since it's done in `updateChangeset`
- wiki.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return wiki;
- };
- wiki.focus = function () {
- _searchInput.node().focus();
- };
+ updateChangeset(changed, onInput);
- return utilRebind(wiki, dispatch, 'on');
- }
+ if (_selection) {
+ _selection.call(render);
+ }
+ }
- function uiFieldWikipedia(field, context) {
- var _arguments = arguments;
- var dispatch = dispatch$8('change');
- var wikipedia = services.wikipedia;
- var wikidata = services.wikidata;
+ function findHashtags(tags, commentOnly) {
+ var detectedHashtags = commentHashtags();
- var _langInput = select(null);
+ if (detectedHashtags.length) {
+ // always remove stored hashtags if there are hashtags in the comment - #4304
+ corePreferences('hashtags', null);
+ }
- var _titleInput = select(null);
+ if (!detectedHashtags.length || !commentOnly) {
+ detectedHashtags = detectedHashtags.concat(hashtagHashtags());
+ }
- var _wikiURL = '';
+ var allLowerCase = new Set();
+ return detectedHashtags.filter(function (hashtag) {
+ // Compare tags as lowercase strings, but keep original case tags
+ var lowerCase = hashtag.toLowerCase();
- var _entityIDs;
+ if (!allLowerCase.has(lowerCase)) {
+ allLowerCase.add(lowerCase);
+ return true;
+ }
- var _tags;
+ return false;
+ }); // Extract hashtags from `comment`
- var _dataWikipedia = [];
- _mainFileFetcher.get('wmf_sitematrix').then(function (d) {
- _dataWikipedia = d;
- if (_tags) updateForTags(_tags);
- })["catch"](function () {
- /* ignore */
- });
- var langCombo = uiCombobox(context, 'wikipedia-lang').fetcher(function (value, callback) {
- var v = value.toLowerCase();
- callback(_dataWikipedia.filter(function (d) {
- return d[0].toLowerCase().indexOf(v) >= 0 || d[1].toLowerCase().indexOf(v) >= 0 || d[2].toLowerCase().indexOf(v) >= 0;
- }).map(function (d) {
- return {
- value: d[1]
- };
- }));
- });
- var titleCombo = uiCombobox(context, 'wikipedia-title').fetcher(function (value, callback) {
- if (!value) {
- value = '';
+ function commentHashtags() {
+ var matches = (tags.comment || '').replace(/http\S*/g, '') // drop anything that looks like a URL - #4289
+ .match(hashtagRegex);
+ return matches || [];
+ } // Extract and clean hashtags from `hashtags`
- for (var i in _entityIDs) {
- var entity = context.hasEntity(_entityIDs[i]);
- if (entity.tags.name) {
- value = entity.tags.name;
- break;
- }
- }
- }
+ function hashtagHashtags() {
+ var matches = (tags.hashtags || '').split(/[,;\s]+/).map(function (s) {
+ if (s[0] !== '#') {
+ s = '#' + s;
+ } // prepend '#'
- var searchfn = value.length > 7 ? wikipedia.search : wikipedia.suggestions;
- searchfn(language()[2], value, function (query, data) {
- callback(data.map(function (d) {
- return {
- value: d
- };
- }));
- });
- });
- function wiki(selection) {
- var wrap = selection.selectAll('.form-field-input-wrap').data([0]);
- wrap = wrap.enter().append('div').attr('class', "form-field-input-wrap form-field-input-".concat(field.type)).merge(wrap);
- var langContainer = wrap.selectAll('.wiki-lang-container').data([0]);
- langContainer = langContainer.enter().append('div').attr('class', 'wiki-lang-container').merge(langContainer);
- _langInput = langContainer.selectAll('input.wiki-lang').data([0]);
- _langInput = _langInput.enter().append('input').attr('type', 'text').attr('class', 'wiki-lang').attr('placeholder', _t('translate.localized_translation_language')).call(utilNoAuto).call(langCombo).merge(_langInput);
+ var matched = s.match(hashtagRegex);
+ return matched && matched[0];
+ }).filter(Boolean); // exclude falsy
- _langInput.on('blur', changeLang).on('change', changeLang);
+ return matches || [];
+ }
+ }
- var titleContainer = wrap.selectAll('.wiki-title-container').data([0]);
- titleContainer = titleContainer.enter().append('div').attr('class', 'wiki-title-container').merge(titleContainer);
- _titleInput = titleContainer.selectAll('input.wiki-title').data([0]);
- _titleInput = _titleInput.enter().append('input').attr('type', 'text').attr('class', 'wiki-title').attr('id', field.domId).call(utilNoAuto).call(titleCombo).merge(_titleInput);
+ function isReviewRequested(tags) {
+ var rr = tags.review_requested;
+ if (rr === undefined) return false;
+ rr = rr.trim().toLowerCase();
+ return !(rr === '' || rr === 'no');
+ }
- _titleInput.on('blur', function () {
- change(true);
- }).on('change', function () {
- change(false);
- });
+ function updateChangeset(changed, onInput) {
+ var tags = Object.assign({}, context.changeset.tags); // shallow copy
- var link = titleContainer.selectAll('.wiki-link').data([0]);
- link = link.enter().append('button').attr('class', 'form-field-button wiki-link').attr('title', _t('icons.view_on', {
- domain: 'wikipedia.org'
- })).call(svgIcon('#iD-icon-out-link')).merge(link);
- link.on('click', function (d3_event) {
- d3_event.preventDefault();
- if (_wikiURL) window.open(_wikiURL, '_blank');
+ Object.keys(changed).forEach(function (k) {
+ var v = changed[k];
+ k = context.cleanTagKey(k);
+ if (readOnlyTags.indexOf(k) !== -1) return;
+
+ if (v === undefined) {
+ delete tags[k];
+ } else if (onInput) {
+ tags[k] = v;
+ } else {
+ tags[k] = context.cleanTagValue(v);
+ }
});
- }
- function defaultLanguageInfo(skipEnglishFallback) {
- var langCode = _mainLocalizer.languageCode().toLowerCase();
+ if (!onInput) {
+ // when changing the comment, override hashtags with any found in comment.
+ var commentOnly = changed.hasOwnProperty('comment') && changed.comment !== '';
+ var arr = findHashtags(tags, commentOnly);
- for (var i in _dataWikipedia) {
- var d = _dataWikipedia[i]; // default to the language of iD's current locale
+ if (arr.length) {
+ tags.hashtags = context.cleanTagValue(arr.join(';'));
+ corePreferences('hashtags', tags.hashtags);
+ } else {
+ delete tags.hashtags;
+ corePreferences('hashtags', null);
+ }
+ } // always update userdetails, just in case user reauthenticates as someone else
- if (d[2] === langCode) return d;
- } // fallback to English
+ if (_userDetails && _userDetails.changesets_count !== undefined) {
+ var changesetsCount = parseInt(_userDetails.changesets_count, 10) + 1; // #4283
- return skipEnglishFallback ? ['', '', ''] : ['English', 'English', 'en'];
- }
+ tags.changesets_count = String(changesetsCount); // first 100 edits - new user
- function language(skipEnglishFallback) {
- var value = utilGetSetValue(_langInput).toLowerCase();
+ if (changesetsCount <= 100) {
+ var s;
+ s = corePreferences('walkthrough_completed');
- for (var i in _dataWikipedia) {
- var d = _dataWikipedia[i]; // return the language already set in the UI, if supported
+ if (s) {
+ tags['ideditor:walkthrough_completed'] = s;
+ }
- if (d[0].toLowerCase() === value || d[1].toLowerCase() === value || d[2] === value) return d;
- } // fallback to English
+ s = corePreferences('walkthrough_progress');
+
+ if (s) {
+ tags['ideditor:walkthrough_progress'] = s;
+ }
+ s = corePreferences('walkthrough_started');
- return defaultLanguageInfo(skipEnglishFallback);
- }
+ if (s) {
+ tags['ideditor:walkthrough_started'] = s;
+ }
+ }
+ } else {
+ delete tags.changesets_count;
+ }
- function changeLang() {
- utilGetSetValue(_langInput, language()[1]);
- change(true);
+ if (!fastDeepEqual(context.changeset.tags, tags)) {
+ context.changeset = context.changeset.update({
+ tags: tags
+ });
+ }
}
- function change(skipWikidata) {
- var value = utilGetSetValue(_titleInput);
- var m = value.match(/https?:\/\/([-a-z]+)\.wikipedia\.org\/(?:wiki|\1-[-a-z]+)\/([^#]+)(?:#(.+))?/);
+ commit.reset = function () {
+ context.changeset = null;
+ };
- var langInfo = m && _dataWikipedia.find(function (d) {
- return m[1] === d[2];
- });
+ return utilRebind(commit, dispatch, 'on');
+ }
- var syncTags = {};
+ function uiConfirm(selection) {
+ var modalSelection = uiModal(selection);
+ modalSelection.select('.modal').classed('modal-alert', true);
+ var section = modalSelection.select('.content');
+ section.append('div').attr('class', 'modal-section header');
+ section.append('div').attr('class', 'modal-section message-text');
+ var buttons = section.append('div').attr('class', 'modal-section buttons cf');
- if (langInfo) {
- var nativeLangName = langInfo[1]; // Normalize title http://www.mediawiki.org/wiki/API:Query#Title_normalization
+ modalSelection.okButton = function () {
+ buttons.append('button').attr('class', 'button ok-button action').on('click.confirm', function () {
+ modalSelection.remove();
+ }).call(_t.append('confirm.okay')).node().focus();
+ return modalSelection;
+ };
- value = decodeURIComponent(m[2]).replace(/_/g, ' ');
+ return modalSelection;
+ }
- if (m[3]) {
- var anchor; // try {
- // leave this out for now - #6232
- // Best-effort `anchordecode:` implementation
- // anchor = decodeURIComponent(m[3].replace(/\.([0-9A-F]{2})/g, '%$1'));
- // } catch (e) {
+ function uiConflicts(context) {
+ var dispatch = dispatch$8('cancel', 'save');
+ var keybinding = utilKeybinding('conflicts');
- anchor = decodeURIComponent(m[3]); // }
+ var _origChanges;
- value += '#' + anchor.replace(/_/g, ' ');
- }
+ var _conflictList;
- value = value.slice(0, 1).toUpperCase() + value.slice(1);
- utilGetSetValue(_langInput, nativeLangName);
- utilGetSetValue(_titleInput, value);
- }
+ var _shownConflictIndex;
- if (value) {
- syncTags.wikipedia = context.cleanTagValue(language()[2] + ':' + value);
- } else {
- syncTags.wikipedia = undefined;
- }
+ function keybindingOn() {
+ select(document).call(keybinding.on('⎋', cancel, true));
+ }
- dispatch.call('change', this, syncTags);
- if (skipWikidata || !value || !language()[2]) return; // attempt asynchronous update of wikidata tag..
+ function keybindingOff() {
+ select(document).call(keybinding.unbind);
+ }
- var initGraph = context.graph();
- var initEntityIDs = _entityIDs;
- wikidata.itemsByTitle(language()[2], value, function (err, data) {
- if (err || !data || !Object.keys(data).length) return; // If graph has changed, we can't apply this update.
+ function tryAgain() {
+ keybindingOff();
+ dispatch.call('save');
+ }
- if (context.graph() !== initGraph) return;
- var qids = Object.keys(data);
- var value = qids && qids.find(function (id) {
- return id.match(/^Q\d+$/);
- });
- var actions = initEntityIDs.map(function (entityID) {
- var entity = context.entity(entityID).tags;
- var currTags = Object.assign({}, entity); // shallow copy
+ function cancel() {
+ keybindingOff();
+ dispatch.call('cancel');
+ }
- if (currTags.wikidata !== value) {
- currTags.wikidata = value;
- return actionChangeTags(entityID, currTags);
- }
+ function conflicts(selection) {
+ keybindingOn();
+ var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
+ headerEnter.append('button').attr('class', 'fr').attr('title', _t('icons.close')).on('click', cancel).call(svgIcon('#iD-icon-close'));
+ headerEnter.append('h2').call(_t.append('save.conflict.header'));
+ var bodyEnter = selection.selectAll('.body').data([0]).enter().append('div').attr('class', 'body fillL');
+ var conflictsHelpEnter = bodyEnter.append('div').attr('class', 'conflicts-help').call(_t.append('save.conflict.help')); // Download changes link
- return null;
- }).filter(Boolean);
- if (!actions.length) return; // Coalesce the update of wikidata tag into the previous tag change
+ var detected = utilDetect();
+ var changeset = new osmChangeset();
+ delete changeset.id; // Export without changeset_id
- context.overwrite(function actionUpdateWikidataTags(graph) {
- actions.forEach(function (action) {
- graph = action(graph);
- });
- return graph;
- }, context.history().undoAnnotation()); // do not dispatch.call('change') here, because entity_editor
- // changeTags() is not intended to be called asynchronously
+ var data = JXON.stringify(changeset.osmChangeJXON(_origChanges));
+ var blob = new Blob([data], {
+ type: 'text/xml;charset=utf-8;'
});
+ var fileName = 'changes.osc';
+ var linkEnter = conflictsHelpEnter.selectAll('.download-changes').append('a').attr('class', 'download-changes');
+
+ if (detected.download) {
+ // All except IE11 and Edge
+ linkEnter // download the data as a file
+ .attr('href', window.URL.createObjectURL(blob)).attr('download', fileName);
+ } else {
+ // IE11 and Edge
+ linkEnter // open data uri in a new tab
+ .attr('target', '_blank').on('click.download', function () {
+ navigator.msSaveBlob(blob, fileName);
+ });
+ }
+
+ linkEnter.call(svgIcon('#iD-icon-load', 'inline')).append('span').call(_t.append('save.conflict.download_changes'));
+ bodyEnter.append('div').attr('class', 'conflict-container fillL3').call(showConflict, 0);
+ bodyEnter.append('div').attr('class', 'conflicts-done').attr('opacity', 0).style('display', 'none').call(_t.append('save.conflict.done'));
+ var buttonsEnter = bodyEnter.append('div').attr('class', 'buttons col12 joined conflicts-buttons');
+ buttonsEnter.append('button').attr('disabled', _conflictList.length > 1).attr('class', 'action conflicts-button col6').call(_t.append('save.title')).on('click.try_again', tryAgain);
+ buttonsEnter.append('button').attr('class', 'secondary-action conflicts-button col6').call(_t.append('confirm.cancel')).on('click.cancel', cancel);
}
- wiki.tags = function (tags) {
- _tags = tags;
- updateForTags(tags);
- };
+ function showConflict(selection, index) {
+ index = utilWrap(index, _conflictList.length);
+ _shownConflictIndex = index;
+ var parent = select(selection.node().parentNode); // enable save button if this is the last conflict being reviewed..
- function updateForTags(tags) {
- var value = typeof tags[field.key] === 'string' ? tags[field.key] : ''; // Expect tag format of `tagLang:tagArticleTitle`, e.g. `fr:Paris`, with
- // optional suffix of `#anchor`
+ if (index === _conflictList.length - 1) {
+ window.setTimeout(function () {
+ parent.select('.conflicts-button').attr('disabled', null);
+ parent.select('.conflicts-done').transition().attr('opacity', 1).style('display', 'block');
+ }, 250);
+ }
- var m = value.match(/([^:]+):([^#]+)(?:#(.+))?/);
- var tagLang = m && m[1];
- var tagArticleTitle = m && m[2];
- var anchor = m && m[3];
+ var conflict = selection.selectAll('.conflict').data([_conflictList[index]]);
+ conflict.exit().remove();
+ var conflictEnter = conflict.enter().append('div').attr('class', 'conflict');
+ conflictEnter.append('h4').attr('class', 'conflict-count').call(_t.append('save.conflict.count', {
+ num: index + 1,
+ total: _conflictList.length
+ }));
+ conflictEnter.append('a').attr('class', 'conflict-description').attr('href', '#').text(function (d) {
+ return d.name;
+ }).on('click', function (d3_event, d) {
+ d3_event.preventDefault();
+ zoomToEntity(d.id);
+ });
+ var details = conflictEnter.append('div').attr('class', 'conflict-detail-container');
+ details.append('ul').attr('class', 'conflict-detail-list').selectAll('li').data(function (d) {
+ return d.details || [];
+ }).enter().append('li').attr('class', 'conflict-detail-item').html(function (d) {
+ return d;
+ });
+ details.append('div').attr('class', 'conflict-choices').call(addChoices);
+ details.append('div').attr('class', 'conflict-nav-buttons joined cf').selectAll('button').data(['previous', 'next']).enter().append('button').html(function (d) {
+ return _t.html('save.conflict.' + d);
+ }).attr('class', 'conflict-nav-button action col6').attr('disabled', function (d, i) {
+ return i === 0 && index === 0 || i === 1 && index === _conflictList.length - 1 || null;
+ }).on('click', function (d3_event, d) {
+ d3_event.preventDefault();
+ var container = parent.selectAll('.conflict-container');
+ var sign = d === 'previous' ? -1 : 1;
+ container.selectAll('.conflict').remove();
+ container.call(showConflict, index + sign);
+ });
+ }
- var tagLangInfo = tagLang && _dataWikipedia.find(function (d) {
- return tagLang === d[2];
- }); // value in correct format
+ function addChoices(selection) {
+ var choices = selection.append('ul').attr('class', 'layer-list').selectAll('li').data(function (d) {
+ return d.choices || [];
+ }); // enter
+ var choicesEnter = choices.enter().append('li').attr('class', 'layer');
+ var labelEnter = choicesEnter.append('label');
+ labelEnter.append('input').attr('type', 'radio').attr('name', function (d) {
+ return d.id;
+ }).on('change', function (d3_event, d) {
+ var ul = this.parentNode.parentNode.parentNode;
+ ul.__data__.chosen = d.id;
+ choose(d3_event, ul, d);
+ });
+ labelEnter.append('span').text(function (d) {
+ return d.text;
+ }); // update
- if (tagLangInfo) {
- var nativeLangName = tagLangInfo[1];
- utilGetSetValue(_langInput, nativeLangName);
- utilGetSetValue(_titleInput, tagArticleTitle + (anchor ? '#' + anchor : ''));
+ choicesEnter.merge(choices).each(function (d) {
+ var ul = this.parentNode;
- if (anchor) {
- try {
- // Best-effort `anchorencode:` implementation
- anchor = encodeURIComponent(anchor.replace(/ /g, '_')).replace(/%/g, '.');
- } catch (e) {
- anchor = anchor.replace(/ /g, '_');
- }
+ if (ul.__data__.chosen === d.id) {
+ choose(null, ul, d);
}
+ });
+ }
- _wikiURL = 'https://' + tagLang + '.wikipedia.org/wiki/' + tagArticleTitle.replace(/ /g, '_') + (anchor ? '#' + anchor : ''); // unrecognized value format
- } else {
- utilGetSetValue(_titleInput, value);
+ function choose(d3_event, ul, datum) {
+ if (d3_event) d3_event.preventDefault();
+ select(ul).selectAll('li').classed('active', function (d) {
+ return d === datum;
+ }).selectAll('input').property('checked', function (d) {
+ return d === datum;
+ });
+ var extent = geoExtent();
+ var entity;
+ entity = context.graph().hasEntity(datum.id);
+ if (entity) extent._extend(entity.extent(context.graph()));
+ datum.action();
+ entity = context.graph().hasEntity(datum.id);
+ if (entity) extent._extend(entity.extent(context.graph()));
+ zoomToEntity(datum.id, extent);
+ }
- if (value && value !== '') {
- utilGetSetValue(_langInput, '');
- var defaultLangInfo = defaultLanguageInfo();
- _wikiURL = "https://".concat(defaultLangInfo[2], ".wikipedia.org/w/index.php?fulltext=1&search=").concat(value);
+ function zoomToEntity(id, extent) {
+ context.surface().selectAll('.hover').classed('hover', false);
+ var entity = context.graph().hasEntity(id);
+
+ if (entity) {
+ if (extent) {
+ context.map().trimmedExtent(extent);
} else {
- var shownOrDefaultLangInfo = language(true
- /* skipEnglishFallback */
- );
- utilGetSetValue(_langInput, shownOrDefaultLangInfo[1]);
- _wikiURL = '';
+ context.map().zoomToEase(entity);
}
+
+ context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph())).classed('hover', true);
}
- }
+ } // The conflict list should be an array of objects like:
+ // {
+ // id: id,
+ // name: entityName(local),
+ // details: merge.conflicts(),
+ // chosen: 1,
+ // choices: [
+ // choice(id, keepMine, forceLocal),
+ // choice(id, keepTheirs, forceRemote)
+ // ]
+ // }
- wiki.entityIDs = function (val) {
- if (!_arguments.length) return _entityIDs;
- _entityIDs = val;
- return wiki;
- };
- wiki.focus = function () {
- _titleInput.node().focus();
+ conflicts.conflictList = function (_) {
+ if (!arguments.length) return _conflictList;
+ _conflictList = _;
+ return conflicts;
};
- return utilRebind(wiki, dispatch, 'on');
- }
- uiFieldWikipedia.supportsMultiselection = false;
-
- var uiFields = {
- access: uiFieldAccess,
- address: uiFieldAddress,
- check: uiFieldCheck,
- combo: uiFieldCombo,
- cycleway: uiFieldCycleway,
- defaultCheck: uiFieldCheck,
- email: uiFieldText,
- identifier: uiFieldText,
- lanes: uiFieldLanes,
- localized: uiFieldLocalized,
- roadheight: uiFieldRoadheight,
- roadspeed: uiFieldRoadspeed,
- manyCombo: uiFieldCombo,
- multiCombo: uiFieldCombo,
- networkCombo: uiFieldCombo,
- number: uiFieldText,
- onewayCheck: uiFieldCheck,
- radio: uiFieldRadio,
- restrictions: uiFieldRestrictions,
- semiCombo: uiFieldCombo,
- structureRadio: uiFieldRadio,
- tel: uiFieldText,
- text: uiFieldText,
- textarea: uiFieldTextarea,
- typeCombo: uiFieldCombo,
- url: uiFieldText,
- wikidata: uiFieldWikidata,
- wikipedia: uiFieldWikipedia
- };
+ conflicts.origChanges = function (_) {
+ if (!arguments.length) return _origChanges;
+ _origChanges = _;
+ return conflicts;
+ };
- function uiField(context, presetField, entityIDs, options) {
- options = Object.assign({
- show: true,
- wrap: true,
- remove: true,
- revert: true,
- info: true
- }, options);
- var dispatch = dispatch$8('change', 'revert');
- var field = Object.assign({}, presetField); // shallow copy
+ conflicts.shownEntityIds = function () {
+ if (_conflictList && typeof _shownConflictIndex === 'number') {
+ return [_conflictList[_shownConflictIndex].id];
+ }
- field.domId = utilUniqueDomId('form-field-' + field.safeid);
- var _show = options.show;
- var _state = '';
- var _tags = {};
+ return [];
+ };
- var _entityExtent;
+ return utilRebind(conflicts, dispatch, 'on');
+ }
- if (entityIDs && entityIDs.length) {
- _entityExtent = entityIDs.reduce(function (extent, entityID) {
- var entity = context.graph().entity(entityID);
- return extent.extend(entity.extent(context.graph()));
- }, geoExtent());
- }
+ function uiSectionEntityIssues(context) {
+ // Does the user prefer to expand the active issue? Useful for viewing tag diff.
+ // Expand by default so first timers see it - #6408, #8143
+ var preference = corePreferences('entity-issues.reference.expanded');
- var _locked = false;
+ var _expanded = preference === null ? true : preference === 'true';
- var _lockedTip = uiTooltip().title(_t.html('inspector.lock.suggestion', {
- label: field.label
- })).placement('bottom');
+ var _entityIDs = [];
+ var _issues = [];
- field.keys = field.keys || [field.key]; // only create the fields that are actually being shown
+ var _activeIssueID;
- if (_show && !field.impl) {
- createField();
- } // Creates the field.. This is done lazily,
- // once we know that the field will be shown.
+ var section = uiSection('entity-issues', context).shouldDisplay(function () {
+ return _issues.length > 0;
+ }).label(function () {
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('issues.list_title')
+ },
+ count: _issues.length
+ });
+ }).disclosureContent(renderDisclosureContent);
+ context.validator().on('validated.entity_issues', function () {
+ // Refresh on validated events
+ reloadIssues();
+ section.reRender();
+ }).on('focusedIssue.entity_issues', function (issue) {
+ makeActiveIssue(issue.id);
+ });
+ function reloadIssues() {
+ _issues = context.validator().getSharedEntityIssues(_entityIDs, {
+ includeDisabledRules: true
+ });
+ }
- function createField() {
- field.impl = uiFields[field.type](field, context).on('change', function (t, onInput) {
- dispatch.call('change', field, t, onInput);
+ function makeActiveIssue(issueID) {
+ _activeIssueID = issueID;
+ section.selection().selectAll('.issue-container').classed('active', function (d) {
+ return d.id === _activeIssueID;
});
+ }
- if (entityIDs) {
- field.entityIDs = entityIDs; // if this field cares about the entities, pass them along
+ function renderDisclosureContent(selection) {
+ selection.classed('grouped-items-area', true);
+ _activeIssueID = _issues.length > 0 ? _issues[0].id : null;
+ var containers = selection.selectAll('.issue-container').data(_issues, function (d) {
+ return d.key;
+ }); // Exit
- if (field.impl.entityIDs) {
- field.impl.entityIDs(entityIDs);
- }
- }
- }
+ containers.exit().remove(); // Enter
- function isModified() {
- if (!entityIDs || !entityIDs.length) return false;
- return entityIDs.some(function (entityID) {
- var original = context.graph().base().entities[entityID];
- var latest = context.graph().entity(entityID);
- return field.keys.some(function (key) {
- return original ? latest.tags[key] !== original.tags[key] : latest.tags[key];
+ var containersEnter = containers.enter().append('div').attr('class', 'issue-container');
+ var itemsEnter = containersEnter.append('div').attr('class', function (d) {
+ return 'issue severity-' + d.severity;
+ }).on('mouseover.highlight', function (d3_event, d) {
+ // don't hover-highlight the selected entity
+ var ids = d.entityIds.filter(function (e) {
+ return _entityIDs.indexOf(e) === -1;
+ });
+ utilHighlightEntities(ids, true, context);
+ }).on('mouseout.highlight', function (d3_event, d) {
+ var ids = d.entityIds.filter(function (e) {
+ return _entityIDs.indexOf(e) === -1;
});
+ utilHighlightEntities(ids, false, context);
});
- }
+ var labelsEnter = itemsEnter.append('div').attr('class', 'issue-label');
+ var textEnter = labelsEnter.append('button').attr('class', 'issue-text').on('click', function (d3_event, d) {
+ makeActiveIssue(d.id); // expand only the clicked item
- function tagsContainFieldKey() {
- return field.keys.some(function (key) {
- if (field.type === 'multiCombo') {
- for (var tagKey in _tags) {
- if (tagKey.indexOf(key) === 0) {
- return true;
- }
- }
+ var extent = d.extent(context.graph());
- return false;
+ if (extent) {
+ var setZoom = Math.max(context.map().zoom(), 19);
+ context.map().unobscuredCenterZoomEase(extent.center(), setZoom);
}
+ });
+ textEnter.each(function (d) {
+ var iconName = '#iD-icon-' + (d.severity === 'warning' ? 'alert' : 'error');
+ select(this).call(svgIcon(iconName, 'issue-icon'));
+ });
+ textEnter.append('span').attr('class', 'issue-message');
+ var infoButton = labelsEnter.append('button').attr('class', 'issue-info-button').attr('title', _t('icons.information')).call(svgIcon('#iD-icon-inspect'));
+ infoButton.on('click', function (d3_event) {
+ d3_event.stopPropagation();
+ d3_event.preventDefault();
+ this.blur(); // avoid keeping focus on the button - #4641
- return _tags[key] !== undefined;
+ var container = select(this.parentNode.parentNode.parentNode);
+ var info = container.selectAll('.issue-info');
+ var isExpanded = info.classed('expanded');
+ _expanded = !isExpanded;
+ corePreferences('entity-issues.reference.expanded', _expanded); // update preference
+
+ if (isExpanded) {
+ info.transition().duration(200).style('max-height', '0px').style('opacity', '0').on('end', function () {
+ info.classed('expanded', false);
+ });
+ } else {
+ info.classed('expanded', true).transition().duration(200).style('max-height', '200px').style('opacity', '1').on('end', function () {
+ info.style('max-height', null);
+ });
+ }
});
- }
+ itemsEnter.append('ul').attr('class', 'issue-fix-list');
+ containersEnter.append('div').attr('class', 'issue-info' + (_expanded ? ' expanded' : '')).style('max-height', _expanded ? null : '0').style('opacity', _expanded ? '1' : '0').each(function (d) {
+ if (typeof d.reference === 'function') {
+ select(this).call(d.reference);
+ } else {
+ select(this).call(_t.append('inspector.no_documentation_key'));
+ }
+ }); // Update
- function revert(d3_event, d) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- if (!entityIDs || _locked) return;
- dispatch.call('revert', d, d.keys);
- }
+ containers = containers.merge(containersEnter).classed('active', function (d) {
+ return d.id === _activeIssueID;
+ });
+ containers.selectAll('.issue-message').html(function (d) {
+ return d.message(context);
+ }); // fixes
- function remove(d3_event, d) {
- d3_event.stopPropagation();
- d3_event.preventDefault();
- if (_locked) return;
- var t = {};
- d.keys.forEach(function (key) {
- t[key] = undefined;
+ var fixLists = containers.selectAll('.issue-fix-list');
+ var fixes = fixLists.selectAll('.issue-fix-item').data(function (d) {
+ return d.fixes ? d.fixes(context) : [];
+ }, function (fix) {
+ return fix.id;
});
- dispatch.call('change', d, t);
- }
+ fixes.exit().remove();
+ var fixesEnter = fixes.enter().append('li').attr('class', 'issue-fix-item');
+ var buttons = fixesEnter.append('button').on('click', function (d3_event, d) {
+ // not all fixes are actionable
+ if (select(this).attr('disabled') || !d.onClick) return; // Don't run another fix for this issue within a second of running one
+ // (Necessary for "Select a feature type" fix. Most fixes should only ever run once)
- field.render = function (selection) {
- var container = selection.selectAll('.form-field').data([field]); // Enter
+ if (d.issue.dateLastRanFix && new Date() - d.issue.dateLastRanFix < 1000) return;
+ d.issue.dateLastRanFix = new Date(); // remove hover-highlighting
- var enter = container.enter().append('div').attr('class', function (d) {
- return 'form-field form-field-' + d.safeid;
- }).classed('nowrap', !options.wrap);
+ utilHighlightEntities(d.issue.entityIds.concat(d.entityIds), false, context);
+ new Promise(function (resolve, reject) {
+ d.onClick(context, resolve, reject);
- if (options.wrap) {
- var labelEnter = enter.append('label').attr('class', 'field-label').attr('for', function (d) {
- return d.domId;
- });
- var textEnter = labelEnter.append('span').attr('class', 'label-text');
- textEnter.append('span').attr('class', 'label-textvalue').html(function (d) {
- return d.label();
+ if (d.onClick.length <= 1) {
+ // if the fix doesn't take any completion parameters then consider it resolved
+ resolve();
+ }
+ }).then(function () {
+ // revalidate whenever the fix has finished running successfully
+ context.validator().validate();
});
- textEnter.append('span').attr('class', 'label-textannotation');
+ }).on('mouseover.highlight', function (d3_event, d) {
+ utilHighlightEntities(d.entityIds, true, context);
+ }).on('mouseout.highlight', function (d3_event, d) {
+ utilHighlightEntities(d.entityIds, false, context);
+ });
+ buttons.each(function (d) {
+ var iconName = d.icon || 'iD-icon-wrench';
- if (options.remove) {
- labelEnter.append('button').attr('class', 'remove-icon').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete'));
+ if (iconName.startsWith('maki')) {
+ iconName += '-15';
}
- if (options.revert) {
- labelEnter.append('button').attr('class', 'modified-icon').attr('title', _t('icons.undo')).call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-redo' : '#iD-icon-undo'));
+ select(this).call(svgIcon('#' + iconName, 'fix-icon'));
+ });
+ buttons.append('span').attr('class', 'fix-message').html(function (d) {
+ return d.title;
+ });
+ fixesEnter.merge(fixes).selectAll('button').classed('actionable', function (d) {
+ return d.onClick;
+ }).attr('disabled', function (d) {
+ return d.onClick ? null : 'true';
+ }).attr('title', function (d) {
+ if (d.disabledReason) {
+ return d.disabledReason;
}
- } // Update
+ return null;
+ });
+ }
- container = container.merge(enter);
- container.select('.field-label > .remove-icon') // propagate bound data
- .on('click', remove);
- container.select('.field-label > .modified-icon') // propagate bound data
- .on('click', revert);
- container.each(function (d) {
- var selection = select(this);
+ section.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
- if (!d.impl) {
- createField();
- }
+ if (!_entityIDs || !val || !utilArrayIdentical(_entityIDs, val)) {
+ _entityIDs = val;
+ _activeIssueID = null;
+ reloadIssues();
+ }
- var reference, help; // instantiate field help
+ return section;
+ };
- if (options.wrap && field.type === 'restrictions') {
- help = uiFieldHelp(context, 'restrictions');
- } // instantiate tag reference
+ return section;
+ }
+ function uiPresetIcon() {
+ var _preset;
- if (options.wrap && options.info) {
- var referenceKey = d.key || '';
+ var _geometry;
- if (d.type === 'multiCombo') {
- // lookup key without the trailing ':'
- referenceKey = referenceKey.replace(/:$/, '');
- }
+ var _sizeClass = 'medium';
- reference = uiTagReference(d.reference || {
- key: referenceKey
- });
+ function isSmall() {
+ return _sizeClass === 'small';
+ }
- if (_state === 'hover') {
- reference.showing(false);
- }
- }
+ function presetIcon(selection) {
+ selection.each(render);
+ }
- selection.call(d.impl); // add field help components
+ function getIcon(p, geom) {
+ if (isSmall() && p.isFallback && p.isFallback()) return 'iD-icon-' + p.id;
+ if (p.icon) return p.icon;
+ if (geom === 'line') return 'iD-other-line';
+ if (geom === 'vertex') return p.isFallback() ? '' : 'temaki-vertex';
+ if (isSmall() && geom === 'point') return '';
+ return 'maki-marker-stroked';
+ }
- if (help) {
- selection.call(help.body).select('.field-label').call(help.button);
- } // add tag reference components
+ function renderPointBorder(container, drawPoint) {
+ var pointBorder = container.selectAll('.preset-icon-point-border').data(drawPoint ? [0] : []);
+ pointBorder.exit().remove();
+ var pointBorderEnter = pointBorder.enter();
+ var w = 40;
+ var h = 40;
+ pointBorderEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-point-border').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h)).append('path').attr('transform', 'translate(11.5, 8)').attr('d', 'M 17,8 C 17,13 11,21 8.5,23.5 C 6,21 0,13 0,8 C 0,4 4,-0.5 8.5,-0.5 C 13,-0.5 17,4 17,8 z');
+ pointBorder = pointBorderEnter.merge(pointBorder);
+ }
+ function renderCategoryBorder(container, category) {
+ var categoryBorder = container.selectAll('.preset-icon-category-border').data(category ? [0] : []);
+ categoryBorder.exit().remove();
+ var categoryBorderEnter = categoryBorder.enter();
+ var d = 60;
+ var svgEnter = categoryBorderEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-category-border').attr('width', d).attr('height', d).attr('viewBox', "0 0 ".concat(d, " ").concat(d));
+ svgEnter.append('path').attr('class', 'area').attr('d', 'M9.5,7.5 L25.5,7.5 L28.5,12.5 L49.5,12.5 C51.709139,12.5 53.5,14.290861 53.5,16.5 L53.5,43.5 C53.5,45.709139 51.709139,47.5 49.5,47.5 L10.5,47.5 C8.290861,47.5 6.5,45.709139 6.5,43.5 L6.5,12.5 L9.5,7.5 Z');
+ categoryBorder = categoryBorderEnter.merge(categoryBorder);
- if (reference) {
- selection.call(reference.body).select('.field-label').call(reference.button);
- }
+ if (category) {
+ categoryBorder.selectAll('path').attr('class', "area ".concat(category.id));
+ }
+ }
- d.impl.tags(_tags);
- });
- container.classed('locked', _locked).classed('modified', isModified()).classed('present', tagsContainFieldKey()); // show a tip and lock icon if the field is locked
+ function renderCircleFill(container, drawVertex) {
+ var vertexFill = container.selectAll('.preset-icon-fill-vertex').data(drawVertex ? [0] : []);
+ vertexFill.exit().remove();
+ var vertexFillEnter = vertexFill.enter();
+ var w = 60;
+ var h = 60;
+ var d = 40;
+ vertexFillEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-fill-vertex').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h)).append('circle').attr('cx', w / 2).attr('cy', h / 2).attr('r', d / 2);
+ vertexFill = vertexFillEnter.merge(vertexFill);
+ }
- var annotation = container.selectAll('.field-label .label-textannotation');
- var icon = annotation.selectAll('.icon').data(_locked ? [0] : []);
- icon.exit().remove();
- icon.enter().append('svg').attr('class', 'icon').append('use').attr('xlink:href', '#fas-lock');
- container.call(_locked ? _lockedTip : _lockedTip.destroy);
- };
+ function renderSquareFill(container, drawArea, tagClasses) {
+ var fill = container.selectAll('.preset-icon-fill-area').data(drawArea ? [0] : []);
+ fill.exit().remove();
+ var fillEnter = fill.enter();
+ var d = isSmall() ? 40 : 60;
+ var w = d;
+ var h = d;
+ var l = d * 2 / 3;
+ var c1 = (w - l) / 2;
+ var c2 = c1 + l;
+ fillEnter = fillEnter.append('svg').attr('class', 'preset-icon-fill preset-icon-fill-area').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
+ ['fill', 'stroke'].forEach(function (klass) {
+ fillEnter.append('path').attr('d', "M".concat(c1, " ").concat(c1, " L").concat(c1, " ").concat(c2, " L").concat(c2, " ").concat(c2, " L").concat(c2, " ").concat(c1, " Z")).attr('class', "area ".concat(klass));
+ });
+ var rVertex = 2.5;
+ [[c1, c1], [c1, c2], [c2, c2], [c2, c1]].forEach(function (point) {
+ fillEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', rVertex);
+ });
- field.state = function (val) {
- if (!arguments.length) return _state;
- _state = val;
- return field;
- };
+ if (!isSmall()) {
+ var rMidpoint = 1.25;
+ [[c1, w / 2], [c2, w / 2], [h / 2, c1], [h / 2, c2]].forEach(function (point) {
+ fillEnter.append('circle').attr('class', 'midpoint').attr('cx', point[0]).attr('cy', point[1]).attr('r', rMidpoint);
+ });
+ }
- field.tags = function (val) {
- if (!arguments.length) return _tags;
- _tags = val;
+ fill = fillEnter.merge(fill);
+ fill.selectAll('path.stroke').attr('class', "area stroke ".concat(tagClasses));
+ fill.selectAll('path.fill').attr('class', "area fill ".concat(tagClasses));
+ }
- if (tagsContainFieldKey() && !_show) {
- // always show a field if it has a value to display
- _show = true;
+ function renderLine(container, drawLine, tagClasses) {
+ var line = container.selectAll('.preset-icon-line').data(drawLine ? [0] : []);
+ line.exit().remove();
+ var lineEnter = line.enter();
+ var d = isSmall() ? 40 : 60; // draw the line parametrically
- if (!field.impl) {
- createField();
- }
- }
+ var w = d;
+ var h = d;
+ var y = Math.round(d * 0.72);
+ var l = Math.round(d * 0.6);
+ var r = 2.5;
+ var x1 = (w - l) / 2;
+ var x2 = x1 + l;
+ lineEnter = lineEnter.append('svg').attr('class', 'preset-icon-line').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
+ ['casing', 'stroke'].forEach(function (klass) {
+ lineEnter.append('path').attr('d', "M".concat(x1, " ").concat(y, " L").concat(x2, " ").concat(y)).attr('class', "line ".concat(klass));
+ });
+ [[x1 - 1, y], [x2 + 1, y]].forEach(function (point) {
+ lineEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', r);
+ });
+ line = lineEnter.merge(line);
+ line.selectAll('path.stroke').attr('class', "line stroke ".concat(tagClasses));
+ line.selectAll('path.casing').attr('class', "line casing ".concat(tagClasses));
+ }
- return field;
- };
+ function renderRoute(container, drawRoute, p) {
+ var route = container.selectAll('.preset-icon-route').data(drawRoute ? [0] : []);
+ route.exit().remove();
+ var routeEnter = route.enter();
+ var d = isSmall() ? 40 : 60; // draw the route parametrically
- field.locked = function (val) {
- if (!arguments.length) return _locked;
- _locked = val;
- return field;
- };
+ var w = d;
+ var h = d;
+ var y1 = Math.round(d * 0.80);
+ var y2 = Math.round(d * 0.68);
+ var l = Math.round(d * 0.6);
+ var r = 2;
+ var x1 = (w - l) / 2;
+ var x2 = x1 + l / 3;
+ var x3 = x2 + l / 3;
+ var x4 = x3 + l / 3;
+ routeEnter = routeEnter.append('svg').attr('class', 'preset-icon-route').attr('width', w).attr('height', h).attr('viewBox', "0 0 ".concat(w, " ").concat(h));
+ ['casing', 'stroke'].forEach(function (klass) {
+ routeEnter.append('path').attr('d', "M".concat(x1, " ").concat(y1, " L").concat(x2, " ").concat(y2)).attr('class', "segment0 line ".concat(klass));
+ routeEnter.append('path').attr('d', "M".concat(x2, " ").concat(y2, " L").concat(x3, " ").concat(y1)).attr('class', "segment1 line ".concat(klass));
+ routeEnter.append('path').attr('d', "M".concat(x3, " ").concat(y1, " L").concat(x4, " ").concat(y2)).attr('class', "segment2 line ".concat(klass));
+ });
+ [[x1, y1], [x2, y2], [x3, y1], [x4, y2]].forEach(function (point) {
+ routeEnter.append('circle').attr('class', 'vertex').attr('cx', point[0]).attr('cy', point[1]).attr('r', r);
+ });
+ route = routeEnter.merge(route);
- field.show = function () {
- _show = true;
+ if (drawRoute) {
+ var routeType = p.tags.type === 'waterway' ? 'waterway' : p.tags.route;
+ var segmentPresetIDs = routeSegments[routeType];
- if (!field.impl) {
- createField();
+ for (var i in segmentPresetIDs) {
+ var segmentPreset = _mainPresetIndex.item(segmentPresetIDs[i]);
+ var segmentTagClasses = svgTagClasses().getClassesString(segmentPreset.tags, '');
+ route.selectAll("path.stroke.segment".concat(i)).attr('class', "segment".concat(i, " line stroke ").concat(segmentTagClasses));
+ route.selectAll("path.casing.segment".concat(i)).attr('class', "segment".concat(i, " line casing ").concat(segmentTagClasses));
+ }
}
+ }
- if (field["default"] && field.key && _tags[field.key] !== field["default"]) {
- var t = {};
- t[field.key] = field["default"];
- dispatch.call('change', this, t);
+ function renderSvgIcon(container, picon, geom, isFramed, category, tagClasses) {
+ var isMaki = picon && /^maki-/.test(picon);
+ var isTemaki = picon && /^temaki-/.test(picon);
+ var isFa = picon && /^fa[srb]-/.test(picon);
+ var isiDIcon = picon && !(isMaki || isTemaki || isFa);
+ var icon = container.selectAll('.preset-icon').data(picon ? [0] : []);
+ icon.exit().remove();
+ icon = icon.enter().append('div').attr('class', 'preset-icon').call(svgIcon('')).merge(icon);
+ icon.attr('class', 'preset-icon ' + (geom ? geom + '-geom' : '')).classed('category', category).classed('framed', isFramed).classed('preset-icon-iD', isiDIcon);
+ icon.selectAll('svg').attr('class', 'icon ' + picon + ' ' + (!isiDIcon && geom !== 'line' ? '' : tagClasses));
+ var suffix = '';
+
+ if (isMaki) {
+ suffix = isSmall() && geom === 'point' ? '-11' : '-15';
}
- }; // A shown field has a visible UI, a non-shown field is in the 'Add field' dropdown
+ icon.selectAll('use').attr('href', '#' + picon + suffix);
+ }
- field.isShown = function () {
- return _show;
- }; // An allowed field can appear in the UI or in the 'Add field' dropdown.
- // A non-allowed field is hidden from the user altogether
+ function renderImageIcon(container, imageURL) {
+ var imageIcon = container.selectAll('img.image-icon').data(imageURL ? [0] : []);
+ imageIcon.exit().remove();
+ imageIcon = imageIcon.enter().append('img').attr('class', 'image-icon').on('load', function () {
+ return container.classed('showing-img', true);
+ }).on('error', function () {
+ return container.classed('showing-img', false);
+ }).merge(imageIcon);
+ imageIcon.attr('src', imageURL);
+ } // Route icons are drawn with a zigzag annotation underneath:
+ // o o
+ // / \ /
+ // o o
+ // This dataset defines the styles that are used to draw the zigzag segments.
- field.isAllowed = function () {
- if (entityIDs && entityIDs.length > 1 && uiFields[field.type].supportsMultiselection === false) return false;
- if (field.geometry && !entityIDs.every(function (entityID) {
- return field.matchGeometry(context.graph().geometry(entityID));
- })) return false;
+ var routeSegments = {
+ bicycle: ['highway/cycleway', 'highway/cycleway', 'highway/cycleway'],
+ bus: ['highway/unclassified', 'highway/secondary', 'highway/primary'],
+ trolleybus: ['highway/unclassified', 'highway/secondary', 'highway/primary'],
+ detour: ['highway/tertiary', 'highway/residential', 'highway/unclassified'],
+ ferry: ['route/ferry', 'route/ferry', 'route/ferry'],
+ foot: ['highway/footway', 'highway/footway', 'highway/footway'],
+ hiking: ['highway/path', 'highway/path', 'highway/path'],
+ horse: ['highway/bridleway', 'highway/bridleway', 'highway/bridleway'],
+ light_rail: ['railway/light_rail', 'railway/light_rail', 'railway/light_rail'],
+ monorail: ['railway/monorail', 'railway/monorail', 'railway/monorail'],
+ mtb: ['highway/path', 'highway/track', 'highway/bridleway'],
+ pipeline: ['man_made/pipeline', 'man_made/pipeline', 'man_made/pipeline'],
+ piste: ['piste/downhill', 'piste/hike', 'piste/nordic'],
+ power: ['power/line', 'power/line', 'power/line'],
+ road: ['highway/secondary', 'highway/primary', 'highway/trunk'],
+ subway: ['railway/subway', 'railway/subway', 'railway/subway'],
+ train: ['railway/rail', 'railway/rail', 'railway/rail'],
+ tram: ['railway/tram', 'railway/tram', 'railway/tram'],
+ waterway: ['waterway/stream', 'waterway/stream', 'waterway/stream']
+ };
- if (entityIDs && _entityExtent && field.locationSetID) {
- // is field allowed in this location?
- var validLocations = _mainLocations.locationsAt(_entityExtent.center());
- if (!validLocations[field.locationSetID]) return false;
- }
+ function render() {
+ var p = _preset.apply(this, arguments);
- var prerequisiteTag = field.prerequisiteTag;
+ var geom = _geometry ? _geometry.apply(this, arguments) : null;
- if (entityIDs && !tagsContainFieldKey() && // ignore tagging prerequisites if a value is already present
- prerequisiteTag) {
- if (!entityIDs.every(function (entityID) {
- var entity = context.graph().entity(entityID);
+ if (geom === 'relation' && p.tags && (p.tags.type === 'route' && p.tags.route && routeSegments[p.tags.route] || p.tags.type === 'waterway')) {
+ geom = 'route';
+ }
- if (prerequisiteTag.key) {
- var value = entity.tags[prerequisiteTag.key];
- if (!value) return false;
+ var showThirdPartyIcons = corePreferences('preferences.privacy.thirdpartyicons') || 'true';
+ var isFallback = isSmall() && p.isFallback && p.isFallback();
+ var imageURL = showThirdPartyIcons === 'true' && p.imageURL;
+ var picon = getIcon(p, geom);
+ var isCategory = !p.setTags;
+ var drawPoint = picon && geom === 'point' && isSmall() && !isFallback;
+ var drawVertex = picon !== null && geom === 'vertex' && (!isSmall() || !isFallback);
+ var drawLine = picon && geom === 'line' && !isFallback && !isCategory;
+ var drawArea = picon && geom === 'area' && !isFallback && !isCategory;
+ var drawRoute = picon && geom === 'route';
+ var isFramed = drawVertex || drawArea || drawLine || drawRoute || isCategory;
+ var tags = !isCategory ? p.setTags({}, geom) : {};
- if (prerequisiteTag.valueNot) {
- return prerequisiteTag.valueNot !== value;
- }
+ for (var k in tags) {
+ if (tags[k] === '*') {
+ tags[k] = 'yes';
+ }
+ }
- if (prerequisiteTag.value) {
- return prerequisiteTag.value === value;
- }
- } else if (prerequisiteTag.keyNot) {
- if (entity.tags[prerequisiteTag.keyNot]) return false;
- }
+ var tagClasses = svgTagClasses().getClassesString(tags, '');
+ var selection = select(this);
+ var container = selection.selectAll('.preset-icon-container').data([0]);
+ container = container.enter().append('div').attr('class', "preset-icon-container ".concat(_sizeClass)).merge(container);
+ container.classed('showing-img', !!imageURL).classed('fallback', isFallback);
+ renderCategoryBorder(container, isCategory && p);
+ renderPointBorder(container, drawPoint);
+ renderCircleFill(container, drawVertex);
+ renderSquareFill(container, drawArea, tagClasses);
+ renderLine(container, drawLine, tagClasses);
+ renderRoute(container, drawRoute, p);
+ renderSvgIcon(container, picon, geom, isFramed, isCategory, tagClasses);
+ renderImageIcon(container, imageURL);
+ }
- return true;
- })) return false;
- }
+ presetIcon.preset = function (val) {
+ if (!arguments.length) return _preset;
+ _preset = utilFunctor(val);
+ return presetIcon;
+ };
- return true;
+ presetIcon.geometry = function (val) {
+ if (!arguments.length) return _geometry;
+ _geometry = utilFunctor(val);
+ return presetIcon;
};
- field.focus = function () {
- if (field.impl) {
- field.impl.focus();
- }
+ presetIcon.sizeClass = function (val) {
+ if (!arguments.length) return _sizeClass;
+ _sizeClass = val;
+ return presetIcon;
};
- return utilRebind(field, dispatch, 'on');
+ return presetIcon;
}
- function uiFormFields(context) {
- var moreCombo = uiCombobox(context, 'more-fields').minItems(1);
- var _fieldsArr = [];
- var _lastPlaceholder = '';
- var _state = '';
- var _klass = '';
+ function uiSectionFeatureType(context) {
+ var dispatch = dispatch$8('choose');
+ var _entityIDs = [];
+ var _presets = [];
+
+ var _tagReference;
- function formFields(selection) {
- var allowedFields = _fieldsArr.filter(function (field) {
- return field.isAllowed();
- });
+ var section = uiSection('feature-type', context).label(_t.html('inspector.feature_type')).disclosureContent(renderDisclosureContent);
- var shown = allowedFields.filter(function (field) {
- return field.isShown();
- });
- var notShown = allowedFields.filter(function (field) {
- return !field.isShown();
- });
- var container = selection.selectAll('.form-fields-container').data([0]);
- container = container.enter().append('div').attr('class', 'form-fields-container ' + (_klass || '')).merge(container);
- var fields = container.selectAll('.wrap-form-field').data(shown, function (d) {
- return d.id + (d.entityIDs ? d.entityIDs.join() : '');
- });
- fields.exit().remove(); // Enter
+ function renderDisclosureContent(selection) {
+ selection.classed('preset-list-item', true);
+ selection.classed('mixed-types', _presets.length > 1);
+ var presetButtonWrap = selection.selectAll('.preset-list-button-wrap').data([0]).enter().append('div').attr('class', 'preset-list-button-wrap');
+ var presetButton = presetButtonWrap.append('button').attr('class', 'preset-list-button preset-reset').call(uiTooltip().title(_t.html('inspector.back_tooltip')).placement('bottom'));
+ presetButton.append('div').attr('class', 'preset-icon-container');
+ presetButton.append('div').attr('class', 'label').append('div').attr('class', 'label-inner');
+ presetButtonWrap.append('div').attr('class', 'accessory-buttons');
+ var tagReferenceBodyWrap = selection.selectAll('.tag-reference-body-wrap').data([0]);
+ tagReferenceBodyWrap = tagReferenceBodyWrap.enter().append('div').attr('class', 'tag-reference-body-wrap').merge(tagReferenceBodyWrap); // update header
- var enter = fields.enter().append('div').attr('class', function (d) {
- return 'wrap-form-field wrap-form-field-' + d.safeid;
- }); // Update
+ if (_tagReference) {
+ selection.selectAll('.preset-list-button-wrap .accessory-buttons').style('display', _presets.length === 1 ? null : 'none').call(_tagReference.button);
+ tagReferenceBodyWrap.style('display', _presets.length === 1 ? null : 'none').call(_tagReference.body);
+ }
- fields = fields.merge(enter);
- fields.order().each(function (d) {
- select(this).call(d.render);
+ selection.selectAll('.preset-reset').on('click', function () {
+ dispatch.call('choose', this, _presets);
+ }).on('pointerdown pointerup mousedown mouseup', function (d3_event) {
+ d3_event.preventDefault();
+ d3_event.stopPropagation();
});
- var titles = [];
- var moreFields = notShown.map(function (field) {
- var title = field.title();
- titles.push(title);
- var terms = field.terms();
- if (field.key) terms.push(field.key);
- if (field.keys) terms = terms.concat(field.keys);
- return {
- display: field.label(),
- value: title,
- title: title,
- field: field,
- terms: terms
- };
+ var geometries = entityGeometries();
+ selection.select('.preset-list-item button').call(uiPresetIcon().geometry(_presets.length === 1 ? geometries.length === 1 && geometries[0] : null).preset(_presets.length === 1 ? _presets[0] : _mainPresetIndex.item('point')));
+ var names = _presets.length === 1 ? [_presets[0].nameLabel(), _presets[0].subtitleLabel()].filter(Boolean) : [_t('inspector.multiple_types')];
+ var label = selection.select('.label-inner');
+ var nameparts = label.selectAll('.namepart').data(names, function (d) {
+ return d;
});
- var placeholder = titles.slice(0, 3).join(', ') + (titles.length > 3 ? '…' : '');
- var more = selection.selectAll('.more-fields').data(_state === 'hover' || moreFields.length === 0 ? [] : [0]);
- more.exit().remove();
- var moreEnter = more.enter().append('div').attr('class', 'more-fields').append('label');
- moreEnter.append('span').html(_t.html('inspector.add_fields'));
- more = moreEnter.merge(more);
- var input = more.selectAll('.value').data([0]);
- input.exit().remove();
- input = input.enter().append('input').attr('class', 'value').attr('type', 'text').attr('placeholder', placeholder).call(utilNoAuto).merge(input);
- input.call(utilGetSetValue, '').call(moreCombo.data(moreFields).on('accept', function (d) {
- if (!d) return; // user entered something that was not matched
+ nameparts.exit().remove();
+ nameparts.enter().append('div').attr('class', 'namepart').html(function (d) {
+ return d;
+ });
+ }
- var field = d.field;
- field.show();
- selection.call(formFields); // rerender
+ section.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return section;
+ };
- field.focus();
- })); // avoid updating placeholder excessively (triggers style recalc)
+ section.presets = function (val) {
+ if (!arguments.length) return _presets; // don't reload the same preset
- if (_lastPlaceholder !== placeholder) {
- input.attr('placeholder', placeholder);
- _lastPlaceholder = placeholder;
+ if (!utilArrayIdentical(val, _presets)) {
+ _presets = val;
+
+ if (_presets.length === 1) {
+ _tagReference = uiTagReference(_presets[0].reference()).showing(false);
+ }
}
- }
- formFields.fieldsArr = function (val) {
- if (!arguments.length) return _fieldsArr;
- _fieldsArr = val || [];
- return formFields;
+ return section;
};
- formFields.state = function (val) {
- if (!arguments.length) return _state;
- _state = val;
- return formFields;
- };
+ function entityGeometries() {
+ var counts = {};
- formFields.klass = function (val) {
- if (!arguments.length) return _klass;
- _klass = val;
- return formFields;
- };
+ for (var i in _entityIDs) {
+ var geometry = context.graph().geometry(_entityIDs[i]);
+ if (!counts[geometry]) counts[geometry] = 0;
+ counts[geometry] += 1;
+ }
- return formFields;
+ return Object.keys(counts).sort(function (geom1, geom2) {
+ return counts[geom2] - counts[geom1];
+ });
+ }
+
+ return utilRebind(section, dispatch, 'on');
}
function uiSectionPresetFields(context) {
if (!entity) return '';
var gt = entity.members.length > _maxMembers ? '>' : '';
var count = gt + entity.members.slice(0, _maxMembers).length;
- return _t('inspector.title_count', {
- title: _t.html('inspector.members'),
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('inspector.members')
+ },
count: count
});
}).disclosureContent(renderDisclosureContent);
var entity = context.entity(d.id);
context.map().zoomToEase(entity); // highlight the feature in case it wasn't previously on-screen
- utilHighlightEntities([d.id], true, context);
+ utilHighlightEntities([d.id], true, context);
+ }
+
+ function selectMember(d3_event, d) {
+ d3_event.preventDefault(); // remove the hover-highlight styling
+
+ utilHighlightEntities([d.id], false, context);
+ var entity = context.entity(d.id);
+ var mapExtent = context.map().extent();
+
+ if (!entity.intersects(mapExtent, context.graph())) {
+ // zoom to the entity if its extent is not visible now
+ context.map().zoomToEase(entity);
+ }
+
+ context.enter(modeSelect(context, [d.id]));
+ }
+
+ function changeRole(d3_event, d) {
+ var oldRole = d.role;
+ var newRole = context.cleanRelationRole(select(this).property('value'));
+
+ if (oldRole !== newRole) {
+ var member = {
+ id: d.id,
+ type: d.type,
+ role: newRole
+ };
+ context.perform(actionChangeMember(d.relation.id, member, d.index), _t('operations.change_role.annotation', {
+ n: 1
+ }));
+ context.validator().validate();
+ }
+ }
+
+ function deleteMember(d3_event, d) {
+ // remove the hover-highlight styling
+ utilHighlightEntities([d.id], false, context);
+ context.perform(actionDeleteMember(d.relation.id, d.index), _t('operations.delete_member.annotation', {
+ n: 1
+ }));
+
+ if (!context.hasEntity(d.relation.id)) {
+ // Removing the last member will also delete the relation.
+ // If this happens we need to exit the selection mode
+ context.enter(modeBrowse(context));
+ } else {
+ // Changing the mode also runs `validate`, but otherwise we need to
+ // rerun it manually
+ context.validator().validate();
+ }
+ }
+
+ function renderDisclosureContent(selection) {
+ var entityID = _entityIDs[0];
+ var memberships = [];
+ var entity = context.entity(entityID);
+ entity.members.slice(0, _maxMembers).forEach(function (member, index) {
+ memberships.push({
+ index: index,
+ id: member.id,
+ type: member.type,
+ role: member.role,
+ relation: entity,
+ member: context.hasEntity(member.id),
+ domId: utilUniqueDomId(entityID + '-member-' + index)
+ });
+ });
+ var list = selection.selectAll('.member-list').data([0]);
+ list = list.enter().append('ul').attr('class', 'member-list').merge(list);
+ var items = list.selectAll('li').data(memberships, function (d) {
+ return osmEntity.key(d.relation) + ',' + d.index + ',' + (d.member ? osmEntity.key(d.member) : 'incomplete');
+ });
+ items.exit().each(unbind).remove();
+ var itemsEnter = items.enter().append('li').attr('class', 'member-row form-field').classed('member-incomplete', function (d) {
+ return !d.member;
+ });
+ itemsEnter.each(function (d) {
+ var item = select(this);
+ var label = item.append('label').attr('class', 'field-label').attr('for', d.domId);
+
+ if (d.member) {
+ // highlight the member feature in the map while hovering on the list item
+ item.on('mouseover', function () {
+ utilHighlightEntities([d.id], true, context);
+ }).on('mouseout', function () {
+ utilHighlightEntities([d.id], false, context);
+ });
+ var labelLink = label.append('span').attr('class', 'label-text').append('a').attr('href', '#').on('click', selectMember);
+ labelLink.append('span').attr('class', 'member-entity-type').text(function (d) {
+ var matched = _mainPresetIndex.match(d.member, context.graph());
+ return matched && matched.name() || utilDisplayType(d.member.id);
+ });
+ labelLink.append('span').attr('class', 'member-entity-name').text(function (d) {
+ return utilDisplayName(d.member);
+ });
+ label.append('button').attr('title', _t('icons.remove')).attr('class', 'remove member-delete').call(svgIcon('#iD-operation-delete'));
+ label.append('button').attr('class', 'member-zoom').attr('title', _t('icons.zoom_to')).call(svgIcon('#iD-icon-framed-dot', 'monochrome')).on('click', zoomToMember);
+ } else {
+ var labelText = label.append('span').attr('class', 'label-text');
+ labelText.append('span').attr('class', 'member-entity-type').call(_t.append('inspector.' + d.type, {
+ id: d.id
+ }));
+ labelText.append('span').attr('class', 'member-entity-name').call(_t.append('inspector.incomplete', {
+ id: d.id
+ }));
+ label.append('button').attr('class', 'member-download').attr('title', _t('icons.download')).call(svgIcon('#iD-icon-load')).on('click', downloadMember);
+ }
+ });
+ var wrapEnter = itemsEnter.append('div').attr('class', 'form-field-input-wrap form-field-input-member');
+ wrapEnter.append('input').attr('class', 'member-role').attr('id', function (d) {
+ return d.domId;
+ }).property('type', 'text').attr('placeholder', _t('inspector.role')).call(utilNoAuto);
+
+ if (taginfo) {
+ wrapEnter.each(bindTypeahead);
+ } // update
+
+
+ items = items.merge(itemsEnter).order();
+ items.select('input.member-role').property('value', function (d) {
+ return d.role;
+ }).on('blur', changeRole).on('change', changeRole);
+ items.select('button.member-delete').on('click', deleteMember);
+ var dragOrigin, targetIndex;
+ items.call(d3_drag().on('start', function (d3_event) {
+ dragOrigin = {
+ x: d3_event.x,
+ y: d3_event.y
+ };
+ targetIndex = null;
+ }).on('drag', function (d3_event) {
+ var x = d3_event.x - dragOrigin.x,
+ y = d3_event.y - dragOrigin.y;
+ if (!select(this).classed('dragging') && // don't display drag until dragging beyond a distance threshold
+ Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;
+ var index = items.nodes().indexOf(this);
+ select(this).classed('dragging', true);
+ targetIndex = null;
+ selection.selectAll('li.member-row').style('transform', function (d2, index2) {
+ var node = select(this).node();
+
+ if (index === index2) {
+ return 'translate(' + x + 'px, ' + y + 'px)';
+ } else if (index2 > index && d3_event.y > node.offsetTop) {
+ if (targetIndex === null || index2 > targetIndex) {
+ targetIndex = index2;
+ }
+
+ return 'translateY(-100%)';
+ } else if (index2 < index && d3_event.y < node.offsetTop + node.offsetHeight) {
+ if (targetIndex === null || index2 < targetIndex) {
+ targetIndex = index2;
+ }
+
+ return 'translateY(100%)';
+ }
+
+ return null;
+ });
+ }).on('end', function (d3_event, d) {
+ if (!select(this).classed('dragging')) return;
+ var index = items.nodes().indexOf(this);
+ select(this).classed('dragging', false);
+ selection.selectAll('li.member-row').style('transform', null);
+
+ if (targetIndex !== null) {
+ // dragged to a new position, reorder
+ context.perform(actionMoveMember(d.relation.id, index, targetIndex), _t('operations.reorder_members.annotation'));
+ context.validator().validate();
+ }
+ }));
+
+ function bindTypeahead(d) {
+ var row = select(this);
+ var role = row.selectAll('input.member-role');
+ var origValue = role.property('value');
+
+ function sort(value, data) {
+ var sameletter = [];
+ var other = [];
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].value.substring(0, value.length) === value) {
+ sameletter.push(data[i]);
+ } else {
+ other.push(data[i]);
+ }
+ }
+
+ return sameletter.concat(other);
+ }
+
+ role.call(uiCombobox(context, 'member-role').fetcher(function (role, callback) {
+ // The `geometry` param is used in the `taginfo.js` interface for
+ // filtering results, as a key into the `tag_members_fractions`
+ // object. If we don't know the geometry because the member is
+ // not yet downloaded, it's ok to guess based on type.
+ var geometry;
+
+ if (d.member) {
+ geometry = context.graph().geometry(d.member.id);
+ } else if (d.type === 'relation') {
+ geometry = 'relation';
+ } else if (d.type === 'way') {
+ geometry = 'line';
+ } else {
+ geometry = 'point';
+ }
+
+ var rtype = entity.tags.type;
+ taginfo.roles({
+ debounce: true,
+ rtype: rtype || '',
+ geometry: geometry,
+ query: role
+ }, function (err, data) {
+ if (!err) callback(sort(role, data));
+ });
+ }).on('cancel', function () {
+ role.property('value', origValue);
+ }));
+ }
+
+ function unbind() {
+ var row = select(this);
+ row.selectAll('input.member-role').call(uiCombobox.off, context);
+ }
+ }
+
+ section.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return section;
+ };
+
+ return section;
+ }
+
+ function actionDeleteMembers(relationId, memberIndexes) {
+ return function (graph) {
+ // Remove the members in descending order so removals won't shift what members
+ // are at the remaining indexes
+ memberIndexes.sort(function (a, b) {
+ return b - a;
+ });
+
+ for (var i in memberIndexes) {
+ graph = actionDeleteMember(relationId, memberIndexes[i])(graph);
+ }
+
+ return graph;
+ };
+ }
+
+ function uiSectionRawMembershipEditor(context) {
+ var section = uiSection('raw-membership-editor', context).shouldDisplay(function () {
+ return _entityIDs && _entityIDs.length;
+ }).label(function () {
+ var parents = getSharedParentRelations();
+ var gt = parents.length > _maxMemberships ? '>' : '';
+ var count = gt + parents.slice(0, _maxMemberships).length;
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('inspector.relations')
+ },
+ count: count
+ });
+ }).disclosureContent(renderDisclosureContent);
+ var taginfo = services.taginfo;
+ var nearbyCombo = uiCombobox(context, 'parent-relation').minItems(1).fetcher(fetchNearbyRelations).itemsMouseEnter(function (d3_event, d) {
+ if (d.relation) utilHighlightEntities([d.relation.id], true, context);
+ }).itemsMouseLeave(function (d3_event, d) {
+ if (d.relation) utilHighlightEntities([d.relation.id], false, context);
+ });
+ var _inChange = false;
+ var _entityIDs = [];
+
+ var _showBlank;
+
+ var _maxMemberships = 1000;
+
+ function getSharedParentRelations() {
+ var parents = [];
+
+ for (var i = 0; i < _entityIDs.length; i++) {
+ var entity = context.graph().hasEntity(_entityIDs[i]);
+ if (!entity) continue;
+
+ if (i === 0) {
+ parents = context.graph().parentRelations(entity);
+ } else {
+ parents = utilArrayIntersection(parents, context.graph().parentRelations(entity));
+ }
+
+ if (!parents.length) break;
+ }
+
+ return parents;
+ }
+
+ function getMemberships() {
+ var memberships = [];
+ var relations = getSharedParentRelations().slice(0, _maxMemberships);
+ var isMultiselect = _entityIDs.length > 1;
+ var i, relation, membership, index, member, indexedMember;
+
+ for (i = 0; i < relations.length; i++) {
+ relation = relations[i];
+ membership = {
+ relation: relation,
+ members: [],
+ hash: osmEntity.key(relation)
+ };
+
+ for (index = 0; index < relation.members.length; index++) {
+ member = relation.members[index];
+
+ if (_entityIDs.indexOf(member.id) !== -1) {
+ indexedMember = Object.assign({}, member, {
+ index: index
+ });
+ membership.members.push(indexedMember);
+ membership.hash += ',' + index.toString();
+
+ if (!isMultiselect) {
+ // For single selections, list one entry per membership per relation.
+ // For multiselections, list one entry per relation.
+ memberships.push(membership);
+ membership = {
+ relation: relation,
+ members: [],
+ hash: osmEntity.key(relation)
+ };
+ }
+ }
+ }
+
+ if (membership.members.length) memberships.push(membership);
+ }
+
+ memberships.forEach(function (membership) {
+ membership.domId = utilUniqueDomId('membership-' + membership.relation.id);
+ var roles = [];
+ membership.members.forEach(function (member) {
+ if (roles.indexOf(member.role) === -1) roles.push(member.role);
+ });
+ membership.role = roles.length === 1 ? roles[0] : roles;
+ });
+ return memberships;
+ }
+
+ function selectRelation(d3_event, d) {
+ d3_event.preventDefault(); // remove the hover-highlight styling
+
+ utilHighlightEntities([d.relation.id], false, context);
+ context.enter(modeSelect(context, [d.relation.id]));
+ }
+
+ function zoomToRelation(d3_event, d) {
+ d3_event.preventDefault();
+ var entity = context.entity(d.relation.id);
+ context.map().zoomToEase(entity); // highlight the relation in case it wasn't previously on-screen
+
+ utilHighlightEntities([d.relation.id], true, context);
+ }
+
+ function changeRole(d3_event, d) {
+ if (d === 0) return; // called on newrow (shouldn't happen)
+
+ if (_inChange) return; // avoid accidental recursive call #5731
+
+ var newRole = context.cleanRelationRole(select(this).property('value'));
+ if (!newRole.trim() && typeof d.role !== 'string') return;
+ var membersToUpdate = d.members.filter(function (member) {
+ return member.role !== newRole;
+ });
+
+ if (membersToUpdate.length) {
+ _inChange = true;
+ context.perform(function actionChangeMemberRoles(graph) {
+ membersToUpdate.forEach(function (member) {
+ var newMember = Object.assign({}, member, {
+ role: newRole
+ });
+ delete newMember.index;
+ graph = actionChangeMember(d.relation.id, newMember, member.index)(graph);
+ });
+ return graph;
+ }, _t('operations.change_role.annotation', {
+ n: membersToUpdate.length
+ }));
+ context.validator().validate();
+ }
+
+ _inChange = false;
}
- function selectMember(d3_event, d) {
- d3_event.preventDefault(); // remove the hover-highlight styling
-
- utilHighlightEntities([d.id], false, context);
- var entity = context.entity(d.id);
- var mapExtent = context.map().extent();
-
- if (!entity.intersects(mapExtent, context.graph())) {
- // zoom to the entity if its extent is not visible now
- context.map().zoomToEase(entity);
- }
+ function addMembership(d, role) {
+ this.blur(); // avoid keeping focus on the button
- context.enter(modeSelect(context, [d.id]));
- }
+ _showBlank = false;
- function changeRole(d3_event, d) {
- var oldRole = d.role;
- var newRole = context.cleanRelationRole(select(this).property('value'));
+ function actionAddMembers(relationId, ids, role) {
+ return function (graph) {
+ for (var i in ids) {
+ var member = {
+ id: ids[i],
+ type: graph.entity(ids[i]).type,
+ role: role
+ };
+ graph = actionAddMember(relationId, member)(graph);
+ }
- if (oldRole !== newRole) {
- var member = {
- id: d.id,
- type: d.type,
- role: newRole
+ return graph;
};
- context.perform(actionChangeMember(d.relation.id, member, d.index), _t('operations.change_role.annotation', {
- n: 1
+ }
+
+ if (d.relation) {
+ context.perform(actionAddMembers(d.relation.id, _entityIDs, role), _t('operations.add_member.annotation', {
+ n: _entityIDs.length
}));
context.validator().validate();
+ } else {
+ var relation = osmRelation();
+ context.perform(actionAddEntity(relation), actionAddMembers(relation.id, _entityIDs, role), _t('operations.add.annotation.relation')); // changing the mode also runs `validate`
+
+ context.enter(modeSelect(context, [relation.id]).newFeature(true));
}
}
- function deleteMember(d3_event, d) {
+ function deleteMembership(d3_event, d) {
+ this.blur(); // avoid keeping focus on the button
+
+ if (d === 0) return; // called on newrow (shouldn't happen)
// remove the hover-highlight styling
- utilHighlightEntities([d.id], false, context);
- context.perform(actionDeleteMember(d.relation.id, d.index), _t('operations.delete_member.annotation', {
- n: 1
+
+ utilHighlightEntities([d.relation.id], false, context);
+ var indexes = d.members.map(function (member) {
+ return member.index;
+ });
+ context.perform(actionDeleteMembers(d.relation.id, indexes), _t('operations.delete_member.annotation', {
+ n: _entityIDs.length
}));
+ context.validator().validate();
+ }
- if (!context.hasEntity(d.relation.id)) {
- // Removing the last member will also delete the relation.
- // If this happens we need to exit the selection mode
- context.enter(modeBrowse(context));
+ function fetchNearbyRelations(q, callback) {
+ var newRelation = {
+ relation: null,
+ value: _t('inspector.new_relation'),
+ display: _t.html('inspector.new_relation')
+ };
+ var entityID = _entityIDs[0];
+ var result = [];
+ var graph = context.graph();
+
+ function baseDisplayLabel(entity) {
+ var matched = _mainPresetIndex.match(entity, graph);
+ var presetName = matched && matched.name() || _t('inspector.relation');
+ var entityName = utilDisplayName(entity) || '';
+ return presetName + ' ' + entityName;
+ }
+
+ var explicitRelation = q && context.hasEntity(q.toLowerCase());
+
+ if (explicitRelation && explicitRelation.type === 'relation' && explicitRelation.id !== entityID) {
+ // loaded relation is specified explicitly, only show that
+ result.push({
+ relation: explicitRelation,
+ value: baseDisplayLabel(explicitRelation) + ' ' + explicitRelation.id
+ });
} else {
- // Changing the mode also runs `validate`, but otherwise we need to
- // rerun it manually
- context.validator().validate();
+ context.history().intersects(context.map().extent()).forEach(function (entity) {
+ if (entity.type !== 'relation' || entity.id === entityID) return;
+ var value = baseDisplayLabel(entity);
+ if (q && (value + ' ' + entity.id).toLowerCase().indexOf(q.toLowerCase()) === -1) return;
+ result.push({
+ relation: entity,
+ value: value
+ });
+ });
+ result.sort(function (a, b) {
+ return osmRelation.creationOrder(a.relation, b.relation);
+ }); // Dedupe identical names by appending relation id - see #2891
+
+ var dupeGroups = Object.values(utilArrayGroupBy(result, 'value')).filter(function (v) {
+ return v.length > 1;
+ });
+ dupeGroups.forEach(function (group) {
+ group.forEach(function (obj) {
+ obj.value += ' ' + obj.relation.id;
+ });
+ });
}
+
+ result.forEach(function (obj) {
+ obj.title = obj.value;
+ });
+ result.unshift(newRelation);
+ callback(result);
}
function renderDisclosureContent(selection) {
- var entityID = _entityIDs[0];
- var memberships = [];
- var entity = context.entity(entityID);
- entity.members.slice(0, _maxMembers).forEach(function (member, index) {
- memberships.push({
- index: index,
- id: member.id,
- type: member.type,
- role: member.role,
- relation: entity,
- member: context.hasEntity(member.id),
- domId: utilUniqueDomId(entityID + '-member-' + index)
- });
- });
+ var memberships = getMemberships();
var list = selection.selectAll('.member-list').data([0]);
list = list.enter().append('ul').attr('class', 'member-list').merge(list);
- var items = list.selectAll('li').data(memberships, function (d) {
- return osmEntity.key(d.relation) + ',' + d.index + ',' + (d.member ? osmEntity.key(d.member) : 'incomplete');
- });
- items.exit().each(unbind).remove();
- var itemsEnter = items.enter().append('li').attr('class', 'member-row form-field').classed('member-incomplete', function (d) {
- return !d.member;
+ var items = list.selectAll('li.member-row-normal').data(memberships, function (d) {
+ return d.hash;
});
- itemsEnter.each(function (d) {
- var item = select(this);
- var label = item.append('label').attr('class', 'field-label').attr('for', d.domId);
+ items.exit().each(unbind).remove(); // Enter
- if (d.member) {
- // highlight the member feature in the map while hovering on the list item
- item.on('mouseover', function () {
- utilHighlightEntities([d.id], true, context);
- }).on('mouseout', function () {
- utilHighlightEntities([d.id], false, context);
- });
- var labelLink = label.append('span').attr('class', 'label-text').append('a').attr('href', '#').on('click', selectMember);
- labelLink.append('span').attr('class', 'member-entity-type').html(function (d) {
- var matched = _mainPresetIndex.match(d.member, context.graph());
- return matched && matched.name() || utilDisplayType(d.member.id);
- });
- labelLink.append('span').attr('class', 'member-entity-name').html(function (d) {
- return utilDisplayName(d.member);
- });
- label.append('button').attr('title', _t('icons.remove')).attr('class', 'remove member-delete').call(svgIcon('#iD-operation-delete'));
- label.append('button').attr('class', 'member-zoom').attr('title', _t('icons.zoom_to')).call(svgIcon('#iD-icon-framed-dot', 'monochrome')).on('click', zoomToMember);
- } else {
- var labelText = label.append('span').attr('class', 'label-text');
- labelText.append('span').attr('class', 'member-entity-type').html(_t.html('inspector.' + d.type, {
- id: d.id
- }));
- labelText.append('span').attr('class', 'member-entity-name').html(_t.html('inspector.incomplete', {
- id: d.id
- }));
- label.append('button').attr('class', 'member-download').attr('title', _t('icons.download')).call(svgIcon('#iD-icon-load')).on('click', downloadMember);
- }
+ var itemsEnter = items.enter().append('li').attr('class', 'member-row member-row-normal form-field'); // highlight the relation in the map while hovering on the list item
+
+ itemsEnter.on('mouseover', function (d3_event, d) {
+ utilHighlightEntities([d.relation.id], true, context);
+ }).on('mouseout', function (d3_event, d) {
+ utilHighlightEntities([d.relation.id], false, context);
+ });
+ var labelEnter = itemsEnter.append('label').attr('class', 'field-label').attr('for', function (d) {
+ return d.domId;
+ });
+ var labelLink = labelEnter.append('span').attr('class', 'label-text').append('a').attr('href', '#').on('click', selectRelation);
+ labelLink.append('span').attr('class', 'member-entity-type').text(function (d) {
+ var matched = _mainPresetIndex.match(d.relation, context.graph());
+ return matched && matched.name() || _t.html('inspector.relation');
});
+ labelLink.append('span').attr('class', 'member-entity-name').text(function (d) {
+ return utilDisplayName(d.relation);
+ });
+ labelEnter.append('button').attr('class', 'remove member-delete').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete')).on('click', deleteMembership);
+ labelEnter.append('button').attr('class', 'member-zoom').attr('title', _t('icons.zoom_to')).call(svgIcon('#iD-icon-framed-dot', 'monochrome')).on('click', zoomToRelation);
var wrapEnter = itemsEnter.append('div').attr('class', 'form-field-input-wrap form-field-input-member');
wrapEnter.append('input').attr('class', 'member-role').attr('id', function (d) {
return d.domId;
- }).property('type', 'text').attr('placeholder', _t('inspector.role')).call(utilNoAuto);
+ }).property('type', 'text').property('value', function (d) {
+ return typeof d.role === 'string' ? d.role : '';
+ }).attr('title', function (d) {
+ return Array.isArray(d.role) ? d.role.filter(Boolean).join('\n') : d.role;
+ }).attr('placeholder', function (d) {
+ return Array.isArray(d.role) ? _t('inspector.multiple_roles') : _t('inspector.role');
+ }).classed('mixed', function (d) {
+ return Array.isArray(d.role);
+ }).call(utilNoAuto).on('blur', changeRole).on('change', changeRole);
if (taginfo) {
wrapEnter.each(bindTypeahead);
- } // update
+ }
+ var newMembership = list.selectAll('.member-row-new').data(_showBlank ? [0] : []); // Exit
- items = items.merge(itemsEnter).order();
- items.select('input.member-role').property('value', function (d) {
- return d.role;
- }).on('blur', changeRole).on('change', changeRole);
- items.select('button.member-delete').on('click', deleteMember);
- var dragOrigin, targetIndex;
- items.call(d3_drag().on('start', function (d3_event) {
- dragOrigin = {
- x: d3_event.x,
- y: d3_event.y
- };
- targetIndex = null;
- }).on('drag', function (d3_event) {
- var x = d3_event.x - dragOrigin.x,
- y = d3_event.y - dragOrigin.y;
- if (!select(this).classed('dragging') && // don't display drag until dragging beyond a distance threshold
- Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return;
- var index = items.nodes().indexOf(this);
- select(this).classed('dragging', true);
- targetIndex = null;
- selection.selectAll('li.member-row').style('transform', function (d2, index2) {
- var node = select(this).node();
+ newMembership.exit().remove(); // Enter
- if (index === index2) {
- return 'translate(' + x + 'px, ' + y + 'px)';
- } else if (index2 > index && d3_event.y > node.offsetTop) {
- if (targetIndex === null || index2 > targetIndex) {
- targetIndex = index2;
- }
+ var newMembershipEnter = newMembership.enter().append('li').attr('class', 'member-row member-row-new form-field');
+ var newLabelEnter = newMembershipEnter.append('label').attr('class', 'field-label');
+ newLabelEnter.append('input').attr('placeholder', _t('inspector.choose_relation')).attr('type', 'text').attr('class', 'member-entity-input').call(utilNoAuto);
+ newLabelEnter.append('button').attr('class', 'remove member-delete').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete')).on('click', function () {
+ list.selectAll('.member-row-new').remove();
+ });
+ var newWrapEnter = newMembershipEnter.append('div').attr('class', 'form-field-input-wrap form-field-input-member');
+ newWrapEnter.append('input').attr('class', 'member-role').property('type', 'text').attr('placeholder', _t('inspector.role')).call(utilNoAuto); // Update
- return 'translateY(-100%)';
- } else if (index2 < index && d3_event.y < node.offsetTop + node.offsetHeight) {
- if (targetIndex === null || index2 < targetIndex) {
- targetIndex = index2;
- }
+ newMembership = newMembership.merge(newMembershipEnter);
+ newMembership.selectAll('.member-entity-input').on('blur', cancelEntity) // if it wasn't accepted normally, cancel it
+ .call(nearbyCombo.on('accept', acceptEntity).on('cancel', cancelEntity)); // Container for the Add button
- return 'translateY(100%)';
- }
+ var addRow = selection.selectAll('.add-row').data([0]); // enter
- return null;
- });
- }).on('end', function (d3_event, d) {
- if (!select(this).classed('dragging')) return;
- var index = items.nodes().indexOf(this);
- select(this).classed('dragging', false);
- selection.selectAll('li.member-row').style('transform', null);
+ var addRowEnter = addRow.enter().append('div').attr('class', 'add-row');
+ var addRelationButton = addRowEnter.append('button').attr('class', 'add-relation').attr('aria-label', _t('inspector.add_to_relation'));
+ addRelationButton.call(svgIcon('#iD-icon-plus', 'light'));
+ addRelationButton.call(uiTooltip().title(_t.html('inspector.add_to_relation')).placement(_mainLocalizer.textDirection() === 'ltr' ? 'right' : 'left'));
+ addRowEnter.append('div').attr('class', 'space-value'); // preserve space
+
+ addRowEnter.append('div').attr('class', 'space-buttons'); // preserve space
+ // update
+
+ addRow = addRow.merge(addRowEnter);
+ addRow.select('.add-relation').on('click', function () {
+ _showBlank = true;
+ section.reRender();
+ list.selectAll('.member-entity-input').node().focus();
+ });
+
+ function acceptEntity(d) {
+ if (!d) {
+ cancelEntity();
+ return;
+ } // remove hover-higlighting
- if (targetIndex !== null) {
- // dragged to a new position, reorder
- context.perform(actionMoveMember(d.relation.id, index, targetIndex), _t('operations.reorder_members.annotation'));
- context.validator().validate();
- }
- }));
+
+ if (d.relation) utilHighlightEntities([d.relation.id], false, context);
+ var role = context.cleanRelationRole(list.selectAll('.member-row-new .member-role').property('value'));
+ addMembership(d, role);
+ }
+
+ function cancelEntity() {
+ var input = newMembership.selectAll('.member-entity-input');
+ input.property('value', ''); // remove hover-higlighting
+
+ context.surface().selectAll('.highlighted').classed('highlighted', false);
+ }
function bindTypeahead(d) {
var row = select(this);
}
role.call(uiCombobox(context, 'member-role').fetcher(function (role, callback) {
- // The `geometry` param is used in the `taginfo.js` interface for
- // filtering results, as a key into the `tag_members_fractions`
- // object. If we don't know the geometry because the member is
- // not yet downloaded, it's ok to guess based on type.
- var geometry;
-
- if (d.member) {
- geometry = context.graph().geometry(d.member.id);
- } else if (d.type === 'relation') {
- geometry = 'relation';
- } else if (d.type === 'way') {
- geometry = 'line';
- } else {
- geometry = 'point';
- }
-
- var rtype = entity.tags.type;
+ var rtype = d.relation.tags.type;
taginfo.roles({
debounce: true,
rtype: rtype || '',
- geometry: geometry,
+ geometry: context.graph().geometry(_entityIDs[0]),
query: role
}, function (err, data) {
if (!err) callback(sort(role, data));
section.entityIDs = function (val) {
if (!arguments.length) return _entityIDs;
_entityIDs = val;
+ _showBlank = false;
return section;
};
return section;
}
- function actionDeleteMembers(relationId, memberIndexes) {
- return function (graph) {
- // Remove the members in descending order so removals won't shift what members
- // are at the remaining indexes
- memberIndexes.sort(function (a, b) {
- return b - a;
+ function uiSectionSelectionList(context) {
+ var _selectedIDs = [];
+ var section = uiSection('selected-features', context).shouldDisplay(function () {
+ return _selectedIDs.length > 1;
+ }).label(function () {
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('inspector.features')
+ },
+ count: _selectedIDs.length
});
-
- for (var i in memberIndexes) {
- graph = actionDeleteMember(relationId, memberIndexes[i])(graph);
+ }).disclosureContent(renderDisclosureContent);
+ context.history().on('change.selectionList', function (difference) {
+ if (difference) {
+ section.reRender();
}
+ });
- return graph;
+ section.entityIDs = function (val) {
+ if (!arguments.length) return _selectedIDs;
+ _selectedIDs = val;
+ return section;
};
+
+ function selectEntity(d3_event, entity) {
+ context.enter(modeSelect(context, [entity.id]));
+ }
+
+ function deselectEntity(d3_event, entity) {
+ var selectedIDs = _selectedIDs.slice();
+
+ var index = selectedIDs.indexOf(entity.id);
+
+ if (index > -1) {
+ selectedIDs.splice(index, 1);
+ context.enter(modeSelect(context, selectedIDs));
+ }
+ }
+
+ function renderDisclosureContent(selection) {
+ var list = selection.selectAll('.feature-list').data([0]);
+ list = list.enter().append('ul').attr('class', 'feature-list').merge(list);
+
+ var entities = _selectedIDs.map(function (id) {
+ return context.hasEntity(id);
+ }).filter(Boolean);
+
+ var items = list.selectAll('.feature-list-item').data(entities, osmEntity.key);
+ items.exit().remove(); // Enter
+
+ var enter = items.enter().append('li').attr('class', 'feature-list-item').each(function (d) {
+ select(this).on('mouseover', function () {
+ utilHighlightEntities([d.id], true, context);
+ }).on('mouseout', function () {
+ utilHighlightEntities([d.id], false, context);
+ });
+ });
+ var label = enter.append('button').attr('class', 'label').on('click', selectEntity);
+ label.append('span').attr('class', 'entity-geom-icon').call(svgIcon('', 'pre-text'));
+ label.append('span').attr('class', 'entity-type');
+ label.append('span').attr('class', 'entity-name');
+ enter.append('button').attr('class', 'close').attr('title', _t('icons.deselect')).on('click', deselectEntity).call(svgIcon('#iD-icon-close')); // Update
+
+ items = items.merge(enter);
+ items.selectAll('.entity-geom-icon use').attr('href', function () {
+ var entity = this.parentNode.parentNode.__data__;
+ return '#iD-icon-' + entity.geometry(context.graph());
+ });
+ items.selectAll('.entity-type').text(function (entity) {
+ return _mainPresetIndex.match(entity, context.graph()).name();
+ });
+ items.selectAll('.entity-name').text(function (d) {
+ // fetch latest entity
+ var entity = context.entity(d.id);
+ return utilDisplayName(entity);
+ });
+ }
+
+ return section;
}
- function uiSectionRawMembershipEditor(context) {
- var section = uiSection('raw-membership-editor', context).shouldDisplay(function () {
- return _entityIDs && _entityIDs.length;
- }).label(function () {
- var parents = getSharedParentRelations();
- var gt = parents.length > _maxMemberships ? '>' : '';
- var count = gt + parents.slice(0, _maxMemberships).length;
- return _t('inspector.title_count', {
- title: _t.html('inspector.relations'),
- count: count
+ function uiEntityEditor(context) {
+ var dispatch = dispatch$8('choose');
+ var _state = 'select';
+ var _coalesceChanges = false;
+ var _modified = false;
+
+ var _base;
+
+ var _entityIDs;
+
+ var _activePresets = [];
+
+ var _newFeature;
+
+ var _sections;
+
+ function entityEditor(selection) {
+ var combinedTags = utilCombinedTags(_entityIDs, context.graph()); // Header
+
+ var header = selection.selectAll('.header').data([0]); // Enter
+
+ var headerEnter = header.enter().append('div').attr('class', 'header fillL');
+ var direction = _mainLocalizer.textDirection() === 'rtl' ? 'forward' : 'backward';
+ headerEnter.append('button').attr('class', 'preset-reset preset-choose').attr('title', _t("icons.".concat(direction))).call(svgIcon("#iD-icon-".concat(direction)));
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ context.enter(modeBrowse(context));
+ }).call(svgIcon(_modified ? '#iD-icon-apply' : '#iD-icon-close'));
+ headerEnter.append('h2'); // Update
+
+ header = header.merge(headerEnter);
+ header.selectAll('h2').html(_entityIDs.length === 1 ? _t.html('inspector.edit') : _t.html('inspector.edit_features'));
+ header.selectAll('.preset-reset').on('click', function () {
+ dispatch.call('choose', this, _activePresets);
+ }); // Body
+
+ var body = selection.selectAll('.inspector-body').data([0]); // Enter
+
+ var bodyEnter = body.enter().append('div').attr('class', 'entity-editor inspector-body sep-top'); // Update
+
+ body = body.merge(bodyEnter);
+
+ if (!_sections) {
+ _sections = [uiSectionSelectionList(context), uiSectionFeatureType(context).on('choose', function (presets) {
+ dispatch.call('choose', this, presets);
+ }), uiSectionEntityIssues(context), uiSectionPresetFields(context).on('change', changeTags).on('revert', revertTags), uiSectionRawTagEditor('raw-tag-editor', context).on('change', changeTags), uiSectionRawMemberEditor(context), uiSectionRawMembershipEditor(context)];
+ }
+
+ _sections.forEach(function (section) {
+ if (section.entityIDs) {
+ section.entityIDs(_entityIDs);
+ }
+
+ if (section.presets) {
+ section.presets(_activePresets);
+ }
+
+ if (section.tags) {
+ section.tags(combinedTags);
+ }
+
+ if (section.state) {
+ section.state(_state);
+ }
+
+ body.call(section.render);
});
- }).disclosureContent(renderDisclosureContent);
- var taginfo = services.taginfo;
- var nearbyCombo = uiCombobox(context, 'parent-relation').minItems(1).fetcher(fetchNearbyRelations).itemsMouseEnter(function (d3_event, d) {
- if (d.relation) utilHighlightEntities([d.relation.id], true, context);
- }).itemsMouseLeave(function (d3_event, d) {
- if (d.relation) utilHighlightEntities([d.relation.id], false, context);
- });
- var _inChange = false;
- var _entityIDs = [];
- var _showBlank;
+ context.history().on('change.entity-editor', historyChanged);
+
+ function historyChanged(difference) {
+ if (selection.selectAll('.entity-editor').empty()) return;
+ if (_state === 'hide') return;
+ var significant = !difference || difference.didChange.properties || difference.didChange.addition || difference.didChange.deletion;
+ if (!significant) return;
+ _entityIDs = _entityIDs.filter(context.hasEntity);
+ if (!_entityIDs.length) return;
+ var priorActivePreset = _activePresets.length === 1 && _activePresets[0];
+ loadActivePresets();
+ var graph = context.graph();
+ entityEditor.modified(_base !== graph);
+ entityEditor(selection);
+
+ if (priorActivePreset && _activePresets.length === 1 && priorActivePreset !== _activePresets[0]) {
+ // flash the button to indicate the preset changed
+ context.container().selectAll('.entity-editor button.preset-reset .label').style('background-color', '#fff').transition().duration(750).style('background-color', null);
+ }
+ }
+ } // Tag changes that fire on input can all get coalesced into a single
+ // history operation when the user leaves the field. #2342
+ // Use explicit entityIDs in case the selection changes before the event is fired.
+
+
+ function changeTags(entityIDs, changed, onInput) {
+ var actions = [];
+
+ for (var i in entityIDs) {
+ var entityID = entityIDs[i];
+ var entity = context.entity(entityID);
+ var tags = Object.assign({}, entity.tags); // shallow copy
+
+ for (var k in changed) {
+ if (!k) continue;
+ var v = changed[k];
+
+ if (_typeof(v) === 'object') {
+ // a "key only" tag change
+ tags[k] = tags[v.oldKey];
+ } else if (v !== undefined || tags.hasOwnProperty(k)) {
+ tags[k] = v;
+ }
+ }
- var _maxMemberships = 1000;
+ if (!onInput) {
+ tags = utilCleanTags(tags);
+ }
- function getSharedParentRelations() {
- var parents = [];
+ if (!fastDeepEqual(entity.tags, tags)) {
+ actions.push(actionChangeTags(entityID, tags));
+ }
+ }
- for (var i = 0; i < _entityIDs.length; i++) {
- var entity = context.graph().hasEntity(_entityIDs[i]);
- if (!entity) continue;
+ if (actions.length) {
+ var combinedAction = function combinedAction(graph) {
+ actions.forEach(function (action) {
+ graph = action(graph);
+ });
+ return graph;
+ };
- if (i === 0) {
- parents = context.graph().parentRelations(entity);
+ var annotation = _t('operations.change_tags.annotation');
+
+ if (_coalesceChanges) {
+ context.overwrite(combinedAction, annotation);
} else {
- parents = utilArrayIntersection(parents, context.graph().parentRelations(entity));
+ context.perform(combinedAction, annotation);
+ _coalesceChanges = !!onInput;
}
+ } // if leaving field (blur event), rerun validation
- if (!parents.length) break;
- }
- return parents;
+ if (!onInput) {
+ context.validator().validate();
+ }
}
- function getMemberships() {
- var memberships = [];
- var relations = getSharedParentRelations().slice(0, _maxMemberships);
- var isMultiselect = _entityIDs.length > 1;
- var i, relation, membership, index, member, indexedMember;
+ function revertTags(keys) {
+ var actions = [];
- for (i = 0; i < relations.length; i++) {
- relation = relations[i];
- membership = {
- relation: relation,
- members: [],
- hash: osmEntity.key(relation)
- };
+ for (var i in _entityIDs) {
+ var entityID = _entityIDs[i];
+ var original = context.graph().base().entities[entityID];
+ var changed = {};
- for (index = 0; index < relation.members.length; index++) {
- member = relation.members[index];
+ for (var j in keys) {
+ var key = keys[j];
+ changed[key] = original ? original.tags[key] : undefined;
+ }
- if (_entityIDs.indexOf(member.id) !== -1) {
- indexedMember = Object.assign({}, member, {
- index: index
- });
- membership.members.push(indexedMember);
- membership.hash += ',' + index.toString();
+ var entity = context.entity(entityID);
+ var tags = Object.assign({}, entity.tags); // shallow copy
- if (!isMultiselect) {
- // For single selections, list one entry per membership per relation.
- // For multiselections, list one entry per relation.
- memberships.push(membership);
- membership = {
- relation: relation,
- members: [],
- hash: osmEntity.key(relation)
- };
- }
+ for (var k in changed) {
+ if (!k) continue;
+ var v = changed[k];
+
+ if (v !== undefined || tags.hasOwnProperty(k)) {
+ tags[k] = v;
}
}
- if (membership.members.length) memberships.push(membership);
+ tags = utilCleanTags(tags);
+
+ if (!fastDeepEqual(entity.tags, tags)) {
+ actions.push(actionChangeTags(entityID, tags));
+ }
}
- memberships.forEach(function (membership) {
- membership.domId = utilUniqueDomId('membership-' + membership.relation.id);
- var roles = [];
- membership.members.forEach(function (member) {
- if (roles.indexOf(member.role) === -1) roles.push(member.role);
- });
- membership.role = roles.length === 1 ? roles[0] : roles;
- });
- return memberships;
- }
+ if (actions.length) {
+ var combinedAction = function combinedAction(graph) {
+ actions.forEach(function (action) {
+ graph = action(graph);
+ });
+ return graph;
+ };
- function selectRelation(d3_event, d) {
- d3_event.preventDefault(); // remove the hover-highlight styling
+ var annotation = _t('operations.change_tags.annotation');
- utilHighlightEntities([d.relation.id], false, context);
- context.enter(modeSelect(context, [d.relation.id]));
+ if (_coalesceChanges) {
+ context.overwrite(combinedAction, annotation);
+ } else {
+ context.perform(combinedAction, annotation);
+ _coalesceChanges = false;
+ }
+ }
+
+ context.validator().validate();
}
- function zoomToRelation(d3_event, d) {
- d3_event.preventDefault();
- var entity = context.entity(d.relation.id);
- context.map().zoomToEase(entity); // highlight the relation in case it wasn't previously on-screen
+ entityEditor.modified = function (val) {
+ if (!arguments.length) return _modified;
+ _modified = val;
+ return entityEditor;
+ };
- utilHighlightEntities([d.relation.id], true, context);
- }
+ entityEditor.state = function (val) {
+ if (!arguments.length) return _state;
+ _state = val;
+ return entityEditor;
+ };
- function changeRole(d3_event, d) {
- if (d === 0) return; // called on newrow (shouldn't happen)
+ entityEditor.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs; // always reload these even if the entityIDs are unchanged, since we
+ // could be reselecting after something like dragging a node
- if (_inChange) return; // avoid accidental recursive call #5731
+ _base = context.graph();
+ _coalesceChanges = false;
+ if (val && _entityIDs && utilArrayIdentical(_entityIDs, val)) return entityEditor; // exit early if no change
- var newRole = context.cleanRelationRole(select(this).property('value'));
- if (!newRole.trim() && typeof d.role !== 'string') return;
- var membersToUpdate = d.members.filter(function (member) {
- return member.role !== newRole;
+ _entityIDs = val;
+ loadActivePresets(true);
+ return entityEditor.modified(false);
+ };
+
+ entityEditor.newFeature = function (val) {
+ if (!arguments.length) return _newFeature;
+ _newFeature = val;
+ return entityEditor;
+ };
+
+ function loadActivePresets(isForNewSelection) {
+ var graph = context.graph();
+ var counts = {};
+
+ for (var i in _entityIDs) {
+ var entity = graph.hasEntity(_entityIDs[i]);
+ if (!entity) return;
+ var match = _mainPresetIndex.match(entity, graph);
+ if (!counts[match.id]) counts[match.id] = 0;
+ counts[match.id] += 1;
+ }
+
+ var matches = Object.keys(counts).sort(function (p1, p2) {
+ return counts[p2] - counts[p1];
+ }).map(function (pID) {
+ return _mainPresetIndex.item(pID);
});
- if (membersToUpdate.length) {
- _inChange = true;
- context.perform(function actionChangeMemberRoles(graph) {
- membersToUpdate.forEach(function (member) {
- var newMember = Object.assign({}, member, {
- role: newRole
- });
- delete newMember.index;
- graph = actionChangeMember(d.relation.id, newMember, member.index)(graph);
- });
- return graph;
- }, _t('operations.change_role.annotation', {
- n: membersToUpdate.length
- }));
- context.validator().validate();
+ if (!isForNewSelection) {
+ // A "weak" preset doesn't set any tags. (e.g. "Address")
+ var weakPreset = _activePresets.length === 1 && !_activePresets[0].isFallback() && Object.keys(_activePresets[0].addTags || {}).length === 0; // Don't replace a weak preset with a fallback preset (e.g. "Point")
+
+ if (weakPreset && matches.length === 1 && matches[0].isFallback()) return;
}
- _inChange = false;
+ entityEditor.presets(matches);
}
- function addMembership(d, role) {
- this.blur(); // avoid keeping focus on the button
+ entityEditor.presets = function (val) {
+ if (!arguments.length) return _activePresets; // don't reload the same preset
- _showBlank = false;
+ if (!utilArrayIdentical(val, _activePresets)) {
+ _activePresets = val;
+ }
- function actionAddMembers(relationId, ids, role) {
- return function (graph) {
- for (var i in ids) {
- var member = {
- id: ids[i],
- type: graph.entity(ids[i]).type,
- role: role
- };
- graph = actionAddMember(relationId, member)(graph);
- }
+ return entityEditor;
+ };
- return graph;
- };
- }
+ return utilRebind(entityEditor, dispatch, 'on');
+ }
- if (d.relation) {
- context.perform(actionAddMembers(d.relation.id, _entityIDs, role), _t('operations.add_member.annotation', {
- n: _entityIDs.length
- }));
- context.validator().validate();
- } else {
- var relation = osmRelation();
- context.perform(actionAddEntity(relation), actionAddMembers(relation.id, _entityIDs, role), _t('operations.add.annotation.relation')); // changing the mode also runs `validate`
+ var sexagesimal = {exports: {}};
- context.enter(modeSelect(context, [relation.id]).newFeature(true));
- }
- }
+ sexagesimal.exports = element;
+ var pair_1 = sexagesimal.exports.pair = pair;
+ sexagesimal.exports.format = format;
+ sexagesimal.exports.formatPair = formatPair;
+ sexagesimal.exports.coordToDMS = coordToDMS;
- function deleteMembership(d3_event, d) {
- this.blur(); // avoid keeping focus on the button
+ function element(input, dims) {
+ var result = search(input, dims);
+ return result === null ? null : result.val;
+ }
- if (d === 0) return; // called on newrow (shouldn't happen)
- // remove the hover-highlight styling
+ function formatPair(input) {
+ return format(input.lat, 'lat') + ' ' + format(input.lon, 'lon');
+ } // Is 0 North or South?
- utilHighlightEntities([d.relation.id], false, context);
- var indexes = d.members.map(function (member) {
- return member.index;
- });
- context.perform(actionDeleteMembers(d.relation.id, indexes), _t('operations.delete_member.annotation', {
- n: _entityIDs.length
- }));
- context.validator().validate();
- }
- function fetchNearbyRelations(q, callback) {
- var newRelation = {
- relation: null,
- value: _t('inspector.new_relation'),
- display: _t.html('inspector.new_relation')
- };
- var entityID = _entityIDs[0];
- var result = [];
- var graph = context.graph();
+ function format(input, dim) {
+ var dms = coordToDMS(input, dim);
+ return dms.whole + '° ' + (dms.minutes ? dms.minutes + '\' ' : '') + (dms.seconds ? dms.seconds + '" ' : '') + dms.dir;
+ }
- function baseDisplayLabel(entity) {
- var matched = _mainPresetIndex.match(entity, graph);
- var presetName = matched && matched.name() || _t('inspector.relation');
- var entityName = utilDisplayName(entity) || '';
- return presetName + ' ' + entityName;
- }
+ function coordToDMS(input, dim) {
+ var dirs = {
+ lat: ['N', 'S'],
+ lon: ['E', 'W']
+ }[dim] || '';
+ var dir = dirs[input >= 0 ? 0 : 1];
+ var abs = Math.abs(input);
+ var whole = Math.floor(abs);
+ var fraction = abs - whole;
+ var fractionMinutes = fraction * 60;
+ var minutes = Math.floor(fractionMinutes);
+ var seconds = Math.floor((fractionMinutes - minutes) * 60);
+ return {
+ whole: whole,
+ minutes: minutes,
+ seconds: seconds,
+ dir: dir
+ };
+ }
- var explicitRelation = q && context.hasEntity(q.toLowerCase());
+ function search(input, dims) {
+ if (!dims) dims = 'NSEW';
+ if (typeof input !== 'string') return null;
+ input = input.toUpperCase();
+ var regex = /^[\s\,]*([NSEW])?\s*([\-|\—|\―]?[0-9.]+)[°º˚]?\s*(?:([0-9.]+)['’′‘]\s*)?(?:([0-9.]+)(?:''|"|”|″)\s*)?([NSEW])?/;
+ var m = input.match(regex);
+ if (!m) return null; // no match
- if (explicitRelation && explicitRelation.type === 'relation' && explicitRelation.id !== entityID) {
- // loaded relation is specified explicitly, only show that
- result.push({
- relation: explicitRelation,
- value: baseDisplayLabel(explicitRelation) + ' ' + explicitRelation.id
- });
- } else {
- context.history().intersects(context.map().extent()).forEach(function (entity) {
- if (entity.type !== 'relation' || entity.id === entityID) return;
- var value = baseDisplayLabel(entity);
- if (q && (value + ' ' + entity.id).toLowerCase().indexOf(q.toLowerCase()) === -1) return;
- result.push({
- relation: entity,
- value: value
- });
- });
- result.sort(function (a, b) {
- return osmRelation.creationOrder(a.relation, b.relation);
- }); // Dedupe identical names by appending relation id - see #2891
+ var matched = m[0]; // extract dimension.. m[1] = leading, m[5] = trailing
- var dupeGroups = Object.values(utilArrayGroupBy(result, 'value')).filter(function (v) {
- return v.length > 1;
- });
- dupeGroups.forEach(function (group) {
- group.forEach(function (obj) {
- obj.value += ' ' + obj.relation.id;
- });
- });
- }
+ var dim;
- result.forEach(function (obj) {
- obj.title = obj.value;
- });
- result.unshift(newRelation);
- callback(result);
+ if (m[1] && m[5]) {
+ // if matched both..
+ dim = m[1]; // keep leading
+
+ matched = matched.slice(0, -1); // remove trailing dimension from match
+ } else {
+ dim = m[1] || m[5];
+ } // if unrecognized dimension
+
+
+ if (dim && dims.indexOf(dim) === -1) return null; // extract DMS
+
+ var deg = m[2] ? parseFloat(m[2]) : 0;
+ var min = m[3] ? parseFloat(m[3]) / 60 : 0;
+ var sec = m[4] ? parseFloat(m[4]) / 3600 : 0;
+ var sign = deg < 0 ? -1 : 1;
+ if (dim === 'S' || dim === 'W') sign *= -1;
+ return {
+ val: (Math.abs(deg) + min + sec) * sign,
+ dim: dim,
+ matched: matched,
+ remain: input.slice(matched.length)
+ };
+ }
+
+ function pair(input, dims) {
+ input = input.trim();
+ var one = search(input, dims);
+ if (!one) return null;
+ input = one.remain.trim();
+ var two = search(input, dims);
+ if (!two || two.remain) return null;
+
+ if (one.dim) {
+ return swapdim(one.val, two.val, one.dim);
+ } else {
+ return [one.val, two.val];
}
+ }
- function renderDisclosureContent(selection) {
- var memberships = getMemberships();
- var list = selection.selectAll('.member-list').data([0]);
- list = list.enter().append('ul').attr('class', 'member-list').merge(list);
- var items = list.selectAll('li.member-row-normal').data(memberships, function (d) {
- return d.hash;
- });
- items.exit().each(unbind).remove(); // Enter
+ function swapdim(a, b, dim) {
+ if (dim === 'N' || dim === 'S') return [a, b];
+ if (dim === 'W' || dim === 'E') return [b, a];
+ }
- var itemsEnter = items.enter().append('li').attr('class', 'member-row member-row-normal form-field'); // highlight the relation in the map while hovering on the list item
+ function uiFeatureList(context) {
+ var _geocodeResults;
- itemsEnter.on('mouseover', function (d3_event, d) {
- utilHighlightEntities([d.relation.id], true, context);
- }).on('mouseout', function (d3_event, d) {
- utilHighlightEntities([d.relation.id], false, context);
- });
- var labelEnter = itemsEnter.append('label').attr('class', 'field-label').attr('for', function (d) {
- return d.domId;
- });
- var labelLink = labelEnter.append('span').attr('class', 'label-text').append('a').attr('href', '#').on('click', selectRelation);
- labelLink.append('span').attr('class', 'member-entity-type').html(function (d) {
- var matched = _mainPresetIndex.match(d.relation, context.graph());
- return matched && matched.name() || _t('inspector.relation');
- });
- labelLink.append('span').attr('class', 'member-entity-name').html(function (d) {
- return utilDisplayName(d.relation);
- });
- labelEnter.append('button').attr('class', 'remove member-delete').call(svgIcon('#iD-operation-delete')).on('click', deleteMembership);
- labelEnter.append('button').attr('class', 'member-zoom').attr('title', _t('icons.zoom_to')).call(svgIcon('#iD-icon-framed-dot', 'monochrome')).on('click', zoomToRelation);
- var wrapEnter = itemsEnter.append('div').attr('class', 'form-field-input-wrap form-field-input-member');
- wrapEnter.append('input').attr('class', 'member-role').attr('id', function (d) {
- return d.domId;
- }).property('type', 'text').property('value', function (d) {
- return typeof d.role === 'string' ? d.role : '';
- }).attr('title', function (d) {
- return Array.isArray(d.role) ? d.role.filter(Boolean).join('\n') : d.role;
- }).attr('placeholder', function (d) {
- return Array.isArray(d.role) ? _t('inspector.multiple_roles') : _t('inspector.role');
- }).classed('mixed', function (d) {
- return Array.isArray(d.role);
- }).call(utilNoAuto).on('blur', changeRole).on('change', changeRole);
+ function featureList(selection) {
+ var header = selection.append('div').attr('class', 'header fillL');
+ header.append('h2').call(_t.append('inspector.feature_list'));
+ var searchWrap = selection.append('div').attr('class', 'search-header');
+ searchWrap.call(svgIcon('#iD-icon-search', 'pre-text'));
+ var search = searchWrap.append('input').attr('placeholder', _t('inspector.search')).attr('type', 'search').call(utilNoAuto).on('keypress', keypress).on('keydown', keydown).on('input', inputevent);
+ var listWrap = selection.append('div').attr('class', 'inspector-body');
+ var list = listWrap.append('div').attr('class', 'feature-list');
+ context.on('exit.feature-list', clearSearch);
+ context.map().on('drawn.feature-list', mapDrawn);
+ context.keybinding().on(uiCmd('⌘F'), focusSearch);
- if (taginfo) {
- wrapEnter.each(bindTypeahead);
+ function focusSearch(d3_event) {
+ var mode = context.mode() && context.mode().id;
+ if (mode !== 'browse') return;
+ d3_event.preventDefault();
+ search.node().focus();
}
- var newMembership = list.selectAll('.member-row-new').data(_showBlank ? [0] : []); // Exit
+ function keydown(d3_event) {
+ if (d3_event.keyCode === 27) {
+ // escape
+ search.node().blur();
+ }
+ }
- newMembership.exit().remove(); // Enter
+ function keypress(d3_event) {
+ var q = search.property('value'),
+ items = list.selectAll('.feature-list-item');
- var newMembershipEnter = newMembership.enter().append('li').attr('class', 'member-row member-row-new form-field');
- var newLabelEnter = newMembershipEnter.append('label').attr('class', 'field-label');
- newLabelEnter.append('input').attr('placeholder', _t('inspector.choose_relation')).attr('type', 'text').attr('class', 'member-entity-input').call(utilNoAuto);
- newLabelEnter.append('button').attr('class', 'remove member-delete').call(svgIcon('#iD-operation-delete')).on('click', function () {
- list.selectAll('.member-row-new').remove();
- });
- var newWrapEnter = newMembershipEnter.append('div').attr('class', 'form-field-input-wrap form-field-input-member');
- newWrapEnter.append('input').attr('class', 'member-role').property('type', 'text').attr('placeholder', _t('inspector.role')).call(utilNoAuto); // Update
+ if (d3_event.keyCode === 13 && // ↩ Return
+ q.length && items.size()) {
+ click(d3_event, items.datum());
+ }
+ }
- newMembership = newMembership.merge(newMembershipEnter);
- newMembership.selectAll('.member-entity-input').on('blur', cancelEntity) // if it wasn't accepted normally, cancel it
- .call(nearbyCombo.on('accept', acceptEntity).on('cancel', cancelEntity)); // Container for the Add button
+ function inputevent() {
+ _geocodeResults = undefined;
+ drawList();
+ }
- var addRow = selection.selectAll('.add-row').data([0]); // enter
+ function clearSearch() {
+ search.property('value', '');
+ drawList();
+ }
- var addRowEnter = addRow.enter().append('div').attr('class', 'add-row');
- var addRelationButton = addRowEnter.append('button').attr('class', 'add-relation');
- addRelationButton.call(svgIcon('#iD-icon-plus', 'light'));
- addRelationButton.call(uiTooltip().title(_t.html('inspector.add_to_relation')).placement(_mainLocalizer.textDirection() === 'ltr' ? 'right' : 'left'));
- addRowEnter.append('div').attr('class', 'space-value'); // preserve space
+ function mapDrawn(e) {
+ if (e.full) {
+ drawList();
+ }
+ }
- addRowEnter.append('div').attr('class', 'space-buttons'); // preserve space
- // update
+ function features() {
+ var result = [];
+ var graph = context.graph();
+ var visibleCenter = context.map().extent().center();
+ var q = search.property('value').toLowerCase();
+ if (!q) return result;
+ var locationMatch = pair_1(q.toUpperCase()) || q.match(/^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)$/);
- addRow = addRow.merge(addRowEnter);
- addRow.select('.add-relation').on('click', function () {
- _showBlank = true;
- section.reRender();
- list.selectAll('.member-entity-input').node().focus();
- });
+ if (locationMatch) {
+ var loc = [parseFloat(locationMatch[0]), parseFloat(locationMatch[1])];
+ result.push({
+ id: -1,
+ geometry: 'point',
+ type: _t('inspector.location'),
+ name: dmsCoordinatePair([loc[1], loc[0]]),
+ location: loc
+ });
+ } // A location search takes priority over an ID search
- function acceptEntity(d) {
- if (!d) {
- cancelEntity();
- return;
- } // remove hover-higlighting
+ var idMatch = !locationMatch && q.match(/(?:^|\W)(node|way|relation|[nwr])\W?0*([1-9]\d*)(?:\W|$)/i);
- if (d.relation) utilHighlightEntities([d.relation.id], false, context);
- var role = context.cleanRelationRole(list.selectAll('.member-row-new .member-role').property('value'));
- addMembership(d, role);
- }
+ if (idMatch) {
+ var elemType = idMatch[1].charAt(0);
+ var elemId = idMatch[2];
+ result.push({
+ id: elemType + elemId,
+ geometry: elemType === 'n' ? 'point' : elemType === 'w' ? 'line' : 'relation',
+ type: elemType === 'n' ? _t('inspector.node') : elemType === 'w' ? _t('inspector.way') : _t('inspector.relation'),
+ name: elemId
+ });
+ }
- function cancelEntity() {
- var input = newMembership.selectAll('.member-entity-input');
- input.property('value', ''); // remove hover-higlighting
+ var allEntities = graph.entities;
+ var localResults = [];
- context.surface().selectAll('.highlighted').classed('highlighted', false);
- }
+ for (var id in allEntities) {
+ var entity = allEntities[id];
+ if (!entity) continue;
+ var name = utilDisplayName(entity) || '';
+ if (name.toLowerCase().indexOf(q) < 0) continue;
+ var matched = _mainPresetIndex.match(entity, graph);
+ var type = matched && matched.name() || utilDisplayType(entity.id);
+ var extent = entity.extent(graph);
+ var distance = extent ? geoSphericalDistance(visibleCenter, extent.center()) : 0;
+ localResults.push({
+ id: entity.id,
+ entity: entity,
+ geometry: entity.geometry(graph),
+ type: type,
+ name: name,
+ distance: distance
+ });
+ if (localResults.length > 100) break;
+ }
- function bindTypeahead(d) {
- var row = select(this);
- var role = row.selectAll('input.member-role');
- var origValue = role.property('value');
+ localResults = localResults.sort(function byDistance(a, b) {
+ return a.distance - b.distance;
+ });
+ result = result.concat(localResults);
- function sort(value, data) {
- var sameletter = [];
- var other = [];
+ (_geocodeResults || []).forEach(function (d) {
+ if (d.osm_type && d.osm_id) {
+ // some results may be missing these - #1890
+ // Make a temporary osmEntity so we can preset match
+ // and better localize the search result - #4725
+ var id = osmEntity.id.fromOSM(d.osm_type, d.osm_id);
+ var tags = {};
+ tags[d["class"]] = d.type;
+ var attrs = {
+ id: id,
+ type: d.osm_type,
+ tags: tags
+ };
- for (var i = 0; i < data.length; i++) {
- if (data[i].value.substring(0, value.length) === value) {
- sameletter.push(data[i]);
- } else {
- other.push(data[i]);
+ if (d.osm_type === 'way') {
+ // for ways, add some fake closed nodes
+ attrs.nodes = ['a', 'a']; // so that geometry area is possible
}
- }
- return sameletter.concat(other);
- }
+ var tempEntity = osmEntity(attrs);
+ var tempGraph = coreGraph([tempEntity]);
+ var matched = _mainPresetIndex.match(tempEntity, tempGraph);
+ var type = matched && matched.name() || utilDisplayType(id);
+ result.push({
+ id: tempEntity.id,
+ geometry: tempEntity.geometry(tempGraph),
+ type: type,
+ name: d.display_name,
+ extent: new geoExtent([parseFloat(d.boundingbox[3]), parseFloat(d.boundingbox[0])], [parseFloat(d.boundingbox[2]), parseFloat(d.boundingbox[1])])
+ });
+ }
+ });
- role.call(uiCombobox(context, 'member-role').fetcher(function (role, callback) {
- var rtype = d.relation.tags.type;
- taginfo.roles({
- debounce: true,
- rtype: rtype || '',
- geometry: context.graph().geometry(_entityIDs[0]),
- query: role
- }, function (err, data) {
- if (!err) callback(sort(role, data));
+ if (q.match(/^[0-9]+$/)) {
+ // if query is just a number, possibly an OSM ID without a prefix
+ result.push({
+ id: 'n' + q,
+ geometry: 'point',
+ type: _t('inspector.node'),
+ name: q
});
- }).on('cancel', function () {
- role.property('value', origValue);
- }));
- }
+ result.push({
+ id: 'w' + q,
+ geometry: 'line',
+ type: _t('inspector.way'),
+ name: q
+ });
+ result.push({
+ id: 'r' + q,
+ geometry: 'relation',
+ type: _t('inspector.relation'),
+ name: q
+ });
+ }
- function unbind() {
- var row = select(this);
- row.selectAll('input.member-role').call(uiCombobox.off, context);
+ return result;
}
- }
- section.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- _showBlank = false;
- return section;
- };
+ function drawList() {
+ var value = search.property('value');
+ var results = features();
+ list.classed('filtered', value.length);
+ var resultsIndicator = list.selectAll('.no-results-item').data([0]).enter().append('button').property('disabled', true).attr('class', 'no-results-item').call(svgIcon('#iD-icon-alert', 'pre-text'));
+ resultsIndicator.append('span').attr('class', 'entity-name');
+ list.selectAll('.no-results-item .entity-name').html('').call(_t.append('geocoder.no_results_worldwide'));
- return section;
- }
+ if (services.geocoder) {
+ list.selectAll('.geocode-item').data([0]).enter().append('button').attr('class', 'geocode-item secondary-action').on('click', geocoderSearch).append('div').attr('class', 'label').append('span').attr('class', 'entity-name').call(_t.append('geocoder.search'));
+ }
- function uiSectionSelectionList(context) {
- var _selectedIDs = [];
- var section = uiSection('selected-features', context).shouldDisplay(function () {
- return _selectedIDs.length > 1;
- }).label(function () {
- return _t('inspector.title_count', {
- title: _t.html('inspector.features'),
- count: _selectedIDs.length
- });
- }).disclosureContent(renderDisclosureContent);
- context.history().on('change.selectionList', function (difference) {
- if (difference) {
- section.reRender();
+ list.selectAll('.no-results-item').style('display', value.length && !results.length ? 'block' : 'none');
+ list.selectAll('.geocode-item').style('display', value && _geocodeResults === undefined ? 'block' : 'none');
+ list.selectAll('.feature-list-item').data([-1]).remove();
+ var items = list.selectAll('.feature-list-item').data(results, function (d) {
+ return d.id;
+ });
+ var enter = items.enter().insert('button', '.geocode-item').attr('class', 'feature-list-item').on('mouseover', mouseover).on('mouseout', mouseout).on('click', click);
+ var label = enter.append('div').attr('class', 'label');
+ label.each(function (d) {
+ select(this).call(svgIcon('#iD-icon-' + d.geometry, 'pre-text'));
+ });
+ label.append('span').attr('class', 'entity-type').text(function (d) {
+ return d.type;
+ });
+ label.append('span').attr('class', 'entity-name').text(function (d) {
+ return d.name;
+ });
+ enter.style('opacity', 0).transition().style('opacity', 1);
+ items.order();
+ items.exit().remove();
}
- });
-
- section.entityIDs = function (val) {
- if (!arguments.length) return _selectedIDs;
- _selectedIDs = val;
- return section;
- };
-
- function selectEntity(d3_event, entity) {
- context.enter(modeSelect(context, [entity.id]));
- }
-
- function deselectEntity(d3_event, entity) {
- var selectedIDs = _selectedIDs.slice();
-
- var index = selectedIDs.indexOf(entity.id);
- if (index > -1) {
- selectedIDs.splice(index, 1);
- context.enter(modeSelect(context, selectedIDs));
+ function mouseover(d3_event, d) {
+ if (d.id === -1) return;
+ utilHighlightEntities([d.id], true, context);
}
- }
-
- function renderDisclosureContent(selection) {
- var list = selection.selectAll('.feature-list').data([0]);
- list = list.enter().append('ul').attr('class', 'feature-list').merge(list);
- var entities = _selectedIDs.map(function (id) {
- return context.hasEntity(id);
- }).filter(Boolean);
+ function mouseout(d3_event, d) {
+ if (d.id === -1) return;
+ utilHighlightEntities([d.id], false, context);
+ }
- var items = list.selectAll('.feature-list-item').data(entities, osmEntity.key);
- items.exit().remove(); // Enter
+ function click(d3_event, d) {
+ d3_event.preventDefault();
- var enter = items.enter().append('li').attr('class', 'feature-list-item').each(function (d) {
- select(this).on('mouseover', function () {
- utilHighlightEntities([d.id], true, context);
- }).on('mouseout', function () {
+ if (d.location) {
+ context.map().centerZoomEase([d.location[1], d.location[0]], 19);
+ } else if (d.entity) {
utilHighlightEntities([d.id], false, context);
- });
- });
- var label = enter.append('button').attr('class', 'label').on('click', selectEntity);
- label.append('span').attr('class', 'entity-geom-icon').call(svgIcon('', 'pre-text'));
- label.append('span').attr('class', 'entity-type');
- label.append('span').attr('class', 'entity-name');
- enter.append('button').attr('class', 'close').attr('title', _t('icons.deselect')).on('click', deselectEntity).call(svgIcon('#iD-icon-close')); // Update
+ context.enter(modeSelect(context, [d.entity.id]));
+ context.map().zoomToEase(d.entity);
+ } else {
+ // download, zoom to, and select the entity with the given ID
+ context.zoomToEntity(d.id);
+ }
+ }
- items = items.merge(enter);
- items.selectAll('.entity-geom-icon use').attr('href', function () {
- var entity = this.parentNode.parentNode.__data__;
- return '#iD-icon-' + entity.geometry(context.graph());
- });
- items.selectAll('.entity-type').html(function (entity) {
- return _mainPresetIndex.match(entity, context.graph()).name();
- });
- items.selectAll('.entity-name').html(function (d) {
- // fetch latest entity
- var entity = context.entity(d.id);
- return utilDisplayName(entity);
- });
+ function geocoderSearch() {
+ services.geocoder.search(search.property('value'), function (err, resp) {
+ _geocodeResults = resp || [];
+ drawList();
+ });
+ }
}
- return section;
+ return featureList;
}
- function uiEntityEditor(context) {
- var dispatch = dispatch$8('choose');
- var _state = 'select';
- var _coalesceChanges = false;
- var _modified = false;
+ function uiImproveOsmComments() {
+ var _qaItem;
- var _base;
+ function issueComments(selection) {
+ // make the div immediately so it appears above the buttons
+ var comments = selection.selectAll('.comments-container').data([0]);
+ comments = comments.enter().append('div').attr('class', 'comments-container').merge(comments); // must retrieve comments from API before they can be displayed
- var _entityIDs;
+ services.improveOSM.getComments(_qaItem).then(function (d) {
+ if (!d.comments) return; // nothing to do here
- var _activePresets = [];
+ var commentEnter = comments.selectAll('.comment').data(d.comments).enter().append('div').attr('class', 'comment');
+ commentEnter.append('div').attr('class', 'comment-avatar').call(svgIcon('#iD-icon-avatar', 'comment-avatar-icon'));
+ var mainEnter = commentEnter.append('div').attr('class', 'comment-main');
+ var metadataEnter = mainEnter.append('div').attr('class', 'comment-metadata');
+ metadataEnter.append('div').attr('class', 'comment-author').each(function (d) {
+ var osm = services.osm;
+ var selection = select(this);
- var _newFeature;
+ if (osm && d.username) {
+ selection = selection.append('a').attr('class', 'comment-author-link').attr('href', osm.userURL(d.username)).attr('target', '_blank');
+ }
- var _sections;
+ selection.text(function (d) {
+ return d.username;
+ });
+ });
+ metadataEnter.append('div').attr('class', 'comment-date').html(function (d) {
+ return _t.html('note.status.commented', {
+ when: localeDateString(d.timestamp)
+ });
+ });
+ mainEnter.append('div').attr('class', 'comment-text').append('p').text(function (d) {
+ return d.text;
+ });
+ })["catch"](function (err) {
+ console.log(err); // eslint-disable-line no-console
+ });
+ }
- function entityEditor(selection) {
- var combinedTags = utilCombinedTags(_entityIDs, context.graph()); // Header
+ function localeDateString(s) {
+ if (!s) return null;
+ var options = {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric'
+ };
+ var d = new Date(s * 1000); // timestamp is served in seconds, date takes ms
- var header = selection.selectAll('.header').data([0]); // Enter
+ if (isNaN(d.getTime())) return null;
+ return d.toLocaleDateString(_mainLocalizer.localeCode(), options);
+ }
- var headerEnter = header.enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'preset-reset preset-choose').call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-forward' : '#iD-icon-backward'));
- headerEnter.append('button').attr('class', 'close').on('click', function () {
- context.enter(modeBrowse(context));
- }).call(svgIcon(_modified ? '#iD-icon-apply' : '#iD-icon-close'));
- headerEnter.append('h3'); // Update
+ issueComments.issue = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return issueComments;
+ };
- header = header.merge(headerEnter);
- header.selectAll('h3').html(_entityIDs.length === 1 ? _t.html('inspector.edit') : _t.html('inspector.edit_features'));
- header.selectAll('.preset-reset').on('click', function () {
- dispatch.call('choose', this, _activePresets);
- }); // Body
+ return issueComments;
+ }
- var body = selection.selectAll('.inspector-body').data([0]); // Enter
+ function uiImproveOsmDetails(context) {
+ var _qaItem;
- var bodyEnter = body.enter().append('div').attr('class', 'entity-editor inspector-body sep-top'); // Update
+ function issueDetail(d) {
+ if (d.desc) return d.desc;
+ var issueKey = d.issueKey;
+ d.replacements = d.replacements || {};
+ d.replacements["default"] = _t.html('inspector.unknown'); // special key `default` works as a fallback string
- body = body.merge(bodyEnter);
+ return _t.html("QA.improveOSM.error_types.".concat(issueKey, ".description"), d.replacements);
+ }
- if (!_sections) {
- _sections = [uiSectionSelectionList(context), uiSectionFeatureType(context).on('choose', function (presets) {
- dispatch.call('choose', this, presets);
- }), uiSectionEntityIssues(context), uiSectionPresetFields(context).on('change', changeTags).on('revert', revertTags), uiSectionRawTagEditor('raw-tag-editor', context).on('change', changeTags), uiSectionRawMemberEditor(context), uiSectionRawMembershipEditor(context)];
- }
+ function improveOsmDetails(selection) {
+ var details = selection.selectAll('.error-details').data(_qaItem ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ });
+ details.exit().remove();
+ var detailsEnter = details.enter().append('div').attr('class', 'error-details qa-details-container'); // description
- _sections.forEach(function (section) {
- if (section.entityIDs) {
- section.entityIDs(_entityIDs);
- }
+ var descriptionEnter = detailsEnter.append('div').attr('class', 'qa-details-subsection');
+ descriptionEnter.append('h4').call(_t.append('QA.keepRight.detail_description'));
+ descriptionEnter.append('div').attr('class', 'qa-details-description-text').text(issueDetail); // If there are entity links in the error message..
- if (section.presets) {
- section.presets(_activePresets);
- }
+ var relatedEntities = [];
+ descriptionEnter.selectAll('.error_entity_link, .error_object_link').attr('href', '#').each(function () {
+ var link = select(this);
+ var isObjectLink = link.classed('error_object_link');
+ var entityID = isObjectLink ? utilEntityRoot(_qaItem.objectType) + _qaItem.objectId : this.textContent;
+ var entity = context.hasEntity(entityID);
+ relatedEntities.push(entityID); // Add click handler
- if (section.tags) {
- section.tags(combinedTags);
- }
+ link.on('mouseenter', function () {
+ utilHighlightEntities([entityID], true, context);
+ }).on('mouseleave', function () {
+ utilHighlightEntities([entityID], false, context);
+ }).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ utilHighlightEntities([entityID], false, context);
+ var osmlayer = context.layers().layer('osm');
- if (section.state) {
- section.state(_state);
- }
+ if (!osmlayer.enabled()) {
+ osmlayer.enabled(true);
+ }
- body.call(section.render);
- });
+ context.map().centerZoom(_qaItem.loc, 20);
- context.history().on('change.entity-editor', historyChanged);
+ if (entity) {
+ context.enter(modeSelect(context, [entityID]));
+ } else {
+ context.loadEntity(entityID, function (err, result) {
+ if (err) return;
+ var entity = result.data.find(function (e) {
+ return e.id === entityID;
+ });
+ if (entity) context.enter(modeSelect(context, [entityID]));
+ });
+ }
+ }); // Replace with friendly name if possible
+ // (The entity may not yet be loaded into the graph)
- function historyChanged(difference) {
- if (selection.selectAll('.entity-editor').empty()) return;
- if (_state === 'hide') return;
- var significant = !difference || difference.didChange.properties || difference.didChange.addition || difference.didChange.deletion;
- if (!significant) return;
- _entityIDs = _entityIDs.filter(context.hasEntity);
- if (!_entityIDs.length) return;
- var priorActivePreset = _activePresets.length === 1 && _activePresets[0];
- loadActivePresets();
- var graph = context.graph();
- entityEditor.modified(_base !== graph);
- entityEditor(selection);
+ if (entity) {
+ var name = utilDisplayName(entity); // try to use common name
- if (priorActivePreset && _activePresets.length === 1 && priorActivePreset !== _activePresets[0]) {
- // flash the button to indicate the preset changed
- context.container().selectAll('.entity-editor button.preset-reset .label').style('background-color', '#fff').transition().duration(750).style('background-color', null);
+ if (!name && !isObjectLink) {
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
+ }
+
+ if (name) {
+ this.innerText = name;
+ }
}
- }
- } // Tag changes that fire on input can all get coalesced into a single
- // history operation when the user leaves the field. #2342
- // Use explicit entityIDs in case the selection changes before the event is fired.
+ }); // Don't hide entities related to this error - #5880
+ context.features().forceVisible(relatedEntities);
+ context.map().pan([0, 0]); // trigger a redraw
+ }
- function changeTags(entityIDs, changed, onInput) {
- var actions = [];
+ improveOsmDetails.issue = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return improveOsmDetails;
+ };
- for (var i in entityIDs) {
- var entityID = entityIDs[i];
- var entity = context.entity(entityID);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ return improveOsmDetails;
+ }
- for (var k in changed) {
- if (!k) continue;
- var v = changed[k];
+ function uiImproveOsmHeader() {
+ var _qaItem;
- if (v !== undefined || tags.hasOwnProperty(k)) {
- tags[k] = v;
- }
- }
+ function issueTitle(d) {
+ var issueKey = d.issueKey;
+ d.replacements = d.replacements || {};
+ d.replacements["default"] = _t.html('inspector.unknown'); // special key `default` works as a fallback string
- if (!onInput) {
- tags = utilCleanTags(tags);
- }
+ return _t.html("QA.improveOSM.error_types.".concat(issueKey, ".title"), d.replacements);
+ }
- if (!fastDeepEqual(entity.tags, tags)) {
- actions.push(actionChangeTags(entityID, tags));
+ function improveOsmHeader(selection) {
+ var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ });
+ header.exit().remove();
+ var headerEnter = header.enter().append('div').attr('class', 'qa-header');
+ var svgEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
+ return d.id < 0;
+ }).append('svg').attr('width', '20px').attr('height', '30px').attr('viewbox', '0 0 20 30').attr('class', function (d) {
+ return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
+ });
+ svgEnter.append('polygon').attr('fill', 'currentColor').attr('class', 'qaItem-fill').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
+ svgEnter.append('use').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('transform', 'translate(3.5, 5)').attr('xlink:href', function (d) {
+ var picon = d.icon;
+
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return "#".concat(picon).concat(isMaki ? '-11' : '');
}
- }
+ });
+ headerEnter.append('div').attr('class', 'qa-header-label').text(issueTitle);
+ }
- if (actions.length) {
- var combinedAction = function combinedAction(graph) {
- actions.forEach(function (action) {
- graph = action(graph);
- });
- return graph;
- };
+ improveOsmHeader.issue = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return improveOsmHeader;
+ };
- var annotation = _t('operations.change_tags.annotation');
+ return improveOsmHeader;
+ }
- if (_coalesceChanges) {
- context.overwrite(combinedAction, annotation);
- } else {
- context.perform(combinedAction, annotation);
- _coalesceChanges = !!onInput;
- }
- } // if leaving field (blur event), rerun validation
+ function uiImproveOsmEditor(context) {
+ var dispatch = dispatch$8('change');
+ var qaDetails = uiImproveOsmDetails(context);
+ var qaComments = uiImproveOsmComments();
+ var qaHeader = uiImproveOsmHeader();
+ var _qaItem;
- if (!onInput) {
- context.validator().validate();
- }
+ function improveOsmEditor(selection) {
+ var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ return context.enter(modeBrowse(context));
+ }).call(svgIcon('#iD-icon-close'));
+ headerEnter.append('h2').call(_t.append('QA.improveOSM.title'));
+ var body = selection.selectAll('.body').data([0]);
+ body = body.enter().append('div').attr('class', 'body').merge(body);
+ var editor = body.selectAll('.qa-editor').data([0]);
+ editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(qaComments.issue(_qaItem)).call(improveOsmSaveSection);
}
- function revertTags(keys) {
- var actions = [];
-
- for (var i in _entityIDs) {
- var entityID = _entityIDs[i];
- var original = context.graph().base().entities[entityID];
- var changed = {};
+ function improveOsmSaveSection(selection) {
+ var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
- for (var j in keys) {
- var key = keys[j];
- changed[key] = original ? original.tags[key] : undefined;
- }
+ var isShown = _qaItem && (isSelected || _qaItem.newComment || _qaItem.comment);
+ var saveSection = selection.selectAll('.qa-save').data(isShown ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ }); // exit
- var entity = context.entity(entityID);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ saveSection.exit().remove(); // enter
- for (var k in changed) {
- if (!k) continue;
- var v = changed[k];
+ var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf');
+ saveSectionEnter.append('h4').attr('class', '.qa-save-header').call(_t.append('note.newComment'));
+ saveSectionEnter.append('textarea').attr('class', 'new-comment-input').attr('placeholder', _t('QA.keepRight.comment_placeholder')).attr('maxlength', 1000).property('value', function (d) {
+ return d.newComment;
+ }).call(utilNoAuto).on('input', changeInput).on('blur', changeInput); // update
- if (v !== undefined || tags.hasOwnProperty(k)) {
- tags[k] = v;
- }
- }
+ saveSection = saveSectionEnter.merge(saveSection).call(qaSaveButtons);
- tags = utilCleanTags(tags);
+ function changeInput() {
+ var input = select(this);
+ var val = input.property('value').trim();
- if (!fastDeepEqual(entity.tags, tags)) {
- actions.push(actionChangeTags(entityID, tags));
- }
- }
+ if (val === '') {
+ val = undefined;
+ } // store the unsaved comment with the issue itself
- if (actions.length) {
- var combinedAction = function combinedAction(graph) {
- actions.forEach(function (action) {
- graph = action(graph);
- });
- return graph;
- };
- var annotation = _t('operations.change_tags.annotation');
+ _qaItem = _qaItem.update({
+ newComment: val
+ });
+ var qaService = services.improveOSM;
- if (_coalesceChanges) {
- context.overwrite(combinedAction, annotation);
- } else {
- context.perform(combinedAction, annotation);
- _coalesceChanges = false;
+ if (qaService) {
+ qaService.replaceItem(_qaItem);
}
- }
- context.validator().validate();
+ saveSection.call(qaSaveButtons);
+ }
}
- entityEditor.modified = function (val) {
- if (!arguments.length) return _modified;
- _modified = val;
- return entityEditor;
- };
-
- entityEditor.state = function (val) {
- if (!arguments.length) return _state;
- _state = val;
- return entityEditor;
- };
-
- entityEditor.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs; // always reload these even if the entityIDs are unchanged, since we
- // could be reselecting after something like dragging a node
+ function qaSaveButtons(selection) {
+ var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
- _base = context.graph();
- _coalesceChanges = false;
- if (val && _entityIDs && utilArrayIdentical(_entityIDs, val)) return entityEditor; // exit early if no change
+ var buttonSection = selection.selectAll('.buttons').data(isSelected ? [_qaItem] : [], function (d) {
+ return d.status + d.id;
+ }); // exit
- _entityIDs = val;
- loadActivePresets(true);
- return entityEditor.modified(false);
- };
+ buttonSection.exit().remove(); // enter
- entityEditor.newFeature = function (val) {
- if (!arguments.length) return _newFeature;
- _newFeature = val;
- return entityEditor;
- };
+ var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
+ buttonEnter.append('button').attr('class', 'button comment-button action').call(_t.append('QA.keepRight.save_comment'));
+ buttonEnter.append('button').attr('class', 'button close-button action');
+ buttonEnter.append('button').attr('class', 'button ignore-button action'); // update
- function loadActivePresets(isForNewSelection) {
- var graph = context.graph();
- var counts = {};
+ buttonSection = buttonSection.merge(buttonEnter);
+ buttonSection.select('.comment-button').attr('disabled', function (d) {
+ return d.newComment ? null : true;
+ }).on('click.comment', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
- for (var i in _entityIDs) {
- var entity = graph.hasEntity(_entityIDs[i]);
- if (!entity) return;
- var match = _mainPresetIndex.match(entity, graph);
- if (!counts[match.id]) counts[match.id] = 0;
- counts[match.id] += 1;
- }
+ var qaService = services.improveOSM;
- var matches = Object.keys(counts).sort(function (p1, p2) {
- return counts[p2] - counts[p1];
- }).map(function (pID) {
- return _mainPresetIndex.item(pID);
+ if (qaService) {
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
});
+ buttonSection.select('.close-button').html(function (d) {
+ var andComment = d.newComment ? '_comment' : '';
+ return _t.html("QA.keepRight.close".concat(andComment));
+ }).on('click.close', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
- if (!isForNewSelection) {
- // A "weak" preset doesn't set any tags. (e.g. "Address")
- var weakPreset = _activePresets.length === 1 && !_activePresets[0].isFallback() && Object.keys(_activePresets[0].addTags || {}).length === 0; // Don't replace a weak preset with a fallback preset (e.g. "Point")
+ var qaService = services.improveOSM;
- if (weakPreset && matches.length === 1 && matches[0].isFallback()) return;
- }
+ if (qaService) {
+ d.newStatus = 'SOLVED';
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
+ });
+ buttonSection.select('.ignore-button').html(function (d) {
+ var andComment = d.newComment ? '_comment' : '';
+ return _t.html("QA.keepRight.ignore".concat(andComment));
+ }).on('click.ignore', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
- entityEditor.presets(matches);
- }
+ var qaService = services.improveOSM;
- entityEditor.presets = function (val) {
- if (!arguments.length) return _activePresets; // don't reload the same preset
+ if (qaService) {
+ d.newStatus = 'INVALID';
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
+ });
+ } // NOTE: Don't change method name until UI v3 is merged
- if (!utilArrayIdentical(val, _activePresets)) {
- _activePresets = val;
- }
- return entityEditor;
+ improveOsmEditor.error = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return improveOsmEditor;
};
- return utilRebind(entityEditor, dispatch, 'on');
+ return utilRebind(improveOsmEditor, dispatch, 'on');
}
function uiPresetList(context) {
var presets = _mainPresetIndex.matchAllGeometry(entityGeometries());
selection.html('');
var messagewrap = selection.append('div').attr('class', 'header fillL');
- var message = messagewrap.append('h3').html(_t.html('inspector.choose'));
- messagewrap.append('button').attr('class', 'preset-choose').on('click', function () {
+ var message = messagewrap.append('h2').call(_t.append('inspector.choose'));
+ var direction = _mainLocalizer.textDirection() === 'rtl' ? 'backward' : 'forward';
+ messagewrap.append('button').attr('class', 'preset-choose').attr('title', direction).on('click', function () {
dispatch.call('cancel', this);
- }).call(svgIcon(_mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward'));
+ }).call(svgIcon("#iD-icon-".concat(direction)));
function initialKeydown(d3_event) {
// hack to let delete shortcut work when search is autofocused
if (value.length) {
results = presets.search(value, entityGeometries()[0], _currLoc);
- messageText = _t('inspector.results', {
+ messageText = _t.html('inspector.results', {
n: results.collection.length,
search: value
});
} else {
results = _mainPresetIndex.defaults(entityGeometries()[0], 36, !context.inIntro(), _currLoc);
- messageText = _t('inspector.choose');
+ messageText = _t.html('inspector.choose');
}
list.call(drawList, results);
function click() {
var isExpanded = select(this).classed('expanded');
var iconName = isExpanded ? _mainLocalizer.textDirection() === 'rtl' ? '#iD-icon-backward' : '#iD-icon-forward' : '#iD-icon-down';
- select(this).classed('expanded', !isExpanded);
+ select(this).classed('expanded', !isExpanded).attr('title', !isExpanded ? _t('icons.collapse') : _t('icons.expand'));
select(this).selectAll('div.label-inner svg.icon use').attr('href', iconName);
item.choose();
}
var geometries = entityGeometries();
- var button = wrap.append('button').attr('class', 'preset-list-button').classed('expanded', false).call(uiPresetIcon().geometry(geometries.length === 1 && geometries[0]).preset(preset)).on('click', click).on('keydown', function (d3_event) {
+ var button = wrap.append('button').attr('class', 'preset-list-button').attr('title', _t('icons.expand')).classed('expanded', false).call(uiPresetIcon().geometry(geometries.length === 1 && geometries[0]).preset(preset)).on('click', click).on('keydown', function (d3_event) {
// right arrow, expand the focused item
if (d3_event.keyCode === utilKeybinding.keyCodes[_mainLocalizer.textDirection() === 'rtl' ? '←' : '→']) {
d3_event.preventDefault();
if (isHiddenPreset) {
var isAutoHidden = context.features().autoHidden(hiddenPresetFeaturesId);
select(this).call(uiTooltip().title(_t.html('inspector.hidden_preset.' + (isAutoHidden ? 'zoom' : 'manual'), {
- features: _t.html('feature.' + hiddenPresetFeaturesId + '.description')
+ features: {
+ html: _t.html('feature.' + hiddenPresetFeaturesId + '.description')
+ }
})).placement(index < 2 ? 'bottom' : 'top'));
}
});
link.exit().remove(); // enter
var linkEnter = link.enter().append('a').attr('class', 'view-on-osm').attr('target', '_blank').attr('href', url).call(svgIcon('#iD-icon-out-link', 'inline'));
- linkEnter.append('span').html(_t.html('inspector.view_on_osm'));
+ linkEnter.append('span').call(_t.append('inspector.view_on_osm'));
}
viewOnOSM.what = function (_) {
editorPane = select(null);
var _state = 'select';
- var _entityIDs;
-
- var _newFeature = false;
-
- function inspector(selection) {
- presetList.entityIDs(_entityIDs).autofocus(_newFeature).on('choose', inspector.setPreset).on('cancel', function () {
- inspector.setPreset();
- });
- entityEditor.state(_state).entityIDs(_entityIDs).on('choose', inspector.showList);
- wrap = selection.selectAll('.panewrap').data([0]);
- var enter = wrap.enter().append('div').attr('class', 'panewrap');
- enter.append('div').attr('class', 'preset-list-pane pane');
- enter.append('div').attr('class', 'entity-editor-pane pane');
- wrap = wrap.merge(enter);
- presetPane = wrap.selectAll('.preset-list-pane');
- editorPane = wrap.selectAll('.entity-editor-pane');
-
- function shouldDefaultToPresetList() {
- // always show the inspector on hover
- if (_state !== 'select') return false; // can only change preset on single selection
-
- if (_entityIDs.length !== 1) return false;
- var entityID = _entityIDs[0];
- var entity = context.hasEntity(entityID);
- if (!entity) return false; // default to inspector if there are already tags
-
- if (entity.hasNonGeometryTags()) return false; // prompt to select preset if feature is new and untagged
-
- if (_newFeature) return true; // all existing features except vertices should default to inspector
-
- if (entity.geometry(context.graph()) !== 'vertex') return false; // show vertex relations if any
-
- if (context.graph().parentRelations(entity).length) return false; // show vertex issues if there are any
-
- if (context.validator().getEntityIssues(entityID).length) return false; // show turn retriction editor for junction vertices
-
- if (entity.isHighwayIntersection(context.graph())) return false; // otherwise show preset list for uninteresting vertices
-
- return true;
- }
-
- if (shouldDefaultToPresetList()) {
- wrap.style('right', '-100%');
- editorPane.classed('hide', true);
- presetPane.classed('hide', false).call(presetList);
- } else {
- wrap.style('right', '0%');
- presetPane.classed('hide', true);
- editorPane.classed('hide', false).call(entityEditor);
- }
-
- var footer = selection.selectAll('.footer').data([0]);
- footer = footer.enter().append('div').attr('class', 'footer').merge(footer);
- footer.call(uiViewOnOSM(context).what(context.hasEntity(_entityIDs.length === 1 && _entityIDs[0])));
- }
-
- inspector.showList = function (presets) {
- presetPane.classed('hide', false);
- wrap.transition().styleTween('right', function () {
- return interpolate$1('0%', '-100%');
- }).on('end', function () {
- editorPane.classed('hide', true);
- });
-
- if (presets) {
- presetList.presets(presets);
- }
-
- presetPane.call(presetList.autofocus(true));
- };
-
- inspector.setPreset = function (preset) {
- // upon setting multipolygon, go to the area preset list instead of the editor
- if (preset && preset.id === 'type/multipolygon') {
- presetPane.call(presetList.autofocus(true));
- } else {
- editorPane.classed('hide', false);
- wrap.transition().styleTween('right', function () {
- return interpolate$1('-100%', '0%');
- }).on('end', function () {
- presetPane.classed('hide', true);
- });
-
- if (preset) {
- entityEditor.presets([preset]);
- }
-
- editorPane.call(entityEditor);
- }
- };
-
- inspector.state = function (val) {
- if (!arguments.length) return _state;
- _state = val;
- entityEditor.state(_state); // remove any old field help overlay that might have gotten attached to the inspector
-
- context.container().selectAll('.field-help-body').remove();
- return inspector;
- };
-
- inspector.entityIDs = function (val) {
- if (!arguments.length) return _entityIDs;
- _entityIDs = val;
- return inspector;
- };
-
- inspector.newFeature = function (val) {
- if (!arguments.length) return _newFeature;
- _newFeature = val;
- return inspector;
- };
-
- return inspector;
- }
-
- function uiImproveOsmComments() {
- var _qaItem;
-
- function issueComments(selection) {
- // make the div immediately so it appears above the buttons
- var comments = selection.selectAll('.comments-container').data([0]);
- comments = comments.enter().append('div').attr('class', 'comments-container').merge(comments); // must retrieve comments from API before they can be displayed
-
- services.improveOSM.getComments(_qaItem).then(function (d) {
- if (!d.comments) return; // nothing to do here
-
- var commentEnter = comments.selectAll('.comment').data(d.comments).enter().append('div').attr('class', 'comment');
- commentEnter.append('div').attr('class', 'comment-avatar').call(svgIcon('#iD-icon-avatar', 'comment-avatar-icon'));
- var mainEnter = commentEnter.append('div').attr('class', 'comment-main');
- var metadataEnter = mainEnter.append('div').attr('class', 'comment-metadata');
- metadataEnter.append('div').attr('class', 'comment-author').each(function (d) {
- var osm = services.osm;
- var selection = select(this);
-
- if (osm && d.username) {
- selection = selection.append('a').attr('class', 'comment-author-link').attr('href', osm.userURL(d.username)).attr('target', '_blank');
- }
-
- selection.html(function (d) {
- return d.username;
- });
- });
- metadataEnter.append('div').attr('class', 'comment-date').html(function (d) {
- return _t.html('note.status.commented', {
- when: localeDateString(d.timestamp)
- });
- });
- mainEnter.append('div').attr('class', 'comment-text').append('p').html(function (d) {
- return d.text;
- });
- })["catch"](function (err) {
- console.log(err); // eslint-disable-line no-console
- });
- }
-
- function localeDateString(s) {
- if (!s) return null;
- var options = {
- day: 'numeric',
- month: 'short',
- year: 'numeric'
- };
- var d = new Date(s * 1000); // timestamp is served in seconds, date takes ms
-
- if (isNaN(d.getTime())) return null;
- return d.toLocaleDateString(_mainLocalizer.localeCode(), options);
- }
-
- issueComments.issue = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return issueComments;
- };
-
- return issueComments;
- }
-
- function uiImproveOsmDetails(context) {
- var _qaItem;
-
- function issueDetail(d) {
- if (d.desc) return d.desc;
- var issueKey = d.issueKey;
- d.replacements = d.replacements || {};
- d.replacements["default"] = _t.html('inspector.unknown'); // special key `default` works as a fallback string
-
- return _t.html("QA.improveOSM.error_types.".concat(issueKey, ".description"), d.replacements);
- }
-
- function improveOsmDetails(selection) {
- var details = selection.selectAll('.error-details').data(_qaItem ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- });
- details.exit().remove();
- var detailsEnter = details.enter().append('div').attr('class', 'error-details qa-details-container'); // description
-
- var descriptionEnter = detailsEnter.append('div').attr('class', 'qa-details-subsection');
- descriptionEnter.append('h4').html(_t.html('QA.keepRight.detail_description'));
- descriptionEnter.append('div').attr('class', 'qa-details-description-text').html(issueDetail); // If there are entity links in the error message..
-
- var relatedEntities = [];
- descriptionEnter.selectAll('.error_entity_link, .error_object_link').attr('href', '#').each(function () {
- var link = select(this);
- var isObjectLink = link.classed('error_object_link');
- var entityID = isObjectLink ? utilEntityRoot(_qaItem.objectType) + _qaItem.objectId : this.textContent;
- var entity = context.hasEntity(entityID);
- relatedEntities.push(entityID); // Add click handler
-
- link.on('mouseenter', function () {
- utilHighlightEntities([entityID], true, context);
- }).on('mouseleave', function () {
- utilHighlightEntities([entityID], false, context);
- }).on('click', function (d3_event) {
- d3_event.preventDefault();
- utilHighlightEntities([entityID], false, context);
- var osmlayer = context.layers().layer('osm');
-
- if (!osmlayer.enabled()) {
- osmlayer.enabled(true);
- }
-
- context.map().centerZoom(_qaItem.loc, 20);
-
- if (entity) {
- context.enter(modeSelect(context, [entityID]));
- } else {
- context.loadEntity(entityID, function (err, result) {
- if (err) return;
- var entity = result.data.find(function (e) {
- return e.id === entityID;
- });
- if (entity) context.enter(modeSelect(context, [entityID]));
- });
- }
- }); // Replace with friendly name if possible
- // (The entity may not yet be loaded into the graph)
-
- if (entity) {
- var name = utilDisplayName(entity); // try to use common name
-
- if (!name && !isObjectLink) {
- var preset = _mainPresetIndex.match(entity, context.graph());
- name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
- }
-
- if (name) {
- this.innerText = name;
- }
- }
- }); // Don't hide entities related to this error - #5880
-
- context.features().forceVisible(relatedEntities);
- context.map().pan([0, 0]); // trigger a redraw
- }
-
- improveOsmDetails.issue = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return improveOsmDetails;
- };
-
- return improveOsmDetails;
- }
-
- function uiImproveOsmHeader() {
- var _qaItem;
-
- function issueTitle(d) {
- var issueKey = d.issueKey;
- d.replacements = d.replacements || {};
- d.replacements["default"] = _t.html('inspector.unknown'); // special key `default` works as a fallback string
-
- return _t.html("QA.improveOSM.error_types.".concat(issueKey, ".title"), d.replacements);
- }
-
- function improveOsmHeader(selection) {
- var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- });
- header.exit().remove();
- var headerEnter = header.enter().append('div').attr('class', 'qa-header');
- var svgEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
- return d.id < 0;
- }).append('svg').attr('width', '20px').attr('height', '30px').attr('viewbox', '0 0 20 30').attr('class', function (d) {
- return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
- });
- svgEnter.append('polygon').attr('fill', 'currentColor').attr('class', 'qaItem-fill').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
- svgEnter.append('use').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('transform', 'translate(3.5, 5)').attr('xlink:href', function (d) {
- var picon = d.icon;
-
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return "#".concat(picon).concat(isMaki ? '-11' : '');
- }
- });
- headerEnter.append('div').attr('class', 'qa-header-label').html(issueTitle);
- }
-
- improveOsmHeader.issue = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return improveOsmHeader;
- };
-
- return improveOsmHeader;
- }
-
- function uiImproveOsmEditor(context) {
- var dispatch = dispatch$8('change');
- var qaDetails = uiImproveOsmDetails(context);
- var qaComments = uiImproveOsmComments();
- var qaHeader = uiImproveOsmHeader();
-
- var _qaItem;
-
- function improveOsmEditor(selection) {
- var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'close').on('click', function () {
- return context.enter(modeBrowse(context));
- }).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('QA.improveOSM.title'));
- var body = selection.selectAll('.body').data([0]);
- body = body.enter().append('div').attr('class', 'body').merge(body);
- var editor = body.selectAll('.qa-editor').data([0]);
- editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(qaComments.issue(_qaItem)).call(improveOsmSaveSection);
- }
+ var _entityIDs;
- function improveOsmSaveSection(selection) {
- var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
+ var _newFeature = false;
- var isShown = _qaItem && (isSelected || _qaItem.newComment || _qaItem.comment);
- var saveSection = selection.selectAll('.qa-save').data(isShown ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- }); // exit
+ function inspector(selection) {
+ presetList.entityIDs(_entityIDs).autofocus(_newFeature).on('choose', inspector.setPreset).on('cancel', function () {
+ inspector.setPreset();
+ });
+ entityEditor.state(_state).entityIDs(_entityIDs).on('choose', inspector.showList);
+ wrap = selection.selectAll('.panewrap').data([0]);
+ var enter = wrap.enter().append('div').attr('class', 'panewrap');
+ enter.append('div').attr('class', 'preset-list-pane pane');
+ enter.append('div').attr('class', 'entity-editor-pane pane');
+ wrap = wrap.merge(enter);
+ presetPane = wrap.selectAll('.preset-list-pane');
+ editorPane = wrap.selectAll('.entity-editor-pane');
- saveSection.exit().remove(); // enter
+ function shouldDefaultToPresetList() {
+ // always show the inspector on hover
+ if (_state !== 'select') return false; // can only change preset on single selection
- var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf');
- saveSectionEnter.append('h4').attr('class', '.qa-save-header').html(_t.html('note.newComment'));
- saveSectionEnter.append('textarea').attr('class', 'new-comment-input').attr('placeholder', _t('QA.keepRight.comment_placeholder')).attr('maxlength', 1000).property('value', function (d) {
- return d.newComment;
- }).call(utilNoAuto).on('input', changeInput).on('blur', changeInput); // update
+ if (_entityIDs.length !== 1) return false;
+ var entityID = _entityIDs[0];
+ var entity = context.hasEntity(entityID);
+ if (!entity) return false; // default to inspector if there are already tags
- saveSection = saveSectionEnter.merge(saveSection).call(qaSaveButtons);
+ if (entity.hasNonGeometryTags()) return false; // prompt to select preset if feature is new and untagged
- function changeInput() {
- var input = select(this);
- var val = input.property('value').trim();
+ if (_newFeature) return true; // all existing features except vertices should default to inspector
- if (val === '') {
- val = undefined;
- } // store the unsaved comment with the issue itself
+ if (entity.geometry(context.graph()) !== 'vertex') return false; // show vertex relations if any
+ if (context.graph().parentRelations(entity).length) return false; // show vertex issues if there are any
- _qaItem = _qaItem.update({
- newComment: val
- });
- var qaService = services.improveOSM;
+ if (context.validator().getEntityIssues(entityID).length) return false; // show turn retriction editor for junction vertices
- if (qaService) {
- qaService.replaceItem(_qaItem);
- }
+ if (entity.isHighwayIntersection(context.graph())) return false; // otherwise show preset list for uninteresting vertices
- saveSection.call(qaSaveButtons);
+ return true;
}
- }
- function qaSaveButtons(selection) {
- var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
+ if (shouldDefaultToPresetList()) {
+ wrap.style('right', '-100%');
+ editorPane.classed('hide', true);
+ presetPane.classed('hide', false).call(presetList);
+ } else {
+ wrap.style('right', '0%');
+ presetPane.classed('hide', true);
+ editorPane.classed('hide', false).call(entityEditor);
+ }
- var buttonSection = selection.selectAll('.buttons').data(isSelected ? [_qaItem] : [], function (d) {
- return d.status + d.id;
- }); // exit
+ var footer = selection.selectAll('.footer').data([0]);
+ footer = footer.enter().append('div').attr('class', 'footer').merge(footer);
+ footer.call(uiViewOnOSM(context).what(context.hasEntity(_entityIDs.length === 1 && _entityIDs[0])));
+ }
- buttonSection.exit().remove(); // enter
+ inspector.showList = function (presets) {
+ presetPane.classed('hide', false);
+ wrap.transition().styleTween('right', function () {
+ return interpolate$1('0%', '-100%');
+ }).on('end', function () {
+ editorPane.classed('hide', true);
+ });
- var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
- buttonEnter.append('button').attr('class', 'button comment-button action').html(_t.html('QA.keepRight.save_comment'));
- buttonEnter.append('button').attr('class', 'button close-button action');
- buttonEnter.append('button').attr('class', 'button ignore-button action'); // update
+ if (presets) {
+ presetList.presets(presets);
+ }
- buttonSection = buttonSection.merge(buttonEnter);
- buttonSection.select('.comment-button').attr('disabled', function (d) {
- return d.newComment ? null : true;
- }).on('click.comment', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
+ presetPane.call(presetList.autofocus(true));
+ };
- var qaService = services.improveOSM;
+ inspector.setPreset = function (preset) {
+ // upon setting multipolygon, go to the area preset list instead of the editor
+ if (preset && preset.id === 'type/multipolygon') {
+ presetPane.call(presetList.autofocus(true));
+ } else {
+ editorPane.classed('hide', false);
+ wrap.transition().styleTween('right', function () {
+ return interpolate$1('-100%', '0%');
+ }).on('end', function () {
+ presetPane.classed('hide', true);
+ });
- if (qaService) {
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
+ if (preset) {
+ entityEditor.presets([preset]);
}
- });
- buttonSection.select('.close-button').html(function (d) {
- var andComment = d.newComment ? '_comment' : '';
- return _t.html("QA.keepRight.close".concat(andComment));
- }).on('click.close', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
-
- var qaService = services.improveOSM;
- if (qaService) {
- d.newStatus = 'SOLVED';
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
- }
- });
- buttonSection.select('.ignore-button').html(function (d) {
- var andComment = d.newComment ? '_comment' : '';
- return _t.html("QA.keepRight.ignore".concat(andComment));
- }).on('click.ignore', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
+ editorPane.call(entityEditor);
+ }
+ };
- var qaService = services.improveOSM;
+ inspector.state = function (val) {
+ if (!arguments.length) return _state;
+ _state = val;
+ entityEditor.state(_state); // remove any old field help overlay that might have gotten attached to the inspector
- if (qaService) {
- d.newStatus = 'INVALID';
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
- }
- });
- } // NOTE: Don't change method name until UI v3 is merged
+ context.container().selectAll('.field-help-body').remove();
+ return inspector;
+ };
+ inspector.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+ _entityIDs = val;
+ return inspector;
+ };
- improveOsmEditor.error = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return improveOsmEditor;
+ inspector.newFeature = function (val) {
+ if (!arguments.length) return _newFeature;
+ _newFeature = val;
+ return inspector;
};
- return utilRebind(improveOsmEditor, dispatch, 'on');
+ return inspector;
}
function uiKeepRightDetails(context) {
var detailsEnter = details.enter().append('div').attr('class', 'error-details qa-details-container'); // description
var descriptionEnter = detailsEnter.append('div').attr('class', 'qa-details-subsection');
- descriptionEnter.append('h4').html(_t.html('QA.keepRight.detail_description'));
+ descriptionEnter.append('h4').call(_t.append('QA.keepRight.detail_description'));
descriptionEnter.append('div').attr('class', 'qa-details-description-text').html(issueDetail); // If there are entity links in the error message..
var relatedEntities = [];
return title;
}
- function keepRightHeader(selection) {
- var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- });
- header.exit().remove();
- var headerEnter = header.enter().append('div').attr('class', 'qa-header');
- var iconEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
- return d.id < 0;
- });
- iconEnter.append('div').attr('class', function (d) {
- return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.parentIssueType);
- }).call(svgIcon('#iD-icon-bolt', 'qaItem-fill'));
- headerEnter.append('div').attr('class', 'qa-header-label').html(issueTitle);
- }
-
- keepRightHeader.issue = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return keepRightHeader;
- };
-
- return keepRightHeader;
- }
-
- function uiViewOnKeepRight() {
- var _qaItem;
-
- function viewOnKeepRight(selection) {
- var url;
-
- if (services.keepRight && _qaItem instanceof QAItem) {
- url = services.keepRight.issueURL(_qaItem);
- }
-
- var link = selection.selectAll('.view-on-keepRight').data(url ? [url] : []); // exit
-
- link.exit().remove(); // enter
-
- var linkEnter = link.enter().append('a').attr('class', 'view-on-keepRight').attr('target', '_blank').attr('rel', 'noopener') // security measure
- .attr('href', function (d) {
- return d;
- }).call(svgIcon('#iD-icon-out-link', 'inline'));
- linkEnter.append('span').html(_t.html('inspector.view_on_keepRight'));
- }
-
- viewOnKeepRight.what = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return viewOnKeepRight;
- };
-
- return viewOnKeepRight;
- }
-
- function uiKeepRightEditor(context) {
- var dispatch = dispatch$8('change');
- var qaDetails = uiKeepRightDetails(context);
- var qaHeader = uiKeepRightHeader();
-
- var _qaItem;
-
- function keepRightEditor(selection) {
- var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'close').on('click', function () {
- return context.enter(modeBrowse(context));
- }).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('QA.keepRight.title'));
- var body = selection.selectAll('.body').data([0]);
- body = body.enter().append('div').attr('class', 'body').merge(body);
- var editor = body.selectAll('.qa-editor').data([0]);
- editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(keepRightSaveSection);
- var footer = selection.selectAll('.footer').data([0]);
- footer.enter().append('div').attr('class', 'footer').merge(footer).call(uiViewOnKeepRight().what(_qaItem));
- }
-
- function keepRightSaveSection(selection) {
- var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
-
- var isShown = _qaItem && (isSelected || _qaItem.newComment || _qaItem.comment);
- var saveSection = selection.selectAll('.qa-save').data(isShown ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- }); // exit
-
- saveSection.exit().remove(); // enter
-
- var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf');
- saveSectionEnter.append('h4').attr('class', '.qa-save-header').html(_t.html('QA.keepRight.comment'));
- saveSectionEnter.append('textarea').attr('class', 'new-comment-input').attr('placeholder', _t('QA.keepRight.comment_placeholder')).attr('maxlength', 1000).property('value', function (d) {
- return d.newComment || d.comment;
- }).call(utilNoAuto).on('input', changeInput).on('blur', changeInput); // update
-
- saveSection = saveSectionEnter.merge(saveSection).call(qaSaveButtons);
-
- function changeInput() {
- var input = select(this);
- var val = input.property('value').trim();
-
- if (val === _qaItem.comment) {
- val = undefined;
- } // store the unsaved comment with the issue itself
-
-
- _qaItem = _qaItem.update({
- newComment: val
- });
- var qaService = services.keepRight;
-
- if (qaService) {
- qaService.replaceItem(_qaItem); // update keepright cache
- }
-
- saveSection.call(qaSaveButtons);
- }
- }
-
- function qaSaveButtons(selection) {
- var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
-
- var buttonSection = selection.selectAll('.buttons').data(isSelected ? [_qaItem] : [], function (d) {
- return d.status + d.id;
- }); // exit
-
- buttonSection.exit().remove(); // enter
-
- var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
- buttonEnter.append('button').attr('class', 'button comment-button action').html(_t.html('QA.keepRight.save_comment'));
- buttonEnter.append('button').attr('class', 'button close-button action');
- buttonEnter.append('button').attr('class', 'button ignore-button action'); // update
-
- buttonSection = buttonSection.merge(buttonEnter);
- buttonSection.select('.comment-button') // select and propagate data
- .attr('disabled', function (d) {
- return d.newComment ? null : true;
- }).on('click.comment', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
-
- var qaService = services.keepRight;
-
- if (qaService) {
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
- }
- });
- buttonSection.select('.close-button') // select and propagate data
- .html(function (d) {
- var andComment = d.newComment ? '_comment' : '';
- return _t.html("QA.keepRight.close".concat(andComment));
- }).on('click.close', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
-
- var qaService = services.keepRight;
-
- if (qaService) {
- d.newStatus = 'ignore_t'; // ignore temporarily (item fixed)
-
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
- }
- });
- buttonSection.select('.ignore-button') // select and propagate data
- .html(function (d) {
- var andComment = d.newComment ? '_comment' : '';
- return _t.html("QA.keepRight.ignore".concat(andComment));
- }).on('click.ignore', function (d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
-
- var qaService = services.keepRight;
-
- if (qaService) {
- d.newStatus = 'ignore'; // ignore permanently (false positive)
-
- qaService.postUpdate(d, function (err, item) {
- return dispatch.call('change', item);
- });
- }
- });
- } // NOTE: Don't change method name until UI v3 is merged
-
-
- keepRightEditor.error = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return keepRightEditor;
- };
-
- return utilRebind(keepRightEditor, dispatch, 'on');
- }
-
- function uiOsmoseDetails(context) {
- var _qaItem;
-
- function issueString(d, type) {
- if (!d) return ''; // Issue strings are cached from Osmose API
-
- var s = services.osmose.getStrings(d.itemType);
- return type in s ? s[type] : '';
- }
-
- function osmoseDetails(selection) {
- var details = selection.selectAll('.error-details').data(_qaItem ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- });
- details.exit().remove();
- var detailsEnter = details.enter().append('div').attr('class', 'error-details qa-details-container'); // Description
-
- if (issueString(_qaItem, 'detail')) {
- var div = detailsEnter.append('div').attr('class', 'qa-details-subsection');
- div.append('h4').html(_t.html('QA.keepRight.detail_description'));
- div.append('p').attr('class', 'qa-details-description-text').html(function (d) {
- return issueString(d, 'detail');
- }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
- } // Elements (populated later as data is requested)
-
-
- var detailsDiv = detailsEnter.append('div').attr('class', 'qa-details-subsection');
- var elemsDiv = detailsEnter.append('div').attr('class', 'qa-details-subsection'); // Suggested Fix (mustn't exist for every issue type)
-
- if (issueString(_qaItem, 'fix')) {
- var _div = detailsEnter.append('div').attr('class', 'qa-details-subsection');
-
- _div.append('h4').html(_t.html('QA.osmose.fix_title'));
-
- _div.append('p').html(function (d) {
- return issueString(d, 'fix');
- }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
- } // Common Pitfalls (mustn't exist for every issue type)
-
-
- if (issueString(_qaItem, 'trap')) {
- var _div2 = detailsEnter.append('div').attr('class', 'qa-details-subsection');
-
- _div2.append('h4').html(_t.html('QA.osmose.trap_title'));
-
- _div2.append('p').html(function (d) {
- return issueString(d, 'trap');
- }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
- } // Save current item to check if UI changed by time request resolves
-
-
- var thisItem = _qaItem;
- services.osmose.loadIssueDetail(_qaItem).then(function (d) {
- // No details to add if there are no associated issue elements
- if (!d.elems || d.elems.length === 0) return; // Do nothing if UI has moved on by the time this resolves
-
- if (context.selectedErrorID() !== thisItem.id && context.container().selectAll(".qaItem.osmose.hover.itemId-".concat(thisItem.id)).empty()) return; // Things like keys and values are dynamically added to a subtitle string
-
- if (d.detail) {
- detailsDiv.append('h4').html(_t.html('QA.osmose.detail_title'));
- detailsDiv.append('p').html(function (d) {
- return d.detail;
- }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
- } // Create list of linked issue elements
-
-
- elemsDiv.append('h4').html(_t.html('QA.osmose.elems_title'));
- elemsDiv.append('ul').selectAll('li').data(d.elems).enter().append('li').append('a').attr('href', '#').attr('class', 'error_entity_link').html(function (d) {
- return d;
- }).each(function () {
- var link = select(this);
- var entityID = this.textContent;
- var entity = context.hasEntity(entityID); // Add click handler
-
- link.on('mouseenter', function () {
- utilHighlightEntities([entityID], true, context);
- }).on('mouseleave', function () {
- utilHighlightEntities([entityID], false, context);
- }).on('click', function (d3_event) {
- d3_event.preventDefault();
- utilHighlightEntities([entityID], false, context);
- var osmlayer = context.layers().layer('osm');
-
- if (!osmlayer.enabled()) {
- osmlayer.enabled(true);
- }
-
- context.map().centerZoom(d.loc, 20);
-
- if (entity) {
- context.enter(modeSelect(context, [entityID]));
- } else {
- context.loadEntity(entityID, function (err, result) {
- if (err) return;
- var entity = result.data.find(function (e) {
- return e.id === entityID;
- });
- if (entity) context.enter(modeSelect(context, [entityID]));
- });
- }
- }); // Replace with friendly name if possible
- // (The entity may not yet be loaded into the graph)
-
- if (entity) {
- var name = utilDisplayName(entity); // try to use common name
-
- if (!name) {
- var preset = _mainPresetIndex.match(entity, context.graph());
- name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
- }
-
- if (name) {
- this.innerText = name;
- }
- }
- }); // Don't hide entities related to this issue - #5880
-
- context.features().forceVisible(d.elems);
- context.map().pan([0, 0]); // trigger a redraw
- })["catch"](function (err) {
- console.log(err); // eslint-disable-line no-console
- });
- }
-
- osmoseDetails.issue = function (val) {
- if (!arguments.length) return _qaItem;
- _qaItem = val;
- return osmoseDetails;
- };
-
- return osmoseDetails;
- }
-
- function uiOsmoseHeader() {
- var _qaItem;
-
- function issueTitle(d) {
- var unknown = _t('inspector.unknown');
- if (!d) return unknown; // Issue titles supplied by Osmose
-
- var s = services.osmose.getStrings(d.itemType);
- return 'title' in s ? s.title : unknown;
- }
-
- function osmoseHeader(selection) {
- var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
- return "".concat(d.id, "-").concat(d.status || 0);
- });
- header.exit().remove();
- var headerEnter = header.enter().append('div').attr('class', 'qa-header');
- var svgEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
- return d.id < 0;
- }).append('svg').attr('width', '20px').attr('height', '30px').attr('viewbox', '0 0 20 30').attr('class', function (d) {
- return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
- });
- svgEnter.append('polygon').attr('fill', function (d) {
- return services.osmose.getColor(d.item);
- }).attr('class', 'qaItem-fill').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
- svgEnter.append('use').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('transform', 'translate(3.5, 5)').attr('xlink:href', function (d) {
- var picon = d.icon;
-
- if (!picon) {
- return '';
- } else {
- var isMaki = /^maki-/.test(picon);
- return "#".concat(picon).concat(isMaki ? '-11' : '');
- }
+ function keepRightHeader(selection) {
+ var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ });
+ header.exit().remove();
+ var headerEnter = header.enter().append('div').attr('class', 'qa-header');
+ var iconEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
+ return d.id < 0;
});
+ iconEnter.append('div').attr('class', function (d) {
+ return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.parentIssueType);
+ }).call(svgIcon('#iD-icon-bolt', 'qaItem-fill'));
headerEnter.append('div').attr('class', 'qa-header-label').html(issueTitle);
}
- osmoseHeader.issue = function (val) {
+ keepRightHeader.issue = function (val) {
if (!arguments.length) return _qaItem;
_qaItem = val;
- return osmoseHeader;
+ return keepRightHeader;
};
- return osmoseHeader;
+ return keepRightHeader;
}
- function uiViewOnOsmose() {
+ function uiViewOnKeepRight() {
var _qaItem;
- function viewOnOsmose(selection) {
+ function viewOnKeepRight(selection) {
var url;
- if (services.osmose && _qaItem instanceof QAItem) {
- url = services.osmose.itemURL(_qaItem);
+ if (services.keepRight && _qaItem instanceof QAItem) {
+ url = services.keepRight.issueURL(_qaItem);
}
- var link = selection.selectAll('.view-on-osmose').data(url ? [url] : []); // exit
+ var link = selection.selectAll('.view-on-keepRight').data(url ? [url] : []); // exit
link.exit().remove(); // enter
- var linkEnter = link.enter().append('a').attr('class', 'view-on-osmose').attr('target', '_blank').attr('rel', 'noopener') // security measure
+ var linkEnter = link.enter().append('a').attr('class', 'view-on-keepRight').attr('target', '_blank').attr('rel', 'noopener') // security measure
.attr('href', function (d) {
return d;
}).call(svgIcon('#iD-icon-out-link', 'inline'));
- linkEnter.append('span').html(_t.html('inspector.view_on_osmose'));
+ linkEnter.append('span').call(_t.append('inspector.view_on_keepRight'));
}
- viewOnOsmose.what = function (val) {
+ viewOnKeepRight.what = function (val) {
if (!arguments.length) return _qaItem;
_qaItem = val;
- return viewOnOsmose;
+ return viewOnKeepRight;
};
- return viewOnOsmose;
+ return viewOnKeepRight;
}
- function uiOsmoseEditor(context) {
+ function uiKeepRightEditor(context) {
var dispatch = dispatch$8('change');
- var qaDetails = uiOsmoseDetails(context);
- var qaHeader = uiOsmoseHeader();
+ var qaDetails = uiKeepRightDetails(context);
+ var qaHeader = uiKeepRightHeader();
var _qaItem;
- function osmoseEditor(selection) {
- var header = selection.selectAll('.header').data([0]);
- var headerEnter = header.enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'close').on('click', function () {
+ function keepRightEditor(selection) {
+ var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
return context.enter(modeBrowse(context));
}).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('QA.osmose.title'));
+ headerEnter.append('h2').call(_t.append('QA.keepRight.title'));
var body = selection.selectAll('.body').data([0]);
body = body.enter().append('div').attr('class', 'body').merge(body);
var editor = body.selectAll('.qa-editor').data([0]);
- editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(osmoseSaveSection);
+ editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(keepRightSaveSection);
var footer = selection.selectAll('.footer').data([0]);
- footer.enter().append('div').attr('class', 'footer').merge(footer).call(uiViewOnOsmose().what(_qaItem));
+ footer.enter().append('div').attr('class', 'footer').merge(footer).call(uiViewOnKeepRight().what(_qaItem));
}
- function osmoseSaveSection(selection) {
+ function keepRightSaveSection(selection) {
var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
- var isShown = _qaItem && isSelected;
+ var isShown = _qaItem && (isSelected || _qaItem.newComment || _qaItem.comment);
var saveSection = selection.selectAll('.qa-save').data(isShown ? [_qaItem] : [], function (d) {
return "".concat(d.id, "-").concat(d.status || 0);
}); // exit
saveSection.exit().remove(); // enter
- var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf'); // update
+ var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf');
+ saveSectionEnter.append('h4').attr('class', '.qa-save-header').call(_t.append('QA.keepRight.comment'));
+ saveSectionEnter.append('textarea').attr('class', 'new-comment-input').attr('placeholder', _t('QA.keepRight.comment_placeholder')).attr('maxlength', 1000).property('value', function (d) {
+ return d.newComment || d.comment;
+ }).call(utilNoAuto).on('input', changeInput).on('blur', changeInput); // update
saveSection = saveSectionEnter.merge(saveSection).call(qaSaveButtons);
+
+ function changeInput() {
+ var input = select(this);
+ var val = input.property('value').trim();
+
+ if (val === _qaItem.comment) {
+ val = undefined;
+ } // store the unsaved comment with the issue itself
+
+
+ _qaItem = _qaItem.update({
+ newComment: val
+ });
+ var qaService = services.keepRight;
+
+ if (qaService) {
+ qaService.replaceItem(_qaItem); // update keepright cache
+ }
+
+ saveSection.call(qaSaveButtons);
+ }
}
function qaSaveButtons(selection) {
buttonSection.exit().remove(); // enter
var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
+ buttonEnter.append('button').attr('class', 'button comment-button action').call(_t.append('QA.keepRight.save_comment'));
buttonEnter.append('button').attr('class', 'button close-button action');
buttonEnter.append('button').attr('class', 'button ignore-button action'); // update
buttonSection = buttonSection.merge(buttonEnter);
- buttonSection.select('.close-button').html(_t.html('QA.keepRight.close')).on('click.close', function (d3_event, d) {
+ buttonSection.select('.comment-button') // select and propagate data
+ .attr('disabled', function (d) {
+ return d.newComment ? null : true;
+ }).on('click.comment', function (d3_event, d) {
this.blur(); // avoid keeping focus on the button - #4641
- var qaService = services.osmose;
+ var qaService = services.keepRight;
if (qaService) {
- d.newStatus = 'done';
qaService.postUpdate(d, function (err, item) {
return dispatch.call('change', item);
});
}
});
- buttonSection.select('.ignore-button').html(_t.html('QA.keepRight.ignore')).on('click.ignore', function (d3_event, d) {
+ buttonSection.select('.close-button') // select and propagate data
+ .html(function (d) {
+ var andComment = d.newComment ? '_comment' : '';
+ return _t.html("QA.keepRight.close".concat(andComment));
+ }).on('click.close', function (d3_event, d) {
this.blur(); // avoid keeping focus on the button - #4641
- var qaService = services.osmose;
+ var qaService = services.keepRight;
if (qaService) {
- d.newStatus = 'false';
+ d.newStatus = 'ignore_t'; // ignore temporarily (item fixed)
+
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
+ });
+ buttonSection.select('.ignore-button') // select and propagate data
+ .html(function (d) {
+ var andComment = d.newComment ? '_comment' : '';
+ return _t.html("QA.keepRight.ignore".concat(andComment));
+ }).on('click.ignore', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
+
+ var qaService = services.keepRight;
+
+ if (qaService) {
+ d.newStatus = 'ignore'; // ignore permanently (false positive)
+
qaService.postUpdate(d, function (err, item) {
return dispatch.call('change', item);
});
} // NOTE: Don't change method name until UI v3 is merged
- osmoseEditor.error = function (val) {
+ keepRightEditor.error = function (val) {
if (!arguments.length) return _qaItem;
_qaItem = val;
- return osmoseEditor;
+ return keepRightEditor;
};
- return utilRebind(osmoseEditor, dispatch, 'on');
+ return utilRebind(keepRightEditor, dispatch, 'on');
+ }
+
+ function uiLasso(context) {
+ var group, polygon;
+ lasso.coordinates = [];
+
+ function lasso(selection) {
+ context.container().classed('lasso', true);
+ group = selection.append('g').attr('class', 'lasso hide');
+ polygon = group.append('path').attr('class', 'lasso-path');
+ group.call(uiToggle(true));
+ }
+
+ function draw() {
+ if (polygon) {
+ polygon.data([lasso.coordinates]).attr('d', function (d) {
+ return 'M' + d.join(' L') + ' Z';
+ });
+ }
+ }
+
+ lasso.extent = function () {
+ return lasso.coordinates.reduce(function (extent, point) {
+ return extent.extend(geoExtent(point));
+ }, geoExtent());
+ };
+
+ lasso.p = function (_) {
+ if (!arguments.length) return lasso;
+ lasso.coordinates.push(_);
+ draw();
+ return lasso;
+ };
+
+ lasso.close = function () {
+ if (group) {
+ group.call(uiToggle(false, function () {
+ select(this).remove();
+ }));
+ }
+
+ context.container().classed('lasso', false);
+ };
+
+ return lasso;
}
function uiNoteComments() {
selection = selection.append('a').attr('class', 'comment-author-link').attr('href', osm.userURL(d.user)).attr('target', '_blank');
}
- selection.html(function (d) {
- return d.user || _t.html('note.anonymous');
- });
+ if (d.user) {
+ selection.text(d.user);
+ } else {
+ selection.call(_t.append('note.anonymous'));
+ }
});
metadataEnter.append('div').attr('class', 'comment-date').html(function (d) {
- return _t('note.status.' + d.action, {
+ return _t.html('note.status.' + d.action, {
when: localeDateString(d.date)
});
});
statusIcon = '#iD-icon-apply';
}
- iconEnter.append('div').attr('class', 'note-icon-annotation').call(svgIcon(statusIcon, 'icon-annotation'));
+ iconEnter.append('div').attr('class', 'note-icon-annotation').attr('title', _t('icons.close')).call(svgIcon(statusIcon, 'icon-annotation'));
});
headerEnter.append('div').attr('class', 'note-header-label').html(function (d) {
if (_note.isNew()) {
- return _t('note.new');
+ return _t.html('note.new');
}
- return _t('note.note') + ' ' + d.id + ' ' + (d.status === 'closed' ? _t('note.closed') : '');
+ return _t.html('note.note') + ' ' + d.id + ' ' + (d.status === 'closed' ? _t.html('note.closed') : '');
});
}
var linkEnter = link.enter().append('a').attr('class', 'note-report').attr('target', '_blank').attr('href', function (d) {
return d;
}).call(svgIcon('#iD-icon-out-link', 'inline'));
- linkEnter.append('span').html(_t.html('note.report'));
+ linkEnter.append('span').call(_t.append('note.report'));
}
noteReport.note = function (val) {
function noteEditor(selection) {
var header = selection.selectAll('.header').data([0]);
var headerEnter = header.enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'close').on('click', function () {
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
context.enter(modeBrowse(context));
}).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('note.title'));
+ headerEnter.append('h2').call(_t.append('note.title'));
var body = selection.selectAll('.body').data([0]);
body = body.enter().append('div').attr('class', 'body').merge(body);
var editor = body.selectAll('.note-editor').data([0]);
// }
noteSaveEnter.append('h4').attr('class', '.note-save-header').html(function () {
- return _note.isNew() ? _t('note.newDescription') : _t('note.newComment');
+ return _note.isNew() ? _t.html('note.newDescription') : _t.html('note.newComment');
});
var commentTextarea = noteSaveEnter.append('textarea').attr('class', 'new-comment-input').attr('placeholder', _t('note.inputPlaceholder')).attr('maxlength', 1000).property('value', function (d) {
return d.newComment;
authWarning.exit().transition().duration(200).style('opacity', 0).remove();
var authEnter = authWarning.enter().insert('div', '.tag-reference-body').attr('class', 'field-warning auth-warning').style('opacity', 0);
authEnter.call(svgIcon('#iD-icon-alert', 'inline'));
- authEnter.append('span').html(_t.html('note.login'));
- authEnter.append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).append('span').html(_t.html('login')).on('click.note-login', function (d3_event) {
+ authEnter.append('span').call(_t.append('note.login'));
+ authEnter.append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).append('span').call(_t.append('login')).on('click.note-login', function (d3_event) {
d3_event.preventDefault();
osm.authenticate();
});
authEnter.transition().duration(200).style('opacity', 1);
var prose = detailSection.selectAll('.note-save-prose').data(hasAuth ? [0] : []);
prose.exit().remove();
- prose = prose.enter().append('p').attr('class', 'note-save-prose').html(_t.html('note.upload_explanation')).merge(prose);
+ prose = prose.enter().append('p').attr('class', 'note-save-prose').call(_t.append('note.upload_explanation')).merge(prose);
osm.userDetails(function (err, user) {
if (err) return;
var userLink = select(document.createElement('div'));
userLink.append('img').attr('src', user.image_url).attr('class', 'icon pre-text user-icon');
}
- userLink.append('a').attr('class', 'user-info').html(user.display_name).attr('href', osm.userURL(user.display_name)).attr('target', '_blank');
+ userLink.append('a').attr('class', 'user-info').text(user.display_name).attr('href', osm.userURL(user.display_name)).attr('target', '_blank');
prose.html(_t.html('note.upload_explanation_with_user', {
- user: userLink.html()
+ user: {
+ html: userLink.html()
+ }
}));
});
}
var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
if (_note.isNew()) {
- buttonEnter.append('button').attr('class', 'button cancel-button secondary-action').html(_t.html('confirm.cancel'));
- buttonEnter.append('button').attr('class', 'button save-button action').html(_t.html('note.save'));
+ buttonEnter.append('button').attr('class', 'button cancel-button secondary-action').call(_t.append('confirm.cancel'));
+ buttonEnter.append('button').attr('class', 'button save-button action').call(_t.append('note.save'));
} else {
buttonEnter.append('button').attr('class', 'button status-button action');
- buttonEnter.append('button').attr('class', 'button comment-button action').html(_t.html('note.comment'));
+ buttonEnter.append('button').attr('class', 'button comment-button action').call(_t.append('note.comment'));
} // update
.attr('disabled', hasAuth ? null : true).html(function (d) {
var action = d.status === 'open' ? 'close' : 'open';
var andComment = d.newComment ? '_comment' : '';
- return _t('note.' + action + andComment);
+ return _t.html('note.' + action + andComment);
}).on('click.status', clickStatus);
buttonSection.select('.comment-button') // select and propagate data
.attr('disabled', isSaveDisabled).on('click.comment', clickComment);
function clickStatus(d3_event, d) {
this.blur(); // avoid keeping focus on the button - #4641
- var osm = services.osm;
-
- if (osm) {
- var setStatus = d.status === 'open' ? 'closed' : 'open';
- osm.postNoteUpdate(d, setStatus, function (err, note) {
- dispatch.call('change', note);
- });
- }
- }
-
- function clickComment(d3_event, d) {
- this.blur(); // avoid keeping focus on the button - #4641
-
- var osm = services.osm;
-
- if (osm) {
- osm.postNoteUpdate(d, d.status, function (err, note) {
- dispatch.call('change', note);
- });
- }
- }
-
- noteEditor.note = function (val) {
- if (!arguments.length) return _note;
- _note = val;
- return noteEditor;
- };
-
- noteEditor.newNote = function (val) {
- if (!arguments.length) return _newNote;
- _newNote = val;
- return noteEditor;
- };
-
- return utilRebind(noteEditor, dispatch, 'on');
- }
-
- function uiSidebar(context) {
- var inspector = uiInspector(context);
- var dataEditor = uiDataEditor(context);
- var noteEditor = uiNoteEditor(context);
- var improveOsmEditor = uiImproveOsmEditor(context);
- var keepRightEditor = uiKeepRightEditor(context);
- var osmoseEditor = uiOsmoseEditor(context);
-
- var _current;
-
- var _wasData = false;
- var _wasNote = false;
- var _wasQaItem = false; // use pointer events on supported platforms; fallback to mouse events
-
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
-
- function sidebar(selection) {
- var container = context.container();
- var minWidth = 240;
- var sidebarWidth;
- var containerWidth;
- var dragOffset; // Set the initial width constraints
-
- selection.style('min-width', minWidth + 'px').style('max-width', '400px').style('width', '33.3333%');
- var resizer = selection.append('div').attr('class', 'sidebar-resizer').on(_pointerPrefix + 'down.sidebar-resizer', pointerdown);
- var downPointerId, lastClientX, containerLocGetter;
-
- function pointerdown(d3_event) {
- if (downPointerId) return;
- if ('button' in d3_event && d3_event.button !== 0) return;
- downPointerId = d3_event.pointerId || 'mouse';
- lastClientX = d3_event.clientX;
- containerLocGetter = utilFastMouse(container.node()); // offset from edge of sidebar-resizer
-
- dragOffset = utilFastMouse(resizer.node())(d3_event)[0] - 1;
- sidebarWidth = selection.node().getBoundingClientRect().width;
- containerWidth = container.node().getBoundingClientRect().width;
- var widthPct = sidebarWidth / containerWidth * 100;
- selection.style('width', widthPct + '%') // lock in current width
- .style('max-width', '85%'); // but allow larger widths
-
- resizer.classed('dragging', true);
- select(window).on('touchmove.sidebar-resizer', function (d3_event) {
- // disable page scrolling while resizing on touch input
- d3_event.preventDefault();
- }, {
- passive: false
- }).on(_pointerPrefix + 'move.sidebar-resizer', pointermove).on(_pointerPrefix + 'up.sidebar-resizer pointercancel.sidebar-resizer', pointerup);
- }
-
- function pointermove(d3_event) {
- if (downPointerId !== (d3_event.pointerId || 'mouse')) return;
- d3_event.preventDefault();
- var dx = d3_event.clientX - lastClientX;
- lastClientX = d3_event.clientX;
- var isRTL = _mainLocalizer.textDirection() === 'rtl';
- var scaleX = isRTL ? 0 : 1;
- var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
- var x = containerLocGetter(d3_event)[0] - dragOffset;
- sidebarWidth = isRTL ? containerWidth - x : x;
- var isCollapsed = selection.classed('collapsed');
- var shouldCollapse = sidebarWidth < minWidth;
- selection.classed('collapsed', shouldCollapse);
-
- if (shouldCollapse) {
- if (!isCollapsed) {
- selection.style(xMarginProperty, '-400px').style('width', '400px');
- context.ui().onResize([(sidebarWidth - dx) * scaleX, 0]);
- }
- } else {
- var widthPct = sidebarWidth / containerWidth * 100;
- selection.style(xMarginProperty, null).style('width', widthPct + '%');
-
- if (isCollapsed) {
- context.ui().onResize([-sidebarWidth * scaleX, 0]);
- } else {
- context.ui().onResize([-dx * scaleX, 0]);
- }
- }
- }
-
- function pointerup(d3_event) {
- if (downPointerId !== (d3_event.pointerId || 'mouse')) return;
- downPointerId = null;
- resizer.classed('dragging', false);
- select(window).on('touchmove.sidebar-resizer', null).on(_pointerPrefix + 'move.sidebar-resizer', null).on(_pointerPrefix + 'up.sidebar-resizer pointercancel.sidebar-resizer', null);
- }
-
- var featureListWrap = selection.append('div').attr('class', 'feature-list-pane').call(uiFeatureList(context));
- var inspectorWrap = selection.append('div').attr('class', 'inspector-hidden inspector-wrap');
-
- var hoverModeSelect = function hoverModeSelect(targets) {
- context.container().selectAll('.feature-list-item button').classed('hover', false);
-
- if (context.selectedIDs().length > 1 && targets && targets.length) {
- var elements = context.container().selectAll('.feature-list-item button').filter(function (node) {
- return targets.indexOf(node) !== -1;
- });
-
- if (!elements.empty()) {
- elements.classed('hover', true);
- }
- }
- };
-
- sidebar.hoverModeSelect = throttle(hoverModeSelect, 200);
-
- function hover(targets) {
- var datum = targets && targets.length && targets[0];
-
- if (datum && datum.__featurehash__) {
- // hovering on data
- _wasData = true;
- sidebar.show(dataEditor.datum(datum));
- selection.selectAll('.sidebar-component').classed('inspector-hover', true);
- } else if (datum instanceof osmNote) {
- if (context.mode().id === 'drag-note') return;
- _wasNote = true;
- var osm = services.osm;
-
- if (osm) {
- datum = osm.getNote(datum.id); // marker may contain stale data - get latest
- }
-
- sidebar.show(noteEditor.note(datum));
- selection.selectAll('.sidebar-component').classed('inspector-hover', true);
- } else if (datum instanceof QAItem) {
- _wasQaItem = true;
- var errService = services[datum.service];
-
- if (errService) {
- // marker may contain stale data - get latest
- datum = errService.getError(datum.id);
- } // Currently only three possible services
-
-
- var errEditor;
-
- if (datum.service === 'keepRight') {
- errEditor = keepRightEditor;
- } else if (datum.service === 'osmose') {
- errEditor = osmoseEditor;
- } else {
- errEditor = improveOsmEditor;
- }
-
- context.container().selectAll('.qaItem.' + datum.service).classed('hover', function (d) {
- return d.id === datum.id;
- });
- sidebar.show(errEditor.error(datum));
- selection.selectAll('.sidebar-component').classed('inspector-hover', true);
- } else if (!_current && datum instanceof osmEntity) {
- featureListWrap.classed('inspector-hidden', true);
- inspectorWrap.classed('inspector-hidden', false).classed('inspector-hover', true);
-
- if (!inspector.entityIDs() || !utilArrayIdentical(inspector.entityIDs(), [datum.id]) || inspector.state() !== 'hover') {
- inspector.state('hover').entityIDs([datum.id]).newFeature(false);
- inspectorWrap.call(inspector);
- }
- } else if (!_current) {
- featureListWrap.classed('inspector-hidden', false);
- inspectorWrap.classed('inspector-hidden', true);
- inspector.state('hide');
- } else if (_wasData || _wasNote || _wasQaItem) {
- _wasNote = false;
- _wasData = false;
- _wasQaItem = false;
- context.container().selectAll('.note').classed('hover', false);
- context.container().selectAll('.qaItem').classed('hover', false);
- sidebar.hide();
- }
- }
-
- sidebar.hover = throttle(hover, 200);
-
- sidebar.intersects = function (extent) {
- var rect = selection.node().getBoundingClientRect();
- return extent.intersects([context.projection.invert([0, rect.height]), context.projection.invert([rect.width, 0])]);
- };
-
- sidebar.select = function (ids, newFeature) {
- sidebar.hide();
-
- if (ids && ids.length) {
- var entity = ids.length === 1 && context.entity(ids[0]);
-
- if (entity && newFeature && selection.classed('collapsed')) {
- // uncollapse the sidebar
- var extent = entity.extent(context.graph());
- sidebar.expand(sidebar.intersects(extent));
- }
-
- featureListWrap.classed('inspector-hidden', true);
- inspectorWrap.classed('inspector-hidden', false).classed('inspector-hover', false); // reload the UI even if the ids are the same since the entities
- // themselves may have changed
-
- inspector.state('select').entityIDs(ids).newFeature(newFeature);
- inspectorWrap.call(inspector);
- } else {
- inspector.state('hide');
- }
- };
-
- sidebar.showPresetList = function () {
- inspector.showList();
- };
-
- sidebar.show = function (component, element) {
- featureListWrap.classed('inspector-hidden', true);
- inspectorWrap.classed('inspector-hidden', true);
- if (_current) _current.remove();
- _current = selection.append('div').attr('class', 'sidebar-component').call(component, element);
- };
-
- sidebar.hide = function () {
- featureListWrap.classed('inspector-hidden', false);
- inspectorWrap.classed('inspector-hidden', true);
- if (_current) _current.remove();
- _current = null;
- };
-
- sidebar.expand = function (moveMap) {
- if (selection.classed('collapsed')) {
- sidebar.toggle(moveMap);
- }
- };
-
- sidebar.collapse = function (moveMap) {
- if (!selection.classed('collapsed')) {
- sidebar.toggle(moveMap);
- }
- };
-
- sidebar.toggle = function (moveMap) {
- // Don't allow sidebar to toggle when the user is in the walkthrough.
- if (context.inIntro()) return;
- var isCollapsed = selection.classed('collapsed');
- var isCollapsing = !isCollapsed;
- var isRTL = _mainLocalizer.textDirection() === 'rtl';
- var scaleX = isRTL ? 0 : 1;
- var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
- sidebarWidth = selection.node().getBoundingClientRect().width; // switch from % to px
-
- selection.style('width', sidebarWidth + 'px');
- var startMargin, endMargin, lastMargin;
-
- if (isCollapsing) {
- startMargin = lastMargin = 0;
- endMargin = -sidebarWidth;
- } else {
- startMargin = lastMargin = -sidebarWidth;
- endMargin = 0;
- }
-
- if (!isCollapsing) {
- // unhide the sidebar's content before it transitions onscreen
- selection.classed('collapsed', isCollapsing);
- }
-
- selection.transition().style(xMarginProperty, endMargin + 'px').tween('panner', function () {
- var i = d3_interpolateNumber(startMargin, endMargin);
- return function (t) {
- var dx = lastMargin - Math.round(i(t));
- lastMargin = lastMargin - dx;
- context.ui().onResize(moveMap ? undefined : [dx * scaleX, 0]);
- };
- }).on('end', function () {
- if (isCollapsing) {
- // hide the sidebar's content after it transitions offscreen
- selection.classed('collapsed', isCollapsing);
- } // switch back from px to %
-
-
- if (!isCollapsing) {
- var containerWidth = container.node().getBoundingClientRect().width;
- var widthPct = sidebarWidth / containerWidth * 100;
- selection.style(xMarginProperty, null).style('width', widthPct + '%');
- }
- });
- }; // toggle the sidebar collapse when double-clicking the resizer
-
-
- resizer.on('dblclick', function (d3_event) {
- d3_event.preventDefault();
-
- if (d3_event.sourceEvent) {
- d3_event.sourceEvent.preventDefault();
- }
-
- sidebar.toggle();
- }); // ensure hover sidebar is closed when zooming out beyond editable zoom
-
- context.map().on('crossEditableZoom.sidebar', function (within) {
- if (!within && !selection.select('.inspector-hover').empty()) {
- hover([]);
- }
- });
- }
-
- sidebar.showPresetList = function () {};
-
- sidebar.hover = function () {};
-
- sidebar.hover.cancel = function () {};
-
- sidebar.intersects = function () {};
+ var osm = services.osm;
- sidebar.select = function () {};
+ if (osm) {
+ var setStatus = d.status === 'open' ? 'closed' : 'open';
+ osm.postNoteUpdate(d, setStatus, function (err, note) {
+ dispatch.call('change', note);
+ });
+ }
+ }
- sidebar.show = function () {};
+ function clickComment(d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
- sidebar.hide = function () {};
+ var osm = services.osm;
- sidebar.expand = function () {};
+ if (osm) {
+ osm.postNoteUpdate(d, d.status, function (err, note) {
+ dispatch.call('change', note);
+ });
+ }
+ }
- sidebar.collapse = function () {};
+ noteEditor.note = function (val) {
+ if (!arguments.length) return _note;
+ _note = val;
+ return noteEditor;
+ };
- sidebar.toggle = function () {};
+ noteEditor.newNote = function (val) {
+ if (!arguments.length) return _newNote;
+ _newNote = val;
+ return noteEditor;
+ };
- return sidebar;
+ return utilRebind(noteEditor, dispatch, 'on');
}
function uiSourceSwitch(context) {
}
var sourceSwitch = function sourceSwitch(selection) {
- selection.append('a').attr('href', '#').html(_t.html('source_switch.live')).attr('class', 'live chip').on('click', click);
+ selection.append('a').attr('href', '#').call(_t.append('source_switch.live')).attr('class', 'live chip').on('click', click);
};
sourceSwitch.keys = function (_) {
};
}
+ function uiSectionPrivacy(context) {
+ var section = uiSection('preferences-third-party', context).label(_t.html('preferences.privacy.title')).disclosureContent(renderDisclosureContent);
+
+ function renderDisclosureContent(selection) {
+ // enter
+ selection.selectAll('.privacy-options-list').data([0]).enter().append('ul').attr('class', 'layer-list privacy-options-list');
+ var thirdPartyIconsEnter = selection.select('.privacy-options-list').selectAll('.privacy-third-party-icons-item').data([corePreferences('preferences.privacy.thirdpartyicons') || 'true']).enter().append('li').attr('class', 'privacy-third-party-icons-item').append('label').call(uiTooltip().title(_t.html('preferences.privacy.third_party_icons.tooltip')).placement('bottom'));
+ thirdPartyIconsEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event, d) {
+ d3_event.preventDefault();
+ corePreferences('preferences.privacy.thirdpartyicons', d === 'true' ? 'false' : 'true');
+ });
+ thirdPartyIconsEnter.append('span').call(_t.append('preferences.privacy.third_party_icons.description')); // update
+
+ selection.selectAll('.privacy-third-party-icons-item').classed('active', function (d) {
+ return d === 'true';
+ }).select('input').property('checked', function (d) {
+ return d === 'true';
+ }); // Privacy Policy link
+
+ selection.selectAll('.privacy-link').data([0]).enter().append('div').attr('class', 'privacy-link').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/openstreetmap/iD/blob/release/PRIVACY.md').append('span').call(_t.append('preferences.privacy.privacy_link'));
+ }
+
+ corePreferences.onChange('preferences.privacy.thirdpartyicons', section.reRender);
+ return section;
+ }
+
function uiSplash(context) {
return function (selection) {
// Exception - if there are restorable changes, skip this splash screen.
var modalSelection = uiModal(selection);
modalSelection.select('.modal').attr('class', 'modal-splash modal');
var introModal = modalSelection.select('.content').append('div').attr('class', 'fillL');
- introModal.append('div').attr('class', 'modal-section').append('h3').html(_t.html('splash.welcome'));
+ introModal.append('div').attr('class', 'modal-section').append('h3').call(_t.append('splash.welcome'));
var modalSection = introModal.append('div').attr('class', 'modal-section');
modalSection.append('p').html(_t.html('splash.text', {
version: context.version,
- website: '<a target="_blank" href="https://github.com/openstreetmap/iD/blob/develop/CHANGELOG.md#whats-new">changelog</a>',
- github: '<a target="_blank" href="https://github.com/openstreetmap/iD/issues">github.com</a>'
+ website: {
+ html: '<a target="_blank" href="https://github.com/openstreetmap/iD/blob/develop/CHANGELOG.md#whats-new">changelog</a>'
+ },
+ github: {
+ html: '<a target="_blank" href="https://github.com/openstreetmap/iD/issues">github.com</a>'
+ }
}));
modalSection.append('p').html(_t.html('splash.privacy', {
updateMessage: updateMessage,
- privacyLink: '<a target="_blank" href="https://github.com/openstreetmap/iD/blob/release/PRIVACY.md">' + _t('splash.privacy_policy') + '</a>'
+ privacyLink: {
+ html: '<a target="_blank" href="https://github.com/openstreetmap/iD/blob/release/PRIVACY.md">' + _t('splash.privacy_policy') + '</a>'
+ }
}));
+ uiSectionPrivacy(context).label(_t.html('splash.privacy_settings')).render(modalSection);
var buttonWrap = introModal.append('div').attr('class', 'modal-actions');
var walkthrough = buttonWrap.append('button').attr('class', 'walkthrough').on('click', function () {
context.container().call(uiIntro(context));
modalSelection.close();
});
walkthrough.append('svg').attr('class', 'logo logo-walkthrough').append('use').attr('xlink:href', '#iD-logo-walkthrough');
- walkthrough.append('div').html(_t.html('splash.walkthrough'));
+ walkthrough.append('div').call(_t.append('splash.walkthrough'));
var startEditing = buttonWrap.append('button').attr('class', 'start-editing').on('click', modalSelection.close);
startEditing.append('svg').attr('class', 'logo logo-features').append('use').attr('xlink:href', '#iD-logo-features');
- startEditing.append('div').html(_t.html('splash.start'));
+ startEditing.append('div').call(_t.append('splash.start'));
modalSelection.select('button.close').attr('class', 'hide');
};
}
// the status (we're getting the status of the previous api)
return;
} else if (apiStatus === 'rateLimited') {
- selection.html(_t.html('osm_api_status.message.rateLimit')).append('a').attr('href', '#').attr('class', 'api-status-login').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).append('span').html(_t.html('login')).on('click.login', function (d3_event) {
+ selection.call(_t.append('osm_api_status.message.rateLimit')).append('a').attr('href', '#').attr('class', 'api-status-login').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).append('span').call(_t.append('login')).on('click.login', function (d3_event) {
d3_event.preventDefault();
osm.authenticate();
});
// TODO: nice messages for different error types
- selection.html(_t.html('osm_api_status.message.error') + ' ').append('a').attr('href', '#') // let the user manually retry their connection directly
- .html(_t.html('osm_api_status.retry')).on('click.retry', function (d3_event) {
+ selection.call(_t.append('osm_api_status.message.error', {
+ suffix: ' '
+ })).append('a').attr('href', '#') // let the user manually retry their connection directly
+ .call(_t.append('osm_api_status.retry')).on('click.retry', function (d3_event) {
d3_event.preventDefault();
throttledRetry();
});
}
} else if (apiStatus === 'readonly') {
- selection.html(_t.html('osm_api_status.message.readonly'));
+ selection.call(_t.append('osm_api_status.message.readonly'));
} else if (apiStatus === 'offline') {
- selection.html(_t.html('osm_api_status.message.offline'));
+ selection.call(_t.append('osm_api_status.message.offline'));
}
selection.attr('class', 'api-status ' + (err ? 'error' : apiStatus));
osm.on('apiStatusChange.uiStatus', update);
context.history().on('storage_error', function () {
- selection.html(_t.html('osm_api_status.message.local_storage_full'));
+ selection.call(_t.append('osm_api_status.message.local_storage_full'));
selection.attr('class', 'api-status error');
}); // reload the status periodically regardless of other factors
};
}
- function modeDrawArea(context, wayID, startGraph, button) {
- var mode = {
- button: button,
- id: 'draw-area'
- };
- var behavior = behaviorDrawWay(context, wayID, mode, startGraph).on('rejectedSelfIntersection.modeDrawArea', function () {
- context.ui().flash.iconName('#iD-icon-no').label(_t('self_intersection.error.areas'))();
- });
- mode.wayID = wayID;
-
- mode.enter = function () {
- context.install(behavior);
- };
+ // for punction see https://stackoverflow.com/a/21224179
- mode.exit = function () {
- context.uninstall(behavior);
- };
+ function simplify(str) {
+ if (typeof str !== 'string') return '';
+ return diacritics.remove(str.replace(/&/g, 'and').replace(/İ/ig, 'i').replace(/[\s\-=_!"#%'*{},.\/:;?\(\)\[\]@\\$\^*+<>«»~`’\u00a1\u00a7\u00b6\u00b7\u00bf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d\u166e\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u200b-\u200f\u2016\u2017\u2020-\u2027\u2030-\u2038\u203b-\u203e\u2041-\u2043\u2047-\u2051\u2053\u2055-\u205e\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00\u2e01\u2e06-\u2e08\u2e0b\u2e0e-\u2e16\u2e18\u2e19\u2e1b\u2e1e\u2e1f\u2e2a-\u2e2e\u2e30-\u2e39\u3001-\u3003\u303d\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a\ufe6b\ufeff\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65]+/g, '').toLowerCase());
+ }
- mode.selectedIDs = function () {
- return [wayID];
- };
+ // `resolveStrings`
+ // Resolves the text strings for a given community index item
+ //
+ // Arguments
+ // `item`: Object containing the community index item
+ // `defaults`: Object containing the community index default strings
+ // `localizerFn?`: optional function we will call to do the localization.
+ // This function should be like the iD `t()` function that
+ // accepts a `stringID` and returns a localized string
+ //
+ // Returns
+ // An Object containing all the resolved strings:
+ // {
+ // name: 'talk-ru Mailing List',
+ // url: 'https://lists.openstreetmap.org/listinfo/talk-ru',
+ // signupUrl: 'https://example.url/signup',
+ // description: 'A one line description',
+ // extendedDescription: 'Extended description',
+ // nameHTML: '<a href="the url">the name</a>',
+ // urlHTML: '<a href="the url">the url</a>',
+ // signupUrlHTML: '<a href="the signupUrl">the signupUrl</a>',
+ // descriptionHTML: the description, with urls and signupUrls linkified,
+ // extendedDescriptionHTML: the extendedDescription with urls and signupUrls linkified
+ // }
+ //
- mode.activeID = function () {
- return behavior && behavior.activeID() || [];
- };
+ function resolveStrings(item, defaults, localizerFn) {
+ var itemStrings = Object.assign({}, item.strings); // shallow clone
- return mode;
- }
+ var defaultStrings = Object.assign({}, defaults[item.type]); // shallow clone
- function modeAddArea(context, mode) {
- mode.id = 'add-area';
- var behavior = behaviorAddWay(context).on('start', start).on('startFromWay', startFromWay).on('startFromNode', startFromNode);
- var defaultTags = {
- area: 'yes'
- };
- if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'area');
+ var anyToken = new RegExp(/(\{\w+\})/, 'gi'); // Pre-localize the item and default strings
- function actionClose(wayId) {
- return function (graph) {
- return graph.replace(graph.entity(wayId).close());
- };
- }
+ if (localizerFn) {
+ if (itemStrings.community) {
+ var communityID = simplify(itemStrings.community);
+ itemStrings.community = localizerFn("_communities.".concat(communityID));
+ }
- function start(loc) {
- var startGraph = context.graph();
- var node = osmNode({
- loc: loc
- });
- var way = osmWay({
- tags: defaultTags
+ ['name', 'description', 'extendedDescription'].forEach(function (prop) {
+ if (defaultStrings[prop]) defaultStrings[prop] = localizerFn("_defaults.".concat(item.type, ".").concat(prop));
+ if (itemStrings[prop]) itemStrings[prop] = localizerFn("".concat(item.id, ".").concat(prop));
});
- context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id));
- context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
}
- function startFromWay(loc, edge) {
- var startGraph = context.graph();
- var node = osmNode({
- loc: loc
- });
- var way = osmWay({
- tags: defaultTags
- });
- context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id), actionAddMidpoint({
- loc: loc,
- edge: edge
- }, node));
- context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
+ var replacements = {
+ account: item.account,
+ community: itemStrings.community,
+ signupUrl: itemStrings.signupUrl,
+ url: itemStrings.url
+ }; // Resolve URLs first (which may refer to {account})
+
+ if (!replacements.signupUrl) {
+ replacements.signupUrl = resolve(itemStrings.signupUrl || defaultStrings.signupUrl);
}
- function startFromNode(node) {
- var startGraph = context.graph();
- var way = osmWay({
- tags: defaultTags
- });
- context.perform(actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id));
- context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
+ if (!replacements.url) {
+ replacements.url = resolve(itemStrings.url || defaultStrings.url);
}
- mode.enter = function () {
- context.install(behavior);
- };
+ var resolved = {
+ name: resolve(itemStrings.name || defaultStrings.name),
+ url: resolve(itemStrings.url || defaultStrings.url),
+ signupUrl: resolve(itemStrings.signupUrl || defaultStrings.signupUrl),
+ description: resolve(itemStrings.description || defaultStrings.description),
+ extendedDescription: resolve(itemStrings.extendedDescription || defaultStrings.extendedDescription)
+ }; // Generate linkified strings
- mode.exit = function () {
- context.uninstall(behavior);
- };
+ resolved.nameHTML = linkify(resolved.url, resolved.name);
+ resolved.urlHTML = linkify(resolved.url);
+ resolved.signupUrlHTML = linkify(resolved.signupUrl);
+ resolved.descriptionHTML = resolve(itemStrings.description || defaultStrings.description, true);
+ resolved.extendedDescriptionHTML = resolve(itemStrings.extendedDescription || defaultStrings.extendedDescription, true);
+ return resolved;
- return mode;
+ function resolve(s, addLinks) {
+ if (!s) return undefined;
+ var result = s;
+
+ for (var key in replacements) {
+ var token = "{".concat(key, "}");
+ var regex = new RegExp(token, 'g');
+
+ if (regex.test(result)) {
+ var replacement = replacements[key];
+
+ if (!replacement) {
+ throw new Error("Cannot resolve token: ".concat(token));
+ } else {
+ if (addLinks && (key === 'signupUrl' || key === 'url')) {
+ replacement = linkify(replacement);
+ }
+
+ result = result.replace(regex, replacement);
+ }
+ }
+ } // There shouldn't be any leftover tokens in a resolved string
+
+
+ var leftovers = result.match(anyToken);
+
+ if (leftovers) {
+ throw new Error("Cannot resolve tokens: ".concat(leftovers));
+ } // Linkify subreddits like `/r/openstreetmap`
+ // https://github.com/osmlab/osm-community-index/issues/82
+ // https://github.com/openstreetmap/iD/issues/4997
+
+
+ if (addLinks && item.type === 'reddit') {
+ result = result.replace(/(\/r\/\w+\/*)/i, function (match) {
+ return linkify(resolved.url, match);
+ });
+ }
+
+ return result;
+ }
+
+ function linkify(url, text) {
+ if (!url) return undefined;
+ text = text || url;
+ return "<a target=\"_blank\" href=\"".concat(url, "\">").concat(text, "</a>");
+ }
}
- function modeAddLine(context, mode) {
- mode.id = 'add-line';
- var behavior = behaviorAddWay(context).on('start', start).on('startFromWay', startFromWay).on('startFromNode', startFromNode);
- var defaultTags = {};
- if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'line');
+ var _oci = null;
+ function uiSuccess(context) {
+ var MAXEVENTS = 2;
+ var dispatch = dispatch$8('cancel');
- function start(loc) {
- var startGraph = context.graph();
- var node = osmNode({
- loc: loc
- });
- var way = osmWay({
- tags: defaultTags
+ var _changeset;
+
+ var _location;
+
+ ensureOSMCommunityIndex(); // start fetching the data
+
+ function ensureOSMCommunityIndex() {
+ var data = _mainFileFetcher;
+ return Promise.all([data.get('oci_features'), data.get('oci_resources'), data.get('oci_defaults')]).then(function (vals) {
+ if (_oci) return _oci; // Merge Custom Features
+
+ if (vals[0] && Array.isArray(vals[0].features)) {
+ _mainLocations.mergeCustomGeoJSON(vals[0]);
+ }
+
+ var ociResources = Object.values(vals[1].resources);
+
+ if (ociResources.length) {
+ // Resolve all locationSet features.
+ return _mainLocations.mergeLocationSets(ociResources).then(function () {
+ _oci = {
+ resources: ociResources,
+ defaults: vals[2].defaults
+ };
+ return _oci;
+ });
+ } else {
+ _oci = {
+ resources: [],
+ // no resources?
+ defaults: vals[2].defaults
+ };
+ return _oci;
+ }
});
- context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id));
- context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
+ } // string-to-date parsing in JavaScript is weird
+
+
+ function parseEventDate(when) {
+ if (!when) return;
+ var raw = when.trim();
+ if (!raw) return;
+
+ if (!/Z$/.test(raw)) {
+ // if no trailing 'Z', add one
+ raw += 'Z'; // this forces date to be parsed as a UTC date
+ }
+
+ var parsed = new Date(raw);
+ return new Date(parsed.toUTCString().substr(0, 25)); // convert to local timezone
}
- function startFromWay(loc, edge) {
- var startGraph = context.graph();
- var node = osmNode({
- loc: loc
- });
- var way = osmWay({
- tags: defaultTags
+ function success(selection) {
+ var header = selection.append('div').attr('class', 'header fillL');
+ header.append('h2').call(_t.append('success.just_edited'));
+ header.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ return dispatch.call('cancel');
+ }).call(svgIcon('#iD-icon-close'));
+ var body = selection.append('div').attr('class', 'body save-success fillL');
+ var summary = body.append('div').attr('class', 'save-summary');
+ summary.append('h3').call(_t.append('success.thank_you' + (_location ? '_location' : ''), {
+ where: _location
+ }));
+ summary.append('p').call(_t.append('success.help_html')).append('a').attr('class', 'link-out').attr('target', '_blank').attr('href', _t('success.help_link_url')).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').call(_t.append('success.help_link_text'));
+ var osm = context.connection();
+ if (!osm) return;
+ var changesetURL = osm.changesetURL(_changeset.id);
+ var table = summary.append('table').attr('class', 'summary-table');
+ var row = table.append('tr').attr('class', 'summary-row');
+ row.append('td').attr('class', 'cell-icon summary-icon').append('a').attr('target', '_blank').attr('href', changesetURL).append('svg').attr('class', 'logo-small').append('use').attr('xlink:href', '#iD-logo-osm');
+ var summaryDetail = row.append('td').attr('class', 'cell-detail summary-detail');
+ summaryDetail.append('a').attr('class', 'cell-detail summary-view-on-osm').attr('target', '_blank').attr('href', changesetURL).call(_t.append('success.view_on_osm'));
+ summaryDetail.append('div').html(_t.html('success.changeset_id', {
+ changeset_id: {
+ html: "<a href=\"".concat(changesetURL, "\" target=\"_blank\">").concat(_changeset.id, "</a>")
+ }
+ })); // Get OSM community index features intersecting the map..
+
+ ensureOSMCommunityIndex().then(function (oci) {
+ var loc = context.map().center();
+ var validLocations = _mainLocations.locationsAt(loc); // Gather the communities
+
+ var communities = [];
+ oci.resources.forEach(function (resource) {
+ var area = validLocations[resource.locationSetID];
+ if (!area) return; // Resolve strings
+
+ var localizer = function localizer(stringID) {
+ return _t.html("community.".concat(stringID));
+ };
+
+ resource.resolved = resolveStrings(resource, oci.defaults, localizer);
+ communities.push({
+ area: area,
+ order: resource.order || 0,
+ resource: resource
+ });
+ }); // sort communities by feature area ascending, community order descending
+
+ communities.sort(function (a, b) {
+ return a.area - b.area || b.order - a.order;
+ });
+ body.call(showCommunityLinks, communities.map(function (c) {
+ return c.resource;
+ }));
});
- context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionAddMidpoint({
- loc: loc,
- edge: edge
- }, node));
- context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
}
- function startFromNode(node) {
- var startGraph = context.graph();
- var way = osmWay({
- tags: defaultTags
+ function showCommunityLinks(selection, resources) {
+ var communityLinks = selection.append('div').attr('class', 'save-communityLinks');
+ communityLinks.append('h3').call(_t.append('success.like_osm'));
+ var table = communityLinks.append('table').attr('class', 'community-table');
+ var row = table.selectAll('.community-row').data(resources);
+ var rowEnter = row.enter().append('tr').attr('class', 'community-row');
+ rowEnter.append('td').attr('class', 'cell-icon community-icon').append('a').attr('target', '_blank').attr('href', function (d) {
+ return d.resolved.url;
+ }).append('svg').attr('class', 'logo-small').append('use').attr('xlink:href', function (d) {
+ return "#community-".concat(d.type);
});
- context.perform(actionAddEntity(way), actionAddVertex(way.id, node.id));
- context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
+ var communityDetail = rowEnter.append('td').attr('class', 'cell-detail community-detail');
+ communityDetail.each(showCommunityDetails);
+ communityLinks.append('div').attr('class', 'community-missing').call(_t.append('success.missing')).append('a').attr('class', 'link-out').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/osmlab/osm-community-index/issues').append('span').call(_t.append('success.tell_us'));
}
- mode.enter = function () {
- context.install(behavior);
+ function showCommunityDetails(d) {
+ var selection = select(this);
+ var communityID = d.id;
+ selection.append('div').attr('class', 'community-name').html(d.resolved.nameHTML);
+ selection.append('div').attr('class', 'community-description').html(d.resolved.descriptionHTML); // Create an expanding section if any of these are present..
+
+ if (d.resolved.extendedDescriptionHTML || d.languageCodes && d.languageCodes.length) {
+ selection.append('div').call(uiDisclosure(context, "community-more-".concat(d.id), false).expanded(false).updatePreference(false).label(_t.html('success.more')).content(showMore));
+ }
+
+ var nextEvents = (d.events || []).map(function (event) {
+ event.date = parseEventDate(event.when);
+ return event;
+ }).filter(function (event) {
+ // date is valid and future (or today)
+ var t = event.date.getTime();
+ var now = new Date().setHours(0, 0, 0, 0);
+ return !isNaN(t) && t >= now;
+ }).sort(function (a, b) {
+ // sort by date ascending
+ return a.date < b.date ? -1 : a.date > b.date ? 1 : 0;
+ }).slice(0, MAXEVENTS); // limit number of events shown
+
+ if (nextEvents.length) {
+ selection.append('div').call(uiDisclosure(context, "community-events-".concat(d.id), false).expanded(false).updatePreference(false).label(_t.html('success.events')).content(showNextEvents)).select('.hide-toggle').append('span').attr('class', 'badge-text').text(nextEvents.length);
+ }
+
+ function showMore(selection) {
+ var more = selection.selectAll('.community-more').data([0]);
+ var moreEnter = more.enter().append('div').attr('class', 'community-more');
+
+ if (d.resolved.extendedDescriptionHTML) {
+ moreEnter.append('div').attr('class', 'community-extended-description').html(d.resolved.extendedDescriptionHTML);
+ }
+
+ if (d.languageCodes && d.languageCodes.length) {
+ var languageList = d.languageCodes.map(function (code) {
+ return _mainLocalizer.languageName(code);
+ }).join(', ');
+ moreEnter.append('div').attr('class', 'community-languages').call(_t.append('success.languages', {
+ languages: languageList
+ }));
+ }
+ }
+
+ function showNextEvents(selection) {
+ var events = selection.append('div').attr('class', 'community-events');
+ var item = events.selectAll('.community-event').data(nextEvents);
+ var itemEnter = item.enter().append('div').attr('class', 'community-event');
+ itemEnter.append('div').attr('class', 'community-event-name').append('a').attr('target', '_blank').attr('href', function (d) {
+ return d.url;
+ }).text(function (d) {
+ var name = d.name;
+
+ if (d.i18n && d.id) {
+ name = _t("community.".concat(communityID, ".events.").concat(d.id, ".name"), {
+ "default": name
+ });
+ }
+
+ return name;
+ });
+ itemEnter.append('div').attr('class', 'community-event-when').text(function (d) {
+ var options = {
+ weekday: 'short',
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric'
+ };
+
+ if (d.date.getHours() || d.date.getMinutes()) {
+ // include time if it has one
+ options.hour = 'numeric';
+ options.minute = 'numeric';
+ }
+
+ return d.date.toLocaleString(_mainLocalizer.localeCode(), options);
+ });
+ itemEnter.append('div').attr('class', 'community-event-where').text(function (d) {
+ var where = d.where;
+
+ if (d.i18n && d.id) {
+ where = _t("community.".concat(communityID, ".events.").concat(d.id, ".where"), {
+ "default": where
+ });
+ }
+
+ return where;
+ });
+ itemEnter.append('div').attr('class', 'community-event-description').text(function (d) {
+ var description = d.description;
+
+ if (d.i18n && d.id) {
+ description = _t("community.".concat(communityID, ".events.").concat(d.id, ".description"), {
+ "default": description
+ });
+ }
+
+ return description;
+ });
+ }
+ }
+
+ success.changeset = function (val) {
+ if (!arguments.length) return _changeset;
+ _changeset = val;
+ return success;
+ };
+
+ success.location = function (val) {
+ if (!arguments.length) return _location;
+ _location = val;
+ return success;
};
- mode.exit = function () {
- context.uninstall(behavior);
+ return utilRebind(success, dispatch, 'on');
+ }
+
+ var sawVersion = null;
+ var isNewVersion = false;
+ var isNewUser = false;
+ function uiVersion(context) {
+ var currVersion = context.version;
+ var matchedVersion = currVersion.match(/\d+\.\d+\.\d+.*/);
+
+ if (sawVersion === null && matchedVersion !== null) {
+ if (corePreferences('sawVersion')) {
+ isNewUser = false;
+ isNewVersion = corePreferences('sawVersion') !== currVersion && currVersion.indexOf('-') === -1;
+ } else {
+ isNewUser = true;
+ isNewVersion = true;
+ }
+
+ corePreferences('sawVersion', currVersion);
+ sawVersion = currVersion;
+ }
+
+ return function (selection) {
+ selection.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD').text(currVersion); // only show new version indicator to users that have used iD before
+
+ if (isNewVersion && !isNewUser) {
+ selection.append('a').attr('class', 'badge').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/blob/release/CHANGELOG.md#whats-new').call(svgIcon('#maki-gift-11')).call(uiTooltip().title(_t.html('version.whats_new', {
+ version: currVersion
+ })).placement('top').scrollContainer(context.container().select('.main-footer-wrap')));
+ }
};
-
- return mode;
}
- function modeAddPoint(context, mode) {
- mode.id = 'add-point';
- var behavior = behaviorDraw(context).on('click', add).on('clickWay', addWay).on('clickNode', addNode).on('cancel', cancel).on('finish', cancel);
- var defaultTags = {};
- if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'point');
+ function uiZoom(context) {
+ var zooms = [{
+ id: 'zoom-in',
+ icon: 'iD-icon-plus',
+ title: _t.html('zoom.in'),
+ action: zoomIn,
+ disabled: function disabled() {
+ return !context.map().canZoomIn();
+ },
+ disabledTitle: _t.html('zoom.disabled.in'),
+ key: '+'
+ }, {
+ id: 'zoom-out',
+ icon: 'iD-icon-minus',
+ title: _t.html('zoom.out'),
+ action: zoomOut,
+ disabled: function disabled() {
+ return !context.map().canZoomOut();
+ },
+ disabledTitle: _t.html('zoom.disabled.out'),
+ key: '-'
+ }];
- function add(loc) {
- var node = osmNode({
- loc: loc,
- tags: defaultTags
- });
- context.perform(actionAddEntity(node), _t('operations.add.annotation.point'));
- enterSelectMode(node);
+ function zoomIn(d3_event) {
+ if (d3_event.shiftKey) return;
+ d3_event.preventDefault();
+ context.map().zoomIn();
}
- function addWay(loc, edge) {
- var node = osmNode({
- tags: defaultTags
- });
- context.perform(actionAddMidpoint({
- loc: loc,
- edge: edge
- }, node), _t('operations.add.annotation.vertex'));
- enterSelectMode(node);
+ function zoomOut(d3_event) {
+ if (d3_event.shiftKey) return;
+ d3_event.preventDefault();
+ context.map().zoomOut();
}
- function enterSelectMode(node) {
- context.enter(modeSelect(context, [node.id]).newFeature(true));
+ function zoomInFurther(d3_event) {
+ if (d3_event.shiftKey) return;
+ d3_event.preventDefault();
+ context.map().zoomInFurther();
}
- function addNode(node) {
- if (Object.keys(defaultTags).length === 0) {
- enterSelectMode(node);
- return;
- }
+ function zoomOutFurther(d3_event) {
+ if (d3_event.shiftKey) return;
+ d3_event.preventDefault();
+ context.map().zoomOutFurther();
+ }
- var tags = Object.assign({}, node.tags); // shallow copy
+ return function (selection) {
+ var tooltipBehavior = uiTooltip().placement(_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left').title(function (d) {
+ if (d.disabled()) {
+ return d.disabledTitle;
+ }
- for (var key in defaultTags) {
- tags[key] = defaultTags[key];
- }
+ return d.title;
+ }).keys(function (d) {
+ return [d.key];
+ });
+ var lastPointerUpType;
+ var buttons = selection.selectAll('button').data(zooms).enter().append('button').attr('class', function (d) {
+ return d.id;
+ }).on('pointerup.editor', function (d3_event) {
+ lastPointerUpType = d3_event.pointerType;
+ }).on('click.editor', function (d3_event, d) {
+ if (!d.disabled()) {
+ d.action(d3_event);
+ } else if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
+ context.ui().flash.duration(2000).iconName('#' + d.icon).iconClass('disabled').label(d.disabledTitle)();
+ }
- context.perform(actionChangeTags(node.id, tags), _t('operations.add.annotation.point'));
- enterSelectMode(node);
- }
+ lastPointerUpType = null;
+ }).call(tooltipBehavior);
+ buttons.each(function (d) {
+ select(this).call(svgIcon('#' + d.icon, 'light'));
+ });
+ utilKeybinding.plusKeys.forEach(function (key) {
+ context.keybinding().on([key], zoomIn);
+ context.keybinding().on([uiCmd('⌥' + key)], zoomInFurther);
+ });
+ utilKeybinding.minusKeys.forEach(function (key) {
+ context.keybinding().on([key], zoomOut);
+ context.keybinding().on([uiCmd('⌥' + key)], zoomOutFurther);
+ });
- function cancel() {
- context.enter(modeBrowse(context));
- }
+ function updateButtonStates() {
+ buttons.classed('disabled', function (d) {
+ return d.disabled();
+ }).each(function () {
+ var selection = select(this);
- mode.enter = function () {
- context.install(behavior);
- };
+ if (!selection.select('.tooltip.in').empty()) {
+ selection.call(tooltipBehavior.updateContent);
+ }
+ });
+ }
- mode.exit = function () {
- context.uninstall(behavior);
+ updateButtonStates();
+ context.map().on('move.uiZoom', updateButtonStates);
};
-
- return mode;
}
- function modeSelectNote(context, selectedNoteID) {
- var mode = {
- id: 'select-note',
- button: 'browse'
- };
-
- var _keybinding = utilKeybinding('select-note');
+ function uiSectionRawTagEditor(id, context) {
+ var section = uiSection(id, context).classes('raw-tag-editor').label(function () {
+ var count = Object.keys(_tags).filter(function (d) {
+ return d;
+ }).length;
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('inspector.tags')
+ },
+ count: count
+ });
+ }).expandedByDefault(false).disclosureContent(renderDisclosureContent);
+ var taginfo = services.taginfo;
+ var dispatch = dispatch$8('change');
+ var availableViews = [{
+ id: 'list',
+ icon: '#fas-th-list'
+ }, {
+ id: 'text',
+ icon: '#fas-i-cursor'
+ }];
- var _noteEditor = uiNoteEditor(context).on('change', function () {
- context.map().pan([0, 0]); // trigger a redraw
+ var _tagView = corePreferences('raw-tag-editor-view') || 'list'; // 'list, 'text'
- var note = checkSelectedID();
- if (!note) return;
- context.ui().sidebar.show(_noteEditor.note(note));
- });
- var _behaviors = [behaviorBreathe(), behaviorHover(context), behaviorSelect(context), behaviorLasso(context), modeDragNode(context).behavior, modeDragNote(context).behavior];
- var _newFeature = false;
+ var _readOnlyTags = []; // the keys in the order we want them to display
- function checkSelectedID() {
- if (!services.osm) return;
- var note = services.osm.getNote(selectedNoteID);
+ var _orderedKeys = [];
+ var _showBlank = false;
+ var _pendingChange = null;
- if (!note) {
- context.enter(modeBrowse(context));
- }
+ var _state;
- return note;
- } // class the note as selected, or return to browse mode if the note is gone
+ var _presets;
+ var _tags;
- function selectNote(d3_event, drawn) {
- if (!checkSelectedID()) return;
- var selection = context.surface().selectAll('.layer-notes .note-' + selectedNoteID);
+ var _entityIDs;
- if (selection.empty()) {
- // Return to browse mode if selected DOM elements have
- // disappeared because the user moved them out of view..
- var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
+ var _didInteract = false;
- if (drawn && source && (source.type === 'pointermove' || source.type === 'mousemove' || source.type === 'touchmove')) {
- context.enter(modeBrowse(context));
- }
- } else {
- selection.classed('selected', true);
- context.selectedNoteID(selectedNoteID);
- }
+ function interacted() {
+ _didInteract = true;
}
- function esc() {
- if (context.container().select('.combobox').size()) return;
- context.enter(modeBrowse(context));
- }
+ function renderDisclosureContent(wrap) {
+ // remove deleted keys
+ _orderedKeys = _orderedKeys.filter(function (key) {
+ return _tags[key] !== undefined;
+ }); // When switching to a different entity or changing the state (hover/select)
+ // reorder the keys alphabetically.
+ // We trigger this by emptying the `_orderedKeys` array, then it will be rebuilt here.
+ // Otherwise leave their order alone - #5857, #5927
- mode.zoomToSelected = function () {
- if (!services.osm) return;
- var note = services.osm.getNote(selectedNoteID);
+ var all = Object.keys(_tags).sort();
+ var missingKeys = utilArrayDifference(all, _orderedKeys);
- if (note) {
- context.map().centerZoomEase(note.loc, 20);
- }
- };
+ for (var i in missingKeys) {
+ _orderedKeys.push(missingKeys[i]);
+ } // assemble row data
- mode.newFeature = function (val) {
- if (!arguments.length) return _newFeature;
- _newFeature = val;
- return mode;
- };
- mode.enter = function () {
- var note = checkSelectedID();
- if (!note) return;
+ var rowData = _orderedKeys.map(function (key, i) {
+ return {
+ index: i,
+ key: key,
+ value: _tags[key]
+ };
+ }); // append blank row last, if necessary
- _behaviors.forEach(context.install);
- _keybinding.on(_t('inspector.zoom_to.key'), mode.zoomToSelected).on('⎋', esc, true);
+ if (!rowData.length || _showBlank) {
+ _showBlank = false;
+ rowData.push({
+ index: rowData.length,
+ key: '',
+ value: ''
+ });
+ } // View Options
- select(document).call(_keybinding);
- selectNote();
- var sidebar = context.ui().sidebar;
- sidebar.show(_noteEditor.note(note).newNote(_newFeature)); // expand the sidebar, avoid obscuring the note if needed
- sidebar.expand(sidebar.intersects(note.extent()));
- context.map().on('drawn.select', selectNote);
- };
+ var options = wrap.selectAll('.raw-tag-options').data([0]);
+ options.exit().remove();
+ var optionsEnter = options.enter().insert('div', ':first-child').attr('class', 'raw-tag-options').attr('role', 'tablist');
+ var optionEnter = optionsEnter.selectAll('.raw-tag-option').data(availableViews, function (d) {
+ return d.id;
+ }).enter();
+ optionEnter.append('button').attr('class', function (d) {
+ return 'raw-tag-option raw-tag-option-' + d.id + (_tagView === d.id ? ' selected' : '');
+ }).attr('aria-selected', function (d) {
+ return _tagView === d.id;
+ }).attr('role', 'tab').attr('title', function (d) {
+ return _t('icons.' + d.id);
+ }).on('click', function (d3_event, d) {
+ _tagView = d.id;
+ corePreferences('raw-tag-editor-view', d.id);
+ wrap.selectAll('.raw-tag-option').classed('selected', function (datum) {
+ return datum === d;
+ }).attr('aria-selected', function (datum) {
+ return datum === d;
+ });
+ wrap.selectAll('.tag-text').classed('hide', d.id !== 'text').each(setTextareaHeight);
+ wrap.selectAll('.tag-list, .add-row').classed('hide', d.id !== 'list');
+ }).each(function (d) {
+ select(this).call(svgIcon(d.icon));
+ }); // View as Text
- mode.exit = function () {
- _behaviors.forEach(context.uninstall);
+ var textData = rowsToText(rowData);
+ var textarea = wrap.selectAll('.tag-text').data([0]);
+ textarea = textarea.enter().append('textarea').attr('class', 'tag-text' + (_tagView !== 'text' ? ' hide' : '')).call(utilNoAuto).attr('placeholder', _t('inspector.key_value')).attr('spellcheck', 'false').merge(textarea);
+ textarea.call(utilGetSetValue, textData).each(setTextareaHeight).on('input', setTextareaHeight).on('focus', interacted).on('blur', textChanged).on('change', textChanged); // View as List
- select(document).call(_keybinding.unbind);
- context.surface().selectAll('.layer-notes .selected').classed('selected hover', false);
- context.map().on('drawn.select', null);
- context.ui().sidebar.hide();
- context.selectedNoteID(null);
- };
+ var list = wrap.selectAll('.tag-list').data([0]);
+ list = list.enter().append('ul').attr('class', 'tag-list' + (_tagView !== 'list' ? ' hide' : '')).merge(list); // Container for the Add button
- return mode;
- }
+ var addRowEnter = wrap.selectAll('.add-row').data([0]).enter().append('div').attr('class', 'add-row' + (_tagView !== 'list' ? ' hide' : ''));
+ addRowEnter.append('button').attr('class', 'add-tag').attr('aria-label', _t('inspector.add_to_tag')).call(svgIcon('#iD-icon-plus', 'light')).call(uiTooltip().title(_t.html('inspector.add_to_tag')).placement(_mainLocalizer.textDirection() === 'ltr' ? 'right' : 'left')).on('click', addTag);
+ addRowEnter.append('div').attr('class', 'space-value'); // preserve space
- function modeAddNote(context) {
- var mode = {
- id: 'add-note',
- button: 'note',
- description: _t.html('modes.add_note.description'),
- key: _t('modes.add_note.key')
- };
- var behavior = behaviorDraw(context).on('click', add).on('cancel', cancel).on('finish', cancel);
+ addRowEnter.append('div').attr('class', 'space-buttons'); // preserve space
+ // Tag list items
- function add(loc) {
- var osm = services.osm;
- if (!osm) return;
- var note = osmNote({
- loc: loc,
- status: 'open',
- comments: []
+ var items = list.selectAll('.tag-row').data(rowData, function (d) {
+ return d.key;
});
- osm.replaceNote(note); // force a reraw (there is no history change that would otherwise do this)
-
- context.map().pan([0, 0]);
- context.selectedNoteID(note.id).enter(modeSelectNote(context, note.id).newFeature(true));
- }
+ items.exit().each(unbind).remove(); // Enter
- function cancel() {
- context.enter(modeBrowse(context));
- }
+ var itemsEnter = items.enter().append('li').attr('class', 'tag-row').classed('readonly', isReadOnly);
+ var innerWrap = itemsEnter.append('div').attr('class', 'inner-wrap');
+ innerWrap.append('div').attr('class', 'key-wrap').append('input').property('type', 'text').attr('class', 'key').call(utilNoAuto).on('focus', interacted).on('blur', keyChange).on('change', keyChange);
+ innerWrap.append('div').attr('class', 'value-wrap').append('input').property('type', 'text').attr('class', 'value').call(utilNoAuto).on('focus', interacted).on('blur', valueChange).on('change', valueChange).on('keydown.push-more', pushMore);
+ innerWrap.append('button').attr('class', 'form-field-button remove').attr('title', _t('icons.remove')).call(svgIcon('#iD-operation-delete')); // Update
- mode.enter = function () {
- context.install(behavior);
- };
+ items = items.merge(itemsEnter).sort(function (a, b) {
+ return a.index - b.index;
+ });
+ items.each(function (d) {
+ var row = select(this);
+ var key = row.select('input.key'); // propagate bound data
- mode.exit = function () {
- context.uninstall(behavior);
- };
+ var value = row.select('input.value'); // propagate bound data
- return mode;
- }
+ if (_entityIDs && taginfo && _state !== 'hover') {
+ bindTypeahead(key, value);
+ }
- var JXON = new function () {
- var sValueProp = 'keyValue',
- sAttributesProp = 'keyAttributes',
- sAttrPref = '@',
+ var referenceOptions = {
+ key: d.key
+ };
- /* you can customize these values */
- aCache = [],
- rIsNull = /^\s*$/,
- rIsBool = /^(?:true|false)$/i;
+ if (typeof d.value === 'string') {
+ referenceOptions.value = d.value;
+ }
- function parseText(sValue) {
- if (rIsNull.test(sValue)) {
- return null;
- }
+ var reference = uiTagReference(referenceOptions);
- if (rIsBool.test(sValue)) {
- return sValue.toLowerCase() === 'true';
- }
+ if (_state === 'hover') {
+ reference.showing(false);
+ }
- if (isFinite(sValue)) {
- return parseFloat(sValue);
- }
+ row.select('.inner-wrap') // propagate bound data
+ .call(reference.button);
+ row.call(reference.body);
+ row.select('button.remove'); // propagate bound data
+ });
+ items.selectAll('input.key').attr('title', function (d) {
+ return d.key;
+ }).call(utilGetSetValue, function (d) {
+ return d.key;
+ }).attr('readonly', function (d) {
+ return isReadOnly(d) || null;
+ });
+ items.selectAll('input.value').attr('title', function (d) {
+ return Array.isArray(d.value) ? d.value.filter(Boolean).join('\n') : d.value;
+ }).classed('mixed', function (d) {
+ return Array.isArray(d.value);
+ }).attr('placeholder', function (d) {
+ return typeof d.value === 'string' ? null : _t('inspector.multiple_values');
+ }).call(utilGetSetValue, function (d) {
+ return typeof d.value === 'string' ? d.value : '';
+ }).attr('readonly', function (d) {
+ return isReadOnly(d) || null;
+ });
+ items.selectAll('button.remove').on(('PointerEvent' in window ? 'pointer' : 'mouse') + 'down', removeTag); // 'click' fires too late - #5878
+ }
- if (isFinite(Date.parse(sValue))) {
- return new Date(sValue);
+ function isReadOnly(d) {
+ for (var i = 0; i < _readOnlyTags.length; i++) {
+ if (d.key.match(_readOnlyTags[i]) !== null) {
+ return true;
+ }
}
- return sValue;
+ return false;
}
- function EmptyTree() {}
-
- EmptyTree.prototype.toString = function () {
- return 'null';
- };
-
- EmptyTree.prototype.valueOf = function () {
- return null;
- };
-
- function objectify(vValue) {
- return vValue === null ? new EmptyTree() : vValue instanceof Object ? vValue : new vValue.constructor(vValue);
+ function setTextareaHeight() {
+ if (_tagView !== 'text') return;
+ var selection = select(this);
+ var matches = selection.node().value.match(/\n/g);
+ var lineCount = 2 + Number(matches && matches.length);
+ var lineHeight = 20;
+ selection.style('height', lineCount * lineHeight + 'px');
}
- function createObjTree(oParentNode, nVerb, bFreeze, bNesteAttr) {
- var nLevelStart = aCache.length,
- bChildren = oParentNode.hasChildNodes(),
- bAttributes = oParentNode.hasAttributes(),
- bHighVerb = Boolean(nVerb & 2);
- var sProp,
- vContent,
- nLength = 0,
- sCollectedTxt = '',
- vResult = bHighVerb ? {} :
- /* put here the default value for empty nodes: */
- true;
+ function stringify(s) {
+ return JSON.stringify(s).slice(1, -1); // without leading/trailing "
+ }
- if (bChildren) {
- for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
- oNode = oParentNode.childNodes.item(nItem);
+ function unstringify(s) {
+ var leading = '';
+ var trailing = '';
- if (oNode.nodeType === 4) {
- /* nodeType is 'CDATASection' (4) */
- sCollectedTxt += oNode.nodeValue;
- } else if (oNode.nodeType === 3) {
- /* nodeType is 'Text' (3) */
- sCollectedTxt += oNode.nodeValue.trim();
- } else if (oNode.nodeType === 1 && !oNode.prefix) {
- /* nodeType is 'Element' (1) */
- aCache.push(oNode);
- }
- }
+ if (s.length < 1 || s.charAt(0) !== '"') {
+ leading = '"';
}
- var nLevelEnd = aCache.length,
- vBuiltVal = parseText(sCollectedTxt);
-
- if (!bHighVerb && (bChildren || bAttributes)) {
- vResult = nVerb === 0 ? objectify(vBuiltVal) : {};
+ if (s.length < 2 || s.charAt(s.length - 1) !== '"' || s.charAt(s.length - 1) === '"' && s.charAt(s.length - 2) === '\\') {
+ trailing = '"';
}
- for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
- sProp = aCache[nElId].nodeName.toLowerCase();
- vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
+ return JSON.parse(leading + s + trailing);
+ }
- if (vResult.hasOwnProperty(sProp)) {
- if (vResult[sProp].constructor !== Array) {
- vResult[sProp] = [vResult[sProp]];
- }
+ function rowsToText(rows) {
+ var str = rows.filter(function (row) {
+ return row.key && row.key.trim() !== '';
+ }).map(function (row) {
+ var rawVal = row.value;
+ if (typeof rawVal !== 'string') rawVal = '*';
+ var val = rawVal ? stringify(rawVal) : '';
+ return stringify(row.key) + '=' + val;
+ }).join('\n');
- vResult[sProp].push(vContent);
- } else {
- vResult[sProp] = vContent;
- nLength++;
- }
+ if (_state !== 'hover' && str.length) {
+ return str + '\n';
}
- if (bAttributes) {
- var nAttrLen = oParentNode.attributes.length,
- sAPrefix = bNesteAttr ? '' : sAttrPref,
- oAttrParent = bNesteAttr ? {} : vResult;
+ return str;
+ }
- for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
- oAttrib = oParentNode.attributes.item(nAttrib);
- oAttrParent[sAPrefix + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
+ function textChanged() {
+ var newText = this.value.trim();
+ var newTags = {};
+ newText.split('\n').forEach(function (row) {
+ var m = row.match(/^\s*([^=]+)=(.*)$/);
+
+ if (m !== null) {
+ var k = context.cleanTagKey(unstringify(m[1].trim()));
+ var v = context.cleanTagValue(unstringify(m[2].trim()));
+ newTags[k] = v;
}
+ });
+ var tagDiff = utilTagDiff(_tags, newTags);
+ if (!tagDiff.length) return;
+ _pendingChange = _pendingChange || {};
+ tagDiff.forEach(function (change) {
+ if (isReadOnly({
+ key: change.key
+ })) return; // skip unchanged multiselection placeholders
- if (bNesteAttr) {
- if (bFreeze) {
- Object.freeze(oAttrParent);
- }
+ if (change.newVal === '*' && typeof change.oldVal !== 'string') return;
- vResult[sAttributesProp] = oAttrParent;
- nLength -= nAttrLen - 1;
+ if (change.type === '-') {
+ _pendingChange[change.key] = undefined;
+ } else if (change.type === '+') {
+ _pendingChange[change.key] = change.newVal || '';
}
- }
-
- if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) {
- vResult[sValueProp] = vBuiltVal;
- } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
- vResult = vBuiltVal;
- }
+ });
- if (bFreeze && (bHighVerb || nLength > 0)) {
- Object.freeze(vResult);
+ if (Object.keys(_pendingChange).length === 0) {
+ _pendingChange = null;
+ return;
}
- aCache.length = nLevelStart;
- return vResult;
+ scheduleChange();
}
- function loadObjTree(oXMLDoc, oParentEl, oParentObj) {
- var vValue, oChild;
-
- if (oParentObj instanceof String || oParentObj instanceof Number || oParentObj instanceof Boolean) {
- oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString()));
- /* verbosity level is 0 */
- } else if (oParentObj.constructor === Date) {
- oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));
+ function pushMore(d3_event) {
+ // if pressing Tab on the last value field with content, add a blank row
+ if (d3_event.keyCode === 9 && !d3_event.shiftKey && section.selection().selectAll('.tag-list li:last-child input.value').node() === this && utilGetSetValue(select(this))) {
+ addTag();
}
+ }
- for (var sName in oParentObj) {
- vValue = oParentObj[sName];
+ function bindTypeahead(key, value) {
+ if (isReadOnly(key.datum())) return;
- if (isFinite(sName) || vValue instanceof Function) {
- continue;
- }
- /* verbosity level is 0 */
+ if (Array.isArray(value.datum().value)) {
+ value.call(uiCombobox(context, 'tag-value').minItems(1).fetcher(function (value, callback) {
+ var keyString = utilGetSetValue(key);
+ if (!_tags[keyString]) return;
+ var data = _tags[keyString].filter(Boolean).map(function (tagValue) {
+ return {
+ value: tagValue,
+ title: tagValue
+ };
+ });
- if (sName === sValueProp) {
- if (vValue !== null && vValue !== true) {
- oParentEl.appendChild(oXMLDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue)));
- }
- } else if (sName === sAttributesProp) {
- /* verbosity level is 3 */
- for (var sAttrib in vValue) {
- oParentEl.setAttribute(sAttrib, vValue[sAttrib]);
- }
- } else if (sName.charAt(0) === sAttrPref) {
- oParentEl.setAttribute(sName.slice(1), vValue);
- } else if (vValue.constructor === Array) {
- for (var nItem = 0; nItem < vValue.length; nItem++) {
- oChild = oXMLDoc.createElement(sName);
- loadObjTree(oXMLDoc, oChild, vValue[nItem]);
- oParentEl.appendChild(oChild);
- }
- } else {
- oChild = oXMLDoc.createElement(sName);
+ callback(data);
+ }));
+ return;
+ }
- if (vValue instanceof Object) {
- loadObjTree(oXMLDoc, oChild, vValue);
- } else if (vValue !== null && vValue !== true) {
- oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
+ var geometry = context.graph().geometry(_entityIDs[0]);
+ key.call(uiCombobox(context, 'tag-key').fetcher(function (value, callback) {
+ taginfo.keys({
+ debounce: true,
+ geometry: geometry,
+ query: value
+ }, function (err, data) {
+ if (!err) {
+ var filtered = data.filter(function (d) {
+ return _tags[d.value] === undefined;
+ });
+ callback(sort(value, filtered));
}
+ });
+ }));
+ value.call(uiCombobox(context, 'tag-value').fetcher(function (value, callback) {
+ taginfo.values({
+ debounce: true,
+ key: utilGetSetValue(key),
+ geometry: geometry,
+ query: value
+ }, function (err, data) {
+ if (!err) callback(sort(value, data));
+ });
+ }));
- oParentEl.appendChild(oChild);
+ function sort(value, data) {
+ var sameletter = [];
+ var other = [];
+
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].value.substring(0, value.length) === value) {
+ sameletter.push(data[i]);
+ } else {
+ other.push(data[i]);
+ }
}
+
+ return sameletter.concat(other);
}
}
- this.build = function (oXMLParent, nVerbosity
- /* optional */
- , bFreeze
- /* optional */
- , bNesteAttributes
- /* optional */
- ) {
- var _nVerb = arguments.length > 1 && typeof nVerbosity === 'number' ? nVerbosity & 3 :
- /* put here the default verbosity level: */
- 1;
+ function unbind() {
+ var row = select(this);
+ row.selectAll('input.key').call(uiCombobox.off, context);
+ row.selectAll('input.value').call(uiCombobox.off, context);
+ }
- return createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
- };
+ function keyChange(d3_event, d) {
+ if (select(this).attr('readonly')) return;
+ var kOld = d.key; // exit if we are currently about to delete this row anyway - #6366
- this.unbuild = function (oObjTree) {
- var oNewDoc = document.implementation.createDocument('', '', null);
- loadObjTree(oNewDoc, oNewDoc, oObjTree);
- return oNewDoc;
- };
+ if (_pendingChange && _pendingChange.hasOwnProperty(kOld) && _pendingChange[kOld] === undefined) return;
+ var kNew = context.cleanTagKey(this.value.trim()); // allow no change if the key should be readonly
- this.stringify = function (oObjTree) {
- return new XMLSerializer().serializeToString(JXON.unbuild(oObjTree));
- };
- }(); // var myObject = JXON.build(doc);
- // we got our javascript object! try: alert(JSON.stringify(myObject));
- // var newDoc = JXON.unbuild(myObject);
- // we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));
+ if (isReadOnly({
+ key: kNew
+ })) {
+ this.value = kOld;
+ return;
+ }
- function uiConflicts(context) {
- var dispatch = dispatch$8('cancel', 'save');
- var keybinding = utilKeybinding('conflicts');
+ if (kNew && kNew !== kOld && _tags[kNew] !== undefined) {
+ // new key is already in use, switch focus to the existing row
+ this.value = kOld; // reset the key
- var _origChanges;
+ section.selection().selectAll('.tag-list input.value').each(function (d) {
+ if (d.key === kNew) {
+ // send focus to that other value combo instead
+ var input = select(this).node();
+ input.focus();
+ input.select();
+ }
+ });
+ return;
+ }
- var _conflictList;
+ _pendingChange = _pendingChange || {};
- var _shownConflictIndex;
+ if (kOld) {
+ if (kOld === kNew) return; // a tag key was renamed
- function keybindingOn() {
- select(document).call(keybinding.on('⎋', cancel, true));
- }
+ _pendingChange[kNew] = _pendingChange[kOld] || {
+ oldKey: kOld
+ };
+ _pendingChange[kOld] = undefined;
+ } else {
+ // a new tag was added
+ var row = this.parentNode.parentNode;
+ var inputVal = select(row).selectAll('input.value');
+ var vNew = context.cleanTagValue(utilGetSetValue(inputVal));
+ _pendingChange[kNew] = vNew;
+ utilGetSetValue(inputVal, vNew);
+ } // update the ordered key index so this row doesn't change position
- function keybindingOff() {
- select(document).call(keybinding.unbind);
- }
- function tryAgain() {
- keybindingOff();
- dispatch.call('save');
- }
+ var existingKeyIndex = _orderedKeys.indexOf(kOld);
- function cancel() {
- keybindingOff();
- dispatch.call('cancel');
+ if (existingKeyIndex !== -1) _orderedKeys[existingKeyIndex] = kNew;
+ d.key = kNew; // update datum to avoid exit/enter on tag update
+
+ this.value = kNew;
+ scheduleChange();
}
- function conflicts(selection) {
- keybindingOn();
- var headerEnter = selection.selectAll('.header').data([0]).enter().append('div').attr('class', 'header fillL');
- headerEnter.append('button').attr('class', 'fr').on('click', cancel).call(svgIcon('#iD-icon-close'));
- headerEnter.append('h3').html(_t.html('save.conflict.header'));
- var bodyEnter = selection.selectAll('.body').data([0]).enter().append('div').attr('class', 'body fillL');
- var conflictsHelpEnter = bodyEnter.append('div').attr('class', 'conflicts-help').html(_t.html('save.conflict.help')); // Download changes link
+ function valueChange(d3_event, d) {
+ if (isReadOnly(d)) return; // exit if this is a multiselection and no value was entered
- var detected = utilDetect();
- var changeset = new osmChangeset();
- delete changeset.id; // Export without changeset_id
+ if (typeof d.value !== 'string' && !this.value) return; // exit if we are currently about to delete this row anyway - #6366
- var data = JXON.stringify(changeset.osmChangeJXON(_origChanges));
- var blob = new Blob([data], {
- type: 'text/xml;charset=utf-8;'
- });
- var fileName = 'changes.osc';
- var linkEnter = conflictsHelpEnter.selectAll('.download-changes').append('a').attr('class', 'download-changes');
+ if (_pendingChange && _pendingChange.hasOwnProperty(d.key) && _pendingChange[d.key] === undefined) return;
+ _pendingChange = _pendingChange || {};
+ _pendingChange[d.key] = context.cleanTagValue(this.value);
+ scheduleChange();
+ }
- if (detected.download) {
- // All except IE11 and Edge
- linkEnter // download the data as a file
- .attr('href', window.URL.createObjectURL(blob)).attr('download', fileName);
+ function removeTag(d3_event, d) {
+ if (isReadOnly(d)) return;
+
+ if (d.key === '') {
+ // removing the blank row
+ _showBlank = false;
+ section.reRender();
} else {
- // IE11 and Edge
- linkEnter // open data uri in a new tab
- .attr('target', '_blank').on('click.download', function () {
- navigator.msSaveBlob(blob, fileName);
+ // remove the key from the ordered key index
+ _orderedKeys = _orderedKeys.filter(function (key) {
+ return key !== d.key;
});
+ _pendingChange = _pendingChange || {};
+ _pendingChange[d.key] = undefined;
+ scheduleChange();
}
-
- linkEnter.call(svgIcon('#iD-icon-load', 'inline')).append('span').html(_t.html('save.conflict.download_changes'));
- bodyEnter.append('div').attr('class', 'conflict-container fillL3').call(showConflict, 0);
- bodyEnter.append('div').attr('class', 'conflicts-done').attr('opacity', 0).style('display', 'none').html(_t.html('save.conflict.done'));
- var buttonsEnter = bodyEnter.append('div').attr('class', 'buttons col12 joined conflicts-buttons');
- buttonsEnter.append('button').attr('disabled', _conflictList.length > 1).attr('class', 'action conflicts-button col6').html(_t.html('save.title')).on('click.try_again', tryAgain);
- buttonsEnter.append('button').attr('class', 'secondary-action conflicts-button col6').html(_t.html('confirm.cancel')).on('click.cancel', cancel);
}
- function showConflict(selection, index) {
- index = utilWrap(index, _conflictList.length);
- _shownConflictIndex = index;
- var parent = select(selection.node().parentNode); // enable save button if this is the last conflict being reviewed..
-
- if (index === _conflictList.length - 1) {
- window.setTimeout(function () {
- parent.select('.conflicts-button').attr('disabled', null);
- parent.select('.conflicts-done').transition().attr('opacity', 1).style('display', 'block');
- }, 250);
- }
-
- var conflict = selection.selectAll('.conflict').data([_conflictList[index]]);
- conflict.exit().remove();
- var conflictEnter = conflict.enter().append('div').attr('class', 'conflict');
- conflictEnter.append('h4').attr('class', 'conflict-count').html(_t.html('save.conflict.count', {
- num: index + 1,
- total: _conflictList.length
- }));
- conflictEnter.append('a').attr('class', 'conflict-description').attr('href', '#').html(function (d) {
- return d.name;
- }).on('click', function (d3_event, d) {
- d3_event.preventDefault();
- zoomToEntity(d.id);
- });
- var details = conflictEnter.append('div').attr('class', 'conflict-detail-container');
- details.append('ul').attr('class', 'conflict-detail-list').selectAll('li').data(function (d) {
- return d.details || [];
- }).enter().append('li').attr('class', 'conflict-detail-item').html(function (d) {
- return d;
- });
- details.append('div').attr('class', 'conflict-choices').call(addChoices);
- details.append('div').attr('class', 'conflict-nav-buttons joined cf').selectAll('button').data(['previous', 'next']).enter().append('button').html(function (d) {
- return _t.html('save.conflict.' + d);
- }).attr('class', 'conflict-nav-button action col6').attr('disabled', function (d, i) {
- return i === 0 && index === 0 || i === 1 && index === _conflictList.length - 1 || null;
- }).on('click', function (d3_event, d) {
- d3_event.preventDefault();
- var container = parent.selectAll('.conflict-container');
- var sign = d === 'previous' ? -1 : 1;
- container.selectAll('.conflict').remove();
- container.call(showConflict, index + sign);
- });
+ function addTag() {
+ // Delay render in case this click is blurring an edited combo.
+ // Without the setTimeout, the `content` render would wipe out the pending tag change.
+ window.setTimeout(function () {
+ _showBlank = true;
+ section.reRender();
+ section.selection().selectAll('.tag-list li:last-child input.key').node().focus();
+ }, 20);
}
- function addChoices(selection) {
- var choices = selection.append('ul').attr('class', 'layer-list').selectAll('li').data(function (d) {
- return d.choices || [];
- }); // enter
-
- var choicesEnter = choices.enter().append('li').attr('class', 'layer');
- var labelEnter = choicesEnter.append('label');
- labelEnter.append('input').attr('type', 'radio').attr('name', function (d) {
- return d.id;
- }).on('change', function (d3_event, d) {
- var ul = this.parentNode.parentNode.parentNode;
- ul.__data__.chosen = d.id;
- choose(d3_event, ul, d);
- });
- labelEnter.append('span').html(function (d) {
- return d.text;
- }); // update
-
- choicesEnter.merge(choices).each(function (d) {
- var ul = this.parentNode;
+ function scheduleChange() {
+ // Cache IDs in case the editor is reloaded before the change event is called. - #6028
+ var entityIDs = _entityIDs; // Delay change in case this change is blurring an edited combo. - #5878
- if (ul.__data__.chosen === d.id) {
- choose(null, ul, d);
- }
- });
+ window.setTimeout(function () {
+ if (!_pendingChange) return;
+ dispatch.call('change', this, entityIDs, _pendingChange);
+ _pendingChange = null;
+ }, 10);
}
- function choose(d3_event, ul, datum) {
- if (d3_event) d3_event.preventDefault();
- select(ul).selectAll('li').classed('active', function (d) {
- return d === datum;
- }).selectAll('input').property('checked', function (d) {
- return d === datum;
- });
- var extent = geoExtent();
- var entity;
- entity = context.graph().hasEntity(datum.id);
- if (entity) extent._extend(entity.extent(context.graph()));
- datum.action();
- entity = context.graph().hasEntity(datum.id);
- if (entity) extent._extend(entity.extent(context.graph()));
- zoomToEntity(datum.id, extent);
- }
+ section.state = function (val) {
+ if (!arguments.length) return _state;
- function zoomToEntity(id, extent) {
- context.surface().selectAll('.hover').classed('hover', false);
- var entity = context.graph().hasEntity(id);
+ if (_state !== val) {
+ _orderedKeys = [];
+ _state = val;
+ }
- if (entity) {
- if (extent) {
- context.map().trimmedExtent(extent);
- } else {
- context.map().zoomToEase(entity);
- }
+ return section;
+ };
- context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph())).classed('hover', true);
- }
- } // The conflict list should be an array of objects like:
- // {
- // id: id,
- // name: entityName(local),
- // details: merge.conflicts(),
- // chosen: 1,
- // choices: [
- // choice(id, keepMine, forceLocal),
- // choice(id, keepTheirs, forceRemote)
- // ]
- // }
+ section.presets = function (val) {
+ if (!arguments.length) return _presets;
+ _presets = val;
+ if (_presets && _presets.length && _presets[0].isFallback()) {
+ section.disclosureExpanded(true); // don't collapse the disclosure if the mapper used the raw tag editor - #1881
+ } else if (!_didInteract) {
+ section.disclosureExpanded(null);
+ }
- conflicts.conflictList = function (_) {
- if (!arguments.length) return _conflictList;
- _conflictList = _;
- return conflicts;
+ return section;
};
- conflicts.origChanges = function (_) {
- if (!arguments.length) return _origChanges;
- _origChanges = _;
- return conflicts;
+ section.tags = function (val) {
+ if (!arguments.length) return _tags;
+ _tags = val;
+ return section;
};
- conflicts.shownEntityIds = function () {
- if (_conflictList && typeof _shownConflictIndex === 'number') {
- return [_conflictList[_shownConflictIndex].id];
+ section.entityIDs = function (val) {
+ if (!arguments.length) return _entityIDs;
+
+ if (!_entityIDs || !val || !utilArrayIdentical(_entityIDs, val)) {
+ _entityIDs = val;
+ _orderedKeys = [];
}
- return [];
+ return section;
+ }; // pass an array of regular expressions to test against the tag key
+
+
+ section.readOnlyTags = function (val) {
+ if (!arguments.length) return _readOnlyTags;
+ _readOnlyTags = val;
+ return section;
};
- return utilRebind(conflicts, dispatch, 'on');
+ return utilRebind(section, dispatch, 'on');
}
- function uiConfirm(selection) {
- var modalSelection = uiModal(selection);
- modalSelection.select('.modal').classed('modal-alert', true);
- var section = modalSelection.select('.content');
- section.append('div').attr('class', 'modal-section header');
- section.append('div').attr('class', 'modal-section message-text');
- var buttons = section.append('div').attr('class', 'modal-section buttons cf');
+ function uiDataEditor(context) {
+ var dataHeader = uiDataHeader();
+ var rawTagEditor = uiSectionRawTagEditor('custom-data-tag-editor', context).expandedByDefault(true).readOnlyTags([/./]);
- modalSelection.okButton = function () {
- buttons.append('button').attr('class', 'button ok-button action').on('click.confirm', function () {
- modalSelection.remove();
- }).html(_t.html('confirm.okay')).node().focus();
- return modalSelection;
+ var _datum;
+
+ function dataEditor(selection) {
+ var header = selection.selectAll('.header').data([0]);
+ var headerEnter = header.enter().append('div').attr('class', 'header fillL');
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ context.enter(modeBrowse(context));
+ }).call(svgIcon('#iD-icon-close'));
+ headerEnter.append('h2').call(_t.append('map_data.title'));
+ var body = selection.selectAll('.body').data([0]);
+ body = body.enter().append('div').attr('class', 'body').merge(body);
+ var editor = body.selectAll('.data-editor').data([0]); // enter/update
+
+ editor.enter().append('div').attr('class', 'modal-section data-editor').merge(editor).call(dataHeader.datum(_datum));
+ var rte = body.selectAll('.raw-tag-editor').data([0]); // enter/update
+
+ rte.enter().append('div').attr('class', 'raw-tag-editor data-editor').merge(rte).call(rawTagEditor.tags(_datum && _datum.properties || {}).state('hover').render).selectAll('textarea.tag-text').attr('readonly', true).classed('readonly', true);
+ }
+
+ dataEditor.datum = function (val) {
+ if (!arguments.length) return _datum;
+ _datum = val;
+ return this;
};
- return modalSelection;
+ return dataEditor;
}
- function uiChangesetEditor(context) {
- var dispatch = dispatch$8('change');
- var formFields = uiFormFields(context);
- var commentCombo = uiCombobox(context, 'comment').caseSensitive(true);
+ function uiOsmoseDetails(context) {
+ var _qaItem;
- var _fieldsArr;
+ function issueString(d, type) {
+ if (!d) return ''; // Issue strings are cached from Osmose API
- var _tags;
+ var s = services.osmose.getStrings(d.itemType);
+ return type in s ? s[type] : '';
+ }
- var _changesetID;
+ function osmoseDetails(selection) {
+ var details = selection.selectAll('.error-details').data(_qaItem ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ });
+ details.exit().remove();
+ var detailsEnter = details.enter().append('div').attr('class', 'error-details qa-details-container'); // Description
- function changesetEditor(selection) {
- render(selection);
- }
+ if (issueString(_qaItem, 'detail')) {
+ var div = detailsEnter.append('div').attr('class', 'qa-details-subsection');
+ div.append('h4').call(_t.append('QA.keepRight.detail_description'));
+ div.append('p').attr('class', 'qa-details-description-text').html(function (d) {
+ return issueString(d, 'detail');
+ }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
+ } // Elements (populated later as data is requested)
- function render(selection) {
- var initial = false;
- if (!_fieldsArr) {
- initial = true;
- var presets = _mainPresetIndex;
- _fieldsArr = [uiField(context, presets.field('comment'), null, {
- show: true,
- revert: false
- }), uiField(context, presets.field('source'), null, {
- show: false,
- revert: false
- }), uiField(context, presets.field('hashtags'), null, {
- show: false,
- revert: false
- })];
+ var detailsDiv = detailsEnter.append('div').attr('class', 'qa-details-subsection');
+ var elemsDiv = detailsEnter.append('div').attr('class', 'qa-details-subsection'); // Suggested Fix (mustn't exist for every issue type)
- _fieldsArr.forEach(function (field) {
- field.on('change', function (t, onInput) {
- dispatch.call('change', field, undefined, t, onInput);
- });
- });
- }
+ if (issueString(_qaItem, 'fix')) {
+ var _div = detailsEnter.append('div').attr('class', 'qa-details-subsection');
- _fieldsArr.forEach(function (field) {
- field.tags(_tags);
- });
+ _div.append('h4').call(_t.append('QA.osmose.fix_title'));
- selection.call(formFields.fieldsArr(_fieldsArr));
+ _div.append('p').html(function (d) {
+ return issueString(d, 'fix');
+ }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
+ } // Common Pitfalls (mustn't exist for every issue type)
- if (initial) {
- var commentField = selection.select('.form-field-comment textarea');
- var commentNode = commentField.node();
- if (commentNode) {
- commentNode.focus();
- commentNode.select();
- } // trigger a 'blur' event so that comment field can be cleaned
- // and checked for hashtags, even if retrieved from localstorage
+ if (issueString(_qaItem, 'trap')) {
+ var _div2 = detailsEnter.append('div').attr('class', 'qa-details-subsection');
+ _div2.append('h4').call(_t.append('QA.osmose.trap_title'));
- utilTriggerEvent(commentField, 'blur');
- var osm = context.connection();
+ _div2.append('p').html(function (d) {
+ return issueString(d, 'trap');
+ }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
+ } // Save current item to check if UI changed by time request resolves
- if (osm) {
- osm.userChangesets(function (err, changesets) {
- if (err) return;
- var comments = changesets.map(function (changeset) {
- var comment = changeset.tags.comment;
- return comment ? {
- title: comment,
- value: comment
- } : null;
- }).filter(Boolean);
- commentField.call(commentCombo.data(utilArrayUniqBy(comments, 'title')));
- });
- }
- } // Add warning if comment mentions Google
+ var thisItem = _qaItem;
+ services.osmose.loadIssueDetail(_qaItem).then(function (d) {
+ // No details to add if there are no associated issue elements
+ if (!d.elems || d.elems.length === 0) return; // Do nothing if UI has moved on by the time this resolves
- var hasGoogle = _tags.comment.match(/google/i);
+ if (context.selectedErrorID() !== thisItem.id && context.container().selectAll(".qaItem.osmose.hover.itemId-".concat(thisItem.id)).empty()) return; // Things like keys and values are dynamically added to a subtitle string
- var commentWarning = selection.select('.form-field-comment').selectAll('.comment-warning').data(hasGoogle ? [0] : []);
- commentWarning.exit().transition().duration(200).style('opacity', 0).remove();
- var commentEnter = commentWarning.enter().insert('div', '.tag-reference-body').attr('class', 'field-warning comment-warning').style('opacity', 0);
- commentEnter.append('a').attr('target', '_blank').call(svgIcon('#iD-icon-alert', 'inline')).attr('href', _t('commit.google_warning_link')).append('span').html(_t.html('commit.google_warning'));
- commentEnter.transition().duration(200).style('opacity', 1);
- }
+ if (d.detail) {
+ detailsDiv.append('h4').call(_t.append('QA.osmose.detail_title'));
+ detailsDiv.append('p').html(function (d) {
+ return d.detail;
+ }).selectAll('a').attr('rel', 'noopener').attr('target', '_blank');
+ } // Create list of linked issue elements
- changesetEditor.tags = function (_) {
- if (!arguments.length) return _tags;
- _tags = _; // Don't reset _fieldsArr here.
- return changesetEditor;
- };
+ elemsDiv.append('h4').call(_t.append('QA.osmose.elems_title'));
+ elemsDiv.append('ul').selectAll('li').data(d.elems).enter().append('li').append('a').attr('href', '#').attr('class', 'error_entity_link').text(function (d) {
+ return d;
+ }).each(function () {
+ var link = select(this);
+ var entityID = this.textContent;
+ var entity = context.hasEntity(entityID); // Add click handler
- changesetEditor.changesetID = function (_) {
- if (!arguments.length) return _changesetID;
- if (_changesetID === _) return changesetEditor;
- _changesetID = _;
- _fieldsArr = null;
- return changesetEditor;
- };
+ link.on('mouseenter', function () {
+ utilHighlightEntities([entityID], true, context);
+ }).on('mouseleave', function () {
+ utilHighlightEntities([entityID], false, context);
+ }).on('click', function (d3_event) {
+ d3_event.preventDefault();
+ utilHighlightEntities([entityID], false, context);
+ var osmlayer = context.layers().layer('osm');
- return utilRebind(changesetEditor, dispatch, 'on');
- }
+ if (!osmlayer.enabled()) {
+ osmlayer.enabled(true);
+ }
- function uiSectionChanges(context) {
- var detected = utilDetect();
- var _discardTags = {};
- _mainFileFetcher.get('discarded').then(function (d) {
- _discardTags = d;
- })["catch"](function () {
- /* ignore */
- });
- var section = uiSection('changes-list', context).label(function () {
- var history = context.history();
- var summary = history.difference().summary();
- return _t('inspector.title_count', {
- title: _t.html('commit.changes'),
- count: summary.length
- });
- }).disclosureContent(renderDisclosureContent);
+ context.map().centerZoom(d.loc, 20);
- function renderDisclosureContent(selection) {
- var history = context.history();
- var summary = history.difference().summary();
- var container = selection.selectAll('.commit-section').data([0]);
- var containerEnter = container.enter().append('div').attr('class', 'commit-section');
- containerEnter.append('ul').attr('class', 'changeset-list');
- container = containerEnter.merge(container);
- var items = container.select('ul').selectAll('li').data(summary);
- var itemsEnter = items.enter().append('li').attr('class', 'change-item');
- var buttons = itemsEnter.append('button').on('mouseover', mouseover).on('mouseout', mouseout).on('click', click);
- buttons.each(function (d) {
- select(this).call(svgIcon('#iD-icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
- });
- buttons.append('span').attr('class', 'change-type').html(function (d) {
- return _t.html('commit.' + d.changeType) + ' ';
- });
- buttons.append('strong').attr('class', 'entity-type').html(function (d) {
- var matched = _mainPresetIndex.match(d.entity, d.graph);
- return matched && matched.name() || utilDisplayType(d.entity.id);
- });
- buttons.append('span').attr('class', 'entity-name').html(function (d) {
- var name = utilDisplayName(d.entity) || '',
- string = '';
+ if (entity) {
+ context.enter(modeSelect(context, [entityID]));
+ } else {
+ context.loadEntity(entityID, function (err, result) {
+ if (err) return;
+ var entity = result.data.find(function (e) {
+ return e.id === entityID;
+ });
+ if (entity) context.enter(modeSelect(context, [entityID]));
+ });
+ }
+ }); // Replace with friendly name if possible
+ // (The entity may not yet be loaded into the graph)
- if (name !== '') {
- string += ':';
- }
+ if (entity) {
+ var name = utilDisplayName(entity); // try to use common name
- return string += ' ' + name;
- });
- items = itemsEnter.merge(items); // Download changeset link
+ if (!name) {
+ var preset = _mainPresetIndex.match(entity, context.graph());
+ name = preset && !preset.isFallback() && preset.name(); // fallback to preset name
+ }
- var changeset = new osmChangeset().update({
- id: undefined
- });
- var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
- delete changeset.id; // Export without chnageset_id
+ if (name) {
+ this.innerText = name;
+ }
+ }
+ }); // Don't hide entities related to this issue - #5880
- var data = JXON.stringify(changeset.osmChangeJXON(changes));
- var blob = new Blob([data], {
- type: 'text/xml;charset=utf-8;'
+ context.features().forceVisible(d.elems);
+ context.map().pan([0, 0]); // trigger a redraw
+ })["catch"](function (err) {
+ console.log(err); // eslint-disable-line no-console
});
- var fileName = 'changes.osc';
- var linkEnter = container.selectAll('.download-changes').data([0]).enter().append('a').attr('class', 'download-changes');
+ }
- if (detected.download) {
- // All except IE11 and Edge
- linkEnter // download the data as a file
- .attr('href', window.URL.createObjectURL(blob)).attr('download', fileName);
- } else {
- // IE11 and Edge
- linkEnter // open data uri in a new tab
- .attr('target', '_blank').on('click.download', function () {
- navigator.msSaveBlob(blob, fileName);
- });
- }
+ osmoseDetails.issue = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return osmoseDetails;
+ };
- linkEnter.call(svgIcon('#iD-icon-load', 'inline')).append('span').html(_t.html('commit.download_changes'));
+ return osmoseDetails;
+ }
- function mouseover(d) {
- if (d.entity) {
- context.surface().selectAll(utilEntityOrMemberSelector([d.entity.id], context.graph())).classed('hover', true);
- }
- }
+ function uiOsmoseHeader() {
+ var _qaItem;
- function mouseout() {
- context.surface().selectAll('.hover').classed('hover', false);
- }
+ function issueTitle(d) {
+ var unknown = _t('inspector.unknown');
+ if (!d) return unknown; // Issue titles supplied by Osmose
- function click(d3_event, change) {
- if (change.changeType !== 'deleted') {
- var entity = change.entity;
- context.map().zoomToEase(entity);
- context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph())).classed('hover', true);
- }
- }
+ var s = services.osmose.getStrings(d.itemType);
+ return 'title' in s ? s.title : unknown;
}
- return section;
- }
-
- function uiCommitWarnings(context) {
- function commitWarnings(selection) {
- var issuesBySeverity = context.validator().getIssuesBySeverity({
- what: 'edited',
- where: 'all',
- includeDisabledRules: true
+ function osmoseHeader(selection) {
+ var header = selection.selectAll('.qa-header').data(_qaItem ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
});
+ header.exit().remove();
+ var headerEnter = header.enter().append('div').attr('class', 'qa-header');
+ var svgEnter = headerEnter.append('div').attr('class', 'qa-header-icon').classed('new', function (d) {
+ return d.id < 0;
+ }).append('svg').attr('width', '20px').attr('height', '30px').attr('viewbox', '0 0 20 30').attr('class', function (d) {
+ return "preset-icon-28 qaItem ".concat(d.service, " itemId-").concat(d.id, " itemType-").concat(d.itemType);
+ });
+ svgEnter.append('polygon').attr('fill', function (d) {
+ return services.osmose.getColor(d.item);
+ }).attr('class', 'qaItem-fill').attr('points', '16,3 4,3 1,6 1,17 4,20 7,20 10,27 13,20 16,20 19,17.033 19,6');
+ svgEnter.append('use').attr('class', 'icon-annotation').attr('width', '13px').attr('height', '13px').attr('transform', 'translate(3.5, 5)').attr('xlink:href', function (d) {
+ var picon = d.icon;
- for (var severity in issuesBySeverity) {
- var issues = issuesBySeverity[severity];
-
- if (severity !== 'error') {
- // exclude 'fixme' and similar - #8603
- issues = issues.filter(function (issue) {
- return issue.type !== 'help_request';
- });
+ if (!picon) {
+ return '';
+ } else {
+ var isMaki = /^maki-/.test(picon);
+ return "#".concat(picon).concat(isMaki ? '-11' : '');
}
-
- var section = severity + '-section';
- var issueItem = severity + '-item';
- var container = selection.selectAll('.' + section).data(issues.length ? [0] : []);
- container.exit().remove();
- var containerEnter = container.enter().append('div').attr('class', 'modal-section ' + section + ' fillL2');
- containerEnter.append('h3').html(severity === 'warning' ? _t.html('commit.warnings') : _t.html('commit.errors'));
- containerEnter.append('ul').attr('class', 'changeset-list');
- container = containerEnter.merge(container);
- var items = container.select('ul').selectAll('li').data(issues, function (d) {
- return d.key;
- });
- items.exit().remove();
- var itemsEnter = items.enter().append('li').attr('class', issueItem);
- var buttons = itemsEnter.append('button').on('mouseover', function (d3_event, d) {
- if (d.entityIds) {
- context.surface().selectAll(utilEntityOrMemberSelector(d.entityIds, context.graph())).classed('hover', true);
- }
- }).on('mouseout', function () {
- context.surface().selectAll('.hover').classed('hover', false);
- }).on('click', function (d3_event, d) {
- context.validator().focusIssue(d);
- });
- buttons.call(svgIcon('#iD-icon-alert', 'pre-text'));
- buttons.append('strong').attr('class', 'issue-message');
- buttons.filter(function (d) {
- return d.tooltip;
- }).call(uiTooltip().title(function (d) {
- return d.tooltip;
- }).placement('top'));
- items = itemsEnter.merge(items);
- items.selectAll('.issue-message').html(function (d) {
- return d.message(context);
- });
- }
+ });
+ headerEnter.append('div').attr('class', 'qa-header-label').text(issueTitle);
}
- return commitWarnings;
- }
+ osmoseHeader.issue = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return osmoseHeader;
+ };
- var readOnlyTags = [/^changesets_count$/, /^created_by$/, /^ideditor:/, /^imagery_used$/, /^host$/, /^locale$/, /^warnings:/, /^resolved:/, /^closed:note$/, /^closed:keepright$/, /^closed:improveosm:/, /^closed:osmose:/]; // treat most punctuation (except -, _, +, &) as hashtag delimiters - #4398
- // from https://stackoverflow.com/a/25575009
+ return osmoseHeader;
+ }
- var hashtagRegex = /(#[^\u2000-\u206F\u2E00-\u2E7F\s\\'!"#$%()*,.\/:;<=>?@\[\]^`{|}~]+)/g;
- function uiCommit(context) {
- var dispatch = dispatch$8('cancel');
+ function uiViewOnOsmose() {
+ var _qaItem;
- var _userDetails;
+ function viewOnOsmose(selection) {
+ var url;
- var _selection;
+ if (services.osmose && _qaItem instanceof QAItem) {
+ url = services.osmose.itemURL(_qaItem);
+ }
- var changesetEditor = uiChangesetEditor(context).on('change', changeTags);
- var rawTagEditor = uiSectionRawTagEditor('changeset-tag-editor', context).on('change', changeTags).readOnlyTags(readOnlyTags);
- var commitChanges = uiSectionChanges(context);
- var commitWarnings = uiCommitWarnings(context);
+ var link = selection.selectAll('.view-on-osmose').data(url ? [url] : []); // exit
- function commit(selection) {
- _selection = selection; // Initialize changeset if one does not exist yet.
+ link.exit().remove(); // enter
- if (!context.changeset) initChangeset();
- loadDerivedChangesetTags();
- selection.call(render);
+ var linkEnter = link.enter().append('a').attr('class', 'view-on-osmose').attr('target', '_blank').attr('rel', 'noopener') // security measure
+ .attr('href', function (d) {
+ return d;
+ }).call(svgIcon('#iD-icon-out-link', 'inline'));
+ linkEnter.append('span').call(_t.append('inspector.view_on_osmose'));
}
- function initChangeset() {
- // expire stored comment, hashtags, source after cutoff datetime - #3947 #4899
- var commentDate = +corePreferences('commentDate') || 0;
- var currDate = Date.now();
- var cutoff = 2 * 86400 * 1000; // 2 days
+ viewOnOsmose.what = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return viewOnOsmose;
+ };
- if (commentDate > currDate || currDate - commentDate > cutoff) {
- corePreferences('comment', null);
- corePreferences('hashtags', null);
- corePreferences('source', null);
- } // load in explicitly-set values, if any
+ return viewOnOsmose;
+ }
+ function uiOsmoseEditor(context) {
+ var dispatch = dispatch$8('change');
+ var qaDetails = uiOsmoseDetails(context);
+ var qaHeader = uiOsmoseHeader();
- if (context.defaultChangesetComment()) {
- corePreferences('comment', context.defaultChangesetComment());
- corePreferences('commentDate', Date.now());
- }
+ var _qaItem;
- if (context.defaultChangesetSource()) {
- corePreferences('source', context.defaultChangesetSource());
- corePreferences('commentDate', Date.now());
- }
+ function osmoseEditor(selection) {
+ var header = selection.selectAll('.header').data([0]);
+ var headerEnter = header.enter().append('div').attr('class', 'header fillL');
+ headerEnter.append('button').attr('class', 'close').attr('title', _t('icons.close')).on('click', function () {
+ return context.enter(modeBrowse(context));
+ }).call(svgIcon('#iD-icon-close'));
+ headerEnter.append('h2').call(_t.append('QA.osmose.title'));
+ var body = selection.selectAll('.body').data([0]);
+ body = body.enter().append('div').attr('class', 'body').merge(body);
+ var editor = body.selectAll('.qa-editor').data([0]);
+ editor.enter().append('div').attr('class', 'modal-section qa-editor').merge(editor).call(qaHeader.issue(_qaItem)).call(qaDetails.issue(_qaItem)).call(osmoseSaveSection);
+ var footer = selection.selectAll('.footer').data([0]);
+ footer.enter().append('div').attr('class', 'footer').merge(footer).call(uiViewOnOsmose().what(_qaItem));
+ }
- if (context.defaultChangesetHashtags()) {
- corePreferences('hashtags', context.defaultChangesetHashtags());
- corePreferences('commentDate', Date.now());
- }
+ function osmoseSaveSection(selection) {
+ var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
- var detected = utilDetect();
- var tags = {
- comment: corePreferences('comment') || '',
- created_by: context.cleanTagValue('iD ' + context.version),
- host: context.cleanTagValue(detected.host),
- locale: context.cleanTagValue(_mainLocalizer.localeCode())
- }; // call findHashtags initially - this will remove stored
- // hashtags if any hashtags are found in the comment - #4304
+ var isShown = _qaItem && isSelected;
+ var saveSection = selection.selectAll('.qa-save').data(isShown ? [_qaItem] : [], function (d) {
+ return "".concat(d.id, "-").concat(d.status || 0);
+ }); // exit
- findHashtags(tags, true);
- var hashtags = corePreferences('hashtags');
+ saveSection.exit().remove(); // enter
- if (hashtags) {
- tags.hashtags = hashtags;
- }
+ var saveSectionEnter = saveSection.enter().append('div').attr('class', 'qa-save save-section cf'); // update
- var source = corePreferences('source');
+ saveSection = saveSectionEnter.merge(saveSection).call(qaSaveButtons);
+ }
- if (source) {
- tags.source = source;
- }
+ function qaSaveButtons(selection) {
+ var isSelected = _qaItem && _qaItem.id === context.selectedErrorID();
- var photoOverlaysUsed = context.history().photoOverlaysUsed();
+ var buttonSection = selection.selectAll('.buttons').data(isSelected ? [_qaItem] : [], function (d) {
+ return d.status + d.id;
+ }); // exit
- if (photoOverlaysUsed.length) {
- var sources = (tags.source || '').split(';'); // include this tag for any photo layer
+ buttonSection.exit().remove(); // enter
- if (sources.indexOf('streetlevel imagery') === -1) {
- sources.push('streetlevel imagery');
- } // add the photo overlays used during editing as sources
+ var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons');
+ buttonEnter.append('button').attr('class', 'button close-button action');
+ buttonEnter.append('button').attr('class', 'button ignore-button action'); // update
+ buttonSection = buttonSection.merge(buttonEnter);
+ buttonSection.select('.close-button').call(_t.append('QA.keepRight.close')).on('click.close', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
- photoOverlaysUsed.forEach(function (photoOverlay) {
- if (sources.indexOf(photoOverlay) === -1) {
- sources.push(photoOverlay);
- }
- });
- tags.source = context.cleanTagValue(sources.join(';'));
- }
+ var qaService = services.osmose;
- context.changeset = new osmChangeset({
- tags: tags
+ if (qaService) {
+ d.newStatus = 'done';
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
});
- } // Calculates read-only metadata tags based on the user's editing session and applies
- // them to the changeset.
+ buttonSection.select('.ignore-button').call(_t.append('QA.keepRight.ignore')).on('click.ignore', function (d3_event, d) {
+ this.blur(); // avoid keeping focus on the button - #4641
+ var qaService = services.osmose;
- function loadDerivedChangesetTags() {
- var osm = context.connection();
- if (!osm) return;
- var tags = Object.assign({}, context.changeset.tags); // shallow copy
- // assign tags for imagery used
+ if (qaService) {
+ d.newStatus = 'false';
+ qaService.postUpdate(d, function (err, item) {
+ return dispatch.call('change', item);
+ });
+ }
+ });
+ } // NOTE: Don't change method name until UI v3 is merged
- var imageryUsed = context.cleanTagValue(context.history().imageryUsed().join(';'));
- tags.imagery_used = imageryUsed || 'None'; // assign tags for closed issues and notes
- var osmClosed = osm.getClosedIDs();
- var itemType;
+ osmoseEditor.error = function (val) {
+ if (!arguments.length) return _qaItem;
+ _qaItem = val;
+ return osmoseEditor;
+ };
- if (osmClosed.length) {
- tags['closed:note'] = context.cleanTagValue(osmClosed.join(';'));
- }
+ return utilRebind(osmoseEditor, dispatch, 'on');
+ }
- if (services.keepRight) {
- var krClosed = services.keepRight.getClosedIDs();
+ function uiSidebar(context) {
+ var inspector = uiInspector(context);
+ var dataEditor = uiDataEditor(context);
+ var noteEditor = uiNoteEditor(context);
+ var improveOsmEditor = uiImproveOsmEditor(context);
+ var keepRightEditor = uiKeepRightEditor(context);
+ var osmoseEditor = uiOsmoseEditor(context);
- if (krClosed.length) {
- tags['closed:keepright'] = context.cleanTagValue(krClosed.join(';'));
- }
- }
+ var _current;
- if (services.improveOSM) {
- var iOsmClosed = services.improveOSM.getClosedCounts();
+ var _wasData = false;
+ var _wasNote = false;
+ var _wasQaItem = false; // use pointer events on supported platforms; fallback to mouse events
- for (itemType in iOsmClosed) {
- tags['closed:improveosm:' + itemType] = context.cleanTagValue(iOsmClosed[itemType].toString());
- }
- }
+ var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
- if (services.osmose) {
- var osmoseClosed = services.osmose.getClosedCounts();
+ function sidebar(selection) {
+ var container = context.container();
+ var minWidth = 240;
+ var sidebarWidth;
+ var containerWidth;
+ var dragOffset; // Set the initial width constraints
- for (itemType in osmoseClosed) {
- tags['closed:osmose:' + itemType] = context.cleanTagValue(osmoseClosed[itemType].toString());
- }
- } // remove existing issue counts
+ selection.style('min-width', minWidth + 'px').style('max-width', '400px').style('width', '33.3333%');
+ var resizer = selection.append('div').attr('class', 'sidebar-resizer').on(_pointerPrefix + 'down.sidebar-resizer', pointerdown);
+ var downPointerId, lastClientX, containerLocGetter;
+
+ function pointerdown(d3_event) {
+ if (downPointerId) return;
+ if ('button' in d3_event && d3_event.button !== 0) return;
+ downPointerId = d3_event.pointerId || 'mouse';
+ lastClientX = d3_event.clientX;
+ containerLocGetter = utilFastMouse(container.node()); // offset from edge of sidebar-resizer
+ dragOffset = utilFastMouse(resizer.node())(d3_event)[0] - 1;
+ sidebarWidth = selection.node().getBoundingClientRect().width;
+ containerWidth = container.node().getBoundingClientRect().width;
+ var widthPct = sidebarWidth / containerWidth * 100;
+ selection.style('width', widthPct + '%') // lock in current width
+ .style('max-width', '85%'); // but allow larger widths
- for (var key in tags) {
- if (key.match(/(^warnings:)|(^resolved:)/)) {
- delete tags[key];
- }
+ resizer.classed('dragging', true);
+ select(window).on('touchmove.sidebar-resizer', function (d3_event) {
+ // disable page scrolling while resizing on touch input
+ d3_event.preventDefault();
+ }, {
+ passive: false
+ }).on(_pointerPrefix + 'move.sidebar-resizer', pointermove).on(_pointerPrefix + 'up.sidebar-resizer pointercancel.sidebar-resizer', pointerup);
}
- function addIssueCounts(issues, prefix) {
- var issuesByType = utilArrayGroupBy(issues, 'type');
-
- for (var issueType in issuesByType) {
- var issuesOfType = issuesByType[issueType];
+ function pointermove(d3_event) {
+ if (downPointerId !== (d3_event.pointerId || 'mouse')) return;
+ d3_event.preventDefault();
+ var dx = d3_event.clientX - lastClientX;
+ lastClientX = d3_event.clientX;
+ var isRTL = _mainLocalizer.textDirection() === 'rtl';
+ var scaleX = isRTL ? 0 : 1;
+ var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
+ var x = containerLocGetter(d3_event)[0] - dragOffset;
+ sidebarWidth = isRTL ? containerWidth - x : x;
+ var isCollapsed = selection.classed('collapsed');
+ var shouldCollapse = sidebarWidth < minWidth;
+ selection.classed('collapsed', shouldCollapse);
- if (issuesOfType[0].subtype) {
- var issuesBySubtype = utilArrayGroupBy(issuesOfType, 'subtype');
+ if (shouldCollapse) {
+ if (!isCollapsed) {
+ selection.style(xMarginProperty, '-400px').style('width', '400px');
+ context.ui().onResize([(sidebarWidth - dx) * scaleX, 0]);
+ }
+ } else {
+ var widthPct = sidebarWidth / containerWidth * 100;
+ selection.style(xMarginProperty, null).style('width', widthPct + '%');
- for (var issueSubtype in issuesBySubtype) {
- var issuesOfSubtype = issuesBySubtype[issueSubtype];
- tags[prefix + ':' + issueType + ':' + issueSubtype] = context.cleanTagValue(issuesOfSubtype.length.toString());
- }
+ if (isCollapsed) {
+ context.ui().onResize([-sidebarWidth * scaleX, 0]);
} else {
- tags[prefix + ':' + issueType] = context.cleanTagValue(issuesOfType.length.toString());
+ context.ui().onResize([-dx * scaleX, 0]);
}
}
- } // add counts of warnings generated by the user's edits
-
+ }
- var warnings = context.validator().getIssuesBySeverity({
- what: 'edited',
- where: 'all',
- includeIgnored: true,
- includeDisabledRules: true
- }).warning.filter(function (issue) {
- return issue.type !== 'help_request';
- }); // exclude 'fixme' and similar - #8603
+ function pointerup(d3_event) {
+ if (downPointerId !== (d3_event.pointerId || 'mouse')) return;
+ downPointerId = null;
+ resizer.classed('dragging', false);
+ select(window).on('touchmove.sidebar-resizer', null).on(_pointerPrefix + 'move.sidebar-resizer', null).on(_pointerPrefix + 'up.sidebar-resizer pointercancel.sidebar-resizer', null);
+ }
- addIssueCounts(warnings, 'warnings'); // add counts of issues resolved by the user's edits
+ var featureListWrap = selection.append('div').attr('class', 'feature-list-pane').call(uiFeatureList(context));
+ var inspectorWrap = selection.append('div').attr('class', 'inspector-hidden inspector-wrap');
- var resolvedIssues = context.validator().getResolvedIssues();
- addIssueCounts(resolvedIssues, 'resolved');
- context.changeset = context.changeset.update({
- tags: tags
- });
- }
+ var hoverModeSelect = function hoverModeSelect(targets) {
+ context.container().selectAll('.feature-list-item button').classed('hover', false);
- function render(selection) {
- var osm = context.connection();
- if (!osm) return;
- var header = selection.selectAll('.header').data([0]);
- var headerTitle = header.enter().append('div').attr('class', 'header fillL');
- headerTitle.append('div').append('h3').html(_t.html('commit.title'));
- headerTitle.append('button').attr('class', 'close').on('click', function () {
- dispatch.call('cancel', this);
- }).call(svgIcon('#iD-icon-close'));
- var body = selection.selectAll('.body').data([0]);
- body = body.enter().append('div').attr('class', 'body').merge(body); // Changeset Section
+ if (context.selectedIDs().length > 1 && targets && targets.length) {
+ var elements = context.container().selectAll('.feature-list-item button').filter(function (node) {
+ return targets.indexOf(node) !== -1;
+ });
- var changesetSection = body.selectAll('.changeset-editor').data([0]);
- changesetSection = changesetSection.enter().append('div').attr('class', 'modal-section changeset-editor').merge(changesetSection);
- changesetSection.call(changesetEditor.changesetID(context.changeset.id).tags(context.changeset.tags)); // Warnings
+ if (!elements.empty()) {
+ elements.classed('hover', true);
+ }
+ }
+ };
- body.call(commitWarnings); // Upload Explanation
+ sidebar.hoverModeSelect = throttle(hoverModeSelect, 200);
- var saveSection = body.selectAll('.save-section').data([0]);
- saveSection = saveSection.enter().append('div').attr('class', 'modal-section save-section fillL').merge(saveSection);
- var prose = saveSection.selectAll('.commit-info').data([0]);
+ function hover(targets) {
+ var datum = targets && targets.length && targets[0];
- if (prose.enter().size()) {
- // first time, make sure to update user details in prose
- _userDetails = null;
- }
+ if (datum && datum.__featurehash__) {
+ // hovering on data
+ _wasData = true;
+ sidebar.show(dataEditor.datum(datum));
+ selection.selectAll('.sidebar-component').classed('inspector-hover', true);
+ } else if (datum instanceof osmNote) {
+ if (context.mode().id === 'drag-note') return;
+ _wasNote = true;
+ var osm = services.osm;
- prose = prose.enter().append('p').attr('class', 'commit-info').html(_t.html('commit.upload_explanation')).merge(prose); // always check if this has changed, but only update prose.html()
- // if needed, because it can trigger a style recalculation
+ if (osm) {
+ datum = osm.getNote(datum.id); // marker may contain stale data - get latest
+ }
- osm.userDetails(function (err, user) {
- if (err) return;
- if (_userDetails === user) return; // no change
+ sidebar.show(noteEditor.note(datum));
+ selection.selectAll('.sidebar-component').classed('inspector-hover', true);
+ } else if (datum instanceof QAItem) {
+ _wasQaItem = true;
+ var errService = services[datum.service];
- _userDetails = user;
- var userLink = select(document.createElement('div'));
+ if (errService) {
+ // marker may contain stale data - get latest
+ datum = errService.getError(datum.id);
+ } // Currently only three possible services
- if (user.image_url) {
- userLink.append('img').attr('src', user.image_url).attr('class', 'icon pre-text user-icon');
- }
- userLink.append('a').attr('class', 'user-info').html(user.display_name).attr('href', osm.userURL(user.display_name)).attr('target', '_blank');
- prose.html(_t.html('commit.upload_explanation_with_user', {
- user: userLink.html()
- }));
- }); // Request Review
+ var errEditor;
- var requestReview = saveSection.selectAll('.request-review').data([0]); // Enter
+ if (datum.service === 'keepRight') {
+ errEditor = keepRightEditor;
+ } else if (datum.service === 'osmose') {
+ errEditor = osmoseEditor;
+ } else {
+ errEditor = improveOsmEditor;
+ }
- var requestReviewEnter = requestReview.enter().append('div').attr('class', 'request-review');
- var requestReviewDomId = utilUniqueDomId('commit-input-request-review');
- var labelEnter = requestReviewEnter.append('label').attr('for', requestReviewDomId);
+ context.container().selectAll('.qaItem.' + datum.service).classed('hover', function (d) {
+ return d.id === datum.id;
+ });
+ sidebar.show(errEditor.error(datum));
+ selection.selectAll('.sidebar-component').classed('inspector-hover', true);
+ } else if (!_current && datum instanceof osmEntity) {
+ featureListWrap.classed('inspector-hidden', true);
+ inspectorWrap.classed('inspector-hidden', false).classed('inspector-hover', true);
- if (!labelEnter.empty()) {
- labelEnter.call(uiTooltip().title(_t.html('commit.request_review_info')).placement('top'));
+ if (!inspector.entityIDs() || !utilArrayIdentical(inspector.entityIDs(), [datum.id]) || inspector.state() !== 'hover') {
+ inspector.state('hover').entityIDs([datum.id]).newFeature(false);
+ inspectorWrap.call(inspector);
+ }
+ } else if (!_current) {
+ featureListWrap.classed('inspector-hidden', false);
+ inspectorWrap.classed('inspector-hidden', true);
+ inspector.state('hide');
+ } else if (_wasData || _wasNote || _wasQaItem) {
+ _wasNote = false;
+ _wasData = false;
+ _wasQaItem = false;
+ context.container().selectAll('.note').classed('hover', false);
+ context.container().selectAll('.qaItem').classed('hover', false);
+ sidebar.hide();
+ }
}
- labelEnter.append('input').attr('type', 'checkbox').attr('id', requestReviewDomId);
- labelEnter.append('span').html(_t.html('commit.request_review')); // Update
-
- requestReview = requestReview.merge(requestReviewEnter);
- var requestReviewInput = requestReview.selectAll('input').property('checked', isReviewRequested(context.changeset.tags)).on('change', toggleRequestReview); // Buttons
+ sidebar.hover = throttle(hover, 200);
- var buttonSection = saveSection.selectAll('.buttons').data([0]); // enter
+ sidebar.intersects = function (extent) {
+ var rect = selection.node().getBoundingClientRect();
+ return extent.intersects([context.projection.invert([0, rect.height]), context.projection.invert([rect.width, 0])]);
+ };
- var buttonEnter = buttonSection.enter().append('div').attr('class', 'buttons fillL');
- buttonEnter.append('button').attr('class', 'secondary-action button cancel-button').append('span').attr('class', 'label').html(_t.html('commit.cancel'));
- var uploadButton = buttonEnter.append('button').attr('class', 'action button save-button');
- uploadButton.append('span').attr('class', 'label').html(_t.html('commit.save'));
- var uploadBlockerTooltipText = getUploadBlockerMessage(); // update
+ sidebar.select = function (ids, newFeature) {
+ sidebar.hide();
- buttonSection = buttonSection.merge(buttonEnter);
- buttonSection.selectAll('.cancel-button').on('click.cancel', function () {
- dispatch.call('cancel', this);
- });
- buttonSection.selectAll('.save-button').classed('disabled', uploadBlockerTooltipText !== null).on('click.save', function () {
- if (!select(this).classed('disabled')) {
- this.blur(); // avoid keeping focus on the button - #4641
+ if (ids && ids.length) {
+ var entity = ids.length === 1 && context.entity(ids[0]);
- for (var key in context.changeset.tags) {
- // remove any empty keys before upload
- if (!key) delete context.changeset.tags[key];
+ if (entity && newFeature && selection.classed('collapsed')) {
+ // uncollapse the sidebar
+ var extent = entity.extent(context.graph());
+ sidebar.expand(sidebar.intersects(extent));
}
- context.uploader().save(context.changeset);
- }
- }); // remove any existing tooltip
-
- uiTooltip().destroyAny(buttonSection.selectAll('.save-button'));
-
- if (uploadBlockerTooltipText) {
- buttonSection.selectAll('.save-button').call(uiTooltip().title(uploadBlockerTooltipText).placement('top'));
- } // Raw Tag Editor
-
+ featureListWrap.classed('inspector-hidden', true);
+ inspectorWrap.classed('inspector-hidden', false).classed('inspector-hover', false); // reload the UI even if the ids are the same since the entities
+ // themselves may have changed
- var tagSection = body.selectAll('.tag-section.raw-tag-editor').data([0]);
- tagSection = tagSection.enter().append('div').attr('class', 'modal-section tag-section raw-tag-editor').merge(tagSection);
- tagSection.call(rawTagEditor.tags(Object.assign({}, context.changeset.tags)) // shallow copy
- .render);
- var changesSection = body.selectAll('.commit-changes-section').data([0]);
- changesSection = changesSection.enter().append('div').attr('class', 'modal-section commit-changes-section').merge(changesSection); // Change summary
+ inspector.state('select').entityIDs(ids).newFeature(newFeature);
+ inspectorWrap.call(inspector);
+ } else {
+ inspector.state('hide');
+ }
+ };
- changesSection.call(commitChanges.render);
+ sidebar.showPresetList = function () {
+ inspector.showList();
+ };
- function toggleRequestReview() {
- var rr = requestReviewInput.property('checked');
- updateChangeset({
- review_requested: rr ? 'yes' : undefined
- });
- tagSection.call(rawTagEditor.tags(Object.assign({}, context.changeset.tags)) // shallow copy
- .render);
- }
- }
+ sidebar.show = function (component, element) {
+ featureListWrap.classed('inspector-hidden', true);
+ inspectorWrap.classed('inspector-hidden', true);
+ if (_current) _current.remove();
+ _current = selection.append('div').attr('class', 'sidebar-component').call(component, element);
+ };
- function getUploadBlockerMessage() {
- var errors = context.validator().getIssuesBySeverity({
- what: 'edited',
- where: 'all'
- }).error;
+ sidebar.hide = function () {
+ featureListWrap.classed('inspector-hidden', false);
+ inspectorWrap.classed('inspector-hidden', true);
+ if (_current) _current.remove();
+ _current = null;
+ };
- if (errors.length) {
- return _t('commit.outstanding_errors_message', {
- count: errors.length
- });
- } else {
- var hasChangesetComment = context.changeset && context.changeset.tags.comment && context.changeset.tags.comment.trim().length;
+ sidebar.expand = function (moveMap) {
+ if (selection.classed('collapsed')) {
+ sidebar.toggle(moveMap);
+ }
+ };
- if (!hasChangesetComment) {
- return _t('commit.comment_needed_message');
+ sidebar.collapse = function (moveMap) {
+ if (!selection.classed('collapsed')) {
+ sidebar.toggle(moveMap);
}
- }
+ };
- return null;
- }
+ sidebar.toggle = function (moveMap) {
+ // Don't allow sidebar to toggle when the user is in the walkthrough.
+ if (context.inIntro()) return;
+ var isCollapsed = selection.classed('collapsed');
+ var isCollapsing = !isCollapsed;
+ var isRTL = _mainLocalizer.textDirection() === 'rtl';
+ var scaleX = isRTL ? 0 : 1;
+ var xMarginProperty = isRTL ? 'margin-right' : 'margin-left';
+ sidebarWidth = selection.node().getBoundingClientRect().width; // switch from % to px
- function changeTags(_, changed, onInput) {
- if (changed.hasOwnProperty('comment')) {
- if (changed.comment === undefined) {
- changed.comment = '';
- }
+ selection.style('width', sidebarWidth + 'px');
+ var startMargin, endMargin, lastMargin;
- if (!onInput) {
- corePreferences('comment', changed.comment);
- corePreferences('commentDate', Date.now());
+ if (isCollapsing) {
+ startMargin = lastMargin = 0;
+ endMargin = -sidebarWidth;
+ } else {
+ startMargin = lastMargin = -sidebarWidth;
+ endMargin = 0;
}
- }
- if (changed.hasOwnProperty('source')) {
- if (changed.source === undefined) {
- corePreferences('source', null);
- } else if (!onInput) {
- corePreferences('source', changed.source);
- corePreferences('commentDate', Date.now());
+ if (!isCollapsing) {
+ // unhide the sidebar's content before it transitions onscreen
+ selection.classed('collapsed', isCollapsing);
}
- } // no need to update `prefs` for `hashtags` here since it's done in `updateChangeset`
+ selection.transition().style(xMarginProperty, endMargin + 'px').tween('panner', function () {
+ var i = d3_interpolateNumber(startMargin, endMargin);
+ return function (t) {
+ var dx = lastMargin - Math.round(i(t));
+ lastMargin = lastMargin - dx;
+ context.ui().onResize(moveMap ? undefined : [dx * scaleX, 0]);
+ };
+ }).on('end', function () {
+ if (isCollapsing) {
+ // hide the sidebar's content after it transitions offscreen
+ selection.classed('collapsed', isCollapsing);
+ } // switch back from px to %
- updateChangeset(changed, onInput);
- if (_selection) {
- _selection.call(render);
- }
- }
+ if (!isCollapsing) {
+ var containerWidth = container.node().getBoundingClientRect().width;
+ var widthPct = sidebarWidth / containerWidth * 100;
+ selection.style(xMarginProperty, null).style('width', widthPct + '%');
+ }
+ });
+ }; // toggle the sidebar collapse when double-clicking the resizer
- function findHashtags(tags, commentOnly) {
- var detectedHashtags = commentHashtags();
- if (detectedHashtags.length) {
- // always remove stored hashtags if there are hashtags in the comment - #4304
- corePreferences('hashtags', null);
- }
+ resizer.on('dblclick', function (d3_event) {
+ d3_event.preventDefault();
- if (!detectedHashtags.length || !commentOnly) {
- detectedHashtags = detectedHashtags.concat(hashtagHashtags());
- }
+ if (d3_event.sourceEvent) {
+ d3_event.sourceEvent.preventDefault();
+ }
- var allLowerCase = new Set();
- return detectedHashtags.filter(function (hashtag) {
- // Compare tags as lowercase strings, but keep original case tags
- var lowerCase = hashtag.toLowerCase();
+ sidebar.toggle();
+ }); // ensure hover sidebar is closed when zooming out beyond editable zoom
- if (!allLowerCase.has(lowerCase)) {
- allLowerCase.add(lowerCase);
- return true;
+ context.map().on('crossEditableZoom.sidebar', function (within) {
+ if (!within && !selection.select('.inspector-hover').empty()) {
+ hover([]);
}
+ });
+ }
- return false;
- }); // Extract hashtags from `comment`
-
- function commentHashtags() {
- var matches = (tags.comment || '').replace(/http\S*/g, '') // drop anything that looks like a URL - #4289
- .match(hashtagRegex);
- return matches || [];
- } // Extract and clean hashtags from `hashtags`
+ sidebar.showPresetList = function () {};
+ sidebar.hover = function () {};
- function hashtagHashtags() {
- var matches = (tags.hashtags || '').split(/[,;\s]+/).map(function (s) {
- if (s[0] !== '#') {
- s = '#' + s;
- } // prepend '#'
+ sidebar.hover.cancel = function () {};
+ sidebar.intersects = function () {};
- var matched = s.match(hashtagRegex);
- return matched && matched[0];
- }).filter(Boolean); // exclude falsy
+ sidebar.select = function () {};
- return matches || [];
- }
- }
+ sidebar.show = function () {};
- function isReviewRequested(tags) {
- var rr = tags.review_requested;
- if (rr === undefined) return false;
- rr = rr.trim().toLowerCase();
- return !(rr === '' || rr === 'no');
- }
+ sidebar.hide = function () {};
- function updateChangeset(changed, onInput) {
- var tags = Object.assign({}, context.changeset.tags); // shallow copy
+ sidebar.expand = function () {};
- Object.keys(changed).forEach(function (k) {
- var v = changed[k];
- k = context.cleanTagKey(k);
- if (readOnlyTags.indexOf(k) !== -1) return;
+ sidebar.collapse = function () {};
- if (v === undefined) {
- delete tags[k];
- } else if (onInput) {
- tags[k] = v;
- } else {
- tags[k] = context.cleanTagValue(v);
- }
- });
+ sidebar.toggle = function () {};
- if (!onInput) {
- // when changing the comment, override hashtags with any found in comment.
- var commentOnly = changed.hasOwnProperty('comment') && changed.comment !== '';
- var arr = findHashtags(tags, commentOnly);
+ return sidebar;
+ }
- if (arr.length) {
- tags.hashtags = context.cleanTagValue(arr.join(';'));
- corePreferences('hashtags', tags.hashtags);
- } else {
- delete tags.hashtags;
- corePreferences('hashtags', null);
- }
- } // always update userdetails, just in case user reauthenticates as someone else
+ function modeDrawArea(context, wayID, startGraph, button) {
+ var mode = {
+ button: button,
+ id: 'draw-area'
+ };
+ var behavior = behaviorDrawWay(context, wayID, mode, startGraph).on('rejectedSelfIntersection.modeDrawArea', function () {
+ context.ui().flash.iconName('#iD-icon-no').label(_t.html('self_intersection.error.areas'))();
+ });
+ mode.wayID = wayID;
+ mode.enter = function () {
+ context.install(behavior);
+ };
- if (_userDetails && _userDetails.changesets_count !== undefined) {
- var changesetsCount = parseInt(_userDetails.changesets_count, 10) + 1; // #4283
+ mode.exit = function () {
+ context.uninstall(behavior);
+ };
- tags.changesets_count = String(changesetsCount); // first 100 edits - new user
+ mode.selectedIDs = function () {
+ return [wayID];
+ };
- if (changesetsCount <= 100) {
- var s;
- s = corePreferences('walkthrough_completed');
+ mode.activeID = function () {
+ return behavior && behavior.activeID() || [];
+ };
- if (s) {
- tags['ideditor:walkthrough_completed'] = s;
- }
+ return mode;
+ }
- s = corePreferences('walkthrough_progress');
+ function modeAddArea(context, mode) {
+ mode.id = 'add-area';
+ var behavior = behaviorAddWay(context).on('start', start).on('startFromWay', startFromWay).on('startFromNode', startFromNode);
+ var defaultTags = {
+ area: 'yes'
+ };
+ if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'area');
- if (s) {
- tags['ideditor:walkthrough_progress'] = s;
- }
+ function actionClose(wayId) {
+ return function (graph) {
+ return graph.replace(graph.entity(wayId).close());
+ };
+ }
- s = corePreferences('walkthrough_started');
+ function start(loc) {
+ var startGraph = context.graph();
+ var node = osmNode({
+ loc: loc
+ });
+ var way = osmWay({
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id));
+ context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
+ }
- if (s) {
- tags['ideditor:walkthrough_started'] = s;
- }
- }
- } else {
- delete tags.changesets_count;
- }
+ function startFromWay(loc, edge) {
+ var startGraph = context.graph();
+ var node = osmNode({
+ loc: loc
+ });
+ var way = osmWay({
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id), actionAddMidpoint({
+ loc: loc,
+ edge: edge
+ }, node));
+ context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
+ }
- if (!fastDeepEqual(context.changeset.tags, tags)) {
- context.changeset = context.changeset.update({
- tags: tags
- });
- }
+ function startFromNode(node) {
+ var startGraph = context.graph();
+ var way = osmWay({
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(way), actionAddVertex(way.id, node.id), actionClose(way.id));
+ context.enter(modeDrawArea(context, way.id, startGraph, mode.button));
}
- commit.reset = function () {
- context.changeset = null;
+ mode.enter = function () {
+ context.install(behavior);
};
- return utilRebind(commit, dispatch, 'on');
- }
-
- // for punction see https://stackoverflow.com/a/21224179
+ mode.exit = function () {
+ context.uninstall(behavior);
+ };
- function simplify(str) {
- if (typeof str !== 'string') return '';
- return diacritics.remove(str.replace(/&/g, 'and').replace(/İ/ig, 'i').replace(/[\s\-=_!"#%'*{},.\/:;?\(\)\[\]@\\$\^*+<>«»~`’\u00a1\u00a7\u00b6\u00b7\u00bf\u037e\u0387\u055a-\u055f\u0589\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d\u07f7-\u07f9\u0830-\u083e\u085e\u0964\u0965\u0970\u0af0\u0df4\u0e4f\u0e5a\u0e5b\u0f04-\u0f12\u0f14\u0f85\u0fd0-\u0fd4\u0fd9\u0fda\u104a-\u104f\u10fb\u1360-\u1368\u166d\u166e\u16eb-\u16ed\u1735\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u1805\u1807-\u180a\u1944\u1945\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-\u1b60\u1bfc-\u1bff\u1c3b-\u1c3f\u1c7e\u1c7f\u1cc0-\u1cc7\u1cd3\u200b-\u200f\u2016\u2017\u2020-\u2027\u2030-\u2038\u203b-\u203e\u2041-\u2043\u2047-\u2051\u2053\u2055-\u205e\u2cf9-\u2cfc\u2cfe\u2cff\u2d70\u2e00\u2e01\u2e06-\u2e08\u2e0b\u2e0e-\u2e16\u2e18\u2e19\u2e1b\u2e1e\u2e1f\u2e2a-\u2e2e\u2e30-\u2e39\u3001-\u3003\u303d\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uaaf0\uaaf1\uabeb\ufe10-\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49-\ufe4c\ufe50-\ufe52\ufe54-\ufe57\ufe5f-\ufe61\ufe68\ufe6a\ufe6b\ufeff\uff01-\uff03\uff05-\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65]+/g, '').toLowerCase());
+ return mode;
}
- // `resolveStrings`
- // Resolves the text strings for a given community index item
- //
- // Arguments
- // `item`: Object containing the community index item
- // `defaults`: Object containing the community index default strings
- // `localizerFn?`: optional function we will call to do the localization.
- // This function should be like the iD `t()` function that
- // accepts a `stringID` and returns a localized string
- //
- // Returns
- // An Object containing all the resolved strings:
- // {
- // name: 'talk-ru Mailing List',
- // url: 'https://lists.openstreetmap.org/listinfo/talk-ru',
- // signupUrl: 'https://example.url/signup',
- // description: 'A one line description',
- // extendedDescription: 'Extended description',
- // nameHTML: '<a href="the url">the name</a>',
- // urlHTML: '<a href="the url">the url</a>',
- // signupUrlHTML: '<a href="the signupUrl">the signupUrl</a>',
- // descriptionHTML: the description, with urls and signupUrls linkified,
- // extendedDescriptionHTML: the extendedDescription with urls and signupUrls linkified
- // }
- //
-
- function resolveStrings(item, defaults, localizerFn) {
- var itemStrings = Object.assign({}, item.strings); // shallow clone
-
- var defaultStrings = Object.assign({}, defaults[item.type]); // shallow clone
-
- var anyToken = new RegExp(/(\{\w+\})/, 'gi'); // Pre-localize the item and default strings
-
- if (localizerFn) {
- if (itemStrings.community) {
- var communityID = simplify(itemStrings.community);
- itemStrings.community = localizerFn("_communities.".concat(communityID));
- }
+ function modeAddLine(context, mode) {
+ mode.id = 'add-line';
+ var behavior = behaviorAddWay(context).on('start', start).on('startFromWay', startFromWay).on('startFromNode', startFromNode);
+ var defaultTags = {};
+ if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'line');
- ['name', 'description', 'extendedDescription'].forEach(function (prop) {
- if (defaultStrings[prop]) defaultStrings[prop] = localizerFn("_defaults.".concat(item.type, ".").concat(prop));
- if (itemStrings[prop]) itemStrings[prop] = localizerFn("".concat(item.id, ".").concat(prop));
+ function start(loc) {
+ var startGraph = context.graph();
+ var node = osmNode({
+ loc: loc
+ });
+ var way = osmWay({
+ tags: defaultTags
});
+ context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id));
+ context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
}
- var replacements = {
- account: item.account,
- community: itemStrings.community,
- signupUrl: itemStrings.signupUrl,
- url: itemStrings.url
- }; // Resolve URLs first (which may refer to {account})
-
- if (!replacements.signupUrl) {
- replacements.signupUrl = resolve(itemStrings.signupUrl || defaultStrings.signupUrl);
+ function startFromWay(loc, edge) {
+ var startGraph = context.graph();
+ var node = osmNode({
+ loc: loc
+ });
+ var way = osmWay({
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(node), actionAddEntity(way), actionAddVertex(way.id, node.id), actionAddMidpoint({
+ loc: loc,
+ edge: edge
+ }, node));
+ context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
}
- if (!replacements.url) {
- replacements.url = resolve(itemStrings.url || defaultStrings.url);
+ function startFromNode(node) {
+ var startGraph = context.graph();
+ var way = osmWay({
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(way), actionAddVertex(way.id, node.id));
+ context.enter(modeDrawLine(context, way.id, startGraph, mode.button));
}
- var resolved = {
- name: resolve(itemStrings.name || defaultStrings.name),
- url: resolve(itemStrings.url || defaultStrings.url),
- signupUrl: resolve(itemStrings.signupUrl || defaultStrings.signupUrl),
- description: resolve(itemStrings.description || defaultStrings.description),
- extendedDescription: resolve(itemStrings.extendedDescription || defaultStrings.extendedDescription)
- }; // Generate linkified strings
-
- resolved.nameHTML = linkify(resolved.url, resolved.name);
- resolved.urlHTML = linkify(resolved.url);
- resolved.signupUrlHTML = linkify(resolved.signupUrl);
- resolved.descriptionHTML = resolve(itemStrings.description || defaultStrings.description, true);
- resolved.extendedDescriptionHTML = resolve(itemStrings.extendedDescription || defaultStrings.extendedDescription, true);
- return resolved;
-
- function resolve(s, addLinks) {
- if (!s) return undefined;
- var result = s;
+ mode.enter = function () {
+ context.install(behavior);
+ };
- for (var key in replacements) {
- var token = "{".concat(key, "}");
- var regex = new RegExp(token, 'g');
+ mode.exit = function () {
+ context.uninstall(behavior);
+ };
- if (regex.test(result)) {
- var replacement = replacements[key];
+ return mode;
+ }
- if (!replacement) {
- throw new Error("Cannot resolve token: ".concat(token));
- } else {
- if (addLinks && (key === 'signupUrl' || key === 'url')) {
- replacement = linkify(replacement);
- }
+ function modeAddPoint(context, mode) {
+ mode.id = 'add-point';
+ var behavior = behaviorDraw(context).on('click', add).on('clickWay', addWay).on('clickNode', addNode).on('cancel', cancel).on('finish', cancel);
+ var defaultTags = {};
+ if (mode.preset) defaultTags = mode.preset.setTags(defaultTags, 'point');
- result = result.replace(regex, replacement);
- }
- }
- } // There shouldn't be any leftover tokens in a resolved string
+ function add(loc) {
+ var node = osmNode({
+ loc: loc,
+ tags: defaultTags
+ });
+ context.perform(actionAddEntity(node), _t('operations.add.annotation.point'));
+ enterSelectMode(node);
+ }
+ function addWay(loc, edge) {
+ var node = osmNode({
+ tags: defaultTags
+ });
+ context.perform(actionAddMidpoint({
+ loc: loc,
+ edge: edge
+ }, node), _t('operations.add.annotation.vertex'));
+ enterSelectMode(node);
+ }
- var leftovers = result.match(anyToken);
+ function enterSelectMode(node) {
+ context.enter(modeSelect(context, [node.id]).newFeature(true));
+ }
- if (leftovers) {
- throw new Error("Cannot resolve tokens: ".concat(leftovers));
- } // Linkify subreddits like `/r/openstreetmap`
- // https://github.com/osmlab/osm-community-index/issues/82
- // https://github.com/openstreetmap/iD/issues/4997
+ function addNode(node) {
+ if (Object.keys(defaultTags).length === 0) {
+ enterSelectMode(node);
+ return;
+ }
+ var tags = Object.assign({}, node.tags); // shallow copy
- if (addLinks && item.type === 'reddit') {
- result = result.replace(/(\/r\/\w+\/*)/i, function (match) {
- return linkify(resolved.url, match);
- });
+ for (var key in defaultTags) {
+ tags[key] = defaultTags[key];
}
- return result;
+ context.perform(actionChangeTags(node.id, tags), _t('operations.add.annotation.point'));
+ enterSelectMode(node);
}
- function linkify(url, text) {
- if (!url) return undefined;
- text = text || url;
- return "<a target=\"_blank\" href=\"".concat(url, "\">").concat(text, "</a>");
+ function cancel() {
+ context.enter(modeBrowse(context));
}
- }
-
- var _oci = null;
- function uiSuccess(context) {
- var MAXEVENTS = 2;
- var dispatch = dispatch$8('cancel');
- var _changeset;
+ mode.enter = function () {
+ context.install(behavior);
+ };
- var _location;
+ mode.exit = function () {
+ context.uninstall(behavior);
+ };
- ensureOSMCommunityIndex(); // start fetching the data
+ return mode;
+ }
- function ensureOSMCommunityIndex() {
- var data = _mainFileFetcher;
- return Promise.all([data.get('oci_features'), data.get('oci_resources'), data.get('oci_defaults')]).then(function (vals) {
- if (_oci) return _oci; // Merge Custom Features
+ function modeSelectNote(context, selectedNoteID) {
+ var mode = {
+ id: 'select-note',
+ button: 'browse'
+ };
- if (vals[0] && Array.isArray(vals[0].features)) {
- _mainLocations.mergeCustomGeoJSON(vals[0]);
- }
+ var _keybinding = utilKeybinding('select-note');
- var ociResources = Object.values(vals[1].resources);
+ var _noteEditor = uiNoteEditor(context).on('change', function () {
+ context.map().pan([0, 0]); // trigger a redraw
- if (ociResources.length) {
- // Resolve all locationSet features.
- return _mainLocations.mergeLocationSets(ociResources).then(function () {
- _oci = {
- resources: ociResources,
- defaults: vals[2].defaults
- };
- return _oci;
- });
- } else {
- _oci = {
- resources: [],
- // no resources?
- defaults: vals[2].defaults
- };
- return _oci;
- }
- });
- } // string-to-date parsing in JavaScript is weird
+ var note = checkSelectedID();
+ if (!note) return;
+ context.ui().sidebar.show(_noteEditor.note(note));
+ });
+ var _behaviors = [behaviorBreathe(), behaviorHover(context), behaviorSelect(context), behaviorLasso(context), modeDragNode(context).behavior, modeDragNote(context).behavior];
+ var _newFeature = false;
- function parseEventDate(when) {
- if (!when) return;
- var raw = when.trim();
- if (!raw) return;
+ function checkSelectedID() {
+ if (!services.osm) return;
+ var note = services.osm.getNote(selectedNoteID);
- if (!/Z$/.test(raw)) {
- // if no trailing 'Z', add one
- raw += 'Z'; // this forces date to be parsed as a UTC date
+ if (!note) {
+ context.enter(modeBrowse(context));
}
- var parsed = new Date(raw);
- return new Date(parsed.toUTCString().substr(0, 25)); // convert to local timezone
- }
-
- function success(selection) {
- var header = selection.append('div').attr('class', 'header fillL');
- header.append('h3').html(_t.html('success.just_edited'));
- header.append('button').attr('class', 'close').on('click', function () {
- return dispatch.call('cancel');
- }).call(svgIcon('#iD-icon-close'));
- var body = selection.append('div').attr('class', 'body save-success fillL');
- var summary = body.append('div').attr('class', 'save-summary');
- summary.append('h3').html(_t.html('success.thank_you' + (_location ? '_location' : ''), {
- where: _location
- }));
- summary.append('p').html(_t.html('success.help_html')).append('a').attr('class', 'link-out').attr('target', '_blank').attr('href', _t('success.help_link_url')).call(svgIcon('#iD-icon-out-link', 'inline')).append('span').html(_t.html('success.help_link_text'));
- var osm = context.connection();
- if (!osm) return;
- var changesetURL = osm.changesetURL(_changeset.id);
- var table = summary.append('table').attr('class', 'summary-table');
- var row = table.append('tr').attr('class', 'summary-row');
- row.append('td').attr('class', 'cell-icon summary-icon').append('a').attr('target', '_blank').attr('href', changesetURL).append('svg').attr('class', 'logo-small').append('use').attr('xlink:href', '#iD-logo-osm');
- var summaryDetail = row.append('td').attr('class', 'cell-detail summary-detail');
- summaryDetail.append('a').attr('class', 'cell-detail summary-view-on-osm').attr('target', '_blank').attr('href', changesetURL).html(_t.html('success.view_on_osm'));
- summaryDetail.append('div').html(_t.html('success.changeset_id', {
- changeset_id: "<a href=\"".concat(changesetURL, "\" target=\"_blank\">").concat(_changeset.id, "</a>")
- })); // Get OSM community index features intersecting the map..
-
- ensureOSMCommunityIndex().then(function (oci) {
- var loc = context.map().center();
- var validLocations = _mainLocations.locationsAt(loc); // Gather the communities
+ return note;
+ } // class the note as selected, or return to browse mode if the note is gone
- var communities = [];
- oci.resources.forEach(function (resource) {
- var area = validLocations[resource.locationSetID];
- if (!area) return; // Resolve strings
- var localizer = function localizer(stringID) {
- return _t.html("community.".concat(stringID));
- };
+ function selectNote(d3_event, drawn) {
+ if (!checkSelectedID()) return;
+ var selection = context.surface().selectAll('.layer-notes .note-' + selectedNoteID);
- resource.resolved = resolveStrings(resource, oci.defaults, localizer);
- communities.push({
- area: area,
- order: resource.order || 0,
- resource: resource
- });
- }); // sort communities by feature area ascending, community order descending
+ if (selection.empty()) {
+ // Return to browse mode if selected DOM elements have
+ // disappeared because the user moved them out of view..
+ var source = d3_event && d3_event.type === 'zoom' && d3_event.sourceEvent;
- communities.sort(function (a, b) {
- return a.area - b.area || b.order - a.order;
- });
- body.call(showCommunityLinks, communities.map(function (c) {
- return c.resource;
- }));
- });
+ if (drawn && source && (source.type === 'pointermove' || source.type === 'mousemove' || source.type === 'touchmove')) {
+ context.enter(modeBrowse(context));
+ }
+ } else {
+ selection.classed('selected', true);
+ context.selectedNoteID(selectedNoteID);
+ }
}
- function showCommunityLinks(selection, resources) {
- var communityLinks = selection.append('div').attr('class', 'save-communityLinks');
- communityLinks.append('h3').html(_t.html('success.like_osm'));
- var table = communityLinks.append('table').attr('class', 'community-table');
- var row = table.selectAll('.community-row').data(resources);
- var rowEnter = row.enter().append('tr').attr('class', 'community-row');
- rowEnter.append('td').attr('class', 'cell-icon community-icon').append('a').attr('target', '_blank').attr('href', function (d) {
- return d.resolved.url;
- }).append('svg').attr('class', 'logo-small').append('use').attr('xlink:href', function (d) {
- return "#community-".concat(d.type);
- });
- var communityDetail = rowEnter.append('td').attr('class', 'cell-detail community-detail');
- communityDetail.each(showCommunityDetails);
- communityLinks.append('div').attr('class', 'community-missing').html(_t.html('success.missing')).append('a').attr('class', 'link-out').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/osmlab/osm-community-index/issues').append('span').html(_t.html('success.tell_us'));
+ function esc() {
+ if (context.container().select('.combobox').size()) return;
+ context.enter(modeBrowse(context));
}
- function showCommunityDetails(d) {
- var selection = select(this);
- var communityID = d.id;
- selection.append('div').attr('class', 'community-name').html(d.resolved.nameHTML);
- selection.append('div').attr('class', 'community-description').html(d.resolved.descriptionHTML); // Create an expanding section if any of these are present..
+ mode.zoomToSelected = function () {
+ if (!services.osm) return;
+ var note = services.osm.getNote(selectedNoteID);
- if (d.resolved.extendedDescriptionHTML || d.languageCodes && d.languageCodes.length) {
- selection.append('div').call(uiDisclosure(context, "community-more-".concat(d.id), false).expanded(false).updatePreference(false).label(_t.html('success.more')).content(showMore));
+ if (note) {
+ context.map().centerZoomEase(note.loc, 20);
}
+ };
- var nextEvents = (d.events || []).map(function (event) {
- event.date = parseEventDate(event.when);
- return event;
- }).filter(function (event) {
- // date is valid and future (or today)
- var t = event.date.getTime();
- var now = new Date().setHours(0, 0, 0, 0);
- return !isNaN(t) && t >= now;
- }).sort(function (a, b) {
- // sort by date ascending
- return a.date < b.date ? -1 : a.date > b.date ? 1 : 0;
- }).slice(0, MAXEVENTS); // limit number of events shown
-
- if (nextEvents.length) {
- selection.append('div').call(uiDisclosure(context, "community-events-".concat(d.id), false).expanded(false).updatePreference(false).label(_t.html('success.events')).content(showNextEvents)).select('.hide-toggle').append('span').attr('class', 'badge-text').html(nextEvents.length);
- }
+ mode.newFeature = function (val) {
+ if (!arguments.length) return _newFeature;
+ _newFeature = val;
+ return mode;
+ };
- function showMore(selection) {
- var more = selection.selectAll('.community-more').data([0]);
- var moreEnter = more.enter().append('div').attr('class', 'community-more');
+ mode.enter = function () {
+ var note = checkSelectedID();
+ if (!note) return;
- if (d.resolved.extendedDescriptionHTML) {
- moreEnter.append('div').attr('class', 'community-extended-description').html(d.resolved.extendedDescriptionHTML);
- }
+ _behaviors.forEach(context.install);
- if (d.languageCodes && d.languageCodes.length) {
- var languageList = d.languageCodes.map(function (code) {
- return _mainLocalizer.languageName(code);
- }).join(', ');
- moreEnter.append('div').attr('class', 'community-languages').html(_t.html('success.languages', {
- languages: languageList
- }));
- }
- }
+ _keybinding.on(_t('inspector.zoom_to.key'), mode.zoomToSelected).on('⎋', esc, true);
- function showNextEvents(selection) {
- var events = selection.append('div').attr('class', 'community-events');
- var item = events.selectAll('.community-event').data(nextEvents);
- var itemEnter = item.enter().append('div').attr('class', 'community-event');
- itemEnter.append('div').attr('class', 'community-event-name').append('a').attr('target', '_blank').attr('href', function (d) {
- return d.url;
- }).html(function (d) {
- var name = d.name;
+ select(document).call(_keybinding);
+ selectNote();
+ var sidebar = context.ui().sidebar;
+ sidebar.show(_noteEditor.note(note).newNote(_newFeature)); // expand the sidebar, avoid obscuring the note if needed
- if (d.i18n && d.id) {
- name = _t("community.".concat(communityID, ".events.").concat(d.id, ".name"), {
- "default": name
- });
- }
+ sidebar.expand(sidebar.intersects(note.extent()));
+ context.map().on('drawn.select', selectNote);
+ };
- return name;
- });
- itemEnter.append('div').attr('class', 'community-event-when').html(function (d) {
- var options = {
- weekday: 'short',
- day: 'numeric',
- month: 'short',
- year: 'numeric'
- };
+ mode.exit = function () {
+ _behaviors.forEach(context.uninstall);
- if (d.date.getHours() || d.date.getMinutes()) {
- // include time if it has one
- options.hour = 'numeric';
- options.minute = 'numeric';
- }
+ select(document).call(_keybinding.unbind);
+ context.surface().selectAll('.layer-notes .selected').classed('selected hover', false);
+ context.map().on('drawn.select', null);
+ context.ui().sidebar.hide();
+ context.selectedNoteID(null);
+ };
- return d.date.toLocaleString(_mainLocalizer.localeCode(), options);
- });
- itemEnter.append('div').attr('class', 'community-event-where').html(function (d) {
- var where = d.where;
+ return mode;
+ }
- if (d.i18n && d.id) {
- where = _t("community.".concat(communityID, ".events.").concat(d.id, ".where"), {
- "default": where
- });
- }
+ function modeAddNote(context) {
+ var mode = {
+ id: 'add-note',
+ button: 'note',
+ description: _t.html('modes.add_note.description'),
+ key: _t('modes.add_note.key')
+ };
+ var behavior = behaviorDraw(context).on('click', add).on('cancel', cancel).on('finish', cancel);
- return where;
- });
- itemEnter.append('div').attr('class', 'community-event-description').html(function (d) {
- var description = d.description;
+ function add(loc) {
+ var osm = services.osm;
+ if (!osm) return;
+ var note = osmNote({
+ loc: loc,
+ status: 'open',
+ comments: []
+ });
+ osm.replaceNote(note); // force a reraw (there is no history change that would otherwise do this)
- if (d.i18n && d.id) {
- description = _t("community.".concat(communityID, ".events.").concat(d.id, ".description"), {
- "default": description
- });
- }
+ context.map().pan([0, 0]);
+ context.selectedNoteID(note.id).enter(modeSelectNote(context, note.id).newFeature(true));
+ }
- return description;
- });
- }
+ function cancel() {
+ context.enter(modeBrowse(context));
}
- success.changeset = function (val) {
- if (!arguments.length) return _changeset;
- _changeset = val;
- return success;
+ mode.enter = function () {
+ context.install(behavior);
};
- success.location = function (val) {
- if (!arguments.length) return _location;
- _location = val;
- return success;
+ mode.exit = function () {
+ context.uninstall(behavior);
};
- return utilRebind(success, dispatch, 'on');
+ return mode;
}
function modeSave(context) {
} // update
- buttons = buttons.merge(buttonsEnter).classed('disabled', function (d) {
+ buttons = buttons.merge(buttonsEnter).attr('aria-disabled', function (d) {
return !enabled();
+ }).classed('disabled', function (d) {
+ return !enabled();
+ }).attr('aria-pressed', function (d) {
+ return context.mode() && context.mode().button === d.button;
}).classed('active', function (d) {
return context.mode() && context.mode().button === d.button;
});
buttons = buttons.merge(buttonsEnter).classed('disabled', function (d) {
return !enabled();
+ }).attr('aria-disabled', function (d) {
+ return !enabled();
}).classed('active', function (d) {
return context.mode() && context.mode().button === d.button;
+ }).attr('aria-pressed', function (d) {
+ return context.mode() && context.mode().button === d.button;
});
}
};
if (button) {
button.classed('disabled', isDisabled()).style('background', bgColor());
- button.select('span.count').html(_numChanges);
+ button.select('span.count').text(_numChanges);
}
}
lastPointerUpType = null;
}).call(tooltipBehavior);
button.call(svgIcon('#iD-icon-save'));
- button.append('span').attr('class', 'count').attr('aria-hidden', 'true').html('0');
+ button.append('span').attr('class', 'count').attr('aria-hidden', 'true').text('0');
updateCount();
context.keybinding().on(key, save, true);
context.history().on('change.save', updateCount);
};
tool.render = function (selection) {
- selection.append('button').attr('class', 'bar-button').on('click', function () {
+ selection.append('button').attr('class', 'bar-button').attr('aria-label', _t('sidebar.tooltip')).on('click', function () {
context.ui().sidebar.toggle();
}).call(uiTooltip().placement('bottom').title(_t.html('sidebar.tooltip')).keys([_t('sidebar.key')]).scrollContainer(context.container().select('.top-toolbar'))).call(svgIcon('#iD-icon-sidebar-' + (_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left')));
};
if (editable() && (lastPointerUpType === 'touch' || lastPointerUpType === 'pen')) {
// there are no tooltips for touch interactions so flash feedback instead
- var text = annotation ? _t(d.id + '.tooltip', {
+ var text = annotation ? _t.html(d.id + '.tooltip', {
action: annotation
- }) : _t(d.id + '.nothing');
+ }) : _t.html(d.id + '.nothing');
context.ui().flash.duration(2000).iconName('#' + d.icon).iconClass(annotation ? '' : 'disabled').label(text)();
}
return topToolbar;
}
- var sawVersion = null;
- var isNewVersion = false;
- var isNewUser = false;
- function uiVersion(context) {
- var currVersion = context.version;
- var matchedVersion = currVersion.match(/\d+\.\d+\.\d+.*/);
-
- if (sawVersion === null && matchedVersion !== null) {
- if (corePreferences('sawVersion')) {
- isNewUser = false;
- isNewVersion = corePreferences('sawVersion') !== currVersion && currVersion.indexOf('-') === -1;
- } else {
- isNewUser = true;
- isNewVersion = true;
- }
-
- corePreferences('sawVersion', currVersion);
- sawVersion = currVersion;
- }
-
- return function (selection) {
- selection.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD').html(currVersion); // only show new version indicator to users that have used iD before
-
- if (isNewVersion && !isNewUser) {
- selection.append('a').attr('class', 'badge').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/blob/release/CHANGELOG.md#whats-new').call(svgIcon('#maki-gift-11')).call(uiTooltip().title(_t.html('version.whats_new', {
- version: currVersion
- })).placement('top').scrollContainer(context.container().select('.main-footer-wrap')));
- }
- };
- }
-
- function uiZoom(context) {
- var zooms = [{
- id: 'zoom-in',
- icon: 'iD-icon-plus',
- title: _t.html('zoom.in'),
- action: zoomIn,
- disabled: function disabled() {
- return !context.map().canZoomIn();
- },
- disabledTitle: _t.html('zoom.disabled.in'),
- key: '+'
- }, {
- id: 'zoom-out',
- icon: 'iD-icon-minus',
- title: _t.html('zoom.out'),
- action: zoomOut,
- disabled: function disabled() {
- return !context.map().canZoomOut();
- },
- disabledTitle: _t.html('zoom.disabled.out'),
- key: '-'
- }];
-
- function zoomIn(d3_event) {
- if (d3_event.shiftKey) return;
- d3_event.preventDefault();
- context.map().zoomIn();
- }
-
- function zoomOut(d3_event) {
- if (d3_event.shiftKey) return;
- d3_event.preventDefault();
- context.map().zoomOut();
- }
-
- function zoomInFurther(d3_event) {
- if (d3_event.shiftKey) return;
- d3_event.preventDefault();
- context.map().zoomInFurther();
- }
-
- function zoomOutFurther(d3_event) {
- if (d3_event.shiftKey) return;
- d3_event.preventDefault();
- context.map().zoomOutFurther();
- }
-
- return function (selection) {
- var tooltipBehavior = uiTooltip().placement(_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left').title(function (d) {
- if (d.disabled()) {
- return d.disabledTitle;
- }
-
- return d.title;
- }).keys(function (d) {
- return [d.key];
- });
- var lastPointerUpType;
- var buttons = selection.selectAll('button').data(zooms).enter().append('button').attr('class', function (d) {
- return d.id;
- }).on('pointerup.editor', function (d3_event) {
- lastPointerUpType = d3_event.pointerType;
- }).on('click.editor', function (d3_event, d) {
- if (!d.disabled()) {
- d.action(d3_event);
- } else if (lastPointerUpType === 'touch' || lastPointerUpType === 'pen') {
- context.ui().flash.duration(2000).iconName('#' + d.icon).iconClass('disabled').label(d.disabledTitle)();
- }
-
- lastPointerUpType = null;
- }).call(tooltipBehavior);
- buttons.each(function (d) {
- select(this).call(svgIcon('#' + d.icon, 'light'));
- });
- utilKeybinding.plusKeys.forEach(function (key) {
- context.keybinding().on([key], zoomIn);
- context.keybinding().on([uiCmd('⌥' + key)], zoomInFurther);
- });
- utilKeybinding.minusKeys.forEach(function (key) {
- context.keybinding().on([key], zoomOut);
- context.keybinding().on([uiCmd('⌥' + key)], zoomOutFurther);
- });
-
- function updateButtonStates() {
- buttons.classed('disabled', function (d) {
- return d.disabled();
- }).each(function () {
- var selection = select(this);
-
- if (!selection.select('.tooltip.in').empty()) {
- selection.call(tooltipBehavior.updateContent);
- }
- });
- }
-
- updateButtonStates();
- context.map().on('move.uiZoom', updateButtonStates);
- };
- }
-
function uiZoomToSelection(context) {
function isDisabled() {
var mode = context.mode();
var heading = _paneSelection.append('div').attr('class', 'pane-heading');
heading.append('h2').html(_label);
- heading.append('button').on('click', hidePane).call(svgIcon('#iD-icon-close'));
+ heading.append('button').attr('title', _t('icons.close')).on('click', hidePane).call(svgIcon('#iD-icon-close'));
_paneSelection.append('div').attr('class', 'pane-content').call(pane.renderContent);
var container = selection.selectAll('.display-options-container').data([0]);
var containerEnter = container.enter().append('div').attr('class', 'display-options-container controls-list'); // add slider controls
- var slidersEnter = containerEnter.selectAll('.display-control').data(_sliders).enter().append('div').attr('class', function (d) {
+ var slidersEnter = containerEnter.selectAll('.display-control').data(_sliders).enter().append('label').attr('class', function (d) {
return 'display-control display-control-' + d;
});
- slidersEnter.append('h5').html(function (d) {
+ slidersEnter.html(function (d) {
return _t.html('background.' + d);
}).append('span').attr('class', function (d) {
return 'display-option-value display-option-value-' + d;
updateValue(d, val);
});
- sildersControlEnter.append('button').attr('title', _t('background.reset')).attr('class', function (d) {
+ sildersControlEnter.append('button').attr('title', function (d) {
+ return "".concat(_t('background.reset'), " ").concat(_t('background.' + d));
+ }).attr('class', function (d) {
return 'display-option-reset display-option-reset-' + d;
}).on('click', function (d3_event, d) {
if (d3_event.button !== 0) return;
updateValue(d, 1);
}).call(svgIcon('#iD-icon-' + (_mainLocalizer.textDirection() === 'rtl' ? 'redo' : 'undo'))); // reset all button
- containerEnter.append('a').attr('class', 'display-option-resetlink').attr('href', '#').html(_t.html('background.reset_all')).on('click', function (d3_event) {
+ containerEnter.append('a').attr('class', 'display-option-resetlink').attr('role', 'button').attr('href', '#').call(_t.append('background.reset_all')).on('click', function (d3_event) {
d3_event.preventDefault();
for (var i = 0; i < _sliders.length; i++) {
container.selectAll('.display-option-input').property('value', function (d) {
return _options[d];
});
- container.selectAll('.display-option-value').html(function (d) {
+ container.selectAll('.display-option-value').text(function (d) {
return Math.floor(_options[d] * 100) + '%';
});
container.selectAll('.display-option-reset').classed('disabled', function (d) {
var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png';
var modal = uiConfirm(selection).okButton();
modal.classed('settings-modal settings-custom-background', true);
- modal.select('.modal-section.header').append('h3').html(_t.html('settings.custom_background.header'));
+ modal.select('.modal-section.header').append('h3').call(_t.append('settings.custom_background.header'));
var textSection = modal.select('.modal-section.message-text');
var instructions = "".concat(_t.html('settings.custom_background.instructions.info'), "\n") + '\n' + "#### ".concat(_t.html('settings.custom_background.instructions.wms.tokens_label'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.wms.tokens.proj'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.wms.tokens.wkid'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.wms.tokens.dimensions'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.wms.tokens.bbox'), "\n") + '\n' + "#### ".concat(_t.html('settings.custom_background.instructions.tms.tokens_label'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.tms.tokens.xyz'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.tms.tokens.flipped_y'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.tms.tokens.switch'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.tms.tokens.quadtile'), "\n") + "* ".concat(_t.html('settings.custom_background.instructions.tms.tokens.scale_factor'), "\n") + '\n' + "#### ".concat(_t.html('settings.custom_background.instructions.example'), "\n") + "`".concat(example, "`");
textSection.append('div').attr('class', 'instructions-template').html(marked_1(instructions));
textSection.append('textarea').attr('class', 'field-template').attr('placeholder', _t('settings.custom_background.template.placeholder')).call(utilNoAuto).property('value', _currSettings.template); // insert a cancel button
var buttonSection = modal.select('.modal-section.buttons');
- buttonSection.insert('button', '.ok-button').attr('class', 'button cancel-button secondary-action').html(_t.html('confirm.cancel'));
+ buttonSection.insert('button', '.ok-button').attr('class', 'button cancel-button secondary-action').call(_t.append('confirm.cancel'));
buttonSection.select('.cancel-button').on('click.cancel', clickCancel);
buttonSection.select('.ok-button').attr('disabled', isSaveDisabled).on('click.save', clickSave);
d3_event.preventDefault();
uiMapInMap.toggle();
});
- minimapLabelEnter.append('span').html(_t.html('background.minimap.description'));
+ minimapLabelEnter.append('span').call(_t.append('background.minimap.description'));
var panelLabelEnter = bgExtrasListEnter.append('li').attr('class', 'background-panel-toggle-item').append('label').call(uiTooltip().title(_t.html('background.panel.tooltip')).keys([uiCmd('⌘⇧' + _t('info_panels.background.key'))]).placement('top'));
panelLabelEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event) {
d3_event.preventDefault();
context.ui().info.toggle('background');
});
- panelLabelEnter.append('span').html(_t.html('background.panel.description'));
+ panelLabelEnter.append('span').call(_t.append('background.panel.description'));
var locPanelLabelEnter = bgExtrasListEnter.append('li').attr('class', 'location-panel-toggle-item').append('label').call(uiTooltip().title(_t.html('background.location_panel.tooltip')).keys([uiCmd('⌘⇧' + _t('info_panels.location.key'))]).placement('top'));
locPanelLabelEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event) {
d3_event.preventDefault();
context.ui().info.toggle('location');
});
- locPanelLabelEnter.append('span').html(_t.html('background.location_panel.description')); // "Info / Report a Problem" link
+ locPanelLabelEnter.append('span').call(_t.append('background.location_panel.description')); // "Info / Report a Problem" link
- selection.selectAll('.imagery-faq').data([0]).enter().append('div').attr('class', 'imagery-faq').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/openstreetmap/iD/blob/develop/FAQ.md#how-can-i-report-an-issue-with-background-imagery').append('span').html(_t.html('background.imagery_problem_faq'));
+ selection.selectAll('.imagery-faq').data([0]).enter().append('div').attr('class', 'imagery-faq').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/openstreetmap/iD/blob/develop/FAQ.md#how-can-i-report-an-issue-with-background-imagery').append('span').call(_t.append('background.imagery_problem_faq'));
_backgroundList.call(drawListItems, 'radio', function (d3_event, d) {
chooseBackground(d);
function renderDisclosureContent(selection) {
var container = selection.selectAll('.nudge-container').data([0]);
var containerEnter = container.enter().append('div').attr('class', 'nudge-container');
- containerEnter.append('div').attr('class', 'nudge-instructions').html(_t.html('background.offset'));
+ containerEnter.append('div').attr('class', 'nudge-instructions').call(_t.append('background.offset'));
var nudgeWrapEnter = containerEnter.append('div').attr('class', 'nudge-controls-wrap');
var nudgeEnter = nudgeWrapEnter.append('div').attr('class', 'nudge-outer-rect').on(_pointerPrefix + 'down', dragOffset);
- nudgeEnter.append('div').attr('class', 'nudge-inner-rect').append('input').attr('type', 'text').on('change', inputOffset);
- nudgeWrapEnter.append('div').selectAll('button').data(_directions).enter().append('button').attr('class', function (d) {
+ nudgeEnter.append('div').attr('class', 'nudge-inner-rect').append('input').attr('type', 'text').attr('aria-label', _t('background.offset_label')).on('change', inputOffset);
+ nudgeWrapEnter.append('div').selectAll('button').data(_directions).enter().append('button').attr('title', function (d) {
+ return _t("background.nudge.".concat(d[0]));
+ }).attr('class', function (d) {
return d[0] + ' nudge';
}).on('click', function (d3_event, d) {
nudge(d[1]);
}
var toc = content.append('ul').attr('class', 'toc');
- var menuItems = toc.selectAll('li').data(docs).enter().append('li').append('a').attr('href', '#').html(function (d) {
+ var menuItems = toc.selectAll('li').data(docs).enter().append('li').append('a').attr('role', 'button').attr('href', '#').html(function (d) {
return d.title;
}).on('click', function (d3_event, d) {
d3_event.preventDefault();
clickHelp(d, docs.indexOf(d));
});
var shortcuts = toc.append('li').attr('class', 'shortcuts').call(uiTooltip().title(_t.html('shortcuts.tooltip')).keys(['?']).placement('top')).append('a').attr('href', '#').on('click', clickShortcuts);
- shortcuts.append('div').html(_t.html('shortcuts.title'));
+ shortcuts.append('div').call(_t.append('shortcuts.title'));
var walkthrough = toc.append('li').attr('class', 'walkthrough').append('a').attr('href', '#').on('click', clickWalkthrough);
walkthrough.append('svg').attr('class', 'logo logo-walkthrough').append('use').attr('xlink:href', '#iD-logo-walkthrough');
- walkthrough.append('div').html(_t.html('splash.walkthrough'));
+ walkthrough.append('div').call(_t.append('splash.walkthrough'));
var helpContent = content.append('div').attr('class', 'left-content');
var body = helpContent.append('div').attr('class', 'body');
var nav = helpContent.append('div').attr('class', 'nav');
var section = uiSection(id, context).label(function () {
if (!_issues) return '';
var issueCountText = _issues.length > 1000 ? '1000+' : String(_issues.length);
- return _t('inspector.title_count', {
- title: _t.html('issues.' + severity + 's.list_title'),
+ return _t.html('inspector.title_count', {
+ title: {
+ html: _t.html('issues.' + severity + 's.list_title')
+ },
count: issueCountText
});
}).disclosureContent(renderDisclosureContent).shouldDisplay(function () {
linkEnter
.append('span')
.attr('class', 'autofix-all-link-text')
- .html(t.html('issues.fix_all.title'));
+ .call(t.append('issues.fix_all.title'));
linkEnter
.append('span')
.attr('class', 'autofix-all-link-icon')
var containerEnter = container.enter().append('div').attr('class', 'issues-rulelist-container');
containerEnter.append('ul').attr('class', 'layer-list issue-rules-list');
var ruleLinks = containerEnter.append('div').attr('class', 'issue-rules-links section-footer');
- ruleLinks.append('a').attr('class', 'issue-rules-link').attr('href', '#').html(_t.html('issues.disable_all')).on('click', function (d3_event) {
+ ruleLinks.append('a').attr('class', 'issue-rules-link').attr('role', 'button').attr('href', '#').call(_t.append('issues.disable_all')).on('click', function (d3_event) {
d3_event.preventDefault();
context.validator().disableRules(_ruleKeys);
});
- ruleLinks.append('a').attr('class', 'issue-rules-link').attr('href', '#').html(_t.html('issues.enable_all')).on('click', function (d3_event) {
+ ruleLinks.append('a').attr('class', 'issue-rules-link').attr('role', 'button').attr('href', '#').call(_t.append('issues.enable_all')).on('click', function (d3_event) {
d3_event.preventDefault();
context.validator().disableRules([]);
}); // Update
var params = {};
if (d === 'unsquare_way') {
- params.val = '<span class="square-degrees"></span>';
+ params.val = {
+ html: '<span class="square-degrees"></span>'
+ };
}
return _t.html('issues.' + d + '.title', params);
resetIgnoredEnter.append('a').attr('href', '#'); // update
resetIgnored = resetIgnored.merge(resetIgnoredEnter);
- resetIgnored.select('a').html(_t('inspector.title_count', {
- title: _t.html('issues.reset_ignored'),
+ resetIgnored.select('a').html(_t.html('inspector.title_count', {
+ title: {
+ html: _t.html('issues.reset_ignored')
+ },
count: ignoredIssues.length
}));
resetIgnored.on('click', function (d3_event) {
var hiddenIssues = context.validator().getIssues(hiddenOpts);
if (hiddenIssues.length) {
- selection.select('.box .details').html(_t.html('issues.no_issues.hidden_issues.' + type, {
+ selection.select('.box .details').html('').call(_t.append('issues.no_issues.hidden_issues.' + type, {
count: hiddenIssues.length.toString()
}));
return;
}
}
- selection.select('.box .details').html(_t.html('issues.no_issues.hidden_issues.none'));
+ selection.select('.box .details').html('').call(_t.append('issues.no_issues.hidden_issues.none'));
}
var messageType;
messageType = 'no_edits';
}
- selection.select('.box .message').html(_t.html('issues.no_issues.message.' + messageType));
+ selection.select('.box .message').html('').call(_t.append('issues.no_issues.message.' + messageType));
}
context.validator().on('validated.uiSectionValidationStatus', function () {
var modal = uiConfirm(selection).okButton();
modal.classed('settings-modal settings-custom-data', true);
- modal.select('.modal-section.header').append('h3').html(_t.html('settings.custom_data.header'));
+ modal.select('.modal-section.header').append('h3').call(_t.append('settings.custom_data.header'));
var textSection = modal.select('.modal-section.message-text');
- textSection.append('pre').attr('class', 'instructions-file').html(_t.html('settings.custom_data.file.instructions'));
- textSection.append('input').attr('class', 'field-file').attr('type', 'file').property('files', _currSettings.fileList) // works for all except IE11
+ textSection.append('pre').attr('class', 'instructions-file').call(_t.append('settings.custom_data.file.instructions'));
+ textSection.append('input').attr('class', 'field-file').attr('type', 'file').attr('accept', '.gpx,.kml,.geojson,.json,application/gpx+xml,application/vnd.google-earth.kml+xml,application/geo+json,application/json').property('files', _currSettings.fileList) // works for all except IE11
.on('change', function (d3_event) {
var files = d3_event.target.files;
_currSettings.fileList = null;
}
});
- textSection.append('h4').html(_t.html('settings.custom_data.or'));
- textSection.append('pre').attr('class', 'instructions-url').html(_t.html('settings.custom_data.url.instructions'));
+ textSection.append('h4').call(_t.append('settings.custom_data.or'));
+ textSection.append('pre').attr('class', 'instructions-url').call(_t.append('settings.custom_data.url.instructions'));
textSection.append('textarea').attr('class', 'field-url').attr('placeholder', _t('settings.custom_data.url.placeholder')).call(utilNoAuto).property('value', _currSettings.url); // insert a cancel button
var buttonSection = modal.select('.modal-section.buttons');
- buttonSection.insert('button', '.ok-button').attr('class', 'button cancel-button secondary-action').html(_t.html('confirm.cancel'));
+ buttonSection.insert('button', '.ok-button').attr('class', 'button cancel-button secondary-action').call(_t.append('confirm.cancel'));
buttonSection.select('.cancel-button').on('click.cancel', clickCancel);
buttonSection.select('.ok-button').attr('disabled', isSaveDisabled).on('click.save', clickSave);
var container = selection.selectAll('.vectortile-container').data(showVectorItems ? [0] : []);
container.exit().remove();
var containerEnter = container.enter().append('div').attr('class', 'vectortile-container');
- containerEnter.append('h4').attr('class', 'vectortile-header').html('Detroit Vector Tiles (Beta)');
+ containerEnter.append('h4').attr('class', 'vectortile-header').text('Detroit Vector Tiles (Beta)');
containerEnter.append('ul').attr('class', 'layer-list layer-list-vectortile');
- containerEnter.append('div').attr('class', 'vectortile-footer').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/osmus/detroit-mapping-challenge').append('span').html('About these layers');
+ containerEnter.append('div').attr('class', 'vectortile-footer').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/osmus/detroit-mapping-challenge').append('span').text('About these layers');
container = container.merge(containerEnter);
var ul = container.selectAll('.layer-list-vectortile');
var li = ul.selectAll('.list-item').data(vtData);
select(this).call(uiTooltip().title(d.tooltip).placement('top'));
});
labelEnter.append('input').attr('type', 'radio').attr('name', 'vectortile').on('change', selectVTLayer);
- labelEnter.append('span').html(function (d) {
+ labelEnter.append('span').text(function (d) {
return d.name;
}); // Update
labelEnter.append('input').attr('type', 'checkbox').on('change', function () {
toggleLayer('data');
});
- labelEnter.append('span').html(_t.html('map_data.layers.custom.title'));
+ labelEnter.append('span').call(_t.append('map_data.layers.custom.title'));
liEnter.append('button').attr('class', 'open-data-options').call(uiTooltip().title(_t.html('settings.custom_data.tooltip')).placement(_mainLocalizer.textDirection() === 'rtl' ? 'right' : 'left')).on('click', function (d3_event) {
d3_event.preventDefault();
editCustom();
d3_event.preventDefault();
context.ui().info.toggle('history');
});
- historyPanelLabelEnter.append('span').html(_t.html('map_data.history_panel.title'));
+ historyPanelLabelEnter.append('span').call(_t.append('map_data.history_panel.title'));
var measurementPanelLabelEnter = panelsListEnter.append('li').attr('class', 'measurement-panel-toggle-item').append('label').call(uiTooltip().title(_t.html('map_data.measurement_panel.tooltip')).keys([uiCmd('⌘⇧' + _t('info_panels.measurement.key'))]).placement('top'));
measurementPanelLabelEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event) {
d3_event.preventDefault();
context.ui().info.toggle('measurement');
});
- measurementPanelLabelEnter.append('span').html(_t.html('map_data.measurement_panel.title'));
+ measurementPanelLabelEnter.append('span').call(_t.append('map_data.measurement_panel.title'));
}
context.layers().on('change.uiSectionDataLayers', section.reRender);
var containerEnter = container.enter().append('div').attr('class', 'layer-feature-list-container');
containerEnter.append('ul').attr('class', 'layer-list layer-feature-list');
var footer = containerEnter.append('div').attr('class', 'feature-list-links section-footer');
- footer.append('a').attr('class', 'feature-list-link').attr('href', '#').html(_t.html('issues.disable_all')).on('click', function (d3_event) {
+ footer.append('a').attr('class', 'feature-list-link').attr('role', 'button').attr('href', '#').call(_t.append('issues.disable_all')).on('click', function (d3_event) {
d3_event.preventDefault();
context.features().disableAll();
});
- footer.append('a').attr('class', 'feature-list-link').attr('href', '#').html(_t.html('issues.enable_all')).on('click', function (d3_event) {
+ footer.append('a').attr('class', 'feature-list-link').attr('role', 'button').attr('href', '#').call(_t.append('issues.enable_all')).on('click', function (d3_event) {
d3_event.preventDefault();
context.features().enableAll();
}); // Update
});
var labelEnter = liEnter.append('label').each(function (d) {
var titleID;
- if (d.id === 'mapillary-signs') titleID = 'mapillary.signs.tooltip';else if (d.id === 'mapillary') titleID = 'mapillary_images.tooltip';else if (d.id === 'openstreetcam') titleID = 'openstreetcam_images.tooltip';else titleID = d.id.replace(/-/g, '_') + '.tooltip';
+ if (d.id === 'mapillary-signs') titleID = 'mapillary.signs.tooltip';else if (d.id === 'mapillary') titleID = 'mapillary_images.tooltip';else if (d.id === 'kartaview') titleID = 'kartaview_images.tooltip';else titleID = d.id.replace(/-/g, '_') + '.tooltip';
select(this).call(uiTooltip().title(_t.html(titleID)).placement('top'));
});
labelEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event, d) {
var labelEnter = liEnter.append('label').each(function () {
select(this).call(uiTooltip().title(_t.html('photo_overlays.username_filter.tooltip')).placement('top'));
});
- labelEnter.append('span').html(_t.html('photo_overlays.username_filter.title'));
+ labelEnter.append('span').call(_t.append('photo_overlays.username_filter.title'));
labelEnter.append('input').attr('type', 'text').attr('class', 'list-item-input').call(utilNoAuto).property('value', usernameValue).on('change', function () {
var value = select(this).property('value');
context.photos().setUsernameFilter(value, true);
return mapDataPane;
}
- function uiSectionPrivacy(context) {
- var section = uiSection('preferences-third-party', context).label(_t.html('preferences.privacy.title')).disclosureContent(renderDisclosureContent);
-
- var _showThirdPartyIcons = corePreferences('preferences.privacy.thirdpartyicons') || 'true';
-
- function renderDisclosureContent(selection) {
- // enter
- var privacyOptionsListEnter = selection.selectAll('.privacy-options-list').data([0]).enter().append('ul').attr('class', 'layer-list privacy-options-list');
- var thirdPartyIconsEnter = privacyOptionsListEnter.append('li').attr('class', 'privacy-third-party-icons-item').append('label').call(uiTooltip().title(_t.html('preferences.privacy.third_party_icons.tooltip')).placement('bottom'));
- thirdPartyIconsEnter.append('input').attr('type', 'checkbox').on('change', function (d3_event) {
- d3_event.preventDefault();
- _showThirdPartyIcons = _showThirdPartyIcons === 'true' ? 'false' : 'true';
- corePreferences('preferences.privacy.thirdpartyicons', _showThirdPartyIcons);
- update();
- });
- thirdPartyIconsEnter.append('span').html(_t.html('preferences.privacy.third_party_icons.description')); // Privacy Policy link
-
- selection.selectAll('.privacy-link').data([0]).enter().append('div').attr('class', 'privacy-link').append('a').attr('target', '_blank').call(svgIcon('#iD-icon-out-link', 'inline')).attr('href', 'https://github.com/openstreetmap/iD/blob/release/PRIVACY.md').append('span').html(_t.html('preferences.privacy.privacy_link'));
- update();
-
- function update() {
- selection.selectAll('.privacy-third-party-icons-item').classed('active', _showThirdPartyIcons === 'true').select('input').property('checked', _showThirdPartyIcons === 'true');
- }
- }
-
- return section;
- }
-
function uiPanePreferences(context) {
var preferencesPane = uiPane('preferences', context).key(_t('preferences.key')).label(_t.html('preferences.title')).description(_t.html('preferences.description')).iconName('fas-user-cog').sections([uiSectionPrivacy(context)]);
return preferencesPane;
overMap.call(uiMapInMap(context)).call(uiNotice(context));
overMap.append('div').attr('class', 'spinner').call(uiSpinner(context)); // Map controls
- var controls = overMap.append('div').attr('class', 'map-controls');
+ var controlsWrap = overMap.append('div').attr('class', 'map-controls-wrap');
+ var controls = controlsWrap.append('div').attr('class', 'map-controls');
controls.append('div').attr('class', 'map-control zoombuttons').call(uiZoom(context));
controls.append('div').attr('class', 'map-control zoom-to-selection-control').call(uiZoomToSelection(context));
controls.append('div').attr('class', 'map-control geolocate-control').call(uiGeolocate(context));
- controls.on('wheel.mapControls', function (d3_event) {
+ controlsWrap.on('wheel.mapControls', function (d3_event) {
if (!d3_event.deltaX) {
- controls.node().scrollTop += d3_event.deltaY;
+ controlsWrap.node().scrollTop += d3_event.deltaY;
}
}); // Add panes
// This should happen after map is initialized, as some require surface()
aboutList.append('li').attr('class', 'issues-info').call(uiIssuesInfo(context));
aboutList.append('li').attr('class', 'feature-warning').call(uiFeatureInfo(context));
var issueLinks = aboutList.append('li');
- issueLinks.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/issues').call(svgIcon('#iD-icon-bug', 'light')).call(uiTooltip().title(_t.html('report_a_bug')).placement('top'));
- issueLinks.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/blob/develop/CONTRIBUTING.md#translating').call(svgIcon('#iD-icon-translate', 'light')).call(uiTooltip().title(_t.html('help_translate')).placement('top'));
+ issueLinks.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/issues').attr('aria-label', _t('report_a_bug')).call(svgIcon('#iD-icon-bug', 'light')).call(uiTooltip().title(_t.html('report_a_bug')).placement('top'));
+ issueLinks.append('a').attr('target', '_blank').attr('href', 'https://github.com/openstreetmap/iD/blob/develop/CONTRIBUTING.md#translating').attr('aria-label', _t('help_translate')).call(svgIcon('#iD-icon-translate', 'light')).call(uiTooltip().title(_t.html('help_translate')).placement('top'));
aboutList.append('li').attr('class', 'version').call(uiVersion(context));
if (!context.embed()) {
var _deferred = new Set();
- context.version = '2.20.2';
+ context.version = '2.20.3';
context.privacyVersion = '20201202'; // iD will alter the hash so cache the parameters intended to setup the session
context.initialHashParams = window.location.hash ? utilStringQs(window.location.hash) : {};
- context.isFirstSession = !corePreferences('sawSplash') && !corePreferences('sawPrivacyVersion');
/* Changeset */
// An osmChangeset object. Not loaded until needed.
newTags: newTags,
matched: null
} : null;
- } // Order the [key,value,name] tuples - test primary before alternate
+ } // Order the [key,value,name] tuples - test primary names before alternate names
var tuples = gatherTuples(tryKVs, tryNames);
+ var foundPrimary = false;
+ var bestItem; // Test [key,value,name] tuples against the NSI matcher until we get a primary match or exhaust all options.
- var _loop = function _loop(i) {
+ for (var i = 0; i < tuples.length && !foundPrimary; i++) {
var tuple = tuples[i];
var hits = _nsi.matcher.match(tuple.k, tuple.v, tuple.n, loc); // Attempt to match an item in NSI
- if (!hits || !hits.length) return "continue"; // no match, try next tuple
+ if (!hits || !hits.length) continue; // no match, try next tuple
- if (hits[0].match !== 'primary' && hits[0].match !== 'alternate') return "break"; // a generic match, stop looking
+ if (hits[0].match !== 'primary' && hits[0].match !== 'alternate') break; // a generic match, stop looking
// A match may contain multiple results, the first one is likely the best one for this location
// e.g. `['pfk-a54c14', 'kfc-1ff19c', 'kfc-658eea']`
- var itemID = void 0,
- item = void 0;
-
for (var j = 0; j < hits.length; j++) {
var hit = hits[j];
- itemID = hit.itemID;
+ var isPrimary = hits[j].match === 'primary';
+ var itemID = hit.itemID;
if (_nsi.dissolved[itemID]) continue; // Don't upgrade to a dissolved item
- item = _nsi.ids.get(itemID);
+ var item = _nsi.ids.get(itemID);
+
if (!item) continue;
var mainTag = item.mainTag; // e.g. `brand:wikidata`
!itemQID || itemQID === notQID || // No `*:wikidata` or matched a `not:*:wikidata`
newTags.office && !item.tags.office // feature may be a corporate office for a brand? - #6416
) {
- item = null;
- continue; // continue looking
- } else {
- break; // use `item`
- }
- } // Can't use any of these hits, try next tuple..
+ continue; // continue looking
+ } // If we get here, the hit is good..
- if (!item) return "continue"; // At this point we have matched a canonical item and can suggest tag upgrades..
+ if (!bestItem || isPrimary) {
+ bestItem = item;
- item = JSON.parse(JSON.stringify(item)); // deep copy
+ if (isPrimary) {
+ foundPrimary = true;
+ }
- var tkv = item.tkv;
- var parts = tkv.split('/', 3); // tkv = "tree/key/value"
+ break; // can ignore the rest of the hits from this match
+ }
+ }
+ } // At this point we have matched a canonical item and can suggest tag upgrades..
- var k = parts[1];
- var v = parts[2];
- var category = _nsi.data[tkv];
- var properties = category.properties || {}; // Preserve some tags that we specifically don't want NSI to overwrite. ('^name', sometimes)
- var preserveTags = item.preserveTags || properties.preserveTags || []; // These tags can be toplevel tags -or- attributes - so we generally want to preserve existing values - #8615
- // We'll only _replace_ the tag value if this tag is the toplevel/defining tag for the matched item (`k`)
+ if (bestItem) {
+ var _ret = function () {
+ var itemID = bestItem.id;
+ var item = JSON.parse(JSON.stringify(bestItem)); // deep copy
- ['building', 'emergency', 'internet_access', 'takeaway'].forEach(function (osmkey) {
- if (k !== osmkey) preserveTags.push("^".concat(osmkey, "$"));
- });
- var regexes = preserveTags.map(function (s) {
- return new RegExp(s, 'i');
- });
- var keepTags = {};
- Object.keys(newTags).forEach(function (osmkey) {
- if (regexes.some(function (regex) {
- return regex.test(osmkey);
- })) {
- keepTags[osmkey] = newTags[osmkey];
- }
- }); // Remove any primary tags ("amenity", "craft", "shop", "man_made", "route", etc) that have a
- // value like `amenity=yes` or `shop=yes` (exceptions have already been added to `keepTags` above)
+ var tkv = item.tkv;
+ var parts = tkv.split('/', 3); // tkv = "tree/key/value"
+
+ var k = parts[1];
+ var v = parts[2];
+ var category = _nsi.data[tkv];
+ var properties = category.properties || {}; // Preserve some tags that we specifically don't want NSI to overwrite. ('^name', sometimes)
- _nsi.kvt.forEach(function (vmap, k) {
- if (newTags[k] === 'yes') delete newTags[k];
- }); // Replace mistagged `wikidata`/`wikipedia` with e.g. `brand:wikidata`/`brand:wikipedia`
+ var preserveTags = item.preserveTags || properties.preserveTags || []; // These tags can be toplevel tags -or- attributes - so we generally want to preserve existing values - #8615
+ // We'll only _replace_ the tag value if this tag is the toplevel/defining tag for the matched item (`k`)
+ ['building', 'emergency', 'internet_access', 'takeaway'].forEach(function (osmkey) {
+ if (k !== osmkey) preserveTags.push("^".concat(osmkey, "$"));
+ });
+ var regexes = preserveTags.map(function (s) {
+ return new RegExp(s, 'i');
+ });
+ var keepTags = {};
+ Object.keys(newTags).forEach(function (osmkey) {
+ if (regexes.some(function (regex) {
+ return regex.test(osmkey);
+ })) {
+ keepTags[osmkey] = newTags[osmkey];
+ }
+ }); // Remove any primary tags ("amenity", "craft", "shop", "man_made", "route", etc) that have a
+ // value like `amenity=yes` or `shop=yes` (exceptions have already been added to `keepTags` above)
- if (foundQID) {
- delete newTags.wikipedia;
- delete newTags.wikidata;
- } // Do the tag upgrade
+ _nsi.kvt.forEach(function (vmap, k) {
+ if (newTags[k] === 'yes') delete newTags[k];
+ }); // Replace mistagged `wikidata`/`wikipedia` with e.g. `brand:wikidata`/`brand:wikipedia`
- Object.assign(newTags, item.tags, keepTags); // Swap `route` back to `route_master` - name-suggestion-index#5184
+ if (foundQID) {
+ delete newTags.wikipedia;
+ delete newTags.wikidata;
+ } // Do the tag upgrade
- if (isRouteMaster) {
- newTags.route_master = newTags.route;
- delete newTags.route;
- } // Special `branch` splitting rules - IF..
- // - NSI is suggesting to replace `name`, AND
- // - `branch` doesn't already contain something, AND
- // - original name has not moved to an alternate name (e.g. "Dunkin' Donuts" -> "Dunkin'"), AND
- // - original name is "some name" + "some stuff", THEN
- // consider splitting `name` into `name`/`branch`..
+ Object.assign(newTags, item.tags, keepTags); // Swap `route` back to `route_master` - name-suggestion-index#5184
- var origName = tags.name;
- var newName = newTags.name;
+ if (isRouteMaster) {
+ newTags.route_master = newTags.route;
+ delete newTags.route;
+ } // Special `branch` splitting rules - IF..
+ // - NSI is suggesting to replace `name`, AND
+ // - `branch` doesn't already contain something, AND
+ // - original name has not moved to an alternate name (e.g. "Dunkin' Donuts" -> "Dunkin'"), AND
+ // - original name is "some name" + "some stuff", THEN
+ // consider splitting `name` into `name`/`branch`..
- if (newName && origName && newName !== origName && !newTags.branch) {
- var newNames = gatherNames(newTags);
- var newSet = new Set([].concat(_toConsumableArray(newNames.primary), _toConsumableArray(newNames.alternate)));
- var isMoved = newSet.has(origName); // another tag holds the original name now
- if (!isMoved) {
- // Test name fragments, longest to shortest, to fit them into a "Name Branch" pattern.
- // e.g. "TUI ReiseCenter - Neuss Innenstadt" -> ["TUI", "ReiseCenter", "Neuss", "Innenstadt"]
- var nameParts = origName.split(/[\s\-\/,.]/);
+ var origName = tags.name;
+ var newName = newTags.name;
- for (var split = nameParts.length; split > 0; split--) {
- var name = nameParts.slice(0, split).join(' '); // e.g. "TUI ReiseCenter"
+ if (newName && origName && newName !== origName && !newTags.branch) {
+ var newNames = gatherNames(newTags);
+ var newSet = new Set([].concat(_toConsumableArray(newNames.primary), _toConsumableArray(newNames.alternate)));
+ var isMoved = newSet.has(origName); // another tag holds the original name now
- var branch = nameParts.slice(split).join(' '); // e.g. "Neuss Innenstadt"
+ if (!isMoved) {
+ // Test name fragments, longest to shortest, to fit them into a "Name Branch" pattern.
+ // e.g. "TUI ReiseCenter - Neuss Innenstadt" -> ["TUI", "ReiseCenter", "Neuss", "Innenstadt"]
+ var nameParts = origName.split(/[\s\-\/,.]/);
- var nameHits = _nsi.matcher.match(k, v, name, loc);
+ for (var split = nameParts.length; split > 0; split--) {
+ var name = nameParts.slice(0, split).join(' '); // e.g. "TUI ReiseCenter"
- if (!nameHits || !nameHits.length) continue; // no match, try next name fragment
+ var branch = nameParts.slice(split).join(' '); // e.g. "Neuss Innenstadt"
- if (nameHits.some(function (hit) {
- return hit.itemID === itemID;
- })) {
- // matched the name fragment to the same itemID above
- if (branch) {
- if (notBranches.test(branch)) {
- // "branch" was detected but is noise ("factory outlet", etc)
- newTags.name = origName; // Leave `name` alone, this part of the name may be significant..
- } else {
- var branchHits = _nsi.matcher.match(k, v, branch, loc);
+ var nameHits = _nsi.matcher.match(k, v, name, loc);
- if (branchHits && branchHits.length) {
- // if "branch" matched something else in NSI..
- if (branchHits[0].match === 'primary' || branchHits[0].match === 'alternate') {
- // if another brand! (e.g. "KFC - Taco Bell"?)
- return {
- v: null
- }; // bail out - can't suggest tags in this case
- } // else a generic (e.g. "gas", "cafe") - ignore
+ if (!nameHits || !nameHits.length) continue; // no match, try next name fragment
+ if (nameHits.some(function (hit) {
+ return hit.itemID === itemID;
+ })) {
+ // matched the name fragment to the same itemID above
+ if (branch) {
+ if (notBranches.test(branch)) {
+ // "branch" was detected but is noise ("factory outlet", etc)
+ newTags.name = origName; // Leave `name` alone, this part of the name may be significant..
} else {
- // "branch" is not noise and not something in NSI
- newTags.branch = branch; // Stick it in the `branch` tag..
+ var branchHits = _nsi.matcher.match(k, v, branch, loc);
+
+ if (branchHits && branchHits.length) {
+ // if "branch" matched something else in NSI..
+ if (branchHits[0].match === 'primary' || branchHits[0].match === 'alternate') {
+ // if another brand! (e.g. "KFC - Taco Bell"?)
+ return {
+ v: null
+ }; // bail out - can't suggest tags in this case
+ } // else a generic (e.g. "gas", "cafe") - ignore
+
+ } else {
+ // "branch" is not noise and not something in NSI
+ newTags.branch = branch; // Stick it in the `branch` tag..
+ }
}
}
- }
- break;
+ break;
+ }
}
}
}
- }
-
- return {
- v: {
- newTags: newTags,
- matched: item
- }
- };
- };
- for (var i = 0; i < tuples.length; i++) {
- var _ret = _loop(i);
+ return {
+ v: {
+ newTags: newTags,
+ matched: item
+ }
+ };
+ }();
- if (_ret === "continue") continue;
- if (_ret === "break") break;
if (_typeof(_ret) === "object") return _ret.v;
}
}
};
- var apibase$1 = 'https://openstreetcam.org';
+ var apibase$1 = 'https://kartaview.org';
var maxResults$1 = 1000;
var tileZoom$1 = 14;
var tiler$3 = utilTiler().zoomExtent([tileZoom$1, tileZoom$1]).skipNullIsland(true);
}, []);
}
- var serviceOpenstreetcam = {
+ var serviceKartaview = {
init: function init() {
if (!_oscCache) {
this.reset();
loadTiles$1('images', url, projection);
},
ensureViewerLoaded: function ensureViewerLoaded(context) {
- if (_loadViewerPromise$1) return _loadViewerPromise$1; // add osc-wrapper
+ if (_loadViewerPromise$1) return _loadViewerPromise$1; // add kartaview-wrapper
- var wrap = context.container().select('.photoviewer').selectAll('.osc-wrapper').data([0]);
+ var wrap = context.container().select('.photoviewer').selectAll('.kartaview-wrapper').data([0]);
var that = this;
- var wrapEnter = wrap.enter().append('div').attr('class', 'photo-wrapper osc-wrapper').classed('hide', true).call(imgZoom.on('zoom', zoomPan)).on('dblclick.zoom', null);
+ var wrapEnter = wrap.enter().append('div').attr('class', 'photo-wrapper kartaview-wrapper').classed('hide', true).call(imgZoom.on('zoom', zoomPan)).on('dblclick.zoom', null);
wrapEnter.append('div').attr('class', 'photo-attribution fillD');
var controlsEnter = wrapEnter.append('div').attr('class', 'photo-controls-wrap').append('div').attr('class', 'photo-controls');
- controlsEnter.append('button').on('click.back', step(-1)).html('◄');
- controlsEnter.append('button').on('click.rotate-ccw', rotate(-90)).html('⤿');
- controlsEnter.append('button').on('click.rotate-cw', rotate(90)).html('⤾');
- controlsEnter.append('button').on('click.forward', step(1)).html('►');
- wrapEnter.append('div').attr('class', 'osc-image-wrap'); // Register viewer resize handler
+ controlsEnter.append('button').on('click.back', step(-1)).text('◄');
+ controlsEnter.append('button').on('click.rotate-ccw', rotate(-90)).text('⤿');
+ controlsEnter.append('button').on('click.rotate-cw', rotate(90)).text('⤾');
+ controlsEnter.append('button').on('click.forward', step(1)).text('►');
+ wrapEnter.append('div').attr('class', 'kartaview-image-wrap'); // Register viewer resize handler
- context.ui().photoviewer.on('resize.openstreetcam', function (dimensions) {
+ context.ui().photoviewer.on('resize.kartaview', function (dimensions) {
imgZoom = d3_zoom().extent([[0, 0], dimensions]).translateExtent([[0, 0], dimensions]).scaleExtent([1, 15]).on('zoom', zoomPan);
});
function zoomPan(d3_event) {
var t = d3_event.transform;
- context.container().select('.photoviewer .osc-image-wrap').call(utilSetTransform, t.x, t.y, t.k);
+ context.container().select('.photoviewer .kartaview-image-wrap').call(utilSetTransform, t.x, t.y, t.k);
}
function rotate(deg) {
if (r > 180) r -= 360;
if (r < -180) r += 360;
sequence.rotation = r;
- var wrap = context.container().select('.photoviewer .osc-wrapper');
+ var wrap = context.container().select('.photoviewer .kartaview-wrapper');
wrap.transition().duration(100).call(imgZoom.transform, identity$2);
- wrap.selectAll('.osc-image').transition().duration(100).style('transform', 'rotate(' + r + 'deg)');
+ wrap.selectAll('.kartaview-image').transition().duration(100).style('transform', 'rotate(' + r + 'deg)');
};
}
},
showViewer: function showViewer(context) {
var viewer = context.container().select('.photoviewer').classed('hide', false);
- var isHidden = viewer.selectAll('.photo-wrapper.osc-wrapper.hide').size();
+ var isHidden = viewer.selectAll('.photo-wrapper.kartaview-wrapper.hide').size();
if (isHidden) {
- viewer.selectAll('.photo-wrapper:not(.osc-wrapper)').classed('hide', true);
- viewer.selectAll('.photo-wrapper.osc-wrapper').classed('hide', false);
+ viewer.selectAll('.photo-wrapper:not(.kartaview-wrapper)').classed('hide', true);
+ viewer.selectAll('.photo-wrapper.kartaview-wrapper').classed('hide', false);
}
return this;
this.setStyles(context, null, true);
context.container().selectAll('.icon-sign').classed('currentView', false);
if (!d) return this;
- var wrap = context.container().select('.photoviewer .osc-wrapper');
- var imageWrap = wrap.selectAll('.osc-image-wrap');
- var attribution = wrap.selectAll('.photo-attribution').html('');
+ var wrap = context.container().select('.photoviewer .kartaview-wrapper');
+ var imageWrap = wrap.selectAll('.kartaview-image-wrap');
+ var attribution = wrap.selectAll('.photo-attribution').text('');
wrap.transition().duration(100).call(imgZoom.transform, identity$2);
- imageWrap.selectAll('.osc-image').remove();
+ imageWrap.selectAll('.kartaview-image').remove();
if (d) {
var sequence = _oscCache.sequences[d.sequence_id];
var r = sequence && sequence.rotation || 0;
- imageWrap.append('img').attr('class', 'osc-image').attr('src', apibase$1 + '/' + d.imagePath).style('transform', 'rotate(' + r + 'deg)');
+ imageWrap.append('img').attr('class', 'kartaview-image').attr('src', apibase$1 + '/' + d.imagePath).style('transform', 'rotate(' + r + 'deg)');
if (d.captured_by) {
- attribution.append('a').attr('class', 'captured_by').attr('target', '_blank').attr('href', 'https://openstreetcam.org/user/' + encodeURIComponent(d.captured_by)).html('@' + d.captured_by);
- attribution.append('span').html('|');
+ attribution.append('a').attr('class', 'captured_by').attr('target', '_blank').attr('href', 'https://kartaview.org/user/' + encodeURIComponent(d.captured_by)).text('@' + d.captured_by);
+ attribution.append('span').text('|');
}
if (d.captured_at) {
- attribution.append('span').attr('class', 'captured_at').html(localeDateString(d.captured_at));
- attribution.append('span').html('|');
+ attribution.append('span').attr('class', 'captured_at').text(localeDateString(d.captured_at));
+ attribution.append('span').text('|');
}
- attribution.append('a').attr('class', 'image-link').attr('target', '_blank').attr('href', 'https://openstreetcam.org/details/' + d.sequence_id + '/' + d.sequence_index).html('openstreetcam.org');
+ attribution.append('a').attr('class', 'image-link').attr('target', '_blank').attr('href', 'https://kartaview.org/details/' + d.sequence_id + '/' + d.sequence_index).text('kartaview.org');
}
return this;
}) || []; // highlight sibling viewfields on either the selected or the hovered sequences
var highlightedImageKeys = utilArrayUnion(hoveredImageKeys, selectedImageKeys);
- context.container().selectAll('.layer-openstreetcam .viewfield-group').classed('highlighted', function (d) {
+ context.container().selectAll('.layer-kartaview .viewfield-group').classed('highlighted', function (d) {
return highlightedImageKeys.indexOf(d.key) !== -1;
}).classed('hovered', function (d) {
return d.key === hoveredImageKey;
}).classed('currentView', function (d) {
return d.key === selectedImageKey;
});
- context.container().selectAll('.layer-openstreetcam .sequence').classed('highlighted', function (d) {
+ context.container().selectAll('.layer-kartaview .sequence').classed('highlighted', function (d) {
return d.properties.key === hoveredSequenceKey;
}).classed('currentView', function (d) {
return d.properties.key === selectedSequenceKey;
}); // update viewfields if needed
- context.container().selectAll('.layer-openstreetcam .viewfield-group .viewfield').attr('d', viewfieldPath);
+ context.container().selectAll('.layer-kartaview .viewfield-group .viewfield').attr('d', viewfieldPath);
function viewfieldPath() {
var d = this.parentNode.__data__;
var hash = utilStringQs(window.location.hash);
if (imageKey) {
- hash.photo = 'openstreetcam/' + imageKey;
+ hash.photo = 'kartaview/' + imageKey;
} else {
delete hash.photo;
}
module.exports = Hashes;
} // in Narwhal or RingoJS v0.7.0-
else {
- freeExports.Hashes = Hashes;
- }
+ freeExports.Hashes = Hashes;
+ }
} else {
// in a browser or Rhino
window.Hashes = Hashes;
module.exports = factory();
}
}(commonjsGlobal, function () {
- function resolveUrl()
- /* ...urls */
- {
+ function
+ /* ...urls */
+ resolveUrl() {
var numUrls = arguments.length;
if (numUrls === 0) {
return urlroot + '/' + entity.type + '/' + entity.osmId() + '/history';
},
userURL: function userURL(username) {
- return urlroot + '/user/' + username;
+ return urlroot + '/user/' + encodeURIComponent(username);
},
noteURL: function noteURL(note) {
return urlroot + '/note/' + note.id;
});
}).append('div').attr('class', 'photo-attribution fillD');
var controlsEnter = wrapEnter.append('div').attr('class', 'photo-controls-wrap').append('div').attr('class', 'photo-controls');
- controlsEnter.append('button').on('click.back', step(-1)).html('◄');
- controlsEnter.append('button').on('click.forward', step(1)).html('►'); // create working canvas for stitching together images
+ controlsEnter.append('button').on('click.back', step(-1)).text('◄');
+ controlsEnter.append('button').on('click.forward', step(1)).text('►'); // create working canvas for stitching together images
wrap = wrap.merge(wrapEnter).call(setupCanvas, true); // Register viewer resize handler
_sceneOptions = Object.assign(_sceneOptions, viewstate);
that.selectImage(context, d.key).showViewer(context);
});
- label.append('span').html(_t.html('streetside.hires'));
+ label.append('span').call(_t.append('streetside.hires'));
var captureInfo = line1.append('div').attr('class', 'attribution-capture-info'); // Add capture date
if (d.captured_by) {
var yyyy = new Date().getFullYear();
- captureInfo.append('a').attr('class', 'captured_by').attr('target', '_blank').attr('href', 'https://www.microsoft.com/en-us/maps/streetside').html('©' + yyyy + ' Microsoft');
- captureInfo.append('span').html('|');
+ captureInfo.append('a').attr('class', 'captured_by').attr('target', '_blank').attr('href', 'https://www.microsoft.com/en-us/maps/streetside').text('©' + yyyy + ' Microsoft');
+ captureInfo.append('span').text('|');
}
if (d.captured_at) {
- captureInfo.append('span').attr('class', 'captured_at').html(localeTimestamp(d.captured_at));
+ captureInfo.append('span').attr('class', 'captured_at').text(localeTimestamp(d.captured_at));
} // Add image links
var line2 = attribution.append('div').attr('class', 'attribution-row');
- line2.append('a').attr('class', 'image-view-link').attr('target', '_blank').attr('href', 'https://www.bing.com/maps?cp=' + d.loc[1] + '~' + d.loc[0] + '&lvl=17&dir=' + d.ca + '&style=x&v=2&sV=1').html(_t.html('streetside.view_on_bing'));
- line2.append('a').attr('class', 'image-report-link').attr('target', '_blank').attr('href', 'https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=' + encodeURIComponent(d.key) + '&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17').html(_t.html('streetside.report'));
+ line2.append('a').attr('class', 'image-view-link').attr('target', '_blank').attr('href', 'https://www.bing.com/maps?cp=' + d.loc[1] + '~' + d.loc[0] + '&lvl=17&dir=' + d.ca + '&style=x&v=2&sV=1').call(_t.append('streetside.view_on_bing'));
+ line2.append('a').attr('class', 'image-report-link').attr('target', '_blank').attr('href', 'https://www.bing.com/maps/privacyreport/streetsideprivacyreport?bubbleid=' + encodeURIComponent(d.key) + '&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17').call(_t.append('streetside.report'));
var bubbleIdQuadKey = d.key.toString(4);
var paddingNeeded = 16 - bubbleIdQuadKey.length;
// A few OSM keys expect values to contain uppercase values (see #3377).
// This is not an exhaustive list (e.g. `name` also has uppercase values)
// but these are the fields where taginfo value lookup is most useful.
- var re = /network|taxon|genus|species|brand|grape_variety|royal_cypher|listed_status|booth|rating|stars|:output|_hours|_times|_ref|manufacturer|country|target|brewery/;
+ var re = /network|taxon|genus|species|brand|grape_variety|royal_cypher|listed_status|booth|rating|stars|:output|_hours|_times|_ref|manufacturer|country|target|brewery|cai_scale/;
var allowUpperCase = re.test(params.key);
var f = filterValues(allowUpperCase);
var result = d.data.filter(f).map(valKeyDescription);
switch (type) {
case "LineString":
case "MultiLineString":
- var lines_1 = [];
+ {
+ var lines_1 = [];
- if (type === "LineString") {
- coords = [coords];
- }
+ if (type === "LineString") {
+ coords = [coords];
+ }
- coords.forEach(function (line) {
- lineclip(line, bbox, lines_1);
- });
+ coords.forEach(function (line) {
+ lineclip(line, bbox, lines_1);
+ });
- if (lines_1.length === 1) {
- return lineString(lines_1[0], properties);
- }
+ if (lines_1.length === 1) {
+ return lineString(lines_1[0], properties);
+ }
- return multiLineString(lines_1, properties);
+ return multiLineString(lines_1, properties);
+ }
case "Polygon":
return polygon(clipPolygon(coords, bbox), properties);
osmose: serviceOsmose,
mapillary: serviceMapillary,
nsi: serviceNsi,
- openstreetcam: serviceOpenstreetcam,
+ kartaview: serviceKartaview,
osm: serviceOsm,
osmWikibase: serviceOsmWikibase,
maprules: serviceMapRules,
});
};
+ operation.icon = function () {
+ if (_waysAmount === 'multiple') {
+ return '#iD-operation-split-multiple';
+ } else {
+ return '#iD-operation-split';
+ }
+ };
+
operation.id = 'split';
operation.keys = [_t('operations.split.key')];
operation.title = _t('operations.split.title');
if (disabled) {
var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
- context.ui().flash.duration(4000).iconName('#iD-icon-no').iconClass('operation disabled').label(_t('operations.scale.' + disabled + '.' + multi))();
+ context.ui().flash.duration(4000).iconName('#iD-icon-no').iconClass('operation disabled').label(_t.html('operations.scale.' + disabled + '.' + multi))();
} else {
var pivot = context.projection(extent.center());
var annotation = _t('operations.scale.annotation.' + (isUp ? 'up' : 'down') + '.feature', {
return mode;
}
- function uiLasso(context) {
- var group, polygon;
- lasso.coordinates = [];
-
- function lasso(selection) {
- context.container().classed('lasso', true);
- group = selection.append('g').attr('class', 'lasso hide');
- polygon = group.append('path').attr('class', 'lasso-path');
- group.call(uiToggle(true));
- }
-
- function draw() {
- if (polygon) {
- polygon.data([lasso.coordinates]).attr('d', function (d) {
- return 'M' + d.join(' L') + ' Z';
- });
- }
- }
-
- lasso.extent = function () {
- return lasso.coordinates.reduce(function (extent, point) {
- return extent.extend(geoExtent(point));
- }, geoExtent());
- };
-
- lasso.p = function (_) {
- if (!arguments.length) return lasso;
- lasso.coordinates.push(_);
- draw();
- return lasso;
- };
-
- lasso.close = function () {
- if (group) {
- group.call(uiToggle(false, function () {
- select(this).remove();
- }));
- }
-
- context.container().classed('lasso', false);
- };
-
- return lasso;
- }
-
function behaviorLasso(context) {
// use pointer events on supported platforms; fallback to mouse events
var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
serviceMapRules: serviceMapRules,
serviceNominatim: serviceNominatim,
serviceNsi: serviceNsi,
- serviceOpenstreetcam: serviceOpenstreetcam,
+ serviceKartaview: serviceKartaview,
serviceOsm: serviceOsm,
serviceOsmWikibase: serviceOsmWikibase,
serviceStreetside: serviceStreetside,
svgMidpoints: svgMidpoints,
svgNotes: svgNotes,
svgMarkerSegments: svgMarkerSegments,
- svgOpenstreetcamImages: svgOpenstreetcamImages,
+ svgKartaviewImages: svgKartaviewImages,
svgOsm: svgOsm,
svgPassiveVertex: svgPassiveVertex,
svgPath: svgPath,
utilKeybinding: utilKeybinding,
utilNoAuto: utilNoAuto,
utilObjectOmit: utilObjectOmit,
+ utilCompareIDs: utilCompareIDs,
+ utilOldestID: utilOldestID,
utilPrefixCSSProperty: utilPrefixCSSProperty,
utilPrefixDOMProperty: utilPrefixDOMProperty,
utilQsString: utilQsString,