mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-18 14:45:12 +02:00
Merge branch 'master' into Psigio-3375
This commit is contained in:
@@ -152,7 +152,9 @@ export function behaviorBreathe() {
|
||||
|
||||
breathe.off = function() {
|
||||
done = true;
|
||||
timer.stop();
|
||||
if (timer) {
|
||||
timer.stop();
|
||||
}
|
||||
selected
|
||||
.interrupt()
|
||||
.call(reset);
|
||||
|
||||
@@ -14,8 +14,8 @@ export function behaviorHash(context) {
|
||||
if (args.length < 3 || args.some(isNaN)) {
|
||||
return true; // replace bogus hash
|
||||
} else if (s !== formatter(map).slice(1)) {
|
||||
map.centerZoom([args[1],
|
||||
Math.min(lat, Math.max(-lat, args[2]))], args[0]);
|
||||
map.centerZoom([args[2],
|
||||
Math.min(lat, Math.max(-lat, args[1]))], args[0]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,8 +40,8 @@ export function behaviorHash(context) {
|
||||
}
|
||||
|
||||
newParams.map = zoom.toFixed(2) +
|
||||
'/' + center[0].toFixed(precision) +
|
||||
'/' + center[1].toFixed(precision);
|
||||
'/' + center[1].toFixed(precision) +
|
||||
'/' + center[0].toFixed(precision);
|
||||
|
||||
return '#' + utilQsString(_.assign(q, newParams), true);
|
||||
};
|
||||
|
||||
+40
-39
@@ -1,11 +1,11 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { t, addTranslation, setLocale } from '../util/locale';
|
||||
import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
|
||||
import { coreHistory } from './history';
|
||||
import { dataLocales, dataEn } from '../../data/index';
|
||||
import { geoRawMercator } from '../geo/raw_mercator';
|
||||
import { modeSelect } from '../modes/select';
|
||||
import { presetInit } from '../presets/init';
|
||||
import { presetIndex } from '../presets/index';
|
||||
import { rendererBackground } from '../renderer/background';
|
||||
import { rendererFeatures } from '../renderer/features';
|
||||
import { rendererMap } from '../renderer/map';
|
||||
@@ -22,12 +22,7 @@ export function setAreaKeys(value) {
|
||||
}
|
||||
|
||||
|
||||
export function coreContext(root) {
|
||||
if (!root.locale) {
|
||||
root.locale = {
|
||||
current: function(_) { this._current = _; }
|
||||
};
|
||||
}
|
||||
export function coreContext() {
|
||||
addTranslation('en', dataEn);
|
||||
setLocale('en');
|
||||
|
||||
@@ -217,6 +212,11 @@ export function coreContext(root) {
|
||||
};
|
||||
|
||||
|
||||
/* Presets */
|
||||
var presets;
|
||||
context.presets = function() { return presets; };
|
||||
|
||||
|
||||
/* Map */
|
||||
var map;
|
||||
context.map = function() { return map; };
|
||||
@@ -254,23 +254,6 @@ export function coreContext(root) {
|
||||
};
|
||||
|
||||
|
||||
/* Presets */
|
||||
var presets;
|
||||
context.presets = function(_) {
|
||||
if (!arguments.length) return presets;
|
||||
presets.load(_);
|
||||
areaKeys = presets.areaKeys();
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Imagery */
|
||||
context.imagery = function(_) {
|
||||
background.load(_);
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Container */
|
||||
var container, embed;
|
||||
context.container = function(_) {
|
||||
@@ -310,24 +293,40 @@ export function coreContext(root) {
|
||||
return context.asset('img/' + _);
|
||||
};
|
||||
|
||||
|
||||
/* locales */
|
||||
// `locale` variable contains a "requested locale".
|
||||
// It won't become the `currentLocale` until after loadLocale() is called.
|
||||
var locale, localePath;
|
||||
|
||||
context.locale = function(loc, path) {
|
||||
if (!arguments.length) return locale;
|
||||
if (!arguments.length) return currentLocale;
|
||||
locale = loc;
|
||||
localePath = path;
|
||||
return context;
|
||||
};
|
||||
|
||||
context.loadLocale = function(cb) {
|
||||
if (locale && locale !== 'en' && dataLocales.indexOf(locale) !== -1) {
|
||||
context.loadLocale = function(callback) {
|
||||
if (locale && locale !== 'en' && dataLocales.hasOwnProperty(locale)) {
|
||||
localePath = localePath || context.asset('locales/' + locale + '.json');
|
||||
d3.json(localePath, function(err, result) {
|
||||
addTranslation(locale, result[locale]);
|
||||
setLocale(locale);
|
||||
cb();
|
||||
if (!err) {
|
||||
addTranslation(locale, result[locale]);
|
||||
setLocale(locale);
|
||||
utilDetect(true);
|
||||
}
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
if (locale) {
|
||||
setLocale(locale);
|
||||
utilDetect(true);
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -336,7 +335,7 @@ export function coreContext(root) {
|
||||
context.reset = context.flush = function() {
|
||||
context.debouncedSave.cancel();
|
||||
_.each(services, function(service) {
|
||||
if (typeof service.reset === 'function') {
|
||||
if (service && typeof service.reset === 'function') {
|
||||
service.reset(context);
|
||||
}
|
||||
});
|
||||
@@ -347,12 +346,12 @@ export function coreContext(root) {
|
||||
|
||||
|
||||
/* Init */
|
||||
context.version = '2.0.0-beta.1';
|
||||
context.version = '2.0.1';
|
||||
|
||||
context.projection = geoRawMercator();
|
||||
|
||||
locale = utilDetect().locale;
|
||||
if (locale && dataLocales.indexOf(locale) === -1) {
|
||||
if (locale && !dataLocales.hasOwnProperty(locale)) {
|
||||
locale = locale.split('-')[0];
|
||||
}
|
||||
|
||||
@@ -382,9 +381,9 @@ export function coreContext(root) {
|
||||
ui = uiInit(context);
|
||||
|
||||
connection = services.osm;
|
||||
|
||||
background = rendererBackground(context);
|
||||
features = rendererFeatures(context);
|
||||
presets = presetIndex();
|
||||
|
||||
map = rendererMap(context);
|
||||
context.mouse = map.mouse;
|
||||
@@ -396,14 +395,16 @@ export function coreContext(root) {
|
||||
context.zoomOutFurther = map.zoomOutFurther;
|
||||
context.redrawEnable = map.redrawEnable;
|
||||
|
||||
presets = presetInit();
|
||||
|
||||
_.each(services, function(service) {
|
||||
if (typeof service.init === 'function') {
|
||||
if (service && typeof service.init === 'function') {
|
||||
service.init(context);
|
||||
}
|
||||
});
|
||||
|
||||
background.init();
|
||||
presets.init();
|
||||
areaKeys = presets.areaKeys();
|
||||
|
||||
|
||||
return utilRebind(context, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ export function coreHistory(context) {
|
||||
annotation = actions.pop();
|
||||
}
|
||||
|
||||
stack[index].transform = context.projection.transform();
|
||||
stack[index].selectedIDs = context.selectedIDs();
|
||||
|
||||
var graph = stack[index].graph;
|
||||
for (var i = 0; i < actions.length; i++) {
|
||||
graph = actions[i](graph);
|
||||
@@ -129,7 +132,7 @@ export function coreHistory(context) {
|
||||
if (stack[index].annotation) break;
|
||||
}
|
||||
|
||||
dispatch.call('undone');
|
||||
dispatch.call('undone', this, stack[index]);
|
||||
return change(previous);
|
||||
},
|
||||
|
||||
@@ -142,7 +145,7 @@ export function coreHistory(context) {
|
||||
if (stack[index].annotation) break;
|
||||
}
|
||||
|
||||
dispatch.call('redone');
|
||||
dispatch.call('redone', this, stack[index]);
|
||||
return change(previous);
|
||||
},
|
||||
|
||||
|
||||
@@ -58,33 +58,37 @@ export function d3keybinding(namespace) {
|
||||
return keybinding;
|
||||
};
|
||||
|
||||
keybinding.on = function(code, callback, capture) {
|
||||
var binding = {
|
||||
event: {
|
||||
keyCode: 0,
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
metaKey: false
|
||||
},
|
||||
capture: capture,
|
||||
callback: callback
|
||||
};
|
||||
keybinding.on = function(codes, callback, capture) {
|
||||
var arr = [].concat(codes);
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var code = arr[i];
|
||||
var binding = {
|
||||
event: {
|
||||
keyCode: 0,
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
metaKey: false
|
||||
},
|
||||
capture: capture,
|
||||
callback: callback
|
||||
};
|
||||
|
||||
code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
|
||||
code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
|
||||
|
||||
for (var i = 0; i < code.length; i++) {
|
||||
// Normalise matching errors
|
||||
if (code[i] === '++') code[i] = '+';
|
||||
for (var j = 0; j < code.length; j++) {
|
||||
// Normalise matching errors
|
||||
if (code[j] === '++') code[i] = '+';
|
||||
|
||||
if (code[i] in d3keybinding.modifierCodes) {
|
||||
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[i]]]] = true;
|
||||
} else if (code[i] in d3keybinding.keyCodes) {
|
||||
binding.event.keyCode = d3keybinding.keyCodes[code[i]];
|
||||
if (code[j] in d3keybinding.modifierCodes) {
|
||||
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[j]]]] = true;
|
||||
} else if (code[j] in d3keybinding.keyCodes) {
|
||||
binding.event.keyCode = d3keybinding.keyCodes[code[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindings.push(binding);
|
||||
bindings.push(binding);
|
||||
}
|
||||
|
||||
return keybinding;
|
||||
};
|
||||
@@ -92,6 +96,7 @@ export function d3keybinding(namespace) {
|
||||
return keybinding;
|
||||
}
|
||||
|
||||
|
||||
d3keybinding.modifierCodes = {
|
||||
// Shift key, ⇧
|
||||
'⇧': 16, shift: 16,
|
||||
@@ -164,7 +169,8 @@ d3keybinding.keyCodes = {
|
||||
'=': 187, 'equals': 187,
|
||||
// Comma, or ,
|
||||
',': 188, comma: 188,
|
||||
'dash': 189, //???
|
||||
// Dash / Underscore key
|
||||
'dash': 189,
|
||||
// Period, or ., or full-stop
|
||||
'.': 190, period: 190, 'full-stop': 190,
|
||||
// Slash, or /, or forward-slash
|
||||
|
||||
+228
-11
@@ -29,7 +29,11 @@ import { modeBrowse } from './browse';
|
||||
import { modeDragNode } from './drag_node';
|
||||
import * as Operations from '../operations/index';
|
||||
import { uiRadialMenu, uiSelectionList } from '../ui/index';
|
||||
import { utilEntityOrMemberSelector } from '../util/index';
|
||||
import { uiCmd } from '../ui/cmd';
|
||||
import { utilEntityOrMemberSelector, utilEntitySelector } from '../util/index';
|
||||
|
||||
|
||||
var relatedParent;
|
||||
|
||||
|
||||
export function modeSelect(context, selectedIDs) {
|
||||
@@ -52,7 +56,9 @@ export function modeSelect(context, selectedIDs) {
|
||||
inspector,
|
||||
radialMenu,
|
||||
newFeature = false,
|
||||
suppressMenu = false;
|
||||
suppressMenu = false,
|
||||
follow = false;
|
||||
|
||||
|
||||
var wrap = context.container()
|
||||
.select('.inspector-wrap');
|
||||
@@ -65,6 +71,73 @@ export function modeSelect(context, selectedIDs) {
|
||||
}
|
||||
|
||||
|
||||
function checkSelectedIDs() {
|
||||
var ids = [];
|
||||
if (Array.isArray(selectedIDs)) {
|
||||
ids = selectedIDs.filter(function(id) {
|
||||
return context.hasEntity(id);
|
||||
});
|
||||
}
|
||||
|
||||
if (ids.length) {
|
||||
selectedIDs = ids;
|
||||
} else {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
return !!ids.length;
|
||||
}
|
||||
|
||||
|
||||
// find the common parent ways for nextVertex, previousVertex
|
||||
function commonParents() {
|
||||
var graph = context.graph(),
|
||||
commonParents = [];
|
||||
|
||||
for (var i = 0; i < selectedIDs.length; i++) {
|
||||
var entity = context.hasEntity(selectedIDs[i]);
|
||||
if (!entity || entity.geometry(graph) !== 'vertex') {
|
||||
return []; // selection includes some not vertexes
|
||||
}
|
||||
|
||||
var currParents = _.map(graph.parentWays(entity), 'id');
|
||||
if (!commonParents.length) {
|
||||
commonParents = currParents;
|
||||
continue;
|
||||
}
|
||||
|
||||
commonParents = _.intersection(commonParents, currParents);
|
||||
if (!commonParents.length) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return commonParents;
|
||||
}
|
||||
|
||||
|
||||
function singularParent() {
|
||||
var parents = commonParents();
|
||||
if (!parents) {
|
||||
relatedParent = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// relatedParent is used when we visit a vertex with multiple
|
||||
// parents, and we want to remember which parent line we started on.
|
||||
|
||||
if (parents.length === 1) {
|
||||
relatedParent = parents[0]; // remember this parent for later
|
||||
return relatedParent;
|
||||
}
|
||||
|
||||
if (parents.indexOf(relatedParent) !== -1) {
|
||||
return relatedParent; // prefer the previously seen parent
|
||||
}
|
||||
|
||||
return parents[0];
|
||||
}
|
||||
|
||||
|
||||
function closeMenu() {
|
||||
if (radialMenu) {
|
||||
context.surface().call(radialMenu.close);
|
||||
@@ -115,6 +188,8 @@ export function modeSelect(context, selectedIDs) {
|
||||
|
||||
|
||||
mode.reselect = function() {
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
var surfaceNode = context.surface().node();
|
||||
if (surfaceNode.focus) { // FF doesn't support it
|
||||
surfaceNode.focus();
|
||||
@@ -139,14 +214,18 @@ export function modeSelect(context, selectedIDs) {
|
||||
};
|
||||
|
||||
|
||||
mode.follow = function(_) {
|
||||
if (!arguments.length) return follow;
|
||||
follow = _;
|
||||
return mode;
|
||||
};
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
|
||||
function update() {
|
||||
closeMenu();
|
||||
if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) {
|
||||
// Exit mode if selected entity gets undone
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
checkSelectedIDs();
|
||||
}
|
||||
|
||||
|
||||
@@ -173,17 +252,33 @@ export function modeSelect(context, selectedIDs) {
|
||||
|
||||
|
||||
function selectElements(drawn) {
|
||||
var entity = singular();
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
var surface = context.surface(),
|
||||
entity = singular();
|
||||
|
||||
if (entity && context.geometry(entity.id) === 'relation') {
|
||||
suppressMenu = true;
|
||||
return;
|
||||
}
|
||||
|
||||
surface.selectAll('.related')
|
||||
.classed('related', false);
|
||||
|
||||
singularParent();
|
||||
if (relatedParent) {
|
||||
surface.selectAll(utilEntitySelector([relatedParent]))
|
||||
.classed('related', true);
|
||||
}
|
||||
|
||||
var selection = context.surface()
|
||||
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));
|
||||
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));
|
||||
|
||||
if (selection.empty()) {
|
||||
if (drawn) { // Exit mode if selected DOM elements have disappeared..
|
||||
// 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;
|
||||
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
} else {
|
||||
@@ -200,6 +295,103 @@ export function modeSelect(context, selectedIDs) {
|
||||
}
|
||||
|
||||
|
||||
function firstVertex() {
|
||||
d3.event.preventDefault();
|
||||
var parent = singularParent();
|
||||
if (parent) {
|
||||
var way = context.entity(parent);
|
||||
context.enter(
|
||||
modeSelect(context, [way.first()]).follow(true).suppressMenu(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function lastVertex() {
|
||||
d3.event.preventDefault();
|
||||
var parent = singularParent();
|
||||
if (parent) {
|
||||
var way = context.entity(parent);
|
||||
context.enter(
|
||||
modeSelect(context, [way.last()]).follow(true).suppressMenu(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function previousVertex() {
|
||||
d3.event.preventDefault();
|
||||
var parent = singularParent();
|
||||
if (!parent) return;
|
||||
|
||||
var way = context.entity(parent),
|
||||
length = way.nodes.length,
|
||||
curr = way.nodes.indexOf(selectedIDs[0]),
|
||||
index = -1;
|
||||
|
||||
if (curr > 0) {
|
||||
index = curr - 1;
|
||||
} else if (way.isClosed()) {
|
||||
index = length - 2;
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
context.enter(
|
||||
modeSelect(context, [way.nodes[index]]).follow(true).suppressMenu(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function nextVertex() {
|
||||
d3.event.preventDefault();
|
||||
var parent = singularParent();
|
||||
if (!parent) return;
|
||||
|
||||
var way = context.entity(parent),
|
||||
length = way.nodes.length,
|
||||
curr = way.nodes.indexOf(selectedIDs[0]),
|
||||
index = -1;
|
||||
|
||||
if (curr < length - 1) {
|
||||
index = curr + 1;
|
||||
} else if (way.isClosed()) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
context.enter(
|
||||
modeSelect(context, [way.nodes[index]]).follow(true).suppressMenu(true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function nextParent() {
|
||||
d3.event.preventDefault();
|
||||
var parents = _.uniq(commonParents());
|
||||
if (!parents || parents.length < 2) return;
|
||||
|
||||
var index = parents.indexOf(relatedParent);
|
||||
if (index < 0 || index > parents.length - 2) {
|
||||
relatedParent = parents[0];
|
||||
} else {
|
||||
relatedParent = parents[index + 1];
|
||||
}
|
||||
|
||||
var surface = context.surface();
|
||||
surface.selectAll('.related')
|
||||
.classed('related', false);
|
||||
|
||||
if (relatedParent) {
|
||||
surface.selectAll(utilEntitySelector([relatedParent]))
|
||||
.classed('related', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
@@ -211,6 +403,11 @@ export function modeSelect(context, selectedIDs) {
|
||||
operations.unshift(Operations.operationDelete(selectedIDs, context));
|
||||
|
||||
keybinding
|
||||
.on(['[','pgup'], previousVertex)
|
||||
.on([']', 'pgdown'], nextVertex)
|
||||
.on([uiCmd('⌘['), 'home'], firstVertex)
|
||||
.on([uiCmd('⌘]'), 'end'], lastVertex)
|
||||
.on(['\\', 'pause'], nextParent)
|
||||
.on('⎋', esc, true)
|
||||
.on('space', toggleMenu);
|
||||
|
||||
@@ -248,6 +445,18 @@ export function modeSelect(context, selectedIDs) {
|
||||
positionMenu();
|
||||
}
|
||||
|
||||
if (follow) {
|
||||
var extent = geoExtent(),
|
||||
graph = context.graph();
|
||||
selectedIDs.forEach(function(id) {
|
||||
var entity = context.entity(id);
|
||||
extent._extend(entity.extent(graph));
|
||||
});
|
||||
|
||||
var loc = extent.center();
|
||||
context.map().centerEase(loc);
|
||||
}
|
||||
|
||||
timeout = window.setTimeout(function() {
|
||||
if (show) {
|
||||
showMenu();
|
||||
@@ -281,11 +490,19 @@ export function modeSelect(context, selectedIDs) {
|
||||
.on('undone.select', null)
|
||||
.on('redone.select', null);
|
||||
|
||||
context.surface()
|
||||
.on('dblclick.select', null)
|
||||
var surface = context.surface();
|
||||
|
||||
surface
|
||||
.on('dblclick.select', null);
|
||||
|
||||
surface
|
||||
.selectAll('.selected')
|
||||
.classed('selected', false);
|
||||
|
||||
surface
|
||||
.selectAll('.related')
|
||||
.classed('related', false);
|
||||
|
||||
context.map().on('drawn.select', null);
|
||||
context.ui().sidebar.hide();
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ export function operationDelete(selectedIDs, context) {
|
||||
annotation = t('operations.delete.annotation.' + geometry);
|
||||
|
||||
// Select the next closest node in the way.
|
||||
if (geometry === 'vertex' && parents.length === 1 && parent.nodes.length > 2) {
|
||||
if (geometry === 'vertex' && parent.nodes.length > 2) {
|
||||
var nodes = parent.nodes,
|
||||
i = nodes.indexOf(id);
|
||||
|
||||
@@ -44,13 +44,16 @@ export function operationDelete(selectedIDs, context) {
|
||||
}
|
||||
}
|
||||
|
||||
context.perform(action, annotation);
|
||||
|
||||
if (nextSelectedID && context.hasEntity(nextSelectedID)) {
|
||||
context.enter(modeSelect(context, [nextSelectedID]));
|
||||
context.enter(
|
||||
modeSelect(context, [nextSelectedID]).follow(true).suppressMenu(true)
|
||||
);
|
||||
} else {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
context.perform(action, annotation);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ _.extend(osmRelation.prototype, {
|
||||
|
||||
for (o = 0; o < outers.length; o++) {
|
||||
outer = outers[o];
|
||||
if (geoPolygonIntersectsPolygon(outer, inner))
|
||||
if (geoPolygonIntersectsPolygon(outer, inner, false))
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
+177
-5
@@ -1,5 +1,177 @@
|
||||
export { presetCategory } from './category.js';
|
||||
export { presetCollection } from './collection.js';
|
||||
export { presetField } from './field.js';
|
||||
export { presetInit } from './init.js';
|
||||
export { presetPreset } from './preset.js';
|
||||
import _ from 'lodash';
|
||||
import { data } from '../../data/index';
|
||||
import { presetCategory } from './category';
|
||||
import { presetCollection } from './collection';
|
||||
import { presetField } from './field';
|
||||
import { presetPreset } from './preset';
|
||||
|
||||
export { presetCategory };
|
||||
export { presetCollection };
|
||||
export { presetField };
|
||||
export { presetPreset };
|
||||
|
||||
|
||||
export function presetIndex() {
|
||||
// a presetCollection with methods for
|
||||
// loading new data and returning defaults
|
||||
|
||||
var all = presetCollection([]),
|
||||
defaults = { area: all, line: all, point: all, vertex: all, relation: all },
|
||||
fields = {},
|
||||
universal = [],
|
||||
recent = presetCollection([]);
|
||||
|
||||
// Index of presets by (geometry, tag key).
|
||||
var index = {
|
||||
point: {},
|
||||
vertex: {},
|
||||
line: {},
|
||||
area: {},
|
||||
relation: {}
|
||||
};
|
||||
|
||||
all.match = function(entity, resolver) {
|
||||
var geometry = entity.geometry(resolver);
|
||||
|
||||
// Treat entities on addr:interpolation lines as points, not vertices (#3241)
|
||||
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
|
||||
geometry = 'point';
|
||||
}
|
||||
|
||||
var geometryMatches = index[geometry],
|
||||
best = -1,
|
||||
match;
|
||||
|
||||
for (var k in entity.tags) {
|
||||
var keyMatches = geometryMatches[k];
|
||||
if (!keyMatches) continue;
|
||||
|
||||
for (var i = 0; i < keyMatches.length; i++) {
|
||||
var score = keyMatches[i].matchScore(entity);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
match = keyMatches[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match || all.item(geometry);
|
||||
};
|
||||
|
||||
|
||||
// Because of the open nature of tagging, iD will never have a complete
|
||||
// list of tags used in OSM, so we want it to have logic like "assume
|
||||
// that a closed way with an amenity tag is an area, unless the amenity
|
||||
// is one of these specific types". This function computes a structure
|
||||
// that allows testing of such conditions, based on the presets designated
|
||||
// as as supporting (or not supporting) the area geometry.
|
||||
//
|
||||
// The returned object L is a whitelist/blacklist of tags. A closed way
|
||||
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
|
||||
// (see `Way#isArea()`). In other words, the keys of L form the whitelist,
|
||||
// and the subkeys form the blacklist.
|
||||
all.areaKeys = function() {
|
||||
var areaKeys = {},
|
||||
ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
|
||||
presets = _.reject(all.collection, 'suggestion');
|
||||
|
||||
// whitelist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
if (d.geometry.indexOf('area') !== -1) {
|
||||
areaKeys[key] = areaKeys[key] || {};
|
||||
}
|
||||
});
|
||||
|
||||
// blacklist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
var value = d.tags[key];
|
||||
if (d.geometry.indexOf('area') === -1 &&
|
||||
d.geometry.indexOf('line') !== -1 &&
|
||||
key in areaKeys && value !== '*') {
|
||||
areaKeys[key][value] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return areaKeys;
|
||||
};
|
||||
|
||||
|
||||
all.init = function() {
|
||||
var d = data.presets;
|
||||
|
||||
if (d.fields) {
|
||||
_.forEach(d.fields, function(d, id) {
|
||||
fields[id] = presetField(id, d);
|
||||
if (d.universal) universal.push(fields[id]);
|
||||
});
|
||||
}
|
||||
|
||||
if (d.presets) {
|
||||
_.forEach(d.presets, function(d, id) {
|
||||
all.collection.push(presetPreset(id, d, fields));
|
||||
});
|
||||
}
|
||||
|
||||
if (d.categories) {
|
||||
_.forEach(d.categories, function(d, id) {
|
||||
all.collection.push(presetCategory(id, d, all));
|
||||
});
|
||||
}
|
||||
|
||||
if (d.defaults) {
|
||||
var getItem = _.bind(all.item, all);
|
||||
defaults = {
|
||||
area: presetCollection(d.defaults.area.map(getItem)),
|
||||
line: presetCollection(d.defaults.line.map(getItem)),
|
||||
point: presetCollection(d.defaults.point.map(getItem)),
|
||||
vertex: presetCollection(d.defaults.vertex.map(getItem)),
|
||||
relation: presetCollection(d.defaults.relation.map(getItem))
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < all.collection.length; i++) {
|
||||
var preset = all.collection[i],
|
||||
geometry = preset.geometry;
|
||||
|
||||
for (var j = 0; j < geometry.length; j++) {
|
||||
var g = index[geometry[j]];
|
||||
for (var k in preset.tags) {
|
||||
(g[k] = g[k] || []).push(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
all.field = function(id) {
|
||||
return fields[id];
|
||||
};
|
||||
|
||||
all.universal = function() {
|
||||
return universal;
|
||||
};
|
||||
|
||||
all.defaults = function(geometry, n) {
|
||||
var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
|
||||
def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
|
||||
return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry))));
|
||||
};
|
||||
|
||||
all.choose = function(preset) {
|
||||
if (!preset.isFallback()) {
|
||||
recent = presetCollection(_.uniq([preset].concat(recent.collection)));
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { presetCategory } from './category';
|
||||
import { presetCollection } from './collection';
|
||||
import { presetField } from './field';
|
||||
import { presetPreset } from './preset';
|
||||
|
||||
|
||||
export function presetInit() {
|
||||
// a presetCollection with methods for
|
||||
// loading new data and returning defaults
|
||||
|
||||
var all = presetCollection([]),
|
||||
defaults = { area: all, line: all, point: all, vertex: all, relation: all },
|
||||
fields = {},
|
||||
universal = [],
|
||||
recent = presetCollection([]);
|
||||
|
||||
// Index of presets by (geometry, tag key).
|
||||
var index = {
|
||||
point: {},
|
||||
vertex: {},
|
||||
line: {},
|
||||
area: {},
|
||||
relation: {}
|
||||
};
|
||||
|
||||
all.match = function(entity, resolver) {
|
||||
var geometry = entity.geometry(resolver);
|
||||
|
||||
// Treat entities on addr:interpolation lines as points, not vertices (#3241)
|
||||
if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
|
||||
geometry = 'point';
|
||||
}
|
||||
|
||||
var geometryMatches = index[geometry],
|
||||
best = -1,
|
||||
match;
|
||||
|
||||
for (var k in entity.tags) {
|
||||
var keyMatches = geometryMatches[k];
|
||||
if (!keyMatches) continue;
|
||||
|
||||
for (var i = 0; i < keyMatches.length; i++) {
|
||||
var score = keyMatches[i].matchScore(entity);
|
||||
if (score > best) {
|
||||
best = score;
|
||||
match = keyMatches[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match || all.item(geometry);
|
||||
};
|
||||
|
||||
// Because of the open nature of tagging, iD will never have a complete
|
||||
// list of tags used in OSM, so we want it to have logic like "assume
|
||||
// that a closed way with an amenity tag is an area, unless the amenity
|
||||
// is one of these specific types". This function computes a structure
|
||||
// that allows testing of such conditions, based on the presets designated
|
||||
// as as supporting (or not supporting) the area geometry.
|
||||
//
|
||||
// The returned object L is a whitelist/blacklist of tags. A closed way
|
||||
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
|
||||
// (see `Way#isArea()`). In other words, the keys of L form the whitelist,
|
||||
// and the subkeys form the blacklist.
|
||||
all.areaKeys = function() {
|
||||
var areaKeys = {},
|
||||
ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
|
||||
presets = _.reject(all.collection, 'suggestion');
|
||||
|
||||
// whitelist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
if (d.geometry.indexOf('area') !== -1) {
|
||||
areaKeys[key] = areaKeys[key] || {};
|
||||
}
|
||||
});
|
||||
|
||||
// blacklist
|
||||
presets.forEach(function(d) {
|
||||
for (var key in d.tags) break;
|
||||
if (!key) return;
|
||||
if (ignore.indexOf(key) !== -1) return;
|
||||
|
||||
var value = d.tags[key];
|
||||
if (d.geometry.indexOf('area') === -1 &&
|
||||
d.geometry.indexOf('line') !== -1 &&
|
||||
key in areaKeys && value !== '*') {
|
||||
areaKeys[key][value] = true;
|
||||
}
|
||||
});
|
||||
|
||||
return areaKeys;
|
||||
};
|
||||
|
||||
all.load = function(d) {
|
||||
|
||||
if (d.fields) {
|
||||
_.forEach(d.fields, function(d, id) {
|
||||
fields[id] = presetField(id, d);
|
||||
if (d.universal) universal.push(fields[id]);
|
||||
});
|
||||
}
|
||||
|
||||
if (d.presets) {
|
||||
_.forEach(d.presets, function(d, id) {
|
||||
all.collection.push(presetPreset(id, d, fields));
|
||||
});
|
||||
}
|
||||
|
||||
if (d.categories) {
|
||||
_.forEach(d.categories, function(d, id) {
|
||||
all.collection.push(presetCategory(id, d, all));
|
||||
});
|
||||
}
|
||||
|
||||
if (d.defaults) {
|
||||
var getItem = _.bind(all.item, all);
|
||||
defaults = {
|
||||
area: presetCollection(d.defaults.area.map(getItem)),
|
||||
line: presetCollection(d.defaults.line.map(getItem)),
|
||||
point: presetCollection(d.defaults.point.map(getItem)),
|
||||
vertex: presetCollection(d.defaults.vertex.map(getItem)),
|
||||
relation: presetCollection(d.defaults.relation.map(getItem))
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < all.collection.length; i++) {
|
||||
var preset = all.collection[i],
|
||||
geometry = preset.geometry;
|
||||
|
||||
for (var j = 0; j < geometry.length; j++) {
|
||||
var g = index[geometry[j]];
|
||||
for (var k in preset.tags) {
|
||||
(g[k] = g[k] || []).push(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
all.field = function(id) {
|
||||
return fields[id];
|
||||
};
|
||||
|
||||
all.universal = function() {
|
||||
return universal;
|
||||
};
|
||||
|
||||
all.defaults = function(geometry, n) {
|
||||
var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
|
||||
def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
|
||||
return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry))));
|
||||
};
|
||||
|
||||
all.choose = function(preset) {
|
||||
if (!preset.isFallback()) {
|
||||
recent = presetCollection(_.uniq([preset].concat(recent.collection)));
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
return all;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import { data } from '../../data/index';
|
||||
import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo/index';
|
||||
import { utilQsString, utilStringQs } from '../util/index';
|
||||
import { rendererBackgroundSource } from './background_source';
|
||||
import { rendererTileLayer } from './tile_layer';
|
||||
import { utilQsString, utilStringQs } from '../util/index';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
export function rendererBackground(context) {
|
||||
@@ -26,9 +27,9 @@ export function rendererBackground(context) {
|
||||
.data([0]);
|
||||
|
||||
base.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-background')
|
||||
.merge(base)
|
||||
.merge(base)
|
||||
.call(baseLayer);
|
||||
|
||||
var overlays = selection.selectAll('.layer-overlay')
|
||||
@@ -38,16 +39,19 @@ export function rendererBackground(context) {
|
||||
.remove();
|
||||
|
||||
overlays.enter()
|
||||
.insert('div', '.layer-data')
|
||||
.insert('div', '.layer-data')
|
||||
.attr('class', 'layer layer-overlay')
|
||||
.merge(overlays)
|
||||
.merge(overlays)
|
||||
.each(function(layer) { d3.select(this).call(layer); });
|
||||
}
|
||||
|
||||
|
||||
background.updateImagery = function() {
|
||||
var b = background.baseLayerSource(),
|
||||
o = overlayLayers.map(function (d) { return d.source().id; }).join(','),
|
||||
o = overlayLayers
|
||||
.filter(function (d) { return !d.source().isLocatorOverlay(); })
|
||||
.map(function (d) { return d.source().id; })
|
||||
.join(','),
|
||||
meters = geoOffsetToMeters(b.offset()),
|
||||
epsilon = 0.01,
|
||||
x = +meters[0].toFixed(2),
|
||||
@@ -81,12 +85,9 @@ export function rendererBackground(context) {
|
||||
|
||||
var imageryUsed = [b.imageryUsed()];
|
||||
|
||||
overlayLayers.forEach(function (d) {
|
||||
var source = d.source();
|
||||
if (!source.isLocatorOverlay()) {
|
||||
imageryUsed.push(source.imageryUsed());
|
||||
}
|
||||
});
|
||||
overlayLayers
|
||||
.filter(function (d) { return !d.source().isLocatorOverlay(); })
|
||||
.forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); });
|
||||
|
||||
var gpx = context.layers().layer('gpx');
|
||||
if (gpx && gpx.enabled() && gpx.hasGpx()) {
|
||||
@@ -126,7 +127,32 @@ export function rendererBackground(context) {
|
||||
|
||||
background.baseLayerSource = function(d) {
|
||||
if (!arguments.length) return baseLayer.source();
|
||||
baseLayer.source(d);
|
||||
|
||||
// test source against OSM imagery blacklists..
|
||||
var blacklists = context.connection().imageryBlacklists();
|
||||
|
||||
var fail = false,
|
||||
tested = 0,
|
||||
regex, i;
|
||||
|
||||
for (i = 0; i < blacklists; i++) {
|
||||
try {
|
||||
regex = new RegExp(blacklists[i]);
|
||||
fail = regex.test(d.template);
|
||||
tested++;
|
||||
if (fail) break;
|
||||
} catch (e) {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ensure at least one test was run.
|
||||
if (!tested) {
|
||||
regex = new RegExp('.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*');
|
||||
fail = regex.test(d.template);
|
||||
}
|
||||
|
||||
baseLayer.source(!fail ? d : rendererBackgroundSource.None());
|
||||
dispatch.call('change');
|
||||
background.updateImagery();
|
||||
return background;
|
||||
@@ -139,9 +165,8 @@ export function rendererBackground(context) {
|
||||
|
||||
|
||||
background.showsLayer = function(d) {
|
||||
return d === baseLayer.source() ||
|
||||
(d.id === 'custom' && baseLayer.source().id === 'custom') ||
|
||||
overlayLayers.some(function(l) { return l.source() === d; });
|
||||
return d.id === baseLayer.source().id ||
|
||||
overlayLayers.some(function(layer) { return d.id === layer.source().id; });
|
||||
};
|
||||
|
||||
|
||||
@@ -191,20 +216,21 @@ export function rendererBackground(context) {
|
||||
};
|
||||
|
||||
|
||||
background.load = function(imagery) {
|
||||
background.init = function() {
|
||||
function parseMap(qmap) {
|
||||
if (!qmap) return false;
|
||||
var args = qmap.split('/').map(Number);
|
||||
if (args.length < 3 || args.some(isNaN)) return false;
|
||||
return geoExtent([args[1], args[2]]);
|
||||
return geoExtent([args[2], args[1]]);
|
||||
}
|
||||
|
||||
var q = utilStringQs(window.location.hash.substring(1)),
|
||||
var dataImagery = data.imagery || [],
|
||||
q = utilStringQs(window.location.hash.substring(1)),
|
||||
chosen = q.background || q.layer,
|
||||
extent = parseMap(q.map),
|
||||
best;
|
||||
|
||||
backgroundSources = imagery.map(function(source) {
|
||||
backgroundSources = dataImagery.map(function(source) {
|
||||
if (source.type === 'bing') {
|
||||
return rendererBackgroundSource.Bing(source, dispatch);
|
||||
} else {
|
||||
|
||||
@@ -47,7 +47,7 @@ export function rendererBackgroundSource(data) {
|
||||
|
||||
|
||||
source.imageryUsed = function() {
|
||||
return source.id || name;
|
||||
return name || source.id;
|
||||
};
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export function rendererBackgroundSource(data) {
|
||||
|
||||
|
||||
source.isLocatorOverlay = function() {
|
||||
return name === 'Locator Overlay';
|
||||
return source.id === 'mapbox_locator_overlay';
|
||||
};
|
||||
|
||||
|
||||
|
||||
+96
-11
@@ -17,6 +17,7 @@ import {
|
||||
} from '../svg/index';
|
||||
|
||||
import { geoExtent } from '../geo/index';
|
||||
import { modeSelect } from '../modes/select';
|
||||
|
||||
import {
|
||||
utilFastMouse,
|
||||
@@ -44,16 +45,17 @@ export function rendererMap(context) {
|
||||
drawAreas = svgAreas(projection, context),
|
||||
drawMidpoints = svgMidpoints(projection, context),
|
||||
drawLabels = svgLabels(projection, context),
|
||||
supersurface,
|
||||
wrapper,
|
||||
surface,
|
||||
supersurface = d3.select(null),
|
||||
wrapper = d3.select(null),
|
||||
surface = d3.select(null),
|
||||
mouse,
|
||||
mousemove;
|
||||
|
||||
var zoom = d3.zoom()
|
||||
.scaleExtent([ztok(2), ztok(24)]) // TODO: uncomment interpolate when d3.zoom 1.0.4 avail:
|
||||
// .interpolate(d3.interpolate) // https://github.com/d3/d3-zoom/issues/54
|
||||
.on('zoom', zoomPan); // default zoom interpolator does a fly-out-in
|
||||
.scaleExtent([ztok(2), ztok(24)])
|
||||
.interpolate(d3.interpolate)
|
||||
.filter(zoomEventFilter)
|
||||
.on('zoom', zoomPan);
|
||||
|
||||
var _selection = d3.select(null);
|
||||
|
||||
@@ -64,12 +66,31 @@ export function rendererMap(context) {
|
||||
|
||||
context
|
||||
.on('change.map', immediateRedraw);
|
||||
context.history()
|
||||
|
||||
context.connection()
|
||||
.on('change.map', immediateRedraw);
|
||||
|
||||
context.history()
|
||||
.on('change.map', immediateRedraw)
|
||||
.on('undone.context redone.context', function(stack) {
|
||||
var followSelected = false;
|
||||
if (Array.isArray(stack.selectedIDs)) {
|
||||
followSelected = (stack.selectedIDs.length === 1 && stack.selectedIDs[0][0] === 'n');
|
||||
context.enter(
|
||||
modeSelect(context, stack.selectedIDs).suppressMenu(true).follow(followSelected)
|
||||
);
|
||||
}
|
||||
if (!followSelected && stack.transform) {
|
||||
map.transformEase(stack.transform);
|
||||
}
|
||||
});
|
||||
|
||||
context.background()
|
||||
.on('change.map', immediateRedraw);
|
||||
|
||||
context.features()
|
||||
.on('redraw.map', immediateRedraw);
|
||||
|
||||
drawLayers
|
||||
.on('change.map', function() {
|
||||
context.background().updateImagery();
|
||||
@@ -143,7 +164,40 @@ export function rendererMap(context) {
|
||||
});
|
||||
|
||||
map.dimensions(utilGetDimensions(selection));
|
||||
}
|
||||
|
||||
|
||||
function zoomEventFilter() {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -264,7 +318,7 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
function redraw(difference, extent) {
|
||||
if (!surface || !redrawEnabled) return;
|
||||
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
|
||||
@@ -324,9 +378,13 @@ export function rendererMap(context) {
|
||||
|
||||
|
||||
map.mouse = function() {
|
||||
var e = mousemove || d3.event, s;
|
||||
while ((s = e.sourceEvent)) e = s;
|
||||
return mouse(e);
|
||||
var event = mousemove || d3.event;
|
||||
if (event) {
|
||||
var s;
|
||||
while ((s = event.sourceEvent)) { event = s; }
|
||||
return mouse(event);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -349,6 +407,26 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
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(zoom.transform, d3.zoomIdentity.translate(t2.x, t2.y).scale(t2.k));
|
||||
} else {
|
||||
projection.transform(t2);
|
||||
transformStart = t2;
|
||||
_selection.call(zoom.transform, transformStart);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setZoom(z2, force, duration) {
|
||||
if (z2 === map.zoom() && !force) {
|
||||
return false;
|
||||
@@ -542,6 +620,13 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.transformEase = function(t2, duration) {
|
||||
duration = duration || 250;
|
||||
setTransform(t2, duration, false);
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
map.startEase = function() {
|
||||
utilBindOnce(surface, 'mousedown.ease', function() {
|
||||
map.cancelEase();
|
||||
|
||||
@@ -16,7 +16,6 @@ var apibase = 'https://a.mapillary.com/v2/',
|
||||
trafficocss = 'traffico/stylesheets/traffico.css',
|
||||
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
|
||||
maxResults = 1000,
|
||||
maxPages = 10,
|
||||
tileZoom = 14,
|
||||
dispatch = d3.dispatch('loadedImages', 'loadedSigns'),
|
||||
mapillaryCache,
|
||||
@@ -43,6 +42,16 @@ function nearNullIsland(x, y, z) {
|
||||
}
|
||||
|
||||
|
||||
function maxPageAtZoom(z) {
|
||||
if (z < 15) return 2;
|
||||
if (z === 15) return 5;
|
||||
if (z === 16) return 10;
|
||||
if (z === 17) return 20;
|
||||
if (z === 18) return 40;
|
||||
if (z > 18) return 80;
|
||||
}
|
||||
|
||||
|
||||
function getTiles(projection) {
|
||||
var s = projection.scale() * 2 * Math.PI,
|
||||
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
|
||||
@@ -62,6 +71,7 @@ function getTiles(projection) {
|
||||
|
||||
return {
|
||||
id: tile.toString(),
|
||||
xyz: tile,
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
@@ -72,9 +82,11 @@ function getTiles(projection) {
|
||||
|
||||
|
||||
function loadTiles(which, url, projection) {
|
||||
var s = projection.scale() * 2 * Math.PI,
|
||||
currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
|
||||
|
||||
var tiles = getTiles(projection).filter(function(t) {
|
||||
var xyz = t.id.split(',');
|
||||
return !nearNullIsland(xyz[0], xyz[1], xyz[2]);
|
||||
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
|
||||
});
|
||||
|
||||
_.filter(which.inflight, function(v, k) {
|
||||
@@ -84,23 +96,27 @@ function loadTiles(which, url, projection) {
|
||||
}).map(abortRequest);
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
loadTilePage(which, url, tile, 0);
|
||||
loadNextTilePage(which, currZoom, url, tile);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadTilePage(which, url, tile, page) {
|
||||
function loadNextTilePage(which, currZoom, url, tile) {
|
||||
var cache = mapillaryCache[which],
|
||||
id = tile.id + ',' + String(page),
|
||||
rect = tile.extent.rectangle();
|
||||
rect = tile.extent.rectangle(),
|
||||
maxPages = maxPageAtZoom(currZoom),
|
||||
nextPage = cache.nextPage[tile.id] || 0;
|
||||
|
||||
if (nextPage > maxPages) return;
|
||||
|
||||
var id = tile.id + ',' + String(nextPage);
|
||||
if (cache.loaded[id] || cache.inflight[id]) return;
|
||||
|
||||
cache.inflight[id] = d3.json(url +
|
||||
utilQsString({
|
||||
geojson: 'true',
|
||||
limit: maxResults,
|
||||
page: page,
|
||||
page: nextPage,
|
||||
client_id: clientId,
|
||||
min_lon: rect[0],
|
||||
min_lat: rect[1],
|
||||
@@ -112,7 +128,6 @@ function loadTilePage(which, url, tile, page) {
|
||||
if (err || !data.features || !data.features.length) return;
|
||||
|
||||
var features = [],
|
||||
nextPage = page + 1,
|
||||
feature, loc, d;
|
||||
|
||||
for (var i = 0; i < data.features.length; i++) {
|
||||
@@ -130,8 +145,11 @@ function loadTilePage(which, url, tile, page) {
|
||||
if (which === 'images') dispatch.call('loadedImages');
|
||||
if (which === 'signs') dispatch.call('loadedSigns');
|
||||
|
||||
if (data.features.length === maxResults && nextPage < maxPages) {
|
||||
loadTilePage(which, url, tile, nextPage);
|
||||
if (data.features.length === maxResults) { // more pages to load
|
||||
cache.nextPage[tile.id] = nextPage + 1;
|
||||
loadNextTilePage(which, currZoom, url, tile);
|
||||
} else {
|
||||
cache.nextPage[tile.id] = Infinity; // no more pages to load
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -196,8 +214,8 @@ export default {
|
||||
}
|
||||
|
||||
mapillaryCache = {
|
||||
images: { inflight: {}, loaded: {}, rtree: rbush() },
|
||||
signs: { inflight: {}, loaded: {}, rtree: rbush() }
|
||||
images: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() },
|
||||
signs: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() }
|
||||
};
|
||||
|
||||
mapillaryImage = null;
|
||||
|
||||
+84
-24
@@ -9,10 +9,11 @@ import { utilDetect } from '../util/detect';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading', 'loaded'),
|
||||
var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded'),
|
||||
useHttps = window.location.protocol === 'https:',
|
||||
protocol = useHttps ? 'https:' : 'http:',
|
||||
urlroot = protocol + '//www.openstreetmap.org',
|
||||
blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'],
|
||||
inflight = {},
|
||||
loadedTiles = {},
|
||||
tileZoom = 16,
|
||||
@@ -20,25 +21,28 @@ var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading',
|
||||
url: urlroot,
|
||||
oauth_consumer_key: '5A043yRSEugj4DJ5TljuapfnrflWDte8jTOcWLlT',
|
||||
oauth_secret: 'aB3jKq1TRsCOUrfOIZ6oQMEDmv2ptV76PA54NGLL',
|
||||
loading: authenticating,
|
||||
done: authenticated
|
||||
loading: authLoading,
|
||||
done: authDone
|
||||
}),
|
||||
rateLimitError,
|
||||
userDetails,
|
||||
off;
|
||||
|
||||
|
||||
function authenticating() {
|
||||
dispatch.call('authenticating');
|
||||
function authLoading() {
|
||||
dispatch.call('authLoading');
|
||||
}
|
||||
|
||||
|
||||
function authenticated() {
|
||||
dispatch.call('authenticated');
|
||||
function authDone() {
|
||||
dispatch.call('authDone');
|
||||
}
|
||||
|
||||
|
||||
function abortRequest(i) {
|
||||
i.abort();
|
||||
if (i) {
|
||||
i.abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,10 +133,10 @@ var parsers = {
|
||||
};
|
||||
|
||||
|
||||
function parse(dom) {
|
||||
if (!dom || !dom.childNodes) return;
|
||||
function parse(xml) {
|
||||
if (!xml || !xml.childNodes) return;
|
||||
|
||||
var root = dom.childNodes[0],
|
||||
var root = xml.childNodes[0],
|
||||
children = root.childNodes,
|
||||
entities = [];
|
||||
|
||||
@@ -151,12 +155,13 @@ function parse(dom) {
|
||||
export default {
|
||||
|
||||
init: function() {
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
|
||||
reset: function() {
|
||||
userDetails = undefined;
|
||||
rateLimitError = undefined;
|
||||
_.forEach(inflight, abortRequest);
|
||||
loadedTiles = {};
|
||||
inflight = {};
|
||||
@@ -189,9 +194,34 @@ export default {
|
||||
|
||||
|
||||
loadFromAPI: function(path, callback) {
|
||||
function done(err, dom) {
|
||||
return callback(err, parse(dom));
|
||||
var that = this;
|
||||
|
||||
function done(err, xml) {
|
||||
var isAuthenticated = that.authenticated();
|
||||
|
||||
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
|
||||
// Logout and retry the request..
|
||||
if (isAuthenticated && err &&
|
||||
(err.status === 400 || err.status === 401 || err.status === 403)) {
|
||||
that.logout();
|
||||
that.loadFromAPI(path, callback);
|
||||
|
||||
// else, no retry..
|
||||
} else {
|
||||
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
|
||||
// Set the rateLimitError flag and trigger a warning..
|
||||
if (!isAuthenticated && !rateLimitError && err &&
|
||||
(err.status === 509 || err.status === 429)) {
|
||||
rateLimitError = err;
|
||||
dispatch.call('change');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(err, parse(xml));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.authenticated()) {
|
||||
return oauth.xhr({ method: 'GET', path: path }, done);
|
||||
} else {
|
||||
@@ -401,16 +431,42 @@ export default {
|
||||
|
||||
|
||||
status: function(callback) {
|
||||
function done(capabilities) {
|
||||
var apiStatus = capabilities.getElementsByTagName('status');
|
||||
callback(undefined, apiStatus[0].getAttribute('api'));
|
||||
function done(xml) {
|
||||
// update blacklists
|
||||
var elements = xml.getElementsByTagName('blacklist'),
|
||||
regexes = [];
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var regex = elements[i].getAttribute('regex'); // needs unencode?
|
||||
if (regex) {
|
||||
regexes.push(regex);
|
||||
}
|
||||
}
|
||||
if (regexes.length) {
|
||||
blacklists = regexes;
|
||||
}
|
||||
|
||||
|
||||
if (rateLimitError) {
|
||||
callback(rateLimitError, 'rateLimited');
|
||||
} else {
|
||||
var apiStatus = xml.getElementsByTagName('status'),
|
||||
val = apiStatus[0].getAttribute('api');
|
||||
|
||||
callback(undefined, val);
|
||||
}
|
||||
}
|
||||
|
||||
d3.xml(urlroot + '/api/capabilities').get()
|
||||
.on('load', done)
|
||||
.on('error', callback);
|
||||
},
|
||||
|
||||
|
||||
imageryBlacklists: function() {
|
||||
return blacklists;
|
||||
},
|
||||
|
||||
|
||||
tileZoom: function(_) {
|
||||
if (!arguments.length) return tileZoom;
|
||||
tileZoom = _;
|
||||
@@ -467,8 +523,10 @@ export default {
|
||||
inflight[id] = that.loadFromAPI(
|
||||
'/api/0.6/map?bbox=' + tile.extent.toParam(),
|
||||
function(err, parsed) {
|
||||
loadedTiles[id] = true;
|
||||
delete inflight[id];
|
||||
if (!err) {
|
||||
loadedTiles[id] = true;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(err, _.extend({ data: parsed }, tile));
|
||||
@@ -477,7 +535,8 @@ export default {
|
||||
if (_.isEmpty(inflight)) {
|
||||
dispatch.call('loaded');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -487,10 +546,10 @@ export default {
|
||||
|
||||
oauth.options(_.extend({
|
||||
url: urlroot,
|
||||
loading: authenticating,
|
||||
done: authenticated
|
||||
loading: authLoading,
|
||||
done: authDone
|
||||
}, options));
|
||||
dispatch.call('auth');
|
||||
dispatch.call('change');
|
||||
this.reset();
|
||||
return this;
|
||||
},
|
||||
@@ -512,7 +571,7 @@ export default {
|
||||
logout: function() {
|
||||
userDetails = undefined;
|
||||
oauth.logout();
|
||||
dispatch.call('auth');
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -520,7 +579,8 @@ export default {
|
||||
authenticate: function(callback) {
|
||||
userDetails = undefined;
|
||||
function done(err, res) {
|
||||
dispatch.call('auth');
|
||||
rateLimitError = undefined;
|
||||
dispatch.call('change');
|
||||
if (callback) callback(err, res);
|
||||
}
|
||||
return oauth.authenticate(done);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as d3 from 'd3';
|
||||
import { geoPolygonIntersectsPolygon } from '../geo/index';
|
||||
import {
|
||||
data,
|
||||
dataImperial,
|
||||
dataDriveLeft,
|
||||
dataImagery
|
||||
dataDriveLeft
|
||||
} from '../../data/index';
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ export function svgDebug(projection, context) {
|
||||
|
||||
|
||||
var extent = context.map().extent(),
|
||||
dataImagery = data.imagery || [],
|
||||
availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) {
|
||||
if (!source.polygon) return false;
|
||||
return source.polygon.some(function(polygon) {
|
||||
|
||||
+62
-27
@@ -244,9 +244,9 @@ export function svgLabels(projection, context) {
|
||||
|
||||
|
||||
function drawLabels(selection, graph, entities, filter, dimensions, fullRedraw) {
|
||||
var hidePoints = !selection.selectAll('.node.point').node();
|
||||
var lowZoom = context.surface().classed('low-zoom');
|
||||
|
||||
var labelable = [], i, j, k, entity;
|
||||
var labelable = [], i, j, k, entity, geometry;
|
||||
for (i = 0; i < labelStack.length; i++) {
|
||||
labelable.push([]);
|
||||
}
|
||||
@@ -272,12 +272,8 @@ export function svgLabels(projection, context) {
|
||||
// Split entities into groups specified by labelStack
|
||||
for (i = 0; i < entities.length; i++) {
|
||||
entity = entities[i];
|
||||
var geometry = entity.geometry(graph);
|
||||
|
||||
if (geometry === 'vertex')
|
||||
continue;
|
||||
if (hidePoints && geometry === 'point')
|
||||
continue;
|
||||
geometry = entity.geometry(graph);
|
||||
if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point
|
||||
|
||||
var preset = geometry === 'area' && context.presets().match(entity, graph),
|
||||
icon = preset && !blacklisted(preset) && preset.icon;
|
||||
@@ -315,29 +311,37 @@ export function svgLabels(projection, context) {
|
||||
var fontSize = labelStack[k][3];
|
||||
for (i = 0; i < labelable[k].length; i++) {
|
||||
entity = labelable[k][i];
|
||||
geometry = entity.geometry(graph);
|
||||
|
||||
var name = utilDisplayName(entity),
|
||||
width = name && textWidth(name, fontSize),
|
||||
p;
|
||||
if (entity.geometry(graph) === 'point') {
|
||||
p = getPointLabel(entity, width, fontSize);
|
||||
} else if (entity.geometry(graph) === 'line') {
|
||||
if (geometry === 'point') {
|
||||
p = getPointLabel(entity, width, fontSize, geometry);
|
||||
} else if (geometry === 'vertex' && !lowZoom) {
|
||||
// don't label vertices at low zoom because they don't have icons
|
||||
p = getPointLabel(entity, width, fontSize, geometry);
|
||||
} else if (geometry === 'line') {
|
||||
p = getLineLabel(entity, width, fontSize);
|
||||
} else if (entity.geometry(graph) === 'area') {
|
||||
} else if (geometry === 'area') {
|
||||
p = getAreaLabel(entity, width, fontSize);
|
||||
}
|
||||
|
||||
if (p) {
|
||||
p.classes = entity.geometry(graph) + ' tag-' + labelStack[k][1];
|
||||
positions[entity.geometry(graph)].push(p);
|
||||
labelled[entity.geometry(graph)].push(entity);
|
||||
if (geometry === 'vertex') { geometry = 'point'; } // treat vertex like point
|
||||
p.classes = geometry + ' tag-' + labelStack[k][1];
|
||||
positions[geometry].push(p);
|
||||
labelled[geometry].push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getPointLabel(entity, width, height) {
|
||||
var pointOffsets = {
|
||||
ltr: [15, -12, 'start'],
|
||||
rtl: [-15, -12, 'end']
|
||||
function getPointLabel(entity, width, height, geometry) {
|
||||
var y = (geometry === 'point' ? -12 : 0),
|
||||
pointOffsets = {
|
||||
ltr: [15, y, 'start'],
|
||||
rtl: [-15, y, 'end']
|
||||
};
|
||||
|
||||
var coord = projection(entity.loc),
|
||||
@@ -584,35 +588,66 @@ export function svgLabels(projection, context) {
|
||||
// debug
|
||||
drawCollisionBoxes(label, rskipped, 'debug-skipped');
|
||||
drawCollisionBoxes(label, rdrawn, 'debug-drawn');
|
||||
|
||||
selection.call(filterLabels);
|
||||
}
|
||||
|
||||
|
||||
function hideOnMouseover() {
|
||||
if (d3.event.buttons) return;
|
||||
|
||||
var layers = d3.select(this)
|
||||
function filterLabels(selection) {
|
||||
var layers = selection
|
||||
.selectAll('.layer-label, .layer-halo');
|
||||
|
||||
layers.selectAll('.proximate')
|
||||
.classed('proximate', false);
|
||||
|
||||
var mouse = context.mouse(),
|
||||
pad = 20,
|
||||
bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad },
|
||||
ids = _.map(rdrawn.search(bbox), 'id');
|
||||
graph = context.graph(),
|
||||
selectedIDs = context.selectedIDs(),
|
||||
ids = [],
|
||||
pad, bbox;
|
||||
|
||||
// hide labels near the mouse
|
||||
if (mouse) {
|
||||
pad = 20;
|
||||
bbox = { minX: mouse[0] - pad, minY: mouse[1] - pad, maxX: mouse[0] + pad, maxY: mouse[1] + pad };
|
||||
ids.push.apply(ids, _.map(rdrawn.search(bbox), 'id'));
|
||||
}
|
||||
|
||||
// hide labels along selected ways, or near selected vertices
|
||||
for (var i = 0; i < selectedIDs.length; i++) {
|
||||
var entity = graph.hasEntity(selectedIDs[i]);
|
||||
if (!entity) continue;
|
||||
var geometry = entity.geometry(graph);
|
||||
|
||||
if (geometry === 'line') {
|
||||
ids.push(selectedIDs[i]);
|
||||
} else if (geometry === 'vertex') {
|
||||
var point = context.projection(entity.loc);
|
||||
pad = 10;
|
||||
bbox = { minX: point[0] - pad, minY: point[1] - pad, maxX: point[0] + pad, maxY: point[1] + pad };
|
||||
ids.push.apply(ids, _.map(rdrawn.search(bbox), 'id'));
|
||||
}
|
||||
}
|
||||
|
||||
layers.selectAll(utilEntitySelector(ids))
|
||||
.classed('proximate', true);
|
||||
}
|
||||
|
||||
|
||||
var throttleFilterLabels = _.throttle(filterLabels, 100);
|
||||
|
||||
|
||||
drawLabels.observe = function(selection) {
|
||||
selection.on('mousemove.hidelabels', hideOnMouseover);
|
||||
var listener = function() { throttleFilterLabels(selection); };
|
||||
selection.on('mousemove.hidelabels', listener);
|
||||
context.on('enter.hidelabels', listener);
|
||||
};
|
||||
|
||||
|
||||
drawLabels.off = function(selection) {
|
||||
throttleFilterLabels.cancel();
|
||||
selection.on('mousemove.hidelabels', null);
|
||||
context.on('enter.hidelabels', null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { services } from '../services/index';
|
||||
export function svgMapillaryImages(projection, context, dispatch) {
|
||||
var throttledRedraw = _.throttle(function () { dispatch.call('change'); }, 1000),
|
||||
minZoom = 12,
|
||||
minViewfieldZoom = 16,
|
||||
minViewfieldZoom = 17,
|
||||
layer = d3.select(null),
|
||||
_mapillary;
|
||||
|
||||
|
||||
@@ -67,7 +67,9 @@ export function uiAccount(context) {
|
||||
.attr('id', 'userLink')
|
||||
.classed('hide', true);
|
||||
|
||||
connection.event.on('auth.account', function() { update(selection); });
|
||||
connection
|
||||
.on('change.account', function() { update(selection); });
|
||||
|
||||
update(selection);
|
||||
};
|
||||
}
|
||||
|
||||
+93
-50
@@ -58,21 +58,23 @@ export function uiBackground(context) {
|
||||
|
||||
|
||||
function setTooltips(selection) {
|
||||
selection.each(function(d) {
|
||||
var item = d3.select(this);
|
||||
selection.each(function(d, i, nodes) {
|
||||
var item = d3.select(this).select('label'),
|
||||
placement = (i < nodes.length / 2) ? 'bottom' : 'top';
|
||||
|
||||
if (d === previous) {
|
||||
item.call(tooltip()
|
||||
.placement(placement)
|
||||
.html(true)
|
||||
.title(function() {
|
||||
var tip = '<div>' + t('background.switch') + '</div>';
|
||||
return uiTooltipHtml(tip, uiCmd('⌘B'));
|
||||
})
|
||||
.placement('top')
|
||||
);
|
||||
} else if (d.description) {
|
||||
item.call(tooltip()
|
||||
.placement(placement)
|
||||
.title(d.description)
|
||||
.placement('top')
|
||||
);
|
||||
} else {
|
||||
item.call(tooltip().destroy);
|
||||
@@ -96,8 +98,8 @@ export function uiBackground(context) {
|
||||
|
||||
|
||||
function clickSetSource(d) {
|
||||
previous = context.background().baseLayerSource();
|
||||
d3.event.preventDefault();
|
||||
previous = context.background().baseLayerSource();
|
||||
context.background().baseLayerSource(d);
|
||||
selectLayer();
|
||||
document.activeElement.blur();
|
||||
@@ -107,21 +109,19 @@ export function uiBackground(context) {
|
||||
function editCustom() {
|
||||
d3.event.preventDefault();
|
||||
var template = window.prompt(t('background.custom_prompt'), customTemplate);
|
||||
if (!template ||
|
||||
template.indexOf('google.com') !== -1 ||
|
||||
template.indexOf('googleapis.com') !== -1 ||
|
||||
template.indexOf('google.ru') !== -1) {
|
||||
if (template) {
|
||||
setCustom(template);
|
||||
} else {
|
||||
selectLayer();
|
||||
return;
|
||||
}
|
||||
setCustom(template);
|
||||
}
|
||||
|
||||
|
||||
function setCustom(template) {
|
||||
context.background().baseLayerSource(rendererBackgroundSource.Custom(template));
|
||||
selectLayer();
|
||||
context.storage('background-custom-template', template);
|
||||
var d = rendererBackgroundSource.Custom(template);
|
||||
content.selectAll('.custom_layer').datum(d);
|
||||
clickSetSource(d);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,9 @@ export function uiBackground(context) {
|
||||
var layerLinks = layerList.selectAll('li.layer')
|
||||
.data(sources, function(d) { return d.name(); });
|
||||
|
||||
layerLinks.exit()
|
||||
.remove();
|
||||
|
||||
var enter = layerLinks.enter()
|
||||
.insert('li', '.custom_layer')
|
||||
.attr('class', 'layer')
|
||||
@@ -155,20 +158,20 @@ export function uiBackground(context) {
|
||||
.append('span')
|
||||
.html('★');
|
||||
|
||||
var label = enter.append('label');
|
||||
var label = enter
|
||||
.append('label');
|
||||
|
||||
label.append('input')
|
||||
label
|
||||
.append('input')
|
||||
.attr('type', type)
|
||||
.attr('name', 'layers')
|
||||
.on('change', change);
|
||||
|
||||
label.append('span')
|
||||
label
|
||||
.append('span')
|
||||
.text(function(d) { return d.name(); });
|
||||
|
||||
|
||||
layerLinks.exit()
|
||||
.remove();
|
||||
|
||||
layerList.selectAll('li.layer')
|
||||
.sort(sortSources)
|
||||
.style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none');
|
||||
@@ -208,6 +211,7 @@ export function uiBackground(context) {
|
||||
|
||||
|
||||
function resetOffset() {
|
||||
if (d3.event.button !== 0) return;
|
||||
context.background().offset([0, 0]);
|
||||
updateOffsetVal();
|
||||
}
|
||||
@@ -220,22 +224,30 @@ export function uiBackground(context) {
|
||||
|
||||
|
||||
function buttonOffset(d) {
|
||||
if (d3.event.button !== 0) return;
|
||||
var timeout = window.setTimeout(function() {
|
||||
interval = window.setInterval(nudge.bind(null, d), 100);
|
||||
}, 500),
|
||||
interval;
|
||||
|
||||
d3.select(window).on('mouseup', function() {
|
||||
window.clearInterval(interval);
|
||||
function doneNudge() {
|
||||
window.clearTimeout(timeout);
|
||||
d3.select(window).on('mouseup', null);
|
||||
});
|
||||
window.clearInterval(interval);
|
||||
d3.select(window)
|
||||
.on('mouseup.buttonoffset', null, true)
|
||||
.on('mousedown.buttonoffset', null, true);
|
||||
}
|
||||
|
||||
d3.select(window)
|
||||
.on('mouseup.buttonoffset', doneNudge, true)
|
||||
.on('mousedown.buttonoffset', doneNudge, true);
|
||||
|
||||
nudge(d);
|
||||
}
|
||||
|
||||
|
||||
function inputOffset() {
|
||||
if (d3.event.button !== 0) return;
|
||||
var input = d3.select(this);
|
||||
var d = input.node().value;
|
||||
|
||||
@@ -257,6 +269,7 @@ export function uiBackground(context) {
|
||||
|
||||
|
||||
function dragOffset() {
|
||||
if (d3.event.button !== 0) return;
|
||||
var origin = [d3.event.clientX, d3.event.clientY];
|
||||
|
||||
context.container()
|
||||
@@ -275,6 +288,7 @@ export function uiBackground(context) {
|
||||
nudge(d);
|
||||
})
|
||||
.on('mouseup.offset', function() {
|
||||
if (d3.event.button !== 0) return;
|
||||
d3.selectAll('.nudge-surface')
|
||||
.remove();
|
||||
|
||||
@@ -313,15 +327,17 @@ export function uiBackground(context) {
|
||||
|
||||
if (show) {
|
||||
selection.on('mousedown.background-inside', function() {
|
||||
return d3.event.stopPropagation();
|
||||
d3.event.stopPropagation();
|
||||
});
|
||||
content.style('display', 'block')
|
||||
content
|
||||
.style('display', 'block')
|
||||
.style('right', '-300px')
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('right', '0px');
|
||||
} else {
|
||||
content.style('display', 'block')
|
||||
content
|
||||
.style('display', 'block')
|
||||
.style('right', '0px')
|
||||
.transition()
|
||||
.duration(200)
|
||||
@@ -335,13 +351,15 @@ export function uiBackground(context) {
|
||||
}
|
||||
|
||||
|
||||
var content = selection.append('div')
|
||||
var content = selection
|
||||
.append('div')
|
||||
.attr('class', 'fillL map-overlay col3 content hide'),
|
||||
tooltipBehavior = tooltip()
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
.html(true)
|
||||
.title(uiTooltipHtml(t('background.description'), key)),
|
||||
button = selection.append('button')
|
||||
button = selection
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.on('click', toggle)
|
||||
.call(svgIcon('#icon-layers', 'light'))
|
||||
@@ -351,13 +369,16 @@ export function uiBackground(context) {
|
||||
|
||||
/* opacity switcher */
|
||||
|
||||
var opa = content.append('div')
|
||||
var opawrap = content
|
||||
.append('div')
|
||||
.attr('class', 'opacity-options-wrapper');
|
||||
|
||||
opa.append('h4')
|
||||
opawrap
|
||||
.append('h4')
|
||||
.text(t('background.title'));
|
||||
|
||||
var opacityList = opa.append('ul')
|
||||
var opacityList = opawrap
|
||||
.append('ul')
|
||||
.attr('class', 'opacity-options');
|
||||
|
||||
opacityList.selectAll('div.opacity')
|
||||
@@ -378,15 +399,18 @@ export function uiBackground(context) {
|
||||
|
||||
/* background switcher */
|
||||
|
||||
var backgroundList = content.append('ul')
|
||||
var backgroundList = content
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list')
|
||||
.attr('dir', 'auto');
|
||||
|
||||
var custom = backgroundList.append('li')
|
||||
var custom = backgroundList
|
||||
.append('li')
|
||||
.attr('class', 'custom_layer')
|
||||
.datum(rendererBackgroundSource.Custom());
|
||||
|
||||
custom.append('button')
|
||||
custom
|
||||
.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(tooltip()
|
||||
.title(t('background.custom_button'))
|
||||
@@ -394,9 +418,11 @@ export function uiBackground(context) {
|
||||
.on('click', editCustom)
|
||||
.call(svgIcon('#icon-search'));
|
||||
|
||||
var label = custom.append('label');
|
||||
var label = custom
|
||||
.append('label');
|
||||
|
||||
label.append('input')
|
||||
label
|
||||
.append('input')
|
||||
.attr('type', 'radio')
|
||||
.attr('name', 'layers')
|
||||
.on('change', function () {
|
||||
@@ -407,10 +433,12 @@ export function uiBackground(context) {
|
||||
}
|
||||
});
|
||||
|
||||
label.append('span')
|
||||
label
|
||||
.append('span')
|
||||
.text(t('background.custom'));
|
||||
|
||||
content.append('div')
|
||||
content
|
||||
.append('div')
|
||||
.attr('class', 'imagery-faq')
|
||||
.append('a')
|
||||
.attr('target', '_blank')
|
||||
@@ -420,10 +448,12 @@ export function uiBackground(context) {
|
||||
.append('span')
|
||||
.text(t('background.imagery_source_faq'));
|
||||
|
||||
var overlayList = content.append('ul')
|
||||
var overlayList = content
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
|
||||
var controls = content.append('div')
|
||||
var controls = content
|
||||
.append('div')
|
||||
.attr('class', 'controls-list');
|
||||
|
||||
|
||||
@@ -437,7 +467,8 @@ export function uiBackground(context) {
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
minimapLabel.classed('minimap-toggle', true)
|
||||
minimapLabel
|
||||
.classed('minimap-toggle', true)
|
||||
.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', function() {
|
||||
@@ -445,57 +476,69 @@ export function uiBackground(context) {
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
minimapLabel.append('span')
|
||||
minimapLabel
|
||||
.append('span')
|
||||
.text(t('background.minimap.description'));
|
||||
|
||||
|
||||
/* imagery offset controls */
|
||||
|
||||
var adjustments = content.append('div')
|
||||
var adjustments = content
|
||||
.append('div')
|
||||
.attr('class', 'adjustments');
|
||||
|
||||
adjustments.append('a')
|
||||
adjustments
|
||||
.append('a')
|
||||
.text(t('background.fix_misalignment'))
|
||||
.attr('href', '#')
|
||||
.classed('hide-toggle', true)
|
||||
.classed('expanded', false)
|
||||
.on('click', function() {
|
||||
if (d3.event.button !== 0) return;
|
||||
var exp = d3.select(this).classed('expanded');
|
||||
nudgeContainer.style('display', exp ? 'none' : 'block');
|
||||
d3.select(this).classed('expanded', !exp);
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
var nudgeContainer = adjustments.append('div')
|
||||
var nudgeContainer = adjustments
|
||||
.append('div')
|
||||
.attr('class', 'nudge-container cf')
|
||||
.style('display', 'none');
|
||||
|
||||
nudgeContainer.append('div')
|
||||
nudgeContainer
|
||||
.append('div')
|
||||
.attr('class', 'nudge-instructions')
|
||||
.text(t('background.offset'));
|
||||
|
||||
var nudgeRect = nudgeContainer.append('div')
|
||||
var nudgeRect = nudgeContainer
|
||||
.append('div')
|
||||
.attr('class', 'nudge-outer-rect')
|
||||
.on('mousedown', dragOffset);
|
||||
|
||||
nudgeRect.append('div')
|
||||
nudgeRect
|
||||
.append('div')
|
||||
.attr('class', 'nudge-inner-rect')
|
||||
.append('input')
|
||||
.on('change', inputOffset)
|
||||
.on('mousedown', function() {
|
||||
if (d3.event.button !== 0) return;
|
||||
d3.event.stopPropagation();
|
||||
});
|
||||
|
||||
nudgeContainer.append('div')
|
||||
nudgeContainer
|
||||
.append('div')
|
||||
.selectAll('button')
|
||||
.data(directions).enter()
|
||||
.append('button')
|
||||
.attr('class', function(d) { return d[0] + ' nudge'; })
|
||||
.on('mousedown', function(d) {
|
||||
if (d3.event.button !== 0) return;
|
||||
buttonOffset(d[1]);
|
||||
});
|
||||
|
||||
nudgeContainer.append('button')
|
||||
nudgeContainer
|
||||
.append('button')
|
||||
.attr('title', t('background.reset'))
|
||||
.attr('class', 'nudge-reset disabled')
|
||||
.on('click', resetOffset)
|
||||
|
||||
+10
-3
@@ -1,11 +1,15 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { d3combobox } from '../lib/d3.combobox.js';
|
||||
import { t } from '../util/locale';
|
||||
import { d3combobox } from '../lib/d3.combobox.js';
|
||||
import { modeSelect } from '../modes/index';
|
||||
import { svgIcon } from '../svg/index';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index';
|
||||
import {
|
||||
utilDisplayName,
|
||||
utilDisplayType,
|
||||
utilEntityOrMemberSelector
|
||||
} from '../util/index';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import { utilTriggerEvent } from '../util/trigger_event';
|
||||
|
||||
@@ -229,7 +233,10 @@ export function uiCommit(context) {
|
||||
|
||||
li.append('strong')
|
||||
.attr('class', 'entity-type')
|
||||
.text(function(d) { return context.presets().match(d.entity, d.graph).name(); });
|
||||
.text(function(d) {
|
||||
var matched = context.presets().match(d.entity, d.graph);
|
||||
return (matched && matched.name()) || utilDisplayType(d.entity.id);
|
||||
});
|
||||
|
||||
li.append('span')
|
||||
.attr('class', 'entity-name')
|
||||
|
||||
@@ -5,7 +5,11 @@ import { geoExtent, geoChooseEdge } from '../geo/index';
|
||||
import { modeSelect } from '../modes/index';
|
||||
import { osmEntity } from '../osm/index';
|
||||
import { svgIcon } from '../svg/index';
|
||||
import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index';
|
||||
import {
|
||||
utilDisplayName,
|
||||
utilDisplayType,
|
||||
utilEntityOrMemberSelector
|
||||
} from '../util/index';
|
||||
|
||||
|
||||
export function uiFeatureList(context) {
|
||||
@@ -111,11 +115,13 @@ export function uiFeatureList(context) {
|
||||
|
||||
var name = utilDisplayName(entity) || '';
|
||||
if (name.toLowerCase().indexOf(q) >= 0) {
|
||||
var matched = context.presets().match(entity, graph),
|
||||
type = (matched && matched.name()) || utilDisplayType(entity.id);
|
||||
result.push({
|
||||
id: entity.id,
|
||||
entity: entity,
|
||||
geometry: context.geometry(entity.id),
|
||||
type: context.presets().match(entity, graph).name(),
|
||||
type: type,
|
||||
name: name
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ export function uiFieldAddress(field, context) {
|
||||
};
|
||||
|
||||
|
||||
function getStreets() {
|
||||
function getNearStreets() {
|
||||
var extent = entity.extent(context.graph()),
|
||||
l = extent.center(),
|
||||
box = geoExtent(l).padByMeters(200);
|
||||
|
||||
return context.intersects(box)
|
||||
var streets = context.intersects(box)
|
||||
.filter(isAddressable)
|
||||
.map(function(d) {
|
||||
var loc = context.projection([
|
||||
@@ -47,22 +47,25 @@ export function uiFieldAddress(field, context) {
|
||||
value: d.tags.name,
|
||||
dist: choice.distance
|
||||
};
|
||||
}).sort(function(a, b) {
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
});
|
||||
|
||||
return _.uniqBy(streets, 'value');
|
||||
|
||||
function isAddressable(d) {
|
||||
return d.tags.highway && d.tags.name && d.type === 'way';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getCities() {
|
||||
function getNearCities() {
|
||||
var extent = entity.extent(context.graph()),
|
||||
l = extent.center(),
|
||||
box = geoExtent(l).padByMeters(200);
|
||||
|
||||
return context.intersects(box)
|
||||
var cities = context.intersects(box)
|
||||
.filter(isAddressable)
|
||||
.map(function(d) {
|
||||
return {
|
||||
@@ -70,10 +73,14 @@ export function uiFieldAddress(field, context) {
|
||||
value: d.tags['addr:city'] || d.tags.name,
|
||||
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
|
||||
};
|
||||
}).sort(function(a, b) {
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
});
|
||||
|
||||
return _.uniqBy(cities, 'value');
|
||||
|
||||
|
||||
function isAddressable(d) {
|
||||
if (d.tags.name &&
|
||||
(d.tags.admin_level === '8' || d.tags.border_type === 'city'))
|
||||
@@ -92,26 +99,27 @@ export function uiFieldAddress(field, context) {
|
||||
}
|
||||
|
||||
|
||||
function getPostCodes() {
|
||||
function getNearValues(key) {
|
||||
var extent = entity.extent(context.graph()),
|
||||
l = extent.center(),
|
||||
box = geoExtent(l).padByMeters(200);
|
||||
|
||||
return context.intersects(box)
|
||||
.filter(isAddressable)
|
||||
var results = context.intersects(box)
|
||||
.filter(function hasTag(d) {
|
||||
return d.tags[key];
|
||||
})
|
||||
.map(function(d) {
|
||||
return {
|
||||
title: d.tags['addr:postcode'],
|
||||
value: d.tags['addr:postcode'],
|
||||
title: d.tags[key],
|
||||
value: d.tags[key],
|
||||
dist: geoSphericalDistance(d.extent(context.graph()).center(), l)
|
||||
};
|
||||
}).sort(function(a, b) {
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
return a.dist - b.dist;
|
||||
});
|
||||
|
||||
function isAddressable(d) {
|
||||
return d.tags['addr:postcode'];
|
||||
}
|
||||
return _.uniqBy(results, 'value');
|
||||
}
|
||||
|
||||
|
||||
@@ -151,23 +159,24 @@ export function uiFieldAddress(field, context) {
|
||||
.style('width', function (d) { return d.width * 100 + '%'; });
|
||||
|
||||
// Update
|
||||
wrap.selectAll('.addr-street')
|
||||
.call(d3combobox()
|
||||
.fetcher(function(value, callback) {
|
||||
callback(getStreets());
|
||||
}));
|
||||
// setup dropdowns for common address tags
|
||||
var addrTags = [
|
||||
'street', 'city', 'state', 'province', 'district',
|
||||
'subdistrict', 'suburb', 'place', 'postcode'
|
||||
];
|
||||
|
||||
wrap.selectAll('.addr-city')
|
||||
.call(d3combobox()
|
||||
.fetcher(function(value, callback) {
|
||||
callback(getCities());
|
||||
}));
|
||||
addrTags.forEach(function(tag) {
|
||||
var nearValues = (tag === 'street') ? getNearStreets
|
||||
: (tag === 'city') ? getNearCities
|
||||
: getNearValues;
|
||||
|
||||
wrap.selectAll('.addr-postcode')
|
||||
.call(d3combobox()
|
||||
.fetcher(function(value, callback) {
|
||||
callback(getPostCodes());
|
||||
}));
|
||||
wrap.selectAll('.addr-' + tag)
|
||||
.call(d3combobox()
|
||||
.minItems(1)
|
||||
.fetcher(function(value, callback) {
|
||||
callback(nearValues('addr:' + tag));
|
||||
}));
|
||||
});
|
||||
|
||||
wrap.selectAll('input')
|
||||
.on('blur', change())
|
||||
|
||||
+18
-15
@@ -11,7 +11,7 @@ export function uiFieldCheck(field) {
|
||||
options = field.strings && field.strings.options,
|
||||
values = [],
|
||||
texts = [],
|
||||
box = d3.select(null),
|
||||
input = d3.select(null),
|
||||
text = d3.select(null),
|
||||
label = d3.select(null),
|
||||
entity, value;
|
||||
@@ -52,25 +52,28 @@ export function uiFieldCheck(field) {
|
||||
.append('label')
|
||||
.attr('class', 'preset-input-wrap');
|
||||
|
||||
enter.append('input')
|
||||
enter
|
||||
.append('input')
|
||||
.property('indeterminate', field.type === 'check')
|
||||
.attr('type', 'checkbox')
|
||||
.attr('id', 'preset-input-' + field.id)
|
||||
.attr('id', 'preset-input-' + field.id);
|
||||
|
||||
enter
|
||||
.append('span')
|
||||
.text(texts[0])
|
||||
.attr('class', 'value');
|
||||
|
||||
label = label.merge(enter);
|
||||
input = label.selectAll('input');
|
||||
text = label.selectAll('span.value');
|
||||
|
||||
input
|
||||
.on('click', function() {
|
||||
var t = {};
|
||||
t[field.key] = values[(values.indexOf(value) + 1) % values.length];
|
||||
dispatch.call('change', this, t);
|
||||
d3.event.stopPropagation();
|
||||
});
|
||||
|
||||
enter.append('span')
|
||||
.text(texts[0])
|
||||
.attr('class', 'value');
|
||||
|
||||
label = label.merge(enter);
|
||||
|
||||
box = label.selectAll('input');
|
||||
text = label.selectAll('span.value');
|
||||
};
|
||||
|
||||
|
||||
@@ -83,15 +86,15 @@ export function uiFieldCheck(field) {
|
||||
|
||||
check.tags = function(tags) {
|
||||
value = tags[field.key];
|
||||
box.property('indeterminate', field.type === 'check' && !value);
|
||||
box.property('checked', value === 'yes');
|
||||
input.property('indeterminate', field.type === 'check' && !value);
|
||||
input.property('checked', value === 'yes');
|
||||
text.text(texts[values.indexOf(value)]);
|
||||
label.classed('set', !!value);
|
||||
};
|
||||
|
||||
|
||||
check.focus = function() {
|
||||
box.node().focus();
|
||||
input.node().focus();
|
||||
};
|
||||
|
||||
return utilRebind(check, dispatch, 'on');
|
||||
|
||||
+7
-2
@@ -6,6 +6,11 @@ import { geoExtent } from '../geo/index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { uiCmd } from './cmd';
|
||||
|
||||
import {
|
||||
geoLength as d3GeoLength,
|
||||
geoCentroid as d3GeoCentroid
|
||||
} from 'd3';
|
||||
|
||||
|
||||
export function uiInfo(context) {
|
||||
var key = uiCmd('⌘I'),
|
||||
@@ -149,9 +154,9 @@ export function uiInfo(context) {
|
||||
if (geometry === 'line' || geometry === 'area') {
|
||||
var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()),
|
||||
feature = entity.asGeoJSON(resolver),
|
||||
length = radiansToMeters(d3.geoLength(toLineString(feature))),
|
||||
length = radiansToMeters(d3GeoLength(toLineString(feature))),
|
||||
lengthLabel = t('infobox.' + (closed ? 'perimeter' : 'length')),
|
||||
centroid = d3.geoCentroid(feature);
|
||||
centroid = d3GeoCentroid(feature);
|
||||
|
||||
list.append('li')
|
||||
.text(t('infobox.geometry') + ': ' +
|
||||
|
||||
+16
-14
@@ -256,14 +256,10 @@ export function uiInit(context) {
|
||||
.on('↑', pan([0, pa]))
|
||||
.on('→', pan([-pa, 0]))
|
||||
.on('↓', pan([0, -pa]))
|
||||
.on('⇧←', pan([mapDimensions[0], 0]))
|
||||
.on('⇧↑', pan([0, mapDimensions[1]]))
|
||||
.on('⇧→', pan([-mapDimensions[0], 0]))
|
||||
.on('⇧↓', pan([0, -mapDimensions[1]]))
|
||||
.on(uiCmd('⌘←'), pan([mapDimensions[0], 0]))
|
||||
.on(uiCmd('⌘↑'), pan([0, mapDimensions[1]]))
|
||||
.on(uiCmd('⌘→'), pan([-mapDimensions[0], 0]))
|
||||
.on(uiCmd('⌘↓'), pan([0, -mapDimensions[1]]));
|
||||
.on(['⇧←', uiCmd('⌘←')], pan([mapDimensions[0], 0]))
|
||||
.on(['⇧↑', uiCmd('⌘↑')], pan([0, mapDimensions[1]]))
|
||||
.on(['⇧→', uiCmd('⌘→')], pan([-mapDimensions[0], 0]))
|
||||
.on(['⇧↓', uiCmd('⌘↓')], pan([0, -mapDimensions[1]]));
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
@@ -275,24 +271,30 @@ export function uiInit(context) {
|
||||
.call(uiRestore(context));
|
||||
|
||||
var authenticating = uiLoading(context)
|
||||
.message(t('loading_auth'));
|
||||
.message(t('loading_auth'))
|
||||
.blocking(true);
|
||||
|
||||
context.connection()
|
||||
.on('authenticating.ui', function() {
|
||||
.on('authLoading.ui', function() {
|
||||
context.container()
|
||||
.call(authenticating);
|
||||
})
|
||||
.on('authenticated.ui', function() {
|
||||
.on('authDone.ui', function() {
|
||||
authenticating.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function ui(node) {
|
||||
function ui(node, callback) {
|
||||
var container = d3.select(node);
|
||||
context.container(container);
|
||||
context.loadLocale(function() {
|
||||
render(container);
|
||||
context.loadLocale(function(err) {
|
||||
if (!err) {
|
||||
render(container);
|
||||
}
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -95,9 +95,14 @@ export function uiPreset(context) {
|
||||
function content(selection) {
|
||||
if (!fieldsArr) {
|
||||
var entity = context.entity(id),
|
||||
geometry = context.geometry(id);
|
||||
geometry = context.geometry(id),
|
||||
presets = context.presets();
|
||||
|
||||
fieldsArr = [UIField(context.presets().field('name'), entity)];
|
||||
fieldsArr = [];
|
||||
|
||||
if (presets.field('name')) {
|
||||
fieldsArr.push(UIField(presets.field('name'), entity));
|
||||
}
|
||||
|
||||
preset.fields.forEach(function(field) {
|
||||
if (field.matchGeometry(geometry)) {
|
||||
@@ -105,11 +110,11 @@ export function uiPreset(context) {
|
||||
}
|
||||
});
|
||||
|
||||
if (entity.isHighwayIntersection(context.graph())) {
|
||||
fieldsArr.push(UIField(context.presets().field('restrictions'), entity, true));
|
||||
if (entity.isHighwayIntersection(context.graph()) && presets.field('restrictions')) {
|
||||
fieldsArr.push(UIField(presets.field('restrictions'), entity, true));
|
||||
}
|
||||
|
||||
context.presets().universal().forEach(function(field) {
|
||||
presets.universal().forEach(function(field) {
|
||||
if (preset.fields.indexOf(field) < 0) {
|
||||
fieldsArr.push(UIField(field, entity));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { osmEntity } from '../osm/index';
|
||||
import { svgIcon } from '../svg/index';
|
||||
import { services } from '../services/index';
|
||||
import { uiDisclosure } from './disclosure';
|
||||
import { utilDisplayName } from '../util/index';
|
||||
import { utilDisplayName, utilDisplayType } from '../util/index';
|
||||
|
||||
|
||||
export function uiRawMemberEditor(context) {
|
||||
@@ -109,7 +109,10 @@ export function uiRawMemberEditor(context) {
|
||||
|
||||
label.append('span')
|
||||
.attr('class', 'member-entity-type')
|
||||
.text(function(d) { return context.presets().match(d.member, context.graph()).name(); });
|
||||
.text(function(d) {
|
||||
var matched = context.presets().match(d.member, context.graph());
|
||||
return (matched && matched.name()) || utilDisplayType(d.member.id);
|
||||
});
|
||||
|
||||
label.append('span')
|
||||
.attr('class', 'member-entity-name')
|
||||
|
||||
@@ -80,7 +80,8 @@ export function uiRawMembershipEditor(context) {
|
||||
if (entity.type !== 'relation' || entity.id === id)
|
||||
return;
|
||||
|
||||
var presetName = context.presets().match(entity, graph).name(),
|
||||
var matched = context.presets().match(entity, graph),
|
||||
presetName = (matched && matched.name()) || t('inspector.relation'),
|
||||
entityName = utilDisplayName(entity) || '';
|
||||
|
||||
var value = presetName + ' ' + entityName;
|
||||
@@ -175,7 +176,8 @@ export function uiRawMembershipEditor(context) {
|
||||
.append('span')
|
||||
.attr('class', 'member-entity-type')
|
||||
.text(function(d) {
|
||||
return context.presets().match(d.relation, context.graph()).name();
|
||||
var matched = context.presets().match(d.relation, context.graph());
|
||||
return (matched && matched.name()) || t('inspector.relation');
|
||||
});
|
||||
|
||||
label
|
||||
|
||||
@@ -28,6 +28,8 @@ export function uiScale(context) {
|
||||
if (dist >= val) {
|
||||
scale.dist = Math.floor(dist / val) * val;
|
||||
break;
|
||||
} else {
|
||||
scale.dist = +dist.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ export function uiSpinner(context) {
|
||||
.attr('src', context.imagePath('loader-black.gif'))
|
||||
.style('opacity', 0);
|
||||
|
||||
connection.event
|
||||
connection
|
||||
.on('loading.spinner', function() {
|
||||
img.transition()
|
||||
.style('opacity', 1);
|
||||
});
|
||||
|
||||
connection.event
|
||||
connection
|
||||
.on('loaded.spinner', function() {
|
||||
img.transition()
|
||||
.style('opacity', 0);
|
||||
|
||||
+23
-6
@@ -1,18 +1,36 @@
|
||||
import * as d3 from 'd3';
|
||||
import { t } from '../util/locale';
|
||||
import { svgIcon } from '../svg/index';
|
||||
|
||||
|
||||
export function uiStatus(context) {
|
||||
var connection = context.connection(),
|
||||
errCount = 0;
|
||||
var connection = context.connection();
|
||||
|
||||
return function(selection) {
|
||||
|
||||
function update() {
|
||||
connection.status(function(err, apiStatus) {
|
||||
selection.html('');
|
||||
if (err && errCount++ < 2) return;
|
||||
|
||||
if (err) {
|
||||
selection.text(t('status.error'));
|
||||
if (apiStatus === 'rateLimited') {
|
||||
selection
|
||||
.text(t('status.rateLimit'))
|
||||
.append('a')
|
||||
.attr('class', 'api-status-login')
|
||||
.attr('target', '_blank')
|
||||
.call(svgIcon('#icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('login'))
|
||||
.on('click.login', function() {
|
||||
d3.event.preventDefault();
|
||||
connection.authenticate();
|
||||
});
|
||||
} else {
|
||||
// TODO: nice messages for different error types
|
||||
selection.text(t('status.error'));
|
||||
}
|
||||
|
||||
} else if (apiStatus === 'readonly') {
|
||||
selection.text(t('status.readonly'));
|
||||
} else if (apiStatus === 'offline') {
|
||||
@@ -20,12 +38,11 @@ export function uiStatus(context) {
|
||||
}
|
||||
|
||||
selection.attr('class', 'api-status ' + (err ? 'error' : apiStatus));
|
||||
if (!err) errCount = 0;
|
||||
});
|
||||
}
|
||||
|
||||
connection
|
||||
.on('auth', function() { update(selection); });
|
||||
.on('change', function() { update(selection); });
|
||||
|
||||
window.setInterval(update, 90000);
|
||||
update(selection);
|
||||
|
||||
+5
-9
@@ -72,16 +72,12 @@ export function uiZoom(context) {
|
||||
var keybinding = d3keybinding('zoom');
|
||||
|
||||
_.each(['=','ffequals','plus','ffplus'], function(key) {
|
||||
keybinding.on(key, zoomIn);
|
||||
keybinding.on('⇧' + key, zoomIn);
|
||||
keybinding.on(uiCmd('⌘' + key), zoomInFurther);
|
||||
keybinding.on(uiCmd('⌘⇧' + key), zoomInFurther);
|
||||
keybinding.on([key, '⇧' + key], zoomIn);
|
||||
keybinding.on([uiCmd('⌘' + key), uiCmd('⌘⇧' + key)], zoomInFurther);
|
||||
});
|
||||
_.each(['-','ffminus','_','dash'], function(key) {
|
||||
keybinding.on(key, zoomOut);
|
||||
keybinding.on('⇧' + key, zoomOut);
|
||||
keybinding.on(uiCmd('⌘' + key), zoomOutFurther);
|
||||
keybinding.on(uiCmd('⌘⇧' + key), zoomOutFurther);
|
||||
_.each(['-','ffminus','dash'], function(key) {
|
||||
keybinding.on([key, '⇧' + key], zoomOut);
|
||||
keybinding.on([uiCmd('⌘' + key), uiCmd('⌘⇧' + key)], zoomOutFurther);
|
||||
});
|
||||
|
||||
d3.select(document)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { currentLocale, setTextDirection } from './locale';
|
||||
import { dataLocales } from '../../data/index';
|
||||
import { utilStringQs } from './index';
|
||||
|
||||
var detected;
|
||||
@@ -70,7 +71,8 @@ export function utilDetect(force) {
|
||||
|
||||
// detect text direction
|
||||
var q = utilStringQs(window.location.hash.substring(1));
|
||||
if (['ar', 'fa', 'iw', 'dv'].indexOf(detected.locale.split('-')[0]) > -1 || q.hasOwnProperty('rtl')) {
|
||||
var lang = dataLocales[detected.locale];
|
||||
if ((lang && lang.rtl) || q.hasOwnProperty('rtl')) {
|
||||
detected.textDirection = 'rtl';
|
||||
} else {
|
||||
detected.textDirection = 'ltr';
|
||||
|
||||
Reference in New Issue
Block a user