mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-27 02:12:24 +02:00
move maprules/mapcss to a service class
ref #remote-presets
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { osmNode } from '../osm';
|
||||
|
||||
|
||||
export function actionDetachNode(nodeID) {
|
||||
|
||||
var action = function(graph) {
|
||||
var node = graph.entity(nodeID);
|
||||
|
||||
// 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
|
||||
graph = graph.parentWays(node)
|
||||
.reduce(function(accGraph, parentWay) {
|
||||
return accGraph.replace(parentWay.replaceNode(nodeID, replacement.id));
|
||||
}, graph);
|
||||
|
||||
// Process any relations too
|
||||
return graph.parentRelations(node)
|
||||
.reduce(function(accGraph, parentRel) {
|
||||
return accGraph.replace(parentRel.replaceMember(node, replacement));
|
||||
}, graph);
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function(graph) {
|
||||
var node = graph.entity(nodeID);
|
||||
var parentRels = graph.parentRelations(node);
|
||||
|
||||
for (var i = 0; i < parentRels.length; i++) {
|
||||
var relation = parentRels[i];
|
||||
if (!relation.isValidRestriction()) continue;
|
||||
|
||||
for (var j = 0; j < relation.members.length; j++) {
|
||||
var m = relation.members[j];
|
||||
if (m.id === nodeID && (m.role === 'via' || m.role === 'location_hint')) {
|
||||
return 'restriction';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
@@ -33,3 +33,4 @@ export { actionSplit } from './split';
|
||||
export { actionStraighten } from './straighten';
|
||||
export { actionUnrestrictTurn } from './unrestrict_turn';
|
||||
export { actionReflect } from './reflect.js';
|
||||
export { actionDetachNode } from './detach_node';
|
||||
|
||||
@@ -40,11 +40,7 @@ export function actionReverse(wayID, options) {
|
||||
forward: 'backward',
|
||||
backward: 'forward',
|
||||
forwards: 'backward',
|
||||
backwards: 'forward',
|
||||
north: 'south',
|
||||
south: 'north',
|
||||
east: 'west',
|
||||
west: 'east'
|
||||
backwards: 'forward'
|
||||
};
|
||||
var onewayReplacements = {
|
||||
yes: '-1',
|
||||
|
||||
@@ -6,8 +6,8 @@ import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
export function behaviorAddWay(context) {
|
||||
var dispatch = d3_dispatch('start', 'startFromWay', 'startFromNode'),
|
||||
draw = behaviorDraw(context);
|
||||
var dispatch = d3_dispatch('start', 'startFromWay', 'startFromNode');
|
||||
var draw = behaviorDraw(context);
|
||||
|
||||
var addWay = function(surface) {
|
||||
draw.on('click', function() { dispatch.apply('start', this, arguments); })
|
||||
|
||||
+39
-38
@@ -11,14 +11,14 @@ import { timer as d3_timer } from 'd3-timer';
|
||||
|
||||
|
||||
export function behaviorBreathe() {
|
||||
var duration = 800,
|
||||
steps = 4,
|
||||
selector = '.selected.shadow, .selected .shadow',
|
||||
selected = d3_select(null),
|
||||
classed = '',
|
||||
params = {},
|
||||
done = false,
|
||||
timer;
|
||||
var duration = 800;
|
||||
var steps = 4;
|
||||
var selector = '.selected.shadow, .selected .shadow';
|
||||
var _selected = d3_select(null);
|
||||
var _classed = '';
|
||||
var _params = {};
|
||||
var _done = false;
|
||||
var _timer;
|
||||
|
||||
|
||||
function ratchetyInterpolator(a, b, steps, units) {
|
||||
@@ -49,30 +49,30 @@ export function behaviorBreathe() {
|
||||
transition
|
||||
.styleTween('stroke-opacity', function(d) {
|
||||
return ratchetyInterpolator(
|
||||
params[d.id][toFrom].opacity,
|
||||
params[d.id][fromTo].opacity,
|
||||
_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,
|
||||
_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,
|
||||
_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,
|
||||
_params[d.id][toFrom].width,
|
||||
_params[d.id][fromTo].width,
|
||||
steps,
|
||||
'px'
|
||||
);
|
||||
@@ -84,10 +84,11 @@ export function behaviorBreathe() {
|
||||
selection
|
||||
.call(reset)
|
||||
.each(function(d) {
|
||||
var s = d3_select(this),
|
||||
tag = s.node().tagName,
|
||||
p = {'from': {}, 'to': {}},
|
||||
opacity, width;
|
||||
var s = d3_select(this);
|
||||
var tag = s.node().tagName;
|
||||
var p = {'from': {}, 'to': {}};
|
||||
var opacity;
|
||||
var width;
|
||||
|
||||
// determine base opacity and width
|
||||
if (tag === 'circle') {
|
||||
@@ -104,28 +105,28 @@ export function behaviorBreathe() {
|
||||
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;
|
||||
_params[d.id] = p;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function run(surface, fromTo) {
|
||||
var toFrom = (fromTo === 'from' ? 'to' : 'from'),
|
||||
currSelected = surface.selectAll(selector),
|
||||
currClassed = surface.attr('class');
|
||||
var toFrom = (fromTo === 'from' ? 'to' : 'from');
|
||||
var currSelected = surface.selectAll(selector);
|
||||
var currClassed = surface.attr('class');
|
||||
|
||||
if (done || currSelected.empty()) {
|
||||
selected.call(reset);
|
||||
if (_done || currSelected.empty()) {
|
||||
_selected.call(reset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isEqual(currSelected.data(), selected.data()) || currClassed !== classed) {
|
||||
selected.call(reset);
|
||||
classed = currClassed;
|
||||
selected = currSelected.call(calcAnimationParams);
|
||||
if (!_isEqual(currSelected.data(), _selected.data()) || currClassed !== _classed) {
|
||||
_selected.call(reset);
|
||||
_classed = currClassed;
|
||||
_selected = currSelected.call(calcAnimationParams);
|
||||
}
|
||||
|
||||
selected
|
||||
_selected
|
||||
.transition()
|
||||
.duration(duration)
|
||||
.call(setAnimationParams, fromTo)
|
||||
@@ -136,26 +137,26 @@ export function behaviorBreathe() {
|
||||
|
||||
|
||||
var breathe = function(surface) {
|
||||
done = false;
|
||||
timer = d3_timer(function() {
|
||||
_done = false;
|
||||
_timer = d3_timer(function() {
|
||||
// wait for elements to actually become selected
|
||||
if (surface.selectAll(selector).empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
surface.call(run, 'from');
|
||||
timer.stop();
|
||||
_timer.stop();
|
||||
return true;
|
||||
}, 20);
|
||||
};
|
||||
|
||||
|
||||
breathe.off = function() {
|
||||
done = true;
|
||||
if (timer) {
|
||||
timer.stop();
|
||||
_done = true;
|
||||
if (_timer) {
|
||||
_timer.stop();
|
||||
}
|
||||
selected
|
||||
_selected
|
||||
.interrupt()
|
||||
.call(reset);
|
||||
};
|
||||
|
||||
@@ -23,8 +23,8 @@ export function behaviorCopy(context) {
|
||||
|
||||
|
||||
function getDescendants(id, graph, descendants) {
|
||||
var entity = graph.entity(id),
|
||||
i, children;
|
||||
var entity = graph.entity(id);
|
||||
var children;
|
||||
|
||||
descendants = descendants || {};
|
||||
|
||||
@@ -36,7 +36,7 @@ export function behaviorCopy(context) {
|
||||
children = [];
|
||||
}
|
||||
|
||||
for (i = 0; i < children.length; i++) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if (!descendants[children[i]]) {
|
||||
descendants[children[i]] = true;
|
||||
descendants = getDescendants(children[i], graph, descendants);
|
||||
@@ -50,11 +50,12 @@ export function behaviorCopy(context) {
|
||||
function doCopy() {
|
||||
if (!getSelectionText()) d3_event.preventDefault();
|
||||
|
||||
var graph = context.graph(),
|
||||
selected = groupEntities(context.selectedIDs(), graph),
|
||||
canCopy = [],
|
||||
skip = {},
|
||||
i, entity;
|
||||
var graph = context.graph();
|
||||
var selected = groupEntities(context.selectedIDs(), graph);
|
||||
var canCopy = [];
|
||||
var skip = {};
|
||||
var entity;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < selected.relation.length; i++) {
|
||||
entity = selected.relation[i];
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
touches as d3_touches
|
||||
} from 'd3-selection';
|
||||
|
||||
import { osmNote } from '../osm';
|
||||
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
import {
|
||||
@@ -162,7 +164,10 @@ export function behaviorDrag() {
|
||||
var target = d3_event.target;
|
||||
for (; target && target !== root; target = target.parentNode) {
|
||||
var datum = target.__data__;
|
||||
var entity = datum && datum.properties && datum.properties.entity;
|
||||
|
||||
var entity = datum instanceof osmNote ?
|
||||
datum : datum && datum.properties && datum.properties.entity;
|
||||
|
||||
if (entity && target[matchesSelector](_selector)) {
|
||||
return dragstart.call(target, entity);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ export function behaviorDraw(context) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch.call('click', this, context.map().mouseCoordinates(), d);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
var parent = parents[i];
|
||||
var nodes = parent.nodes.map(function(nodeID) { return graph.entity(nodeID); });
|
||||
|
||||
|
||||
if (origWay.isClosed()) { // Check if Area
|
||||
if (finishDraw) {
|
||||
nodes.splice(-2, 1);
|
||||
|
||||
+25
-23
@@ -6,10 +6,7 @@ import {
|
||||
} from 'd3-selection';
|
||||
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
import {
|
||||
osmEntity,
|
||||
osmNote
|
||||
} from '../osm';
|
||||
import { osmEntity, osmNote } from '../osm';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
@@ -110,13 +107,32 @@ export function behaviorHover(context) {
|
||||
_selection.selectAll('.hover-suppressed')
|
||||
.classed('hover-suppressed', false);
|
||||
|
||||
var entity;
|
||||
if (datum instanceof osmNote || datum instanceof osmEntity) {
|
||||
// What are we hovering over?
|
||||
var entity, selector;
|
||||
if (datum && datum.__featurehash__) {
|
||||
entity = datum;
|
||||
} else {
|
||||
entity = datum && datum.properties && datum.properties.entity;
|
||||
selector = '.data' + datum.__featurehash__;
|
||||
|
||||
} else if (datum instanceof osmNote) {
|
||||
entity = datum;
|
||||
selector = '.note-' + datum.id;
|
||||
|
||||
} else if (datum instanceof osmEntity) {
|
||||
entity = datum;
|
||||
selector = '.' + entity.id;
|
||||
if (entity.type === 'relation') {
|
||||
entity.members.forEach(function(member) { selector += ', .' + member.id; });
|
||||
}
|
||||
|
||||
} else if (datum && datum.properties && (datum.properties.entity instanceof osmEntity)) {
|
||||
entity = datum.properties.entity;
|
||||
selector = '.' + entity.id;
|
||||
if (entity.type === 'relation') {
|
||||
entity.members.forEach(function(member) { selector += ', .' + member.id; });
|
||||
}
|
||||
}
|
||||
|
||||
// Update hover state and dispatch event
|
||||
if (entity && entity.id !== _newId) {
|
||||
// If drawing a way, don't hover on a node that was just placed. #3974
|
||||
var mode = context.mode() && context.mode().id;
|
||||
@@ -125,30 +141,16 @@ export function behaviorHover(context) {
|
||||
return;
|
||||
}
|
||||
|
||||
var selector = (datum instanceof osmNote) ? 'note-' + entity.id : '.' + entity.id;
|
||||
|
||||
if (entity.type === 'relation') {
|
||||
entity.members.forEach(function(member) {
|
||||
selector += ', .' + member.id;
|
||||
});
|
||||
}
|
||||
|
||||
var suppressed = _altDisables && d3_event && d3_event.altKey;
|
||||
|
||||
_selection.selectAll(selector)
|
||||
.classed(suppressed ? 'hover-suppressed' : 'hover', true);
|
||||
|
||||
if (datum instanceof osmNote) {
|
||||
dispatch.call('hover', this, !suppressed && entity);
|
||||
} else {
|
||||
dispatch.call('hover', this, !suppressed && entity.id);
|
||||
}
|
||||
dispatch.call('hover', this, !suppressed && entity);
|
||||
|
||||
} else {
|
||||
dispatch.call('hover', this, null);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ export function behaviorLasso(context) {
|
||||
function lassoed() {
|
||||
if (!lasso) return [];
|
||||
|
||||
var graph = context.graph(),
|
||||
bounds = lasso.extent().map(context.projection.invert),
|
||||
extent = geoExtent(normalize(bounds[0], bounds[1]));
|
||||
var graph = context.graph();
|
||||
var bounds = lasso.extent().map(context.projection.invert);
|
||||
var extent = geoExtent(normalize(bounds[0], bounds[1]));
|
||||
|
||||
return _map(context.intersects(extent).filter(function(entity) {
|
||||
return entity.type === 'node' &&
|
||||
|
||||
@@ -9,7 +9,8 @@ import { uiFlash } from '../ui';
|
||||
|
||||
/* Creates a keybinding behavior for an operation */
|
||||
export function behaviorOperation() {
|
||||
var _operation, keybinding;
|
||||
var keybinding;
|
||||
var _operation;
|
||||
|
||||
var behavior = function () {
|
||||
if (_operation && _operation.available()) {
|
||||
|
||||
+17
-16
@@ -15,8 +15,9 @@ import {
|
||||
|
||||
import {
|
||||
geoExtent,
|
||||
geoPointInPolygon
|
||||
} from '../geo/';
|
||||
geoPointInPolygon,
|
||||
geoVecSubtract
|
||||
} from '../geo';
|
||||
|
||||
import { modeMove } from '../modes';
|
||||
import { uiCmd } from '../ui';
|
||||
@@ -29,17 +30,17 @@ export function behaviorPaste(context) {
|
||||
function doPaste() {
|
||||
d3_event.preventDefault();
|
||||
|
||||
var baseGraph = context.graph(),
|
||||
mouse = context.mouse(),
|
||||
projection = context.projection,
|
||||
viewport = geoExtent(projection.clipExtent()).polygon();
|
||||
var baseGraph = context.graph();
|
||||
var mouse = context.mouse();
|
||||
var projection = context.projection;
|
||||
var viewport = geoExtent(projection.clipExtent()).polygon();
|
||||
|
||||
if (!geoPointInPolygon(mouse, viewport)) return;
|
||||
|
||||
var extent = geoExtent(),
|
||||
oldIDs = context.copyIDs(),
|
||||
oldGraph = context.copyGraph(),
|
||||
newIDs = [];
|
||||
var extent = geoExtent();
|
||||
var oldIDs = context.copyIDs();
|
||||
var oldGraph = context.copyGraph();
|
||||
var newIDs = [];
|
||||
|
||||
if (!oldIDs.length) return;
|
||||
|
||||
@@ -49,14 +50,14 @@ export function behaviorPaste(context) {
|
||||
var copies = action.copies();
|
||||
var originals = _invert(_mapValues(copies, 'id'));
|
||||
for (var id in copies) {
|
||||
var oldEntity = oldGraph.entity(id),
|
||||
newEntity = copies[id];
|
||||
var oldEntity = oldGraph.entity(id);
|
||||
var newEntity = copies[id];
|
||||
|
||||
extent._extend(oldEntity.extent(oldGraph));
|
||||
|
||||
// Exclude child nodes from newIDs if their parent way was also copied.
|
||||
var parents = context.graph().parentWays(newEntity),
|
||||
parentCopied = false;
|
||||
var parents = context.graph().parentWays(newEntity);
|
||||
var parentCopied = false;
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
if (originals[parents[i].id]) {
|
||||
parentCopied = true;
|
||||
@@ -70,8 +71,8 @@ export function behaviorPaste(context) {
|
||||
}
|
||||
|
||||
// Put pasted objects where mouse pointer is..
|
||||
var center = projection(extent.center()),
|
||||
delta = [ mouse[0] - center[0], mouse[1] - center[1] ];
|
||||
var center = projection(extent.center());
|
||||
var delta = geoVecSubtract(mouse, center);
|
||||
|
||||
context.perform(actionMove(newIDs, delta, projection));
|
||||
context.enter(modeMove(context, newIDs, baseGraph));
|
||||
|
||||
@@ -11,6 +11,7 @@ import { geoVecLength } from '../geo';
|
||||
import {
|
||||
modeBrowse,
|
||||
modeSelect,
|
||||
modeSelectData,
|
||||
modeSelectNote
|
||||
} from '../modes';
|
||||
|
||||
@@ -157,13 +158,17 @@ export function behaviorSelect(context) {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (datum && datum.__featurehash__ && !isMultiselect) { // clicked Data..
|
||||
context
|
||||
.selectedNoteID(null)
|
||||
.enter(modeSelectData(context, datum));
|
||||
|
||||
} else if (datum instanceof osmNote && !isMultiselect) { // clicked a Note..
|
||||
context
|
||||
.selectedNoteID(datum.id)
|
||||
.enter(modeSelectNote(context, datum.id));
|
||||
|
||||
} else { // clicked nothing..
|
||||
|
||||
context.selectedNoteID(null);
|
||||
if (!isMultiselect && mode.id !== 'browse') {
|
||||
context.enter(modeBrowse(context));
|
||||
|
||||
+11
-11
@@ -8,15 +8,15 @@ import { utilGetDimensions } from '../util/dimensions';
|
||||
|
||||
|
||||
export function behaviorTail() {
|
||||
var text,
|
||||
container,
|
||||
xmargin = 25,
|
||||
tooltipSize = [0, 0],
|
||||
selectionSize = [0, 0];
|
||||
var container;
|
||||
var xmargin = 25;
|
||||
var tooltipSize = [0, 0];
|
||||
var selectionSize = [0, 0];
|
||||
var _text;
|
||||
|
||||
|
||||
function tail(selection) {
|
||||
if (!text) return;
|
||||
if (!_text) return;
|
||||
|
||||
d3_select(window)
|
||||
.on('resize.tail', function() { selectionSize = utilGetDimensions(selection); });
|
||||
@@ -27,7 +27,7 @@ export function behaviorTail() {
|
||||
.attr('class', 'tail tooltip-inner');
|
||||
|
||||
container.append('div')
|
||||
.text(text);
|
||||
.text(_text);
|
||||
|
||||
selection
|
||||
.on('mousemove.tail', mousemove)
|
||||
@@ -72,7 +72,7 @@ export function behaviorTail() {
|
||||
|
||||
|
||||
tail.off = function(selection) {
|
||||
if (!text) return;
|
||||
if (!_text) return;
|
||||
|
||||
container
|
||||
.on('mousemove.tail', null)
|
||||
@@ -88,9 +88,9 @@ export function behaviorTail() {
|
||||
};
|
||||
|
||||
|
||||
tail.text = function(_) {
|
||||
if (!arguments.length) return text;
|
||||
text = _;
|
||||
tail.text = function(val) {
|
||||
if (!arguments.length) return _text;
|
||||
_text = val;
|
||||
return tail;
|
||||
};
|
||||
|
||||
|
||||
+22
-15
@@ -44,7 +44,6 @@ import {
|
||||
utilCallWhenIdle,
|
||||
utilExternalPresets,
|
||||
utilExternalValidationRules,
|
||||
utilMapCSSRule,
|
||||
utilRebind,
|
||||
utilStringQs
|
||||
} from '../util';
|
||||
@@ -58,7 +57,7 @@ export function setAreaKeys(value) {
|
||||
|
||||
export function coreContext() {
|
||||
var context = {};
|
||||
context.version = '2.9.2';
|
||||
context.version = '2.11.1';
|
||||
|
||||
// create a special translation that contains the keys in place of the strings
|
||||
var tkeys = _cloneDeep(dataEn);
|
||||
@@ -124,7 +123,7 @@ export function coreContext() {
|
||||
return context;
|
||||
};
|
||||
|
||||
context.loadTiles = utilCallWhenIdle(function(projection, dimensions, callback) {
|
||||
context.loadTiles = utilCallWhenIdle(function(projection, callback) {
|
||||
var cid;
|
||||
function done(err, result) {
|
||||
if (connection.getConnectionId() !== cid) {
|
||||
@@ -136,11 +135,11 @@ export function coreContext() {
|
||||
}
|
||||
if (connection && context.editable()) {
|
||||
cid = connection.getConnectionId();
|
||||
connection.loadTiles(projection, dimensions, done);
|
||||
connection.loadTiles(projection, done);
|
||||
}
|
||||
});
|
||||
|
||||
context.loadEntity = function(entityId, callback) {
|
||||
context.loadEntity = function(entityID, callback) {
|
||||
var cid;
|
||||
function done(err, result) {
|
||||
if (connection.getConnectionId() !== cid) {
|
||||
@@ -152,24 +151,24 @@ export function coreContext() {
|
||||
}
|
||||
if (connection) {
|
||||
cid = connection.getConnectionId();
|
||||
connection.loadEntity(entityId, done);
|
||||
connection.loadEntity(entityID, done);
|
||||
}
|
||||
};
|
||||
|
||||
context.zoomToEntity = function(entityId, zoomTo) {
|
||||
context.zoomToEntity = function(entityID, zoomTo) {
|
||||
if (zoomTo !== false) {
|
||||
this.loadEntity(entityId, function(err, result) {
|
||||
this.loadEntity(entityID, function(err, result) {
|
||||
if (err) return;
|
||||
var entity = _find(result.data, function(e) { return e.id === entityId; });
|
||||
var entity = _find(result.data, function(e) { return e.id === entityID; });
|
||||
if (entity) { map.zoomTo(entity); }
|
||||
});
|
||||
}
|
||||
|
||||
map.on('drawn.zoomToEntity', function() {
|
||||
if (!context.hasEntity(entityId)) return;
|
||||
if (!context.hasEntity(entityID)) return;
|
||||
map.on('drawn.zoomToEntity', null);
|
||||
context.on('enter.zoomToEntity', null);
|
||||
context.enter(modeSelect(context, [entityId]));
|
||||
context.enter(modeSelect(context, [entityID]));
|
||||
});
|
||||
|
||||
context.on('enter.zoomToEntity', function() {
|
||||
@@ -206,6 +205,13 @@ export function coreContext() {
|
||||
var canSave;
|
||||
if (mode && mode.id === 'save') {
|
||||
canSave = false;
|
||||
|
||||
// Attempt to prevent user from creating duplicate changes - see #5200
|
||||
if (services.osm && services.osm.isChangesetInflight()) {
|
||||
history.clearSaved();
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
canSave = context.selectedIDs().every(function(id) {
|
||||
var entity = context.hasEntity(id);
|
||||
@@ -458,11 +464,12 @@ export function coreContext() {
|
||||
var validationsUrl = utilStringQs(window.location.hash).validations;
|
||||
d3_json(validationsUrl, function (err, mapcss) {
|
||||
if (err) return;
|
||||
var mapcssConfigs = mapcss.rules;
|
||||
var validations = _map(mapcssConfigs, function(mapcssConfig) {
|
||||
return utilMapCSSRule(mapcssConfig, context.presets().areaKeys());
|
||||
services.maprules.init();
|
||||
var areaKeys = context.presets().areaKeys();
|
||||
_each(mapcss, function(mapcssSelector) {
|
||||
return services.maprules.addRule(mapcssSelector, areaKeys);
|
||||
});
|
||||
context.validationRules = function() { return validations; };
|
||||
context.validationRules = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+18
-29
@@ -52,9 +52,8 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
transient: function(entity, key, fn) {
|
||||
var id = entity.id,
|
||||
transients = this.transients[id] ||
|
||||
(this.transients[id] = {});
|
||||
var id = entity.id;
|
||||
var transients = this.transients[id] || (this.transients[id] = {});
|
||||
|
||||
if (transients[key] !== undefined) {
|
||||
return transients[key];
|
||||
@@ -67,8 +66,8 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
parentWays: function(entity) {
|
||||
var parents = this._parentWays[entity.id],
|
||||
result = [];
|
||||
var parents = this._parentWays[entity.id];
|
||||
var result = [];
|
||||
|
||||
if (parents) {
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
@@ -92,8 +91,8 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
parentRelations: function(entity) {
|
||||
var parents = this._parentRels[entity.id],
|
||||
result = [];
|
||||
var parents = this._parentRels[entity.id];
|
||||
var result = [];
|
||||
|
||||
if (parents) {
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
@@ -134,8 +133,8 @@ coreGraph.prototype = {
|
||||
// data into each state. To external consumers, it should appear as if the
|
||||
// graph always contained the newly downloaded data.
|
||||
rebase: function(entities, stack, force) {
|
||||
var base = this.base(),
|
||||
i, j, k, id;
|
||||
var base = this.base();
|
||||
var i, j, k, id;
|
||||
|
||||
for (i = 0; i < entities.length; i++) {
|
||||
var entity = entities[i];
|
||||
@@ -168,8 +167,8 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
_updateRebased: function() {
|
||||
var base = this.base(),
|
||||
i, k, child, id, keys;
|
||||
var base = this.base();
|
||||
var i, k, child, id, keys;
|
||||
|
||||
keys = Object.keys(this._parentWays);
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
@@ -206,17 +205,13 @@ coreGraph.prototype = {
|
||||
|
||||
// Updates calculated properties (parentWays, parentRels) for the specified change
|
||||
_updateCalculated: function(oldentity, entity, parentWays, parentRels) {
|
||||
|
||||
parentWays = parentWays || this._parentWays;
|
||||
parentRels = parentRels || this._parentRels;
|
||||
|
||||
var type = entity && entity.type || oldentity && oldentity.type,
|
||||
removed, added, ways, rels, i;
|
||||
var type = entity && entity.type || oldentity && oldentity.type;
|
||||
var removed, added, ways, rels, i;
|
||||
|
||||
|
||||
if (type === 'way') {
|
||||
|
||||
// Update parentWays
|
||||
if (type === 'way') { // Update parentWays
|
||||
if (oldentity && entity) {
|
||||
removed = _difference(oldentity.nodes, entity.nodes);
|
||||
added = _difference(entity.nodes, oldentity.nodes);
|
||||
@@ -236,9 +231,7 @@ coreGraph.prototype = {
|
||||
parentWays[added[i]] = ways;
|
||||
}
|
||||
|
||||
} else if (type === 'relation') {
|
||||
|
||||
// Update parentRels
|
||||
} else if (type === 'relation') { // Update parentRels
|
||||
if (oldentity && entity) {
|
||||
removed = _difference(oldentity.members, entity.members);
|
||||
added = _difference(entity.members, oldentity);
|
||||
@@ -262,8 +255,7 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
replace: function(entity) {
|
||||
if (this.entities[entity.id] === entity)
|
||||
return this;
|
||||
if (this.entities[entity.id] === entity) return this;
|
||||
|
||||
return this.update(function() {
|
||||
this._updateCalculated(this.entities[entity.id], entity);
|
||||
@@ -281,11 +273,9 @@ coreGraph.prototype = {
|
||||
|
||||
|
||||
revert: function(id) {
|
||||
var baseEntity = this.base().entities[id],
|
||||
headEntity = this.entities[id];
|
||||
|
||||
if (headEntity === baseEntity)
|
||||
return this;
|
||||
var baseEntity = this.base().entities[id];
|
||||
var headEntity = this.entities[id];
|
||||
if (headEntity === baseEntity) return this;
|
||||
|
||||
return this.update(function() {
|
||||
this._updateCalculated(headEntity, baseEntity);
|
||||
@@ -296,7 +286,6 @@ coreGraph.prototype = {
|
||||
|
||||
update: function() {
|
||||
var graph = this.frozen ? coreGraph(this, true) : this;
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
arguments[i].call(graph, graph);
|
||||
}
|
||||
|
||||
+4
-13
@@ -9,7 +9,7 @@ import _isEmpty from 'lodash-es/isEmpty';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
import _map from 'lodash-es/map';
|
||||
import _omit from 'lodash-es/omit';
|
||||
import _pickBy from 'lodash-es/pickBy';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _reject from 'lodash-es/reject';
|
||||
import _values from 'lodash-es/values';
|
||||
import _without from 'lodash-es/without';
|
||||
@@ -282,18 +282,9 @@ export function coreHistory(context) {
|
||||
|
||||
|
||||
validate: function(changes) {
|
||||
// strip mapcss checks if no mapcss to check against.
|
||||
var validationsToRun = _pickBy(Validations, function(validation) {
|
||||
return validation === Validations.validationMapCSSChecks && context.hasOwnProperty('validationRules') ||
|
||||
validation !== Validations.validationMapCSSChecks;
|
||||
});
|
||||
return _flatten(
|
||||
_map(validationsToRun, function(fn, name) {
|
||||
return name === 'validationMapCSSChecks' ?
|
||||
fn()(changes, _stack[_index].graph, context.validationRules()) :
|
||||
fn()(changes, _stack[_index].graph);
|
||||
})
|
||||
);
|
||||
return _flatten(_map(Validations, function(fn) {
|
||||
return fn()(changes, _stack[_index].graph);
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export * from './svg/index';
|
||||
export * from './ui/fields/index';
|
||||
export * from './ui/intro/index';
|
||||
export * from './ui/panels/index';
|
||||
export * from './ui/settings/index';
|
||||
export * from './ui/index';
|
||||
export * from './util/index';
|
||||
export * from './lib/index';
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { range as d3_range } from 'd3-array';
|
||||
|
||||
|
||||
export function d3geoTile() {
|
||||
var _size = [960, 500];
|
||||
var _scale = 256;
|
||||
var _scaleExtent = [0, 20];
|
||||
var _translate = [_size[0] / 2, _size[1] / 2];
|
||||
var _zoomDelta = 0;
|
||||
var _margin = 0;
|
||||
|
||||
function bound(val) {
|
||||
return Math.min(_scaleExtent[1], Math.max(_scaleExtent[0], val));
|
||||
}
|
||||
|
||||
function tile() {
|
||||
var z = Math.max(Math.log(_scale) / Math.LN2 - 8, 0);
|
||||
var z0 = bound(Math.round(z + _zoomDelta));
|
||||
var k = Math.pow(2, z - z0 + 8);
|
||||
var origin = [
|
||||
(_translate[0] - _scale / 2) / k,
|
||||
(_translate[1] - _scale / 2) / k
|
||||
];
|
||||
|
||||
var cols = d3_range(
|
||||
Math.max(0, Math.floor(-origin[0]) - _margin),
|
||||
Math.max(0, Math.ceil(_size[0] / k - origin[0]) + _margin)
|
||||
);
|
||||
var rows = d3_range(
|
||||
Math.max(0, Math.floor(-origin[1]) - _margin),
|
||||
Math.max(0, Math.ceil(_size[1] / k - origin[1]) + _margin)
|
||||
);
|
||||
|
||||
var tiles = [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var y = rows[i];
|
||||
for (var j = 0; j < cols.length; j++) {
|
||||
var x = cols[j];
|
||||
|
||||
if (i >= _margin && i <= rows.length - _margin &&
|
||||
j >= _margin && j <= cols.length - _margin) {
|
||||
tiles.unshift([x, y, z0]); // tiles in view at beginning
|
||||
} else {
|
||||
tiles.push([x, y, z0]); // tiles in margin at the end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tiles.translate = origin;
|
||||
tiles.scale = k;
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
tile.scaleExtent = function(val) {
|
||||
if (!arguments.length) return _scaleExtent;
|
||||
_scaleExtent = val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
tile.size = function(val) {
|
||||
if (!arguments.length) return _size;
|
||||
_size = val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
tile.scale = function(val) {
|
||||
if (!arguments.length) return _scale;
|
||||
_scale = val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
tile.translate = function(val) {
|
||||
if (!arguments.length) return _translate;
|
||||
_translate = val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
tile.zoomDelta = function(val) {
|
||||
if (!arguments.length) return _zoomDelta;
|
||||
_zoomDelta = +val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
// number to extend the rows/columns beyond those covering the viewport
|
||||
tile.margin = function(val) {
|
||||
if (!arguments.length) return _margin;
|
||||
_margin = +val;
|
||||
return tile;
|
||||
};
|
||||
|
||||
return tile;
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export { d3combobox } from './d3.combobox';
|
||||
export { d3geoTile } from './d3.geo.tile';
|
||||
export { d3keybinding } from './d3.keybinding';
|
||||
|
||||
+14
-13
@@ -20,11 +20,12 @@ export function modeAddArea(context) {
|
||||
};
|
||||
|
||||
var behavior = behaviorAddWay(context)
|
||||
.tail(t('modes.add_area.tail'))
|
||||
.on('start', start)
|
||||
.on('startFromWay', startFromWay)
|
||||
.on('startFromNode', startFromNode),
|
||||
defaultTags = { area: 'yes' };
|
||||
.tail(t('modes.add_area.tail'))
|
||||
.on('start', start)
|
||||
.on('startFromWay', startFromWay)
|
||||
.on('startFromNode', startFromNode);
|
||||
|
||||
var defaultTags = { area: 'yes' };
|
||||
|
||||
|
||||
function actionClose(wayId) {
|
||||
@@ -35,9 +36,9 @@ export function modeAddArea(context) {
|
||||
|
||||
|
||||
function start(loc) {
|
||||
var startGraph = context.graph(),
|
||||
node = osmNode({ loc: loc }),
|
||||
way = osmWay({ tags: defaultTags });
|
||||
var startGraph = context.graph();
|
||||
var node = osmNode({ loc: loc });
|
||||
var way = osmWay({ tags: defaultTags });
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(node),
|
||||
@@ -51,9 +52,9 @@ export function modeAddArea(context) {
|
||||
|
||||
|
||||
function startFromWay(loc, edge) {
|
||||
var startGraph = context.graph(),
|
||||
node = osmNode({ loc: loc }),
|
||||
way = osmWay({ tags: defaultTags });
|
||||
var startGraph = context.graph();
|
||||
var node = osmNode({ loc: loc });
|
||||
var way = osmWay({ tags: defaultTags });
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(node),
|
||||
@@ -68,8 +69,8 @@ export function modeAddArea(context) {
|
||||
|
||||
|
||||
function startFromNode(node) {
|
||||
var startGraph = context.graph(),
|
||||
way = osmWay({ tags: defaultTags });
|
||||
var startGraph = context.graph();
|
||||
var way = osmWay({ tags: defaultTags });
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(way),
|
||||
|
||||
@@ -27,9 +27,9 @@ export function modeAddLine(context) {
|
||||
|
||||
|
||||
function start(loc) {
|
||||
var startGraph = context.graph(),
|
||||
node = osmNode({ loc: loc }),
|
||||
way = osmWay();
|
||||
var startGraph = context.graph();
|
||||
var node = osmNode({ loc: loc });
|
||||
var way = osmWay();
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(node),
|
||||
@@ -42,9 +42,9 @@ export function modeAddLine(context) {
|
||||
|
||||
|
||||
function startFromWay(loc, edge) {
|
||||
var startGraph = context.graph(),
|
||||
node = osmNode({ loc: loc }),
|
||||
way = osmWay();
|
||||
var startGraph = context.graph();
|
||||
var node = osmNode({ loc: loc });
|
||||
var way = osmWay();
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(node),
|
||||
@@ -58,8 +58,8 @@ export function modeAddLine(context) {
|
||||
|
||||
|
||||
function startFromNode(node) {
|
||||
var startGraph = context.graph(),
|
||||
way = osmWay();
|
||||
var startGraph = context.graph();
|
||||
var way = osmWay();
|
||||
|
||||
context.perform(
|
||||
actionAddEntity(way),
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { t } from '../util/locale';
|
||||
import { behaviorDraw } from '../behavior';
|
||||
import { modeBrowse, modeSelectNote } from './index';
|
||||
import { osmNote } from '../osm';
|
||||
import { services } from '../services';
|
||||
|
||||
|
||||
export function modeAddNote(context) {
|
||||
var mode = {
|
||||
id: 'add-note',
|
||||
button: 'note',
|
||||
title: t('modes.add_note.title'),
|
||||
description: t('modes.add_note.description'),
|
||||
key: '4'
|
||||
};
|
||||
|
||||
var behavior = behaviorDraw(context)
|
||||
.tail(t('modes.add_note.tail'))
|
||||
.on('click', add)
|
||||
.on('cancel', cancel)
|
||||
.on('finish', cancel);
|
||||
|
||||
|
||||
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)
|
||||
context.pan([0,0]);
|
||||
|
||||
context
|
||||
.selectedNoteID(note.id)
|
||||
.enter(modeSelectNote(context, note.id).newFeature(true));
|
||||
}
|
||||
|
||||
|
||||
function cancel() {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
context.install(behavior);
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
context.uninstall(behavior);
|
||||
};
|
||||
|
||||
|
||||
return mode;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../behavior';
|
||||
|
||||
import { modeDragNode } from './drag_node';
|
||||
import { modeDragNote } from './drag_note';
|
||||
|
||||
|
||||
export function modeBrowse(context) {
|
||||
@@ -23,14 +24,13 @@ export function modeBrowse(context) {
|
||||
behaviorHover(context).on('hover', context.ui().sidebar.hover),
|
||||
behaviorSelect(context),
|
||||
behaviorLasso(context),
|
||||
modeDragNode(context).behavior
|
||||
modeDragNode(context).behavior,
|
||||
modeDragNote(context).behavior
|
||||
];
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
// Get focus on the body.
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
@@ -47,9 +47,7 @@ export function modeBrowse(context) {
|
||||
|
||||
mode.exit = function() {
|
||||
context.ui().sidebar.hover.cancel();
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
behaviors.forEach(context.uninstall);
|
||||
|
||||
if (sidebar) {
|
||||
context.ui().sidebar.hide();
|
||||
|
||||
@@ -446,9 +446,6 @@ export function modeDragNode(context) {
|
||||
context.history()
|
||||
.on('undone.drag-node', null);
|
||||
|
||||
context.map()
|
||||
.on('drawn.drag-node', null);
|
||||
|
||||
_activeEntity = null;
|
||||
|
||||
context.surface()
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { services } from '../services';
|
||||
import { actionNoop } from '../actions';
|
||||
import { behaviorEdit, behaviorDrag } from '../behavior';
|
||||
import { geoVecSubtract, geoViewportEdge } from '../geo';
|
||||
import { modeSelectNote } from './index';
|
||||
|
||||
|
||||
export function modeDragNote(context) {
|
||||
var mode = {
|
||||
id: 'drag-note',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
var edit = behaviorEdit(context);
|
||||
|
||||
var _nudgeInterval;
|
||||
var _lastLoc;
|
||||
|
||||
|
||||
function startNudge(note, nudge) {
|
||||
if (_nudgeInterval) window.clearInterval(_nudgeInterval);
|
||||
_nudgeInterval = window.setInterval(function() {
|
||||
context.pan(nudge);
|
||||
doMove(note, nudge);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
||||
function stopNudge() {
|
||||
if (_nudgeInterval) {
|
||||
window.clearInterval(_nudgeInterval);
|
||||
_nudgeInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function origin(note) {
|
||||
return context.projection(note.loc);
|
||||
}
|
||||
|
||||
|
||||
function start(note) {
|
||||
context.surface().selectAll('.note-' + note.id)
|
||||
.classed('active', true);
|
||||
|
||||
context.perform(actionNoop());
|
||||
context.enter(mode);
|
||||
context.selectedNoteID(note.id);
|
||||
}
|
||||
|
||||
|
||||
function move(note) {
|
||||
d3_event.sourceEvent.stopPropagation();
|
||||
_lastLoc = context.projection.invert(d3_event.point);
|
||||
|
||||
doMove(note);
|
||||
var nudge = geoViewportEdge(d3_event.point, context.map().dimensions());
|
||||
if (nudge) {
|
||||
startNudge(note, nudge);
|
||||
} else {
|
||||
stopNudge();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function doMove(note, 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);
|
||||
|
||||
note = note.move(loc);
|
||||
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
osm.replaceNote(note); // update note cache
|
||||
}
|
||||
|
||||
context.replace(actionNoop()); // trigger redraw
|
||||
}
|
||||
|
||||
|
||||
function end(note) {
|
||||
context.replace(actionNoop()); // trigger redraw
|
||||
|
||||
context
|
||||
.selectedNoteID(note.id)
|
||||
.enter(modeSelectNote(context, note.id));
|
||||
}
|
||||
|
||||
|
||||
var drag = behaviorDrag()
|
||||
.selector('.layer-notes .new')
|
||||
.surface(d3_select('#map').node())
|
||||
.origin(origin)
|
||||
.on('start', start)
|
||||
.on('move', move)
|
||||
.on('end', end);
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
context.install(edit);
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
context.ui().sidebar.hover.cancel();
|
||||
context.uninstall(edit);
|
||||
|
||||
context.surface()
|
||||
.selectAll('.active')
|
||||
.classed('active', false);
|
||||
|
||||
stopNudge();
|
||||
};
|
||||
|
||||
mode.behavior = drag;
|
||||
|
||||
return mode;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { t } from '../util/locale';
|
||||
import { behaviorDrawWay } from '../behavior';
|
||||
|
||||
|
||||
export function modeDrawLine(context, wayId, startGraph, affix) {
|
||||
export function modeDrawLine(context, wayID, startGraph, affix) {
|
||||
var mode = {
|
||||
button: 'line',
|
||||
id: 'draw-line'
|
||||
@@ -12,16 +12,16 @@ export function modeDrawLine(context, wayId, startGraph, affix) {
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
var way = context.entity(wayId);
|
||||
var way = context.entity(wayID);
|
||||
var index = (affix === 'prefix') ? 0 : undefined;
|
||||
var headId = (affix === 'prefix') ? way.first() : way.last();
|
||||
var headID = (affix === 'prefix') ? way.first() : way.last();
|
||||
|
||||
behavior = behaviorDrawWay(context, wayId, index, mode, startGraph)
|
||||
behavior = behaviorDrawWay(context, wayID, index, mode, startGraph)
|
||||
.tail(t('modes.draw_line.tail'));
|
||||
|
||||
var addNode = behavior.addNode;
|
||||
behavior.addNode = function(node, d) {
|
||||
if (node.id === headId) {
|
||||
if (node.id === headID) {
|
||||
behavior.finish();
|
||||
} else {
|
||||
addNode(node, d);
|
||||
@@ -38,7 +38,7 @@ export function modeDrawLine(context, wayId, startGraph, affix) {
|
||||
|
||||
|
||||
mode.selectedIDs = function() {
|
||||
return [wayId];
|
||||
return [wayID];
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
export { modeAddArea } from './add_area';
|
||||
export { modeAddLine } from './add_line';
|
||||
export { modeAddPoint } from './add_point';
|
||||
export { modeAddNote } from './add_note';
|
||||
export { modeBrowse } from './browse';
|
||||
export { modeDragNode } from './drag_node';
|
||||
export { modeDragNote } from './drag_note';
|
||||
export { modeDrawArea } from './draw_area';
|
||||
export { modeDrawLine } from './draw_line';
|
||||
export { modeMove } from './move';
|
||||
export { modeRotate } from './rotate';
|
||||
export { modeSave } from './save';
|
||||
export { modeSelect } from './select';
|
||||
export { modeSelectData } from './select_data';
|
||||
export { modeSelectNote } from './select_note';
|
||||
|
||||
@@ -120,9 +120,7 @@ export function modeRotate(context, entityIDs) {
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', doRotate)
|
||||
@@ -141,9 +139,7 @@ export function modeRotate(context, entityIDs) {
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
behaviors.forEach(context.uninstall);
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', null)
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeDragNode } from './drag_node';
|
||||
import { modeDragNote } from './drag_note';
|
||||
import * as Operations from '../operations/index';
|
||||
import { uiEditMenu, uiSelectionList } from '../ui';
|
||||
import { uiCmd } from '../ui/cmd';
|
||||
@@ -63,7 +64,8 @@ export function modeSelect(context, selectedIDs) {
|
||||
behaviorHover(context),
|
||||
behaviorSelect(context),
|
||||
behaviorLasso(context),
|
||||
modeDragNode(context).restoreSelectedIDs(selectedIDs).behavior
|
||||
modeDragNode(context).restoreSelectedIDs(selectedIDs).behavior,
|
||||
modeDragNote(context).behavior
|
||||
];
|
||||
var inspector;
|
||||
var editMenu;
|
||||
@@ -449,9 +451,7 @@ export function modeSelect(context, selectedIDs) {
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
behaviors.forEach(context.install);
|
||||
|
||||
keybinding
|
||||
.on(['[', 'pgup'], previousVertex)
|
||||
@@ -520,10 +520,7 @@ export function modeSelect(context, selectedIDs) {
|
||||
if (timeout) window.clearTimeout(timeout);
|
||||
if (inspector) wrap.call(inspector.close);
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
|
||||
behaviors.forEach(context.uninstall);
|
||||
keybinding.off();
|
||||
closeMenu();
|
||||
editMenu = undefined;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
|
||||
import {
|
||||
behaviorBreathe,
|
||||
behaviorHover,
|
||||
behaviorLasso,
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
import {
|
||||
modeDragNode,
|
||||
modeDragNote
|
||||
} from '../modes';
|
||||
|
||||
import { modeBrowse } from './browse';
|
||||
import { uiDataEditor } from '../ui';
|
||||
|
||||
|
||||
export function modeSelectData(context, selectedDatum) {
|
||||
var mode = {
|
||||
id: 'select-data',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
var keybinding = d3_keybinding('select-data');
|
||||
var dataEditor = uiDataEditor(context);
|
||||
|
||||
var behaviors = [
|
||||
behaviorBreathe(context),
|
||||
behaviorHover(context),
|
||||
behaviorSelect(context),
|
||||
behaviorLasso(context),
|
||||
modeDragNode(context).behavior,
|
||||
modeDragNote(context).behavior
|
||||
];
|
||||
|
||||
|
||||
// class the data as selected, or return to browse mode if the data is gone
|
||||
function selectData(drawn) {
|
||||
var selection = context.surface().selectAll('.layer-mapdata .data' + selectedDatum.__featurehash__);
|
||||
|
||||
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;
|
||||
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
} else {
|
||||
selection.classed('selected', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function esc() {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
behaviors.forEach(context.install);
|
||||
keybinding.on('⎋', esc, true);
|
||||
d3_select(document).call(keybinding);
|
||||
|
||||
selectData();
|
||||
|
||||
context.ui().sidebar
|
||||
.show(dataEditor.datum(selectedDatum));
|
||||
|
||||
context.map()
|
||||
.on('drawn.select-data', selectData);
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
behaviors.forEach(context.uninstall);
|
||||
keybinding.off();
|
||||
|
||||
context.surface()
|
||||
.selectAll('.layer-mapdata .selected')
|
||||
.classed('selected hover', false);
|
||||
|
||||
context.map()
|
||||
.on('drawn.select-data', null);
|
||||
|
||||
context.ui().sidebar
|
||||
.hide();
|
||||
};
|
||||
|
||||
|
||||
return mode;
|
||||
}
|
||||
@@ -6,11 +6,17 @@ import {
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
|
||||
import {
|
||||
behaviorBreathe,
|
||||
behaviorHover,
|
||||
behaviorLasso,
|
||||
behaviorSelect
|
||||
} from '../behavior';
|
||||
|
||||
import {
|
||||
modeDragNode,
|
||||
modeDragNote
|
||||
} from '../modes';
|
||||
|
||||
import { services } from '../services';
|
||||
import { modeBrowse } from './browse';
|
||||
import { uiNoteEditor } from '../ui';
|
||||
@@ -18,7 +24,7 @@ import { uiNoteEditor } from '../ui';
|
||||
|
||||
export function modeSelectNote(context, selectedNoteID) {
|
||||
var mode = {
|
||||
id: 'select_note',
|
||||
id: 'select-note',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
@@ -34,11 +40,16 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
});
|
||||
|
||||
var behaviors = [
|
||||
behaviorBreathe(context),
|
||||
behaviorHover(context),
|
||||
behaviorSelect(context),
|
||||
behaviorLasso(context),
|
||||
modeDragNode(context).behavior,
|
||||
modeDragNote(context).behavior
|
||||
];
|
||||
|
||||
var newFeature = false;
|
||||
|
||||
|
||||
function checkSelectedID() {
|
||||
if (!osm) return;
|
||||
@@ -50,72 +61,73 @@ export function modeSelectNote(context, selectedNoteID) {
|
||||
}
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
// class the note as selected, or return to browse mode if the note is gone
|
||||
function selectNote(drawn) {
|
||||
if (!checkSelectedID()) return;
|
||||
|
||||
// class the note as selected, or return to browse mode if the note is gone
|
||||
function selectNote(drawn) {
|
||||
if (!checkSelectedID()) return;
|
||||
var selection = context.surface().selectAll('.layer-notes .note-' + selectedNoteID);
|
||||
|
||||
var selection = context.surface()
|
||||
.selectAll('.note-' + selectedNoteID);
|
||||
|
||||
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;
|
||||
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
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;
|
||||
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
}
|
||||
|
||||
function esc() {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
context.selectedNoteID(selectedNoteID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function esc() {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
|
||||
mode.newFeature = function(_) {
|
||||
if (!arguments.length) return newFeature;
|
||||
newFeature = _;
|
||||
return mode;
|
||||
};
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
var note = checkSelectedID();
|
||||
if (!note) return;
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
behaviors.forEach(context.install);
|
||||
keybinding.on('⎋', esc, true);
|
||||
d3_select(document).call(keybinding);
|
||||
|
||||
keybinding
|
||||
.on('⎋', esc, true);
|
||||
|
||||
d3_select(document)
|
||||
.call(keybinding);
|
||||
selectNote();
|
||||
|
||||
context.ui().sidebar
|
||||
.show(noteEditor.note(note));
|
||||
|
||||
context.map()
|
||||
.on('drawn.select', selectNote);
|
||||
|
||||
selectNote();
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
|
||||
behaviors.forEach(context.uninstall);
|
||||
keybinding.off();
|
||||
|
||||
context.surface()
|
||||
.selectAll('.note.selected')
|
||||
.classed('selected hovered', false);
|
||||
.selectAll('.layer-notes .selected')
|
||||
.classed('selected hover', false);
|
||||
|
||||
context.map()
|
||||
.on('drawn.select', null);
|
||||
|
||||
context.ui().sidebar
|
||||
.hide();
|
||||
|
||||
context.selectedNoteID(null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import _some from 'lodash-es/some';
|
||||
|
||||
import { actionDetachNode, actionMoveNode } from '../actions';
|
||||
import { behaviorOperation } from '../behavior';
|
||||
import { modeMove } from '../modes';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
|
||||
export function operationDetachNode(selectedIDs, context) {
|
||||
var nodeID = selectedIDs.length && selectedIDs[0];
|
||||
var action = actionDetachNode(nodeID);
|
||||
|
||||
var operation = function () {
|
||||
context.perform(action); // do the detach
|
||||
|
||||
var mouse = context.map().mouseCoordinates();
|
||||
if (mouse.some(isNaN)) {
|
||||
enterMoveMode();
|
||||
|
||||
} else {
|
||||
// move detached node to the mouse location (transitioned)
|
||||
context.perform(actionMoveNode(nodeID, mouse));
|
||||
|
||||
// after transition completes, put at final mouse location and enter move mode.
|
||||
window.setTimeout(function() {
|
||||
mouse = context.map().mouseCoordinates();
|
||||
context.replace(actionMoveNode(nodeID, mouse));
|
||||
enterMoveMode();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function enterMoveMode() {
|
||||
var baseGraph = context.graph();
|
||||
context.enter(modeMove(context, [nodeID], baseGraph));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.available = function () {
|
||||
if (selectedIDs.length !== 1) return false;
|
||||
|
||||
var graph = context.graph();
|
||||
var entity = graph.hasEntity(nodeID);
|
||||
if (!entity) return false;
|
||||
|
||||
return entity.type === 'node' &&
|
||||
entity.hasInterestingTags() &&
|
||||
graph.parentWays(entity).length > 0;
|
||||
};
|
||||
|
||||
|
||||
operation.disabled = function () {
|
||||
var reason;
|
||||
if (_some(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
|
||||
operation.tooltip = function () {
|
||||
var disableReason = operation.disabled();
|
||||
if (disableReason) {
|
||||
return t('operations.detach_node.' + disableReason,
|
||||
{ relation: context.presets().item('type/restriction').name() });
|
||||
} else {
|
||||
return t('operations.detach_node.description');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.annotation = function () {
|
||||
return t('operations.detach_node.annotation');
|
||||
};
|
||||
|
||||
|
||||
operation.id = 'detach-node';
|
||||
operation.keys = [t('operations.detach_node.key')];
|
||||
operation.title = t('operations.detach_node.title');
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -10,3 +10,4 @@ export { operationReverse } from './reverse';
|
||||
export { operationRotate } from './rotate';
|
||||
export { operationSplit } from './split';
|
||||
export { operationStraighten } from './straighten';
|
||||
export { operationDetachNode } from './detach_node';
|
||||
|
||||
+6
-2
@@ -39,7 +39,7 @@ _extend(osmNote.prototype, {
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
this.id = osmNote.id();
|
||||
this.id = osmNote.id() + ''; // as string
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -50,11 +50,15 @@ _extend(osmNote.prototype, {
|
||||
},
|
||||
|
||||
update: function(attrs) {
|
||||
return osmNote(this, attrs, {v: 1 + (this.v || 0)});
|
||||
return osmNote(this, attrs); // {v: 1 + (this.v || 0)}
|
||||
},
|
||||
|
||||
isNew: function() {
|
||||
return this.id < 0;
|
||||
},
|
||||
|
||||
move: function(loc) {
|
||||
return this.update({ loc: loc });
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -42,8 +42,7 @@ _extend(osmRelation.prototype, {
|
||||
|
||||
|
||||
copy: function(resolver, copies) {
|
||||
if (copies[this.id])
|
||||
return copies[this.id];
|
||||
if (copies[this.id]) return copies[this.id];
|
||||
|
||||
var copy = osmEntity.prototype.copy.call(this, resolver, copies);
|
||||
|
||||
|
||||
+18
-19
@@ -31,8 +31,7 @@ _extend(osmWay.prototype, {
|
||||
|
||||
|
||||
copy: function(resolver, copies) {
|
||||
if (copies[this.id])
|
||||
return copies[this.id];
|
||||
if (copies[this.id]) return copies[this.id];
|
||||
|
||||
var copy = osmEntity.prototype.copy.call(this, resolver, copies);
|
||||
|
||||
@@ -239,9 +238,9 @@ _extend(osmWay.prototype, {
|
||||
unclose: function() {
|
||||
if (!this.isClosed()) return this;
|
||||
|
||||
var nodes = this.nodes.slice(),
|
||||
connector = this.first(),
|
||||
i = nodes.length - 1;
|
||||
var nodes = this.nodes.slice();
|
||||
var connector = this.first();
|
||||
var i = nodes.length - 1;
|
||||
|
||||
// remove trailing connectors..
|
||||
while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
|
||||
@@ -260,9 +259,9 @@ _extend(osmWay.prototype, {
|
||||
// Consecutive duplicates are eliminated including existing ones.
|
||||
// Circularity is always preserved when adding a node.
|
||||
addNode: function(id, index) {
|
||||
var nodes = this.nodes.slice(),
|
||||
isClosed = this.isClosed(),
|
||||
max = isClosed ? nodes.length - 1 : nodes.length;
|
||||
var nodes = this.nodes.slice();
|
||||
var isClosed = this.isClosed();
|
||||
var max = isClosed ? nodes.length - 1 : nodes.length;
|
||||
|
||||
if (index === undefined) {
|
||||
index = max;
|
||||
@@ -309,9 +308,9 @@ _extend(osmWay.prototype, {
|
||||
// Consecutive duplicates are eliminated including existing ones.
|
||||
// Circularity is preserved when updating a node.
|
||||
updateNode: function(id, index) {
|
||||
var nodes = this.nodes.slice(),
|
||||
isClosed = this.isClosed(),
|
||||
max = nodes.length - 1;
|
||||
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);
|
||||
@@ -353,13 +352,13 @@ _extend(osmWay.prototype, {
|
||||
// Replaces each occurrence of node id needle with replacement.
|
||||
// Consecutive duplicates are eliminated including existing ones.
|
||||
// Circularity is preserved.
|
||||
replaceNode: function(needle, replacement) {
|
||||
var nodes = this.nodes.slice(),
|
||||
isClosed = this.isClosed();
|
||||
replaceNode: function(needleID, replacementID) {
|
||||
var nodes = this.nodes.slice();
|
||||
var isClosed = this.isClosed();
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i] === needle) {
|
||||
nodes[i] = replacement;
|
||||
if (nodes[i] === needleID) {
|
||||
nodes[i] = replacementID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,12 +373,12 @@ _extend(osmWay.prototype, {
|
||||
},
|
||||
|
||||
|
||||
// Removes each occurrence of node id needle with replacement.
|
||||
// Removes each occurrence of node id.
|
||||
// Consecutive duplicates are eliminated including existing ones.
|
||||
// Circularity is preserved.
|
||||
removeNode: function(id) {
|
||||
var nodes = this.nodes.slice(),
|
||||
isClosed = this.isClosed();
|
||||
var nodes = this.nodes.slice();
|
||||
var isClosed = this.isClosed();
|
||||
|
||||
nodes = nodes
|
||||
.filter(function(node) { return node !== id; })
|
||||
|
||||
@@ -21,7 +21,7 @@ export function presetField(id, field) {
|
||||
|
||||
|
||||
field.label = function() {
|
||||
return field.t('label', {'default': id});
|
||||
return field.overrideLabel || field.t('label', {'default': id});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { interpolateNumber as d3_interpolateNumber } from 'd3-interpolate';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import whichPolygon from 'which-polygon';
|
||||
|
||||
import { data } from '../../data';
|
||||
import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo';
|
||||
import { rendererBackgroundSource } from './background_source';
|
||||
@@ -168,12 +170,9 @@ export function rendererBackground(context) {
|
||||
.filter(function (d) { return !d.source().isLocatorOverlay() && !d.source().isHidden(); })
|
||||
.forEach(function (d) { imageryUsed.push(d.source().imageryUsed()); });
|
||||
|
||||
var gpx = context.layers().layer('gpx');
|
||||
if (gpx && gpx.enabled() && gpx.hasGpx()) {
|
||||
// Include a string like '.gpx data file' or '.geojson data file'
|
||||
var match = gpx.getSrc().match(/(kml|gpx|(?:geo)?json)$/i);
|
||||
var extension = match ? ('.' + match[0].toLowerCase() + ' ') : '';
|
||||
imageryUsed.push(extension + 'data file');
|
||||
var data = context.layers().layer('data');
|
||||
if (data && data.enabled() && data.hasData()) {
|
||||
imageryUsed.push(data.getSrc());
|
||||
}
|
||||
|
||||
var streetside = context.layers().layer('streetside');
|
||||
@@ -181,14 +180,6 @@ export function rendererBackground(context) {
|
||||
imageryUsed.push('Bing Streetside');
|
||||
}
|
||||
|
||||
var mvt = context.layers().layer('mvt');
|
||||
if (mvt && mvt.enabled() && mvt.hasMvt()) {
|
||||
// Include a string like '.mvt data file' or '.geojson data file'
|
||||
var matchmvt = mvt.getSrc().match(/(pbf|mvt|(?:geo)?json)$/i);
|
||||
var extensionmvt = matchmvt ? ('.' + matchmvt[0].toLowerCase() + ' ') : '';
|
||||
imageryUsed.push(extensionmvt + 'data file');
|
||||
}
|
||||
|
||||
var mapillary_images = context.layers().layer('mapillary-images');
|
||||
if (mapillary_images && mapillary_images.enabled()) {
|
||||
imageryUsed.push('Mapillary Images');
|
||||
@@ -209,18 +200,24 @@ export function rendererBackground(context) {
|
||||
|
||||
|
||||
background.sources = function(extent) {
|
||||
if (!data.imagery || !data.imagery.query) return []; // called before init()?
|
||||
|
||||
var matchIDs = {};
|
||||
var matchImagery = data.imagery.query.bbox(extent.rectangle(), true) || [];
|
||||
matchImagery.forEach(function(d) { matchIDs[d.id] = true; });
|
||||
|
||||
return _backgroundSources.filter(function(source) {
|
||||
return source.intersects(extent);
|
||||
return matchIDs[source.id] || !source.polygon; // no polygon = worldwide
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
background.dimensions = function(_) {
|
||||
if (!_) return;
|
||||
baseLayer.dimensions(_);
|
||||
background.dimensions = function(d) {
|
||||
if (!d) return;
|
||||
baseLayer.dimensions(d);
|
||||
|
||||
_overlayLayers.forEach(function(layer) {
|
||||
layer.dimensions(_);
|
||||
layer.dimensions(d);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -366,15 +363,37 @@ export function rendererBackground(context) {
|
||||
return geoExtent([args[2], args[1]]);
|
||||
}
|
||||
|
||||
var dataImagery = data.imagery || [];
|
||||
var q = utilStringQs(window.location.hash.substring(1));
|
||||
var requested = q.background || q.layer;
|
||||
var extent = parseMap(q.map);
|
||||
var first;
|
||||
var best;
|
||||
|
||||
|
||||
data.imagery = data.imagery || [];
|
||||
data.imagery.features = {};
|
||||
|
||||
// build efficient index and querying for data.imagery
|
||||
var features = data.imagery.map(function(source) {
|
||||
if (!source.polygon) return null;
|
||||
var feature = {
|
||||
type: 'Feature',
|
||||
properties: { id: source.id },
|
||||
geometry: { type: 'MultiPolygon', coordinates: [ source.polygon ] }
|
||||
};
|
||||
|
||||
data.imagery.features[source.id] = feature;
|
||||
return feature;
|
||||
}).filter(Boolean);
|
||||
|
||||
data.imagery.query = whichPolygon({
|
||||
type: 'FeatureCollection',
|
||||
features: features
|
||||
});
|
||||
|
||||
|
||||
// Add all the available imagery sources
|
||||
_backgroundSources = dataImagery.map(function(source) {
|
||||
_backgroundSources = data.imagery.map(function(source) {
|
||||
if (source.type === 'bing') {
|
||||
return rendererBackgroundSource.Bing(source, dispatch);
|
||||
} else if (/^EsriWorldImagery/.test(source.id)) {
|
||||
@@ -431,16 +450,9 @@ export function rendererBackground(context) {
|
||||
});
|
||||
|
||||
if (q.gpx) {
|
||||
var gpx = context.layers().layer('gpx');
|
||||
var gpx = context.layers().layer('data');
|
||||
if (gpx) {
|
||||
gpx.url(q.gpx);
|
||||
}
|
||||
}
|
||||
|
||||
if (q.mvt) {
|
||||
var mvt = context.layers().layer('mvt');
|
||||
if (mvt) {
|
||||
mvt.url(q.mvt);
|
||||
gpx.url(q.gpx, '.gpx');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,13 +9,7 @@ import {
|
||||
import { json as d3_json } from 'd3-request';
|
||||
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import {
|
||||
geoExtent,
|
||||
geoPolygonIntersectsPolygon,
|
||||
geoSphericalDistance
|
||||
} from '../geo';
|
||||
|
||||
import { geoExtent, geoSphericalDistance } from '../geo';
|
||||
import { utilDetect } from '../util/detect';
|
||||
|
||||
|
||||
@@ -48,10 +42,10 @@ export function rendererBackgroundSource(data) {
|
||||
var best = !!source.best;
|
||||
var template = source.template;
|
||||
|
||||
source.scaleExtent = data.scaleExtent || [0, 22];
|
||||
source.tileSize = data.tileSize || 256;
|
||||
source.zoomExtent = data.zoomExtent || [0, 22];
|
||||
source.overzoom = data.overzoom !== false;
|
||||
|
||||
|
||||
source.offset = function(_) {
|
||||
if (!arguments.length) return offset;
|
||||
offset = _;
|
||||
@@ -116,7 +110,7 @@ export function rendererBackgroundSource(data) {
|
||||
var lat = Math.atan(sinh(Math.PI * (1 - 2 * y / zoomSize)));
|
||||
|
||||
switch (this.projection) {
|
||||
case 'EPSG:4326': // todo: alternative codes of WGS 84?
|
||||
case 'EPSG:4326':
|
||||
return {
|
||||
x: lon * 180 / Math.PI,
|
||||
y: lat * 180 / Math.PI
|
||||
@@ -133,8 +127,8 @@ export function rendererBackgroundSource(data) {
|
||||
var minXmaxY = tileToProjectedCoords(coord[0], coord[1], coord[2]);
|
||||
var maxXminY = tileToProjectedCoords(coord[0]+1, coord[1]+1, coord[2]);
|
||||
return template
|
||||
.replace('{width}', 256)
|
||||
.replace('{height}', 256)
|
||||
.replace('{width}', this.tileSize)
|
||||
.replace('{height}', this.tileSize)
|
||||
.replace('{proj}', this.projection)
|
||||
.replace('{bbox}', minXmaxY.x + ',' + maxXminY.y + ',' + maxXminY.x + ',' + minXmaxY.y);
|
||||
}
|
||||
@@ -162,17 +156,9 @@ export function rendererBackgroundSource(data) {
|
||||
};
|
||||
|
||||
|
||||
source.intersects = function(extent) {
|
||||
extent = extent.polygon();
|
||||
return !data.polygon || data.polygon.some(function(polygon) {
|
||||
return geoPolygonIntersectsPolygon(polygon, extent, true);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
source.validZoom = function(z) {
|
||||
return source.scaleExtent[0] <= z &&
|
||||
(source.overzoom || source.scaleExtent[1] > z);
|
||||
return source.zoomExtent[0] <= z &&
|
||||
(source.overzoom || source.zoomExtent[1] > z);
|
||||
};
|
||||
|
||||
|
||||
@@ -330,8 +316,8 @@ rendererBackgroundSource.Esri = function(data) {
|
||||
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 because responses to leverage cache
|
||||
var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+/, 'tilemap') + '/' + z + '/' + y + ' /' + x + '/8/8';
|
||||
// fetch an 8x8 grid to leverage cache
|
||||
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
|
||||
d3_json(tilemapUrl, function (err, tilemap) {
|
||||
@@ -347,13 +333,13 @@ rendererBackgroundSource.Esri = function(data) {
|
||||
}
|
||||
|
||||
// if any tiles are missing at level 20 we restrict maxZoom to 19
|
||||
esri.scaleExtent[1] = (hasTiles ? 22 : 19);
|
||||
esri.zoomExtent[1] = (hasTiles ? 22 : 19);
|
||||
});
|
||||
};
|
||||
|
||||
esri.getMetadata = function(center, tileCoord, callback) {
|
||||
var tileId = tileCoord.slice(0, 3).join('/');
|
||||
var zoom = Math.min(tileCoord[2], esri.scaleExtent[1]);
|
||||
var zoom = Math.min(tileCoord[2], esri.zoomExtent[1]);
|
||||
var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be)
|
||||
var unknown = t('info_panels.background.unknown');
|
||||
var metadataLayer;
|
||||
|
||||
+10
-4
@@ -213,7 +213,6 @@ export function rendererMap(context) {
|
||||
|
||||
context.on('enter.map', function() {
|
||||
if (map.editable() && !_transformed) {
|
||||
|
||||
// redraw immediately any objects affected by a change in selectedIDs.
|
||||
var graph = context.graph();
|
||||
var selectedAndParents = {};
|
||||
@@ -241,7 +240,6 @@ export function rendererMap(context) {
|
||||
|
||||
dispatch.call('drawn', this, { full: false });
|
||||
|
||||
|
||||
// redraw everything else later
|
||||
scheduleRedraw();
|
||||
}
|
||||
@@ -350,7 +348,7 @@ export function rendererMap(context) {
|
||||
surface.selectAll('.layer-osm *').remove();
|
||||
|
||||
var mode = context.mode();
|
||||
if (mode && mode.id !== 'save' && mode.id !== 'select_note') {
|
||||
if (mode && mode.id !== 'save' && mode.id !== 'select-note' && mode.id !== 'select-data') {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
@@ -483,7 +481,7 @@ export function rendererMap(context) {
|
||||
|
||||
// OSM
|
||||
if (map.editable()) {
|
||||
context.loadTiles(projection, dimensions);
|
||||
context.loadTiles(projection);
|
||||
drawVector(difference, extent);
|
||||
} else {
|
||||
editOff();
|
||||
@@ -849,6 +847,14 @@ export function rendererMap(context) {
|
||||
};
|
||||
|
||||
|
||||
map.notesEditable = function() {
|
||||
var noteLayer = surface.selectAll('.data-layer-notes');
|
||||
if (!noteLayer.empty() && noteLayer.classed('disabled')) return false;
|
||||
|
||||
return map.zoom() >= context.minEditableZoom();
|
||||
};
|
||||
|
||||
|
||||
map.minzoom = function(_) {
|
||||
if (!arguments.length) return minzoom;
|
||||
minzoom = _;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
||||
import { geoScaleToZoom, geoVecLength } from '../geo';
|
||||
import { utilPrefixCSSProperty } from '../util';
|
||||
import { utilPrefixCSSProperty, utilTiler } from '../util';
|
||||
|
||||
|
||||
export function rendererTileLayer(context) {
|
||||
var tileSize = 256;
|
||||
var transformProp = utilPrefixCSSProperty('Transform');
|
||||
var geotile = d3_geoTile();
|
||||
var tiler = utilTiler();
|
||||
|
||||
var _tileSize = 256;
|
||||
var _projection;
|
||||
var _cache = {};
|
||||
var _tileOrigin;
|
||||
@@ -18,22 +17,9 @@ export function rendererTileLayer(context) {
|
||||
var _source;
|
||||
|
||||
|
||||
// blacklist overlay tiles around Null Island..
|
||||
function nearNullIsland(x, y, z) {
|
||||
if (z >= 7) {
|
||||
var center = Math.pow(2, z - 1);
|
||||
var width = Math.pow(2, z - 6);
|
||||
var min = center - (width / 2);
|
||||
var max = center + (width / 2) - 1;
|
||||
return x >= min && x <= max && y >= min && y <= max;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function tileSizeAtZoom(d, z) {
|
||||
var EPSILON = 0.002;
|
||||
return ((tileSize * Math.pow(2, z - d[2])) / tileSize) + EPSILON;
|
||||
var EPSILON = 0.002; // close seams
|
||||
return ((_tileSize * Math.pow(2, z - d[2])) / _tileSize) + EPSILON;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +64,7 @@ export function rendererTileLayer(context) {
|
||||
|
||||
// Update tiles based on current state of `projection`.
|
||||
function background(selection) {
|
||||
_zoom = geoScaleToZoom(_projection.scale(), tileSize);
|
||||
_zoom = geoScaleToZoom(_projection.scale(), _tileSize);
|
||||
|
||||
var pixelOffset;
|
||||
if (_source) {
|
||||
@@ -95,7 +81,7 @@ export function rendererTileLayer(context) {
|
||||
_projection.translate()[1] + pixelOffset[1]
|
||||
];
|
||||
|
||||
geotile
|
||||
tiler
|
||||
.scale(_projection.scale() * 2 * Math.PI)
|
||||
.translate(translate);
|
||||
|
||||
@@ -117,7 +103,9 @@ export function rendererTileLayer(context) {
|
||||
var showDebug = context.getDebug('tile') && !_source.overlay;
|
||||
|
||||
if (_source.validZoom(_zoom)) {
|
||||
geotile().forEach(function(d) {
|
||||
tiler.skipNullIsland(!!_source.overlay);
|
||||
|
||||
tiler().forEach(function(d) {
|
||||
addSource(d);
|
||||
if (d[3] === '') return;
|
||||
if (typeof d[3] !== 'string') return; // Workaround for #2295
|
||||
@@ -128,15 +116,11 @@ export function rendererTileLayer(context) {
|
||||
});
|
||||
|
||||
requests = uniqueBy(requests, 3).filter(function(r) {
|
||||
if (!!_source.overlay && nearNullIsland(r[0], r[1], r[2])) {
|
||||
return false;
|
||||
}
|
||||
// don't re-request tiles which have failed in the past
|
||||
return _cache[r[3]] !== false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function load(d) {
|
||||
_cache[d[3]] = true;
|
||||
d3_select(this)
|
||||
@@ -156,7 +140,7 @@ export function rendererTileLayer(context) {
|
||||
}
|
||||
|
||||
function imageTransform(d) {
|
||||
var ts = tileSize * Math.pow(2, _zoom - d[2]);
|
||||
var ts = _tileSize * Math.pow(2, _zoom - d[2]);
|
||||
var scale = tileSizeAtZoom(d, _zoom);
|
||||
return 'translate(' +
|
||||
((d[0] * ts) - _tileOrigin[0]) + 'px,' +
|
||||
@@ -165,7 +149,7 @@ export function rendererTileLayer(context) {
|
||||
}
|
||||
|
||||
function tileCenter(d) {
|
||||
var ts = tileSize * Math.pow(2, _zoom - d[2]);
|
||||
var ts = _tileSize * Math.pow(2, _zoom - d[2]);
|
||||
return [
|
||||
((d[0] * ts) - _tileOrigin[0] + (ts / 2)),
|
||||
((d[1] * ts) - _tileOrigin[1] + (ts / 2))
|
||||
@@ -180,7 +164,7 @@ export function rendererTileLayer(context) {
|
||||
|
||||
// Pick a representative tile near the center of the viewport
|
||||
// (This is useful for sampling the imagery vintage)
|
||||
var dims = geotile.size();
|
||||
var dims = tiler.size();
|
||||
var mapCenter = [dims[0] / 2, dims[1] / 2];
|
||||
var minDist = Math.max(dims[0], dims[1]);
|
||||
var nearCenter;
|
||||
@@ -277,8 +261,8 @@ export function rendererTileLayer(context) {
|
||||
|
||||
|
||||
background.dimensions = function(_) {
|
||||
if (!arguments.length) return geotile.size();
|
||||
geotile.size(_);
|
||||
if (!arguments.length) return tiler.size();
|
||||
tiler.size(_);
|
||||
return background;
|
||||
};
|
||||
|
||||
@@ -286,8 +270,9 @@ export function rendererTileLayer(context) {
|
||||
background.source = function(_) {
|
||||
if (!arguments.length) return _source;
|
||||
_source = _;
|
||||
_tileSize = _source.tileSize;
|
||||
_cache = {};
|
||||
geotile.scaleExtent(_source.scaleExtent);
|
||||
tiler.tileSize(_source.tileSize).zoomExtent(_source.zoomExtent);
|
||||
return background;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
import serviceMapillary from './mapillary';
|
||||
import serviceMapRules from './maprules';
|
||||
import serviceNominatim from './nominatim';
|
||||
import serviceOpenstreetcam from './openstreetcam';
|
||||
import serviceOsm from './osm';
|
||||
import serviceStreetside from './streetside';
|
||||
import serviceTaginfo from './taginfo';
|
||||
import serviceVectorTile from './vector_tile';
|
||||
import serviceWikidata from './wikidata';
|
||||
import serviceWikipedia from './wikipedia';
|
||||
|
||||
|
||||
export var services = {
|
||||
geocoder: serviceNominatim,
|
||||
mapillary: serviceMapillary,
|
||||
openstreetcam: serviceOpenstreetcam,
|
||||
osm: serviceOsm,
|
||||
maprules: serviceMapRules,
|
||||
streetside: serviceStreetside,
|
||||
taginfo: serviceTaginfo,
|
||||
vectorTile: serviceVectorTile,
|
||||
wikidata: serviceWikidata,
|
||||
wikipedia: serviceWikipedia
|
||||
};
|
||||
|
||||
export {
|
||||
serviceMapillary,
|
||||
serviceMapRules,
|
||||
serviceNominatim,
|
||||
serviceOpenstreetcam,
|
||||
serviceOsm,
|
||||
serviceStreetside,
|
||||
serviceTaginfo,
|
||||
serviceVectorTile,
|
||||
serviceWikidata,
|
||||
serviceWikipedia
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* global Mapillary:false */
|
||||
import _filter from 'lodash-es/filter';
|
||||
import _find from 'lodash-es/find';
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
@@ -18,10 +17,10 @@ import {
|
||||
|
||||
import rbush from 'rbush';
|
||||
|
||||
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
||||
import { geoExtent } from '../geo';
|
||||
import { geoExtent, geoScaleToZoom } from '../geo';
|
||||
import { svgDefs } from '../svg';
|
||||
import { utilQsString, utilRebind } from '../util';
|
||||
import { utilQsString, utilRebind, utilTiler } from '../util';
|
||||
|
||||
|
||||
var apibase = 'https://a.mapillary.com/v3/';
|
||||
var viewercss = 'mapillary-js/mapillary.min.css';
|
||||
@@ -29,7 +28,8 @@ var viewerjs = 'mapillary-js/mapillary.min.js';
|
||||
var clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi';
|
||||
var maxResults = 1000;
|
||||
var tileZoom = 14;
|
||||
var dispatch = d3_dispatch('loadedImages', 'loadedSigns');
|
||||
var tiler = utilTiler().zoomExtent([tileZoom, tileZoom]).skipNullIsland(true);
|
||||
var dispatch = d3_dispatch('loadedImages', 'loadedSigns', 'bearingChanged');
|
||||
var _mlyFallback = false;
|
||||
var _mlyCache;
|
||||
var _mlyClicks;
|
||||
@@ -42,18 +42,6 @@ function abortRequest(i) {
|
||||
}
|
||||
|
||||
|
||||
function nearNullIsland(x, y, z) {
|
||||
if (z >= 7) {
|
||||
var center = Math.pow(2, z - 1);
|
||||
var width = Math.pow(2, z - 6);
|
||||
var min = center - (width / 2);
|
||||
var max = center + (width / 2) - 1;
|
||||
return x >= min && x <= max && y >= min && y <= max;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function maxPageAtZoom(z) {
|
||||
if (z < 15) return 2;
|
||||
if (z === 15) return 5;
|
||||
@@ -63,50 +51,22 @@ function maxPageAtZoom(z) {
|
||||
if (z > 18) return 80;
|
||||
}
|
||||
|
||||
function getTiles(projection) {
|
||||
var s = projection.scale() * 2 * Math.PI;
|
||||
var z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
var ts = 256 * Math.pow(2, z - tileZoom);
|
||||
var origin = [
|
||||
s / 2 - projection.translate()[0],
|
||||
s / 2 - projection.translate()[1]
|
||||
];
|
||||
|
||||
return d3_geoTile()
|
||||
.scaleExtent([tileZoom, tileZoom])
|
||||
.scale(s)
|
||||
.size(projection.clipExtent()[1])
|
||||
.translate(projection.translate())()
|
||||
.map(function(tile) {
|
||||
var x = tile[0] * ts - origin[0];
|
||||
var y = tile[1] * ts - origin[1];
|
||||
|
||||
return {
|
||||
id: tile.toString(),
|
||||
xyz: tile,
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadTiles(which, url, projection) {
|
||||
var s = projection.scale() * 2 * Math.PI;
|
||||
var currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
|
||||
var currZoom = Math.floor(geoScaleToZoom(projection.scale()));
|
||||
var tiles = tiler.getTiles(projection);
|
||||
|
||||
var tiles = getTiles(projection).filter(function(t) {
|
||||
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
|
||||
// abort inflight requests that are no longer needed
|
||||
var cache = _mlyCache[which];
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k.indexOf(tile.id + ',') === 0; });
|
||||
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
});
|
||||
|
||||
_filter(which.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); });
|
||||
if (!wanted) delete which.inflight[k];
|
||||
return !wanted;
|
||||
}).map(abortRequest);
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
loadNextTilePage(which, currZoom, url, tile);
|
||||
});
|
||||
@@ -410,6 +370,13 @@ export default {
|
||||
// load mapillary signs sprite
|
||||
var defs = context.container().select('defs');
|
||||
defs.call(svgDefs(context).addSprites, ['mapillary-sprite']);
|
||||
|
||||
// Register viewer resize handler
|
||||
context.ui().on('photoviewerResize', function() {
|
||||
if (_mlyViewer) {
|
||||
_mlyViewer.resize();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -504,6 +471,7 @@ export default {
|
||||
|
||||
_mlyViewer = new Mapillary.Viewer('mly', clientId, null, opts);
|
||||
_mlyViewer.on('nodechanged', nodeChanged);
|
||||
_mlyViewer.on('bearingchanged', bearingChanged);
|
||||
_mlyViewer.moveToKey(imageKey)
|
||||
.catch(function(e) { console.error('mly3', e); }); // eslint-disable-line no-console
|
||||
}
|
||||
@@ -538,6 +506,10 @@ export default {
|
||||
that.selectImage(undefined, node.key, true);
|
||||
}
|
||||
}
|
||||
|
||||
function bearingChanged(e) {
|
||||
dispatch.call('bearingChanged', undefined, e);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import _isMatch from 'lodash-es/isMatch';
|
||||
import _intersection from 'lodash-es/intersection';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _every from 'lodash-es/every';
|
||||
|
||||
var ruleChecks,
|
||||
validationRules;
|
||||
|
||||
var buildRuleChecks = function() {
|
||||
return {
|
||||
equals: function (equals) {
|
||||
return function(tags) {
|
||||
return _isMatch(tags, equals);
|
||||
};
|
||||
},
|
||||
notEquals: function (notEquals) {
|
||||
return function(tags) {
|
||||
return !_isMatch(tags, notEquals);
|
||||
};
|
||||
},
|
||||
absence: function(absence) {
|
||||
return function(tags) {
|
||||
return Object.keys(tags).indexOf(absence) === -1;
|
||||
};
|
||||
},
|
||||
presence: function(presence) {
|
||||
return function(tags) {
|
||||
return Object.keys(tags).indexOf(presence) > -1;
|
||||
};
|
||||
},
|
||||
greaterThan: function(greaterThan) {
|
||||
var key = Object.keys(greaterThan)[0];
|
||||
var value = greaterThan[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] > value;
|
||||
};
|
||||
},
|
||||
greaterThanEqual: function(greaterThanEqual) {
|
||||
var key = Object.keys(greaterThanEqual)[0];
|
||||
var value = greaterThanEqual[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] >= value;
|
||||
};
|
||||
},
|
||||
lessThan: function(lessThan) {
|
||||
var key = Object.keys(lessThan)[0];
|
||||
var value = lessThan[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] < value;
|
||||
};
|
||||
},
|
||||
lessThanEqual: function(lessThanEqual) {
|
||||
var key = Object.keys(lessThanEqual)[0];
|
||||
var value = lessThanEqual[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] <= value;
|
||||
};
|
||||
},
|
||||
positiveRegex: function(positiveRegex) {
|
||||
var tagKey = Object.keys(positiveRegex)[0];
|
||||
var expression = positiveRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
|
||||
return function(tags) {
|
||||
return regex.test(tags[tagKey]);
|
||||
};
|
||||
},
|
||||
negativeRegex: function(negativeRegex) {
|
||||
var tagKey = Object.keys(negativeRegex)[0];
|
||||
var expression = negativeRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
|
||||
return function(tags) {
|
||||
return !regex.test(tags[tagKey]);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
init: function() {
|
||||
ruleChecks = buildRuleChecks();
|
||||
validationRules = [];
|
||||
},
|
||||
// list of rules only relevant to tag checks...
|
||||
filterRuleChecks: function(selector) {
|
||||
return _reduce(Object.keys(selector), function(rules, key) {
|
||||
if (['geometry', 'error', 'warning'].indexOf(key) === -1) {
|
||||
rules.push(ruleChecks[key](selector[key]));
|
||||
}
|
||||
return rules;
|
||||
}, []);
|
||||
},
|
||||
// builds tagMap from mapcss-parse selector object...
|
||||
buildTagMap: function(selector) {
|
||||
var getRegexValues = function(regexes) {
|
||||
return regexes.map(function(regex) {
|
||||
return regex.replace(/\$|\^/g, '');
|
||||
});
|
||||
};
|
||||
|
||||
var selectorKeys = Object.keys(selector);
|
||||
var tagMap = _reduce(selectorKeys, 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]);
|
||||
|
||||
if (expectedTags.hasOwnProperty(selectorKey)) {
|
||||
values = values.concat(expectedTags[selectorKey]);
|
||||
}
|
||||
|
||||
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];
|
||||
expectedTags[tagKey] = [];
|
||||
|
||||
}
|
||||
|
||||
return expectedTags;
|
||||
}, {});
|
||||
|
||||
return tagMap;
|
||||
},
|
||||
// inspired by osmWay#isArea()
|
||||
inferGeometry: function(tagMap, areaKeys) {
|
||||
var lineKeys = {
|
||||
highway: {
|
||||
rest_area: true,
|
||||
services: true
|
||||
},
|
||||
railway: {
|
||||
roundhouse: true,
|
||||
station: true,
|
||||
traverser: true,
|
||||
turntable: true,
|
||||
wash: true
|
||||
}
|
||||
};
|
||||
var isAreaKeyBlackList = function(key) {
|
||||
return _intersection(tagMap[key], Object.keys(areaKeys[key])).length > 0;
|
||||
};
|
||||
var isLineKeysWhiteList = function(key) {
|
||||
return _intersection(tagMap[key], Object.keys(lineKeys[key])).length > 0;
|
||||
};
|
||||
|
||||
if (tagMap.hasOwnProperty('area')) {
|
||||
if (tagMap.area.indexOf('yes') > -1) {
|
||||
return 'area';
|
||||
}
|
||||
if (tagMap.area.indexOf('no') > -1) {
|
||||
return 'line';
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in tagMap) {
|
||||
if (key in areaKeys && !isAreaKeyBlackList(key)) {
|
||||
return 'area';
|
||||
}
|
||||
if (key in lineKeys && isLineKeysWhiteList(key)) {
|
||||
return 'area';
|
||||
}
|
||||
}
|
||||
|
||||
return 'line';
|
||||
},
|
||||
// adds from mapcss-parse selector check...
|
||||
addRule: function(selector, areaKeys) {
|
||||
var rule = {
|
||||
// checks relevant to mapcss-selector
|
||||
checks: this.filterRuleChecks(selector),
|
||||
// true if all conditions for a tag error are true..
|
||||
matches: function(entity) {
|
||||
return _every(this.checks, function(check) {
|
||||
return check(entity.tags);
|
||||
});
|
||||
},
|
||||
// borrowed from Way#isArea()
|
||||
inferredGeometry: this.inferGeometry(this.buildTagMap(selector), areaKeys),
|
||||
geometryMatches: function(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...
|
||||
findWarnings: function (entity, graph, warnings) {
|
||||
if (this.geometryMatches(entity, graph) && this.matches(entity)) {
|
||||
var type = Object.keys(selector).indexOf('error') > -1 ? 'error' : 'warning';
|
||||
warnings.push({
|
||||
id: 'mapcss_' + type,
|
||||
message: selector[type],
|
||||
entity: entity
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
validationRules.push(rule);
|
||||
},
|
||||
// returns validationRules...
|
||||
validationRules: function() { return validationRules; }
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import _filter from 'lodash-es/filter';
|
||||
import _find from 'lodash-es/find';
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
@@ -22,29 +21,29 @@ import {
|
||||
|
||||
import rbush from 'rbush';
|
||||
|
||||
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
||||
import { geoExtent } from '../geo';
|
||||
|
||||
import { geoExtent, geoScaleToZoom } from '../geo';
|
||||
import { utilDetect } from '../util/detect';
|
||||
|
||||
import {
|
||||
utilQsString,
|
||||
utilRebind,
|
||||
utilSetTransform
|
||||
utilSetTransform,
|
||||
utilTiler
|
||||
} from '../util';
|
||||
|
||||
|
||||
var apibase = 'https://openstreetcam.org',
|
||||
maxResults = 1000,
|
||||
tileZoom = 14,
|
||||
dispatch = d3_dispatch('loadedImages'),
|
||||
imgZoom = d3_zoom()
|
||||
.extent([[0, 0], [320, 240]])
|
||||
.translateExtent([[0, 0], [320, 240]])
|
||||
.scaleExtent([1, 15])
|
||||
.on('zoom', zoomPan),
|
||||
_oscCache,
|
||||
_oscSelectedImage;
|
||||
var apibase = 'https://openstreetcam.org';
|
||||
var maxResults = 1000;
|
||||
var tileZoom = 14;
|
||||
var tiler = utilTiler().zoomExtent([tileZoom, tileZoom]).skipNullIsland(true);
|
||||
var dispatch = d3_dispatch('loadedImages');
|
||||
var imgZoom = d3_zoom()
|
||||
.extent([[0, 0], [320, 240]])
|
||||
.translateExtent([[0, 0], [320, 240]])
|
||||
.scaleExtent([1, 15])
|
||||
.on('zoom', zoomPan);
|
||||
var _oscCache;
|
||||
var _oscSelectedImage;
|
||||
|
||||
|
||||
function abortRequest(i) {
|
||||
@@ -52,18 +51,6 @@ function abortRequest(i) {
|
||||
}
|
||||
|
||||
|
||||
function nearNullIsland(x, y, z) {
|
||||
if (z >= 7) {
|
||||
var center = Math.pow(2, z - 1),
|
||||
width = Math.pow(2, z - 6),
|
||||
min = center - (width / 2),
|
||||
max = center + (width / 2) - 1;
|
||||
return x >= min && x <= max && y >= min && y <= max;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function maxPageAtZoom(z) {
|
||||
if (z < 15) return 2;
|
||||
if (z === 15) return 5;
|
||||
@@ -74,48 +61,20 @@ function maxPageAtZoom(z) {
|
||||
}
|
||||
|
||||
|
||||
function getTiles(projection) {
|
||||
var s = projection.scale() * 2 * Math.PI,
|
||||
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
|
||||
ts = 256 * Math.pow(2, z - tileZoom),
|
||||
origin = [
|
||||
s / 2 - projection.translate()[0],
|
||||
s / 2 - projection.translate()[1]];
|
||||
|
||||
return d3_geoTile()
|
||||
.scaleExtent([tileZoom, tileZoom])
|
||||
.scale(s)
|
||||
.size(projection.clipExtent()[1])
|
||||
.translate(projection.translate())()
|
||||
.map(function(tile) {
|
||||
var x = tile[0] * ts - origin[0],
|
||||
y = tile[1] * ts - origin[1];
|
||||
|
||||
return {
|
||||
id: tile.toString(),
|
||||
xyz: tile,
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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 currZoom = Math.floor(geoScaleToZoom(projection.scale()));
|
||||
var tiles = tiler.getTiles(projection);
|
||||
|
||||
var tiles = getTiles(projection).filter(function(t) {
|
||||
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
|
||||
});
|
||||
// abort inflight requests that are no longer needed
|
||||
var cache = _oscCache[which];
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k.indexOf(tile.id + ',') === 0; });
|
||||
|
||||
_filter(which.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); });
|
||||
if (!wanted) delete which.inflight[k];
|
||||
return !wanted;
|
||||
}).map(abortRequest);
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
});
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
loadNextTilePage(which, currZoom, url, tile);
|
||||
@@ -129,12 +88,12 @@ function loadNextTilePage(which, currZoom, url, tile) {
|
||||
var maxPages = maxPageAtZoom(currZoom);
|
||||
var nextPage = cache.nextPage[tile.id] || 1;
|
||||
var params = utilQsString({
|
||||
ipp: maxResults,
|
||||
page: nextPage,
|
||||
// client_id: clientId,
|
||||
bbTopLeft: [bbox.maxY, bbox.minX].join(','),
|
||||
bbBottomRight: [bbox.minY, bbox.maxX].join(',')
|
||||
}, true);
|
||||
ipp: maxResults,
|
||||
page: nextPage,
|
||||
// client_id: clientId,
|
||||
bbTopLeft: [bbox.maxY, bbox.minX].join(','),
|
||||
bbBottomRight: [bbox.minY, bbox.maxX].join(',')
|
||||
}, true);
|
||||
|
||||
if (nextPage > maxPages) return;
|
||||
|
||||
@@ -160,8 +119,8 @@ function loadNextTilePage(which, currZoom, url, tile) {
|
||||
}
|
||||
|
||||
var features = data.currentPageItems.map(function(item) {
|
||||
var loc = [+item.lng, +item.lat],
|
||||
d;
|
||||
var loc = [+item.lng, +item.lat];
|
||||
var d;
|
||||
|
||||
if (which === 'images') {
|
||||
d = {
|
||||
@@ -209,14 +168,14 @@ function loadNextTilePage(which, currZoom, url, tile) {
|
||||
function partitionViewport(psize, projection) {
|
||||
var dimensions = projection.clipExtent()[1];
|
||||
psize = psize || 16;
|
||||
var cols = d3_range(0, dimensions[0], psize),
|
||||
rows = d3_range(0, dimensions[1], psize),
|
||||
partitions = [];
|
||||
var cols = d3_range(0, dimensions[0], psize);
|
||||
var rows = d3_range(0, dimensions[1], psize);
|
||||
var partitions = [];
|
||||
|
||||
rows.forEach(function(y) {
|
||||
cols.forEach(function(x) {
|
||||
var min = [x, y + psize],
|
||||
max = [x + psize, y];
|
||||
var min = [x, y + psize];
|
||||
var max = [x + psize, y];
|
||||
partitions.push(
|
||||
geoExtent(projection.invert(min), projection.invert(max)));
|
||||
});
|
||||
@@ -367,6 +326,16 @@ export default {
|
||||
.attr('class', 'osc-image-wrap');
|
||||
|
||||
|
||||
// Register viewer resize handler
|
||||
context.ui().on('photoviewerResize', function(dimensions) {
|
||||
imgZoom = d3_zoom()
|
||||
.extent([[0, 0], dimensions])
|
||||
.translateExtent([[0, 0], dimensions])
|
||||
.scaleExtent([1, 15])
|
||||
.on('zoom', zoomPan);
|
||||
});
|
||||
|
||||
|
||||
function rotate(deg) {
|
||||
return function() {
|
||||
if (!_oscSelectedImage) return;
|
||||
|
||||
+153
-97
@@ -2,7 +2,6 @@ import _chunk from 'lodash-es/chunk';
|
||||
import _cloneDeep from 'lodash-es/cloneDeep';
|
||||
import _extend from 'lodash-es/extend';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
import _filter from 'lodash-es/filter';
|
||||
import _find from 'lodash-es/find';
|
||||
import _groupBy from 'lodash-es/groupBy';
|
||||
import _isEmpty from 'lodash-es/isEmpty';
|
||||
@@ -17,7 +16,6 @@ import { xml as d3_xml } from 'd3-request';
|
||||
|
||||
import osmAuth from 'osm-auth';
|
||||
import { JXON } from '../util/jxon';
|
||||
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
||||
import { geoExtent, geoVecAdd } from '../geo';
|
||||
|
||||
import {
|
||||
@@ -31,10 +29,12 @@ import {
|
||||
import {
|
||||
utilRebind,
|
||||
utilIdleWorker,
|
||||
utilTiler,
|
||||
utilQsString
|
||||
} from '../util';
|
||||
|
||||
|
||||
var tiler = utilTiler();
|
||||
var dispatch = d3_dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded', 'loadedNotes');
|
||||
var urlroot = 'https://www.openstreetmap.org';
|
||||
var oauth = osmAuth({
|
||||
@@ -77,6 +77,17 @@ function abortRequest(i) {
|
||||
}
|
||||
|
||||
|
||||
function abortUnwantedRequests(cache, tiles) {
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k === tile.id; });
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getLoc(attrs) {
|
||||
var lon = attrs.lon && attrs.lon.value;
|
||||
var lat = attrs.lat && attrs.lat.value;
|
||||
@@ -159,6 +170,17 @@ function parseComments(comments) {
|
||||
}
|
||||
|
||||
|
||||
function encodeNoteRtree(note) {
|
||||
return {
|
||||
minX: note.loc[0],
|
||||
minY: note.loc[1],
|
||||
maxX: note.loc[0],
|
||||
maxY: note.loc[1],
|
||||
data: note
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var parsers = {
|
||||
node: function nodeData(obj, uid) {
|
||||
var attrs = obj.attributes;
|
||||
@@ -239,9 +261,10 @@ var parsers = {
|
||||
}
|
||||
|
||||
var note = new osmNote(props);
|
||||
var item = { minX: note.loc[0], minY: note.loc[1], maxX: note.loc[0], maxY: note.loc[1], data: note };
|
||||
_noteCache.rtree.insert(item);
|
||||
var item = encodeNoteRtree(note);
|
||||
_noteCache.note[note.id] = note;
|
||||
_noteCache.rtree.insert(item);
|
||||
|
||||
return note;
|
||||
},
|
||||
|
||||
@@ -314,6 +337,16 @@ function parseXML(xml, callback, options) {
|
||||
}
|
||||
|
||||
|
||||
// replace or remove note from rtree
|
||||
function updateRtree(item, replace) {
|
||||
_noteCache.rtree.remove(item, function isEql(a, b) { return a.data.id === b.data.id; });
|
||||
|
||||
if (replace) {
|
||||
_noteCache.rtree.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function wrapcb(thisArg, callback, cid) {
|
||||
return function(err, result) {
|
||||
if (err) {
|
||||
@@ -519,7 +552,7 @@ export default {
|
||||
return callback({ message: 'Changeset already inflight', status: -2 }, changeset);
|
||||
|
||||
} else if (_changeset.open) { // reuse existing open changeset..
|
||||
return createdChangeset(null, _changeset.open);
|
||||
return createdChangeset.call(this, null, _changeset.open);
|
||||
|
||||
} else { // Open a new changeset..
|
||||
var options = {
|
||||
@@ -749,103 +782,44 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Load data (entities or notes) from the API in tiles
|
||||
// Load data (entities) from the API in tiles
|
||||
// GET /api/0.6/map?bbox=
|
||||
// GET /api/0.6/notes?bbox=
|
||||
loadTiles: function(projection, dimensions, callback, noteOptions) {
|
||||
loadTiles: function(projection, callback) {
|
||||
if (_off) return;
|
||||
|
||||
var that = this;
|
||||
var path = '/api/0.6/map?bbox=';
|
||||
|
||||
// are we loading entities or notes?
|
||||
var loadingNotes = (noteOptions !== undefined);
|
||||
var path, cache, tilezoom, throttleLoadUsers;
|
||||
// determine the needed tiles to cover the view
|
||||
var tiles = tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);
|
||||
|
||||
if (loadingNotes) {
|
||||
noteOptions = _extend({ limit: 10000, closed: 7}, noteOptions);
|
||||
path = '/api/0.6/notes?limit=' + noteOptions.limit + '&closed=' + noteOptions.closed + '&bbox=';
|
||||
cache = _noteCache;
|
||||
tilezoom = _noteZoom;
|
||||
throttleLoadUsers = _throttle(function() {
|
||||
var uids = Object.keys(_userCache.toLoad);
|
||||
if (!uids.length) return;
|
||||
that.loadUsers(uids, function() {}); // eagerly load user details
|
||||
}, 750);
|
||||
} else {
|
||||
path = '/api/0.6/map?bbox=';
|
||||
cache = _tileCache;
|
||||
tilezoom = _tileZoom;
|
||||
}
|
||||
|
||||
var s = projection.scale() * 2 * Math.PI;
|
||||
var z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
var ts = 256 * Math.pow(2, z - tilezoom);
|
||||
var origin = [
|
||||
s / 2 - projection.translate()[0],
|
||||
s / 2 - projection.translate()[1]
|
||||
];
|
||||
|
||||
// what tiles cover the view?
|
||||
var tiler = d3_geoTile()
|
||||
.scaleExtent([tilezoom, tilezoom])
|
||||
.scale(s)
|
||||
.size(dimensions)
|
||||
.translate(projection.translate());
|
||||
|
||||
var tiles = tiler().map(function(tile) {
|
||||
var x = tile[0] * ts - origin[0];
|
||||
var y = tile[1] * ts - origin[1];
|
||||
|
||||
return {
|
||||
id: tile.toString(),
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
)
|
||||
};
|
||||
});
|
||||
|
||||
// remove inflight requests that no longer cover the view..
|
||||
var hadRequests = !_isEmpty(cache.inflight);
|
||||
_filter(cache.inflight, function(v, i) {
|
||||
var wanted = _find(tiles, function(tile) { return i === tile.id; });
|
||||
if (!wanted) {
|
||||
delete cache.inflight[i];
|
||||
}
|
||||
return !wanted;
|
||||
}).map(abortRequest);
|
||||
|
||||
if (hadRequests && !loadingNotes && _isEmpty(cache.inflight)) {
|
||||
// abort inflight requests that are no longer needed
|
||||
var hadRequests = !_isEmpty(_tileCache.inflight);
|
||||
abortUnwantedRequests(_tileCache, tiles);
|
||||
if (hadRequests && _isEmpty(_tileCache.inflight)) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(function(tile) {
|
||||
if (cache.loaded[tile.id] || cache.inflight[tile.id]) return;
|
||||
if (!loadingNotes && _isEmpty(cache.inflight)) {
|
||||
if (_tileCache.loaded[tile.id] || _tileCache.inflight[tile.id]) return;
|
||||
if (_isEmpty(_tileCache.inflight)) {
|
||||
dispatch.call('loading'); // start the spinner
|
||||
}
|
||||
|
||||
var options = { skipSeen: !loadingNotes };
|
||||
cache.inflight[tile.id] = that.loadFromAPI(
|
||||
var options = { skipSeen: true };
|
||||
_tileCache.inflight[tile.id] = that.loadFromAPI(
|
||||
path + tile.extent.toParam(),
|
||||
function(err, parsed) {
|
||||
delete cache.inflight[tile.id];
|
||||
delete _tileCache.inflight[tile.id];
|
||||
if (!err) {
|
||||
cache.loaded[tile.id] = true;
|
||||
_tileCache.loaded[tile.id] = true;
|
||||
}
|
||||
|
||||
if (loadingNotes) {
|
||||
throttleLoadUsers();
|
||||
dispatch.call('loadedNotes');
|
||||
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(err, _extend({ data: parsed }, tile));
|
||||
}
|
||||
if (_isEmpty(cache.inflight)) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
if (callback) {
|
||||
callback(err, _extend({ data: parsed }, tile));
|
||||
}
|
||||
if (_isEmpty(_tileCache.inflight)) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
},
|
||||
options
|
||||
@@ -854,18 +828,86 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Load notes from the API (just calls this.loadTiles)
|
||||
// Load notes from the API in tiles
|
||||
// GET /api/0.6/notes?bbox=
|
||||
loadNotes: function(projection, dimensions, noteOptions) {
|
||||
noteOptions = _extend({ limit: 10000, closed: 7}, noteOptions);
|
||||
this.loadTiles(projection, dimensions, null, noteOptions);
|
||||
loadNotes: function(projection, noteOptions) {
|
||||
noteOptions = _extend({ limit: 10000, closed: 7 }, noteOptions);
|
||||
if (_off) return;
|
||||
|
||||
var that = this;
|
||||
var path = '/api/0.6/notes?limit=' + noteOptions.limit + '&closed=' + noteOptions.closed + '&bbox=';
|
||||
var throttleLoadUsers = _throttle(function() {
|
||||
var uids = Object.keys(_userCache.toLoad);
|
||||
if (!uids.length) return;
|
||||
that.loadUsers(uids, function() {}); // eagerly load user details
|
||||
}, 750);
|
||||
|
||||
// determine the needed tiles to cover the view
|
||||
var tiles = tiler.zoomExtent([_noteZoom, _noteZoom]).getTiles(projection);
|
||||
|
||||
// abort inflight requests that are no longer needed
|
||||
abortUnwantedRequests(_noteCache, tiles);
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(function(tile) {
|
||||
if (_noteCache.loaded[tile.id] || _noteCache.inflight[tile.id]) return;
|
||||
|
||||
var options = { skipSeen: false };
|
||||
_noteCache.inflight[tile.id] = that.loadFromAPI(
|
||||
path + tile.extent.toParam(),
|
||||
function(err) {
|
||||
delete _noteCache.inflight[tile.id];
|
||||
if (!err) {
|
||||
_noteCache.loaded[tile.id] = true;
|
||||
}
|
||||
throttleLoadUsers();
|
||||
dispatch.call('loadedNotes');
|
||||
},
|
||||
options
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// Create a note
|
||||
// POST /api/0.6/notes?params
|
||||
postNoteCreate: function(note, callback) {
|
||||
// todo
|
||||
if (!this.authenticated()) {
|
||||
return callback({ message: 'Not Authenticated', status: -3 }, note);
|
||||
}
|
||||
if (_noteCache.inflightPost[note.id]) {
|
||||
return callback({ message: 'Note update already inflight', status: -2 }, note);
|
||||
}
|
||||
|
||||
if (!note.loc[0] || !note.loc[1] || !note.newComment) return; // location & description required
|
||||
|
||||
var comment = note.newComment;
|
||||
if (note.newCategory && note.newCategory !== 'None') { comment += ' #' + note.newCategory; }
|
||||
|
||||
var path = '/api/0.6/notes?' + utilQsString({ lon: note.loc[0], lat: note.loc[1], text: comment });
|
||||
|
||||
_noteCache.inflightPost[note.id] = oauth.xhr(
|
||||
{ method: 'POST', path: path },
|
||||
wrapcb(this, done, _connectionID)
|
||||
);
|
||||
|
||||
|
||||
function done(err, xml) {
|
||||
delete _noteCache.inflightPost[note.id];
|
||||
if (err) { return callback(err); }
|
||||
|
||||
// we get the updated note back, remove from caches and reparse..
|
||||
this.removeNote(note);
|
||||
|
||||
var options = { skipSeen: false };
|
||||
return parseXML(xml, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
} else {
|
||||
return callback(undefined, results[0]);
|
||||
}
|
||||
}, options);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -888,6 +930,7 @@ export default {
|
||||
action = 'reopen';
|
||||
} else {
|
||||
action = 'comment';
|
||||
if (!note.newComment) return; // when commenting, comment required
|
||||
}
|
||||
|
||||
var path = '/api/0.6/notes/' + note.id + '/' + action;
|
||||
@@ -906,9 +949,7 @@ export default {
|
||||
if (err) { return callback(err); }
|
||||
|
||||
// we get the updated note back, remove from caches and reparse..
|
||||
var item = { minX: note.loc[0], minY: note.loc[1], maxX: note.loc[0], maxY: note.loc[1], data: note };
|
||||
_noteCache.rtree.remove(item, function isEql(a, b) { return a.data.id === b.data.id; });
|
||||
delete _noteCache.note[note.id];
|
||||
this.removeNote(note);
|
||||
|
||||
var options = { skipSeen: false };
|
||||
return parseXML(xml, function(err, results) {
|
||||
@@ -944,6 +985,11 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
isChangesetInflight: function() {
|
||||
return !!_changeset.inflight;
|
||||
},
|
||||
|
||||
|
||||
// get/set cached data
|
||||
// This is used to save/restore the state when entering/exiting the walkthrough
|
||||
// Also used for testing purposes.
|
||||
@@ -1051,12 +1097,22 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// remove a single note from the cache
|
||||
removeNote: function(note) {
|
||||
if (!(note instanceof osmNote) || !note.id) return;
|
||||
|
||||
delete _noteCache.note[note.id];
|
||||
updateRtree(encodeNoteRtree(note), false); // false = remove
|
||||
},
|
||||
|
||||
|
||||
// replace a single note in the cache
|
||||
replaceNote: function(n) {
|
||||
if (n instanceof osmNote) {
|
||||
_noteCache.note[n.id] = n;
|
||||
}
|
||||
return n;
|
||||
replaceNote: function(note) {
|
||||
if (!(note instanceof osmNote) || !note.id) return;
|
||||
|
||||
_noteCache.note[note.id] = note;
|
||||
updateRtree(encodeNoteRtree(note), true); // true = replace
|
||||
return note;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _extend from 'lodash-es/extend';
|
||||
import _find from 'lodash-es/find';
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
import _map from 'lodash-es/map';
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
import rbush from 'rbush';
|
||||
import { t } from '../util/locale';
|
||||
import { jsonpRequest } from '../util/jsonp_request';
|
||||
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
||||
|
||||
import {
|
||||
geoExtent,
|
||||
@@ -29,10 +29,11 @@ import {
|
||||
} from '../geo';
|
||||
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { utilQsString, utilRebind } from '../util';
|
||||
import { utilQsString, utilRebind, utilTiler } from '../util';
|
||||
|
||||
import Q from 'q';
|
||||
|
||||
|
||||
var bubbleApi = 'https://dev.virtualearth.net/mapcontrol/HumanScaleServices/GetBubbles.ashx?';
|
||||
var streetsideImagesApi = 'https://t.ssl.ak.tiles.virtualearth.net/tiles/';
|
||||
var bubbleAppKey = 'AuftgJsO0Xs8Ts4M1xZUQJQXJNsvmh3IV8DkNieCiy3tCwCUMq76-WpkrBtNAuEm';
|
||||
@@ -40,10 +41,12 @@ var pannellumViewerCSS = 'pannellum-streetside/pannellum.css';
|
||||
var pannellumViewerJS = 'pannellum-streetside/pannellum.js';
|
||||
var maxResults = 2000;
|
||||
var tileZoom = 16.5;
|
||||
var tiler = utilTiler().zoomExtent([tileZoom, tileZoom]).skipNullIsland(true);
|
||||
var dispatch = d3_dispatch('loadedBubbles', 'viewerChanged');
|
||||
var minHfov = 10; // zoom in degrees: 20, 10, 5
|
||||
var maxHfov = 90; // zoom out degrees
|
||||
var defaultHfov = 45;
|
||||
|
||||
var _hires = false;
|
||||
var _resolution = 512; // higher numbers are slower - 512, 1024, 2048, 4096
|
||||
var _currScene = 0;
|
||||
@@ -52,6 +55,7 @@ var _pannellumViewer;
|
||||
var _sceneOptions;
|
||||
var _dataUrlArray = [];
|
||||
|
||||
|
||||
/**
|
||||
* abortRequest().
|
||||
*/
|
||||
@@ -59,19 +63,6 @@ function abortRequest(i) {
|
||||
i.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* nearNullIsland().
|
||||
*/
|
||||
function nearNullIsland(x, y, z) {
|
||||
if (z >= 7) {
|
||||
var center = Math.pow(2, z - 1);
|
||||
var width = Math.pow(2, z - 6);
|
||||
var min = center - (width / 2);
|
||||
var max = center + (width / 2) - 1;
|
||||
return x >= min && x <= max && y >= min && y <= max;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* localeTimeStamp().
|
||||
@@ -85,68 +76,33 @@ function localeTimestamp(s) {
|
||||
return d.toLocaleString(detected.locale, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* getTiles() returns array of d3 geo tiles.
|
||||
* Using d3.geo.tiles.js from lib, gets tile extents for each grid tile in a grid created from
|
||||
* an area around (and including) the current map view extents.
|
||||
*/
|
||||
function getTiles(projection, margin) {
|
||||
// s is the current map scale
|
||||
// z is the 'Level of Detail', or zoom-level, where Level 1 is far from the earth, and Level 23 is close to the ground.
|
||||
// ts ('tile size') here is the formula for determining the width/height of the map in pixels, but with a modification.
|
||||
// See 'Ground Resolution and Map Scale': //https://msdn.microsoft.com/en-us/library/bb259689.aspx.
|
||||
// As used here, by subtracting constant 'tileZoom' from z (the level), you end up with a much smaller value for the tile size (in pixels).
|
||||
var s = projection.scale() * 2 * Math.PI;
|
||||
var z = Math.max(Math.log(s) / Math.log(2) - 8, 0);
|
||||
var ts = 256 * Math.pow(2, z - tileZoom);
|
||||
var origin = [
|
||||
s / 2 - projection.translate()[0],
|
||||
s / 2 - projection.translate()[1]
|
||||
];
|
||||
|
||||
var tiler = d3_geoTile()
|
||||
.scaleExtent([tileZoom, tileZoom])
|
||||
.scale(s)
|
||||
.size(projection.clipExtent()[1])
|
||||
.translate(projection.translate())
|
||||
.margin(margin || 0); // request nearby tiles so we can connect sequences.
|
||||
|
||||
return tiler()
|
||||
.map(function(tile) {
|
||||
var x = tile[0] * ts - origin[0];
|
||||
var y = tile[1] * ts - origin[1];
|
||||
return {
|
||||
id: tile.toString(),
|
||||
xyz: tile,
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* loadTiles() wraps the process of generating tiles and then fetching image points for each tile.
|
||||
*/
|
||||
function loadTiles(which, url, projection, margin) {
|
||||
var s = projection.scale() * 2 * Math.PI;
|
||||
var currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
|
||||
var tiles = tiler.margin(margin).getTiles(projection);
|
||||
|
||||
// breakup the map view into tiles
|
||||
var tiles = getTiles(projection, margin).filter(function (t) {
|
||||
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
|
||||
// abort inflight requests that are no longer needed
|
||||
var cache = _ssCache[which];
|
||||
_forEach(cache.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k.indexOf(tile.id + ',') === 0; });
|
||||
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
});
|
||||
|
||||
tiles.forEach(function (tile) {
|
||||
loadNextTilePage(which, currZoom, url, tile);
|
||||
loadNextTilePage(which, url, tile);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* loadNextTilePage() load data for the next tile page in line.
|
||||
*/
|
||||
function loadNextTilePage(which, currZoom, url, tile) {
|
||||
function loadNextTilePage(which, url, tile) {
|
||||
var cache = _ssCache[which];
|
||||
var nextPage = cache.nextPage[tile.id] || 0;
|
||||
var id = tile.id + ',' + String(nextPage);
|
||||
@@ -160,7 +116,7 @@ function loadNextTilePage(which, currZoom, url, tile) {
|
||||
// [].shift() removes the first element, some statistics info, not a bubble point
|
||||
bubbles.shift();
|
||||
|
||||
var features = bubbles.map(function (bubble) {
|
||||
var features = bubbles.map(function(bubble) {
|
||||
if (cache.points[bubble.id]) return null; // skip duplicates
|
||||
|
||||
var loc = [bubble.lo, bubble.la];
|
||||
@@ -669,6 +625,14 @@ export default {
|
||||
.attr('src', context.asset(pannellumViewerJS));
|
||||
|
||||
|
||||
// Register viewer resize handler
|
||||
context.ui().on('photoviewerResize', function() {
|
||||
if (_pannellumViewer) {
|
||||
_pannellumViewer.resize();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function step(stepBy) {
|
||||
return function() {
|
||||
var viewer = d3_select('#photoviewer');
|
||||
|
||||
@@ -264,7 +264,7 @@ export default {
|
||||
// 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|booth|rating|:output|_hours|_times/;
|
||||
var re = /network|taxon|genus|species|brand|grape_variety|royal_cypher|listed_status|booth|rating|stars|:output|_hours|_times/;
|
||||
var allowUpperCase = (params.key.match(re) !== null);
|
||||
var f = filterValues(allowUpperCase);
|
||||
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
import _clone from 'lodash-es/clone';
|
||||
import _find from 'lodash-es/find';
|
||||
import _isEqual from 'lodash-es/isEqual';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { request as d3_request } from 'd3-request';
|
||||
|
||||
import turf_bboxClip from '@turf/bbox-clip';
|
||||
import stringify from 'fast-json-stable-stringify';
|
||||
import martinez from 'martinez-polygon-clipping';
|
||||
|
||||
import Protobuf from 'pbf';
|
||||
import vt from '@mapbox/vector-tile';
|
||||
|
||||
import { utilHashcode, utilRebind, utilTiler } from '../util';
|
||||
|
||||
|
||||
var tiler = utilTiler().tileSize(512).margin(1);
|
||||
var dispatch = d3_dispatch('loadedData');
|
||||
var _vtCache;
|
||||
|
||||
|
||||
function abortRequest(i) {
|
||||
i.abort();
|
||||
}
|
||||
|
||||
|
||||
function vtToGeoJSON(data, tile, mergeCache) {
|
||||
var vectorTile = new vt.VectorTile(new Protobuf(data.response));
|
||||
var layers = Object.keys(vectorTile.layers);
|
||||
if (!Array.isArray(layers)) { layers = [layers]; }
|
||||
|
||||
var features = [];
|
||||
layers.forEach(function(layerID) {
|
||||
var layer = vectorTile.layers[layerID];
|
||||
if (layer) {
|
||||
for (var i = 0; i < layer.length; i++) {
|
||||
var feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
|
||||
var geometry = feature.geometry;
|
||||
|
||||
// Treat all Polygons as MultiPolygons
|
||||
if (geometry.type === 'Polygon') {
|
||||
geometry.type = 'MultiPolygon';
|
||||
geometry.coordinates = [geometry.coordinates];
|
||||
}
|
||||
|
||||
// Clip to tile bounds
|
||||
if (geometry.type === 'MultiPolygon') {
|
||||
var isClipped = false;
|
||||
var featureClip = turf_bboxClip(feature, tile.extent.rectangle());
|
||||
if (!_isEqual(feature.geometry, featureClip.geometry)) {
|
||||
// feature = featureClip;
|
||||
isClipped = true;
|
||||
}
|
||||
if (!feature.geometry.coordinates.length) continue; // not actually on this tile
|
||||
if (!feature.geometry.coordinates[0].length) continue; // not actually on this tile
|
||||
}
|
||||
|
||||
// Generate some unique IDs and add some metadata
|
||||
var featurehash = utilHashcode(stringify(feature));
|
||||
var propertyhash = utilHashcode(stringify(feature.properties || {}));
|
||||
feature.__layerID__ = layerID.replace(/[^_a-zA-Z0-9\-]/g, '_');
|
||||
feature.__featurehash__ = featurehash;
|
||||
feature.__propertyhash__ = propertyhash;
|
||||
features.push(feature);
|
||||
|
||||
// Clipped Polygons at same zoom with identical properties can get merged
|
||||
if (isClipped && geometry.type === 'MultiPolygon') {
|
||||
var merged = mergeCache[propertyhash];
|
||||
if (merged && merged.length) {
|
||||
var other = merged[0];
|
||||
var coords = martinez.union(
|
||||
feature.geometry.coordinates, other.geometry.coordinates
|
||||
);
|
||||
|
||||
if (!coords || !coords.length) {
|
||||
continue; // something failed in martinez union
|
||||
}
|
||||
|
||||
merged.push(feature);
|
||||
for (var j = 0; j < merged.length; j++) { // all these features get...
|
||||
merged[j].geometry.coordinates = coords; // same coords
|
||||
merged[j].__featurehash__ = featurehash; // same hash, so deduplication works
|
||||
}
|
||||
} else {
|
||||
mergeCache[propertyhash] = [feature];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
|
||||
function loadTile(source, tile) {
|
||||
if (source.loaded[tile.id] || source.inflight[tile.id]) return;
|
||||
|
||||
var url = source.template
|
||||
.replace('{x}', tile.xyz[0])
|
||||
.replace('{y}', tile.xyz[1])
|
||||
// TMS-flipped y coordinate
|
||||
.replace(/\{[t-]y\}/, Math.pow(2, tile.xyz[2]) - tile.xyz[1] - 1)
|
||||
.replace(/\{z(oom)?\}/, tile.xyz[2])
|
||||
.replace(/\{switch:([^}]+)\}/, function(s, r) {
|
||||
var subdomains = r.split(',');
|
||||
return subdomains[(tile.xyz[0] + tile.xyz[1]) % subdomains.length];
|
||||
});
|
||||
|
||||
source.inflight[tile.id] = d3_request(url)
|
||||
.responseType('arraybuffer')
|
||||
.get(function(err, data) {
|
||||
source.loaded[tile.id] = [];
|
||||
delete source.inflight[tile.id];
|
||||
if (err || !data) return;
|
||||
|
||||
var z = tile.xyz[2];
|
||||
if (!source.canMerge[z]) {
|
||||
source.canMerge[z] = {}; // initialize mergeCache
|
||||
}
|
||||
|
||||
source.loaded[tile.id] = vtToGeoJSON(data, tile, source.canMerge[z]);
|
||||
dispatch.call('loadedData');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
init: function() {
|
||||
if (!_vtCache) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
|
||||
reset: function() {
|
||||
for (var sourceID in _vtCache) {
|
||||
var source = _vtCache[sourceID];
|
||||
if (source && source.inflight) {
|
||||
_forEach(source.inflight, abortRequest);
|
||||
}
|
||||
}
|
||||
|
||||
_vtCache = {};
|
||||
},
|
||||
|
||||
|
||||
addSource: function(sourceID, template) {
|
||||
_vtCache[sourceID] = { template: template, inflight: {}, loaded: {}, canMerge: {} };
|
||||
return _vtCache[sourceID];
|
||||
},
|
||||
|
||||
|
||||
data: function(sourceID, projection) {
|
||||
var source = _vtCache[sourceID];
|
||||
if (!source) return [];
|
||||
|
||||
var tiles = tiler.getTiles(projection);
|
||||
var seen = {};
|
||||
var results = [];
|
||||
|
||||
for (var i = 0; i < tiles.length; i++) {
|
||||
var features = source.loaded[tiles[i].id];
|
||||
if (!features || !features.length) continue;
|
||||
|
||||
for (var j = 0; j < features.length; j++) {
|
||||
var feature = features[j];
|
||||
var hash = feature.__featurehash__;
|
||||
if (seen[hash]) continue;
|
||||
seen[hash] = true;
|
||||
|
||||
// return a shallow clone, because the hash may change
|
||||
// later if this feature gets merged with another
|
||||
results.push(_clone(feature));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
|
||||
loadTiles: function(sourceID, template, projection) {
|
||||
var source = _vtCache[sourceID];
|
||||
if (!source) {
|
||||
source = this.addSource(sourceID, template);
|
||||
}
|
||||
|
||||
var tiles = tiler.getTiles(projection);
|
||||
|
||||
// abort inflight requests that are no longer needed
|
||||
_forEach(source.inflight, function(v, k) {
|
||||
var wanted = _find(tiles, function(tile) { return k === tile.id; });
|
||||
|
||||
if (!wanted) {
|
||||
abortRequest(v);
|
||||
delete source.inflight[k];
|
||||
}
|
||||
});
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
loadTile(source, tile);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
cache: function() {
|
||||
return _vtCache;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -132,7 +132,7 @@ export function svgAreas(projection, context) {
|
||||
fill: areas
|
||||
};
|
||||
|
||||
var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath')
|
||||
var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath-osm')
|
||||
.filter(filter)
|
||||
.data(data.clip, osmEntity.key);
|
||||
|
||||
@@ -141,7 +141,7 @@ export function svgAreas(projection, context) {
|
||||
|
||||
var clipPathsEnter = clipPaths.enter()
|
||||
.append('clipPath')
|
||||
.attr('class', 'clipPath')
|
||||
.attr('class', 'clipPath-osm')
|
||||
.attr('id', function(entity) { return entity.id + '-clippath'; });
|
||||
|
||||
clipPathsEnter
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _isEmpty from 'lodash-es/isEmpty';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _union from 'lodash-es/union';
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
|
||||
import {
|
||||
geoBounds as d3_geoBounds,
|
||||
geoPath as d3_geoPath
|
||||
} from 'd3-geo';
|
||||
|
||||
import { text as d3_text } from 'd3-request';
|
||||
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import stringify from 'fast-json-stable-stringify';
|
||||
import toGeoJSON from '@mapbox/togeojson';
|
||||
|
||||
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
|
||||
import { services } from '../services';
|
||||
import { svgPath } from './index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { utilHashcode } from '../util';
|
||||
|
||||
|
||||
var _initialized = false;
|
||||
var _enabled = false;
|
||||
var _geojson;
|
||||
|
||||
|
||||
export function svgData(projection, context, dispatch) {
|
||||
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
|
||||
var _showLabels = true;
|
||||
var detected = utilDetect();
|
||||
var layer = d3_select(null);
|
||||
var _vtService;
|
||||
var _fileList;
|
||||
var _template;
|
||||
var _src;
|
||||
|
||||
|
||||
function init() {
|
||||
if (_initialized) return; // run once
|
||||
|
||||
_geojson = {};
|
||||
_enabled = true;
|
||||
|
||||
function over() {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
d3_event.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
d3_select('body')
|
||||
.attr('dropzone', 'copy')
|
||||
.on('drop.svgData', function() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
function getService() {
|
||||
if (services.vectorTile && !_vtService) {
|
||||
_vtService = services.vectorTile;
|
||||
_vtService.event.on('loadedData', throttledRedraw);
|
||||
} else if (!services.vectorTile && _vtService) {
|
||||
_vtService = null;
|
||||
}
|
||||
|
||||
return _vtService;
|
||||
}
|
||||
|
||||
|
||||
function showLayer() {
|
||||
layerOn();
|
||||
|
||||
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', layerOff);
|
||||
}
|
||||
|
||||
|
||||
function layerOn() {
|
||||
layer.style('display', 'block');
|
||||
}
|
||||
|
||||
|
||||
function layerOff() {
|
||||
layer.selectAll('.viewfield-group').remove();
|
||||
layer.style('display', 'none');
|
||||
}
|
||||
|
||||
|
||||
// ensure that all geojson features in a collection have IDs
|
||||
function ensureIDs(gj) {
|
||||
if (!gj) return null;
|
||||
|
||||
if (gj.type === 'FeatureCollection') {
|
||||
for (var i = 0; i < gj.features.length; i++) {
|
||||
ensureFeatureID(gj.features[i]);
|
||||
}
|
||||
} else {
|
||||
ensureFeatureID(gj);
|
||||
}
|
||||
return gj;
|
||||
}
|
||||
|
||||
|
||||
// ensure that each single Feature object has a unique ID
|
||||
function ensureFeatureID(feature) {
|
||||
if (!feature) return;
|
||||
feature.__featurehash__ = utilHashcode(stringify(feature));
|
||||
return feature;
|
||||
}
|
||||
|
||||
|
||||
// Prefer an array of Features instead of a FeatureCollection
|
||||
function getFeatures(gj) {
|
||||
if (!gj) return [];
|
||||
|
||||
if (gj.type === 'FeatureCollection') {
|
||||
return gj.features;
|
||||
} else {
|
||||
return [gj];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function featureKey(d) {
|
||||
return d.__featurehash__;
|
||||
}
|
||||
|
||||
|
||||
function isPolygon(d) {
|
||||
return d.geometry.type === 'Polygon' || d.geometry.type === 'MultiPolygon';
|
||||
}
|
||||
|
||||
|
||||
function clipPathID(d) {
|
||||
return 'data-' + d.__featurehash__ + '-clippath';
|
||||
}
|
||||
|
||||
|
||||
function featureClasses(d) {
|
||||
return [
|
||||
'data' + d.__featurehash__,
|
||||
d.geometry.type,
|
||||
isPolygon(d) ? 'area' : '',
|
||||
d.__layerID__ || ''
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
var geoData, polygonData;
|
||||
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);
|
||||
}
|
||||
geoData = geoData.filter(getPath);
|
||||
polygonData = geoData.filter(isPolygon);
|
||||
|
||||
|
||||
// Draw clip paths for polygons
|
||||
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
|
||||
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
|
||||
var pathData = {
|
||||
fill: polygonData,
|
||||
shadow: geoData,
|
||||
stroke: geoData
|
||||
};
|
||||
|
||||
var paths = datagroups
|
||||
.selectAll('path')
|
||||
.data(function(layer) { return pathData[layer]; }, featureKey);
|
||||
|
||||
// exit
|
||||
paths.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
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
|
||||
layer
|
||||
.call(drawLabels, 'label-halo', geoData)
|
||||
.call(drawLabels, 'label', geoData);
|
||||
|
||||
|
||||
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
|
||||
labels.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
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 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 xmlToDom(textdata) {
|
||||
return (new DOMParser()).parseFromString(textdata, 'text/xml');
|
||||
}
|
||||
|
||||
|
||||
drawData.setFile = function(extension, data) {
|
||||
_template = null;
|
||||
_fileList = null;
|
||||
_geojson = null;
|
||||
_src = null;
|
||||
|
||||
var gj;
|
||||
switch (extension) {
|
||||
case '.gpx':
|
||||
gj = toGeoJSON.gpx(xmlToDom(data));
|
||||
break;
|
||||
case '.kml':
|
||||
gj = toGeoJSON.kml(xmlToDom(data));
|
||||
break;
|
||||
case '.geojson':
|
||||
case '.json':
|
||||
gj = JSON.parse(data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_isEmpty(gj)) {
|
||||
_geojson = ensureIDs(gj);
|
||||
_src = extension + ' data file';
|
||||
this.fitZoom();
|
||||
}
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawData.showLabels = function(val) {
|
||||
if (!arguments.length) return _showLabels;
|
||||
|
||||
_showLabels = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawData.enabled = function(val) {
|
||||
if (!arguments.length) return _enabled;
|
||||
|
||||
_enabled = val;
|
||||
if (_enabled) {
|
||||
showLayer();
|
||||
} else {
|
||||
hideLayer();
|
||||
}
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawData.hasData = function() {
|
||||
return !!(_template || !_isEmpty(_geojson));
|
||||
};
|
||||
|
||||
|
||||
drawData.template = function(val, src) {
|
||||
if (!arguments.length) return _template;
|
||||
|
||||
// test source against OSM imagery blacklists..
|
||||
var osm = context.connection();
|
||||
if (osm) {
|
||||
var blacklists = osm.imageryBlacklists();
|
||||
var fail = false;
|
||||
var tested = 0;
|
||||
var regex;
|
||||
|
||||
for (var i = 0; i < blacklists.length; i++) {
|
||||
try {
|
||||
regex = new RegExp(blacklists[i]);
|
||||
fail = regex.test(val);
|
||||
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(val);
|
||||
}
|
||||
}
|
||||
|
||||
_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;
|
||||
|
||||
if (!_isEmpty(gj)) {
|
||||
_geojson = ensureIDs(gj);
|
||||
_src = src || 'unknown.geojson';
|
||||
}
|
||||
|
||||
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();
|
||||
reader.onload = (function() {
|
||||
return function(e) {
|
||||
drawData.setFile(extension, e.target.result);
|
||||
};
|
||||
})(f);
|
||||
|
||||
reader.readAsText(f);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawData.url = function(url, defaultExtension) {
|
||||
_template = null;
|
||||
_fileList = null;
|
||||
_geojson = null;
|
||||
_src = null;
|
||||
|
||||
// strip off any querystring/hash from the url before checking extension
|
||||
var testUrl = url.split(/[?#]/)[0];
|
||||
var extension = getExtension(testUrl) || defaultExtension;
|
||||
if (extension) {
|
||||
_template = null;
|
||||
d3_text(url, function(err, data) {
|
||||
if (err) return;
|
||||
drawData.setFile(extension, data);
|
||||
});
|
||||
} else {
|
||||
drawData.template(url);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawData.getSrc = function() {
|
||||
return _src || '';
|
||||
};
|
||||
|
||||
|
||||
drawData.fitZoom = function() {
|
||||
var features = getFeatures(_geojson);
|
||||
if (!features.length) return;
|
||||
|
||||
var map = context.map();
|
||||
var viewport = map.trimmedExtent().polygon();
|
||||
var coords = _reduce(features, function(coords, feature) {
|
||||
var c = feature.geometry.coordinates;
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (feature.geometry.type) {
|
||||
case 'Point':
|
||||
c = [c];
|
||||
case 'MultiPoint':
|
||||
case 'LineString':
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
c = _flatten(c);
|
||||
case 'Polygon':
|
||||
case 'MultiLineString':
|
||||
c = _flatten(c);
|
||||
break;
|
||||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
|
||||
return _union(coords, c);
|
||||
}, []);
|
||||
|
||||
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
|
||||
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
|
||||
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
init();
|
||||
return drawData;
|
||||
}
|
||||
+3
-18
@@ -2,22 +2,12 @@ import _values from 'lodash-es/values';
|
||||
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { geoPolygonIntersectsPolygon } from '../geo';
|
||||
import { data, dataImperial, dataDriveLeft } from '../../data';
|
||||
import { svgPath } from './index';
|
||||
|
||||
|
||||
export function svgDebug(projection, context) {
|
||||
|
||||
function multipolygons(imagery) {
|
||||
return imagery.map(function(data) {
|
||||
return {
|
||||
type: 'MultiPolygon',
|
||||
coordinates: [ data.polygon ]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function drawDebug(selection) {
|
||||
var showsTile = context.getDebug('tile');
|
||||
var showsCollision = context.getDebug('collision');
|
||||
@@ -89,16 +79,11 @@ export function svgDebug(projection, context) {
|
||||
|
||||
|
||||
var extent = context.map().extent();
|
||||
var dataImagery = data.imagery || [];
|
||||
var availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) {
|
||||
if (!source.polygon) return false;
|
||||
return source.polygon.some(function(polygon) {
|
||||
return geoPolygonIntersectsPolygon(polygon, extent, true);
|
||||
});
|
||||
}));
|
||||
var matchImagery = (showsImagery && data.imagery.query.bbox(extent.rectangle(), true)) || [];
|
||||
var features = matchImagery.map(function(d) { return data.imagery.features[d.id]; });
|
||||
|
||||
var imagery = layer.selectAll('path.debug-imagery')
|
||||
.data(showsImagery ? availableImagery : []);
|
||||
.data(features);
|
||||
|
||||
imagery.exit()
|
||||
.remove();
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _isEmpty from 'lodash-es/isEmpty';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _union from 'lodash-es/union';
|
||||
|
||||
import { geoBounds as d3_geoBounds } from 'd3-geo';
|
||||
import { text as d3_text } from 'd3-request';
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
|
||||
import { svgPath } from './index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import toGeoJSON from '@mapbox/togeojson';
|
||||
|
||||
|
||||
var _initialized = false;
|
||||
var _enabled = false;
|
||||
var _geojson;
|
||||
|
||||
|
||||
export function svgGpx(projection, context, dispatch) {
|
||||
var _showLabels = true;
|
||||
var detected = utilDetect();
|
||||
var layer;
|
||||
var _src;
|
||||
|
||||
|
||||
function init() {
|
||||
if (_initialized) return; // run once
|
||||
|
||||
_geojson = {};
|
||||
_enabled = true;
|
||||
|
||||
function over() {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
d3_event.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
d3_select('body')
|
||||
.attr('dropzone', 'copy')
|
||||
.on('drop.localgpx', function() {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
if (!detected.filedrop) return;
|
||||
drawGpx.files(d3_event.dataTransfer.files);
|
||||
})
|
||||
.on('dragenter.localgpx', over)
|
||||
.on('dragexit.localgpx', over)
|
||||
.on('dragover.localgpx', over);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
function drawGpx(selection) {
|
||||
var getPath = svgPath(projection).geojson;
|
||||
|
||||
layer = selection.selectAll('.layer-gpx')
|
||||
.data(_enabled ? [0] : []);
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
|
||||
layer = layer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-gpx')
|
||||
.merge(layer);
|
||||
|
||||
|
||||
var paths = layer
|
||||
.selectAll('path')
|
||||
.data([_geojson]);
|
||||
|
||||
paths.exit()
|
||||
.remove();
|
||||
|
||||
paths = paths.enter()
|
||||
.append('path')
|
||||
.attr('class', 'gpx')
|
||||
.merge(paths);
|
||||
|
||||
paths
|
||||
.attr('d', getPath);
|
||||
|
||||
|
||||
var labelData = _showLabels && _geojson.features ? _geojson.features : [];
|
||||
labelData = labelData.filter(getPath);
|
||||
|
||||
layer
|
||||
.call(drawLabels, 'gpxlabel-halo', labelData)
|
||||
.call(drawLabels, 'gpxlabel', labelData);
|
||||
|
||||
|
||||
function drawLabels(selection, textClass, data) {
|
||||
var labels = selection.selectAll('text.' + textClass)
|
||||
.data(data);
|
||||
|
||||
// exit
|
||||
labels.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
labels = labels.enter()
|
||||
.append('text')
|
||||
.attr('class', textClass)
|
||||
.merge(labels)
|
||||
.text(function(d) {
|
||||
if (d.properties) {
|
||||
return d.properties.desc || d.properties.name;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.attr('x', function(d) {
|
||||
var centroid = getPath.centroid(d);
|
||||
return centroid[0] + 11;
|
||||
})
|
||||
.attr('y', function(d) {
|
||||
var centroid = getPath.centroid(d);
|
||||
return centroid[1];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toDom(x) {
|
||||
return (new DOMParser()).parseFromString(x, 'text/xml');
|
||||
}
|
||||
|
||||
|
||||
function getExtension(fileName) {
|
||||
if (fileName === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex < 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return fileName.substr(lastDotIndex);
|
||||
}
|
||||
|
||||
|
||||
function parseSaveAndZoom(extension, data, src) {
|
||||
switch (extension) {
|
||||
default:
|
||||
drawGpx.geojson(toGeoJSON.gpx(toDom(data)), src).fitZoom();
|
||||
break;
|
||||
case '.kml':
|
||||
drawGpx.geojson(toGeoJSON.kml(toDom(data)), src).fitZoom();
|
||||
break;
|
||||
case '.geojson':
|
||||
case '.json':
|
||||
drawGpx.geojson(JSON.parse(data), src).fitZoom();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawGpx.showLabels = function(_) {
|
||||
if (!arguments.length) return _showLabels;
|
||||
_showLabels = _;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.enabled = function(_) {
|
||||
if (!arguments.length) return _enabled;
|
||||
_enabled = _;
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.hasGpx = function() {
|
||||
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
|
||||
};
|
||||
|
||||
|
||||
drawGpx.geojson = function(gj, src) {
|
||||
if (!arguments.length) return _geojson;
|
||||
if (_isEmpty(gj) || _isEmpty(gj.features)) return this;
|
||||
_geojson = gj;
|
||||
_src = src || 'unknown.geojson';
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.url = function(url) {
|
||||
d3_text(url, function(err, data) {
|
||||
if (!err) {
|
||||
var extension = getExtension(url);
|
||||
parseSaveAndZoom(extension, data, url);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.files = function(fileList) {
|
||||
if (!fileList.length) return this;
|
||||
var f = fileList[0];
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = (function(file) {
|
||||
var extension = getExtension(file.name);
|
||||
return function (e) {
|
||||
parseSaveAndZoom(extension, e.target.result, file.name);
|
||||
};
|
||||
})(f);
|
||||
|
||||
reader.readAsText(f);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.getSrc = function () {
|
||||
return _src;
|
||||
};
|
||||
|
||||
|
||||
drawGpx.fitZoom = function() {
|
||||
if (!this.hasGpx()) return this;
|
||||
|
||||
var map = context.map();
|
||||
var viewport = map.trimmedExtent().polygon();
|
||||
var coords = _reduce(_geojson.features, function(coords, feature) {
|
||||
var c = feature.geometry.coordinates;
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (feature.geometry.type) {
|
||||
case 'Point':
|
||||
c = [c];
|
||||
case 'MultiPoint':
|
||||
case 'LineString':
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
c = _flatten(c);
|
||||
case 'Polygon':
|
||||
case 'MultiLineString':
|
||||
c = _flatten(c);
|
||||
break;
|
||||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
|
||||
return _union(coords, c);
|
||||
}, []);
|
||||
|
||||
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
|
||||
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
|
||||
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
init();
|
||||
return drawGpx;
|
||||
}
|
||||
+11
-1
@@ -168,7 +168,17 @@ export function svgPath(projection, graph, isArea) {
|
||||
}
|
||||
};
|
||||
|
||||
svgpath.geojson = path;
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return svgpath;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
export { svgAreas } from './areas.js';
|
||||
export { svgData } from './data.js';
|
||||
export { svgDebug } from './debug.js';
|
||||
export { svgDefs } from './defs.js';
|
||||
export { svgGpx } from './gpx.js';
|
||||
export { svgMvt } from './mvt.js';
|
||||
export { svgIcon } from './icon.js';
|
||||
export { svgLabels } from './labels.js';
|
||||
export { svgLayers } from './layers.js';
|
||||
|
||||
@@ -7,10 +7,9 @@ import _reject from 'lodash-es/reject';
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import { svgData } from './data';
|
||||
import { svgDebug } from './debug';
|
||||
import { svgGpx } from './gpx';
|
||||
import { svgStreetside } from './streetside';
|
||||
import { svgMvt } from './mvt';
|
||||
import { svgMapillaryImages } from './mapillary_images';
|
||||
import { svgMapillarySigns } from './mapillary_signs';
|
||||
import { svgOpenstreetcamImages } from './openstreetcam_images';
|
||||
@@ -26,8 +25,7 @@ export function svgLayers(projection, context) {
|
||||
var layers = [
|
||||
{ id: 'osm', layer: svgOsm(projection, context, dispatch) },
|
||||
{ id: 'notes', layer: svgNotes(projection, context, dispatch) },
|
||||
{ id: 'gpx', layer: svgGpx(projection, context, dispatch) },
|
||||
{ id: 'mvt', layer: svgMvt(projection, context, dispatch) },
|
||||
{ id: 'data', layer: svgData(projection, context, dispatch) },
|
||||
{ id: 'streetside', layer: svgStreetside(projection, context, dispatch)},
|
||||
{ id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) },
|
||||
{ id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) },
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
import _isNumber from 'lodash-es/isNumber';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import { svgPath, svgPointTransform } from './index';
|
||||
import { services } from '../services';
|
||||
@@ -11,6 +12,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
var minViewfieldZoom = 18;
|
||||
var layer = d3_select(null);
|
||||
var _mapillary;
|
||||
var viewerCompassAngle;
|
||||
|
||||
|
||||
function init() {
|
||||
@@ -24,6 +26,19 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
if (services.mapillary && !_mapillary) {
|
||||
_mapillary = services.mapillary;
|
||||
_mapillary.event.on('loadedImages', throttledRedraw);
|
||||
_mapillary.event.on('bearingChanged', function(e) {
|
||||
viewerCompassAngle = e;
|
||||
|
||||
// avoid updating if the map is currently transformed
|
||||
// e.g. during drags or easing.
|
||||
if (context.map().isTransformed()) return;
|
||||
|
||||
layer.selectAll('.viewfield-group.selected')
|
||||
.filter(function(d) {
|
||||
return d.pano;
|
||||
})
|
||||
.attr('transform', transform);
|
||||
});
|
||||
} else if (!services.mapillary && _mapillary) {
|
||||
_mapillary = null;
|
||||
}
|
||||
@@ -102,7 +117,9 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
|
||||
function transform(d) {
|
||||
var t = svgPointTransform(projection)(d);
|
||||
if (d.ca) {
|
||||
if (d.pano && _isNumber(viewerCompassAngle)) {
|
||||
t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)';
|
||||
} else if (d.ca) {
|
||||
t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
|
||||
}
|
||||
return t;
|
||||
@@ -184,6 +201,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
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__.pano; })
|
||||
.attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
import _flatten from 'lodash-es/flatten';
|
||||
import _isEmpty from 'lodash-es/isEmpty';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _union from 'lodash-es/union';
|
||||
|
||||
import { geoBounds as d3_geoBounds } from 'd3-geo';
|
||||
import { request as d3_request } from 'd3-request';
|
||||
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import vt from '@mapbox/vector-tile';
|
||||
import Protobuf from 'pbf';
|
||||
|
||||
import { geoExtent, geoPolygonIntersectsPolygon } from '../geo';
|
||||
import { svgPath } from './index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
|
||||
|
||||
var _initialized = false;
|
||||
var _enabled = false;
|
||||
var _geojson;
|
||||
|
||||
|
||||
export function svgMvt(projection, context, dispatch) {
|
||||
var _showLabels = true;
|
||||
var detected = utilDetect();
|
||||
var layer;
|
||||
var _src;
|
||||
|
||||
|
||||
function init() {
|
||||
if (_initialized) return; // run once
|
||||
|
||||
_geojson = {};
|
||||
_enabled = true;
|
||||
|
||||
function over() {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
d3_event.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
d3_select('body')
|
||||
.attr('dropzone', 'copy')
|
||||
.on('drop.localmvt', function() {
|
||||
d3_event.stopPropagation();
|
||||
d3_event.preventDefault();
|
||||
if (!detected.filedrop) return;
|
||||
drawMvt.files(d3_event.dataTransfer.files);
|
||||
})
|
||||
.on('dragenter.localmvt', over)
|
||||
.on('dragexit.localmvt', over)
|
||||
.on('dragover.localmvt', over);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
function drawMvt(selection) {
|
||||
var getPath = svgPath(projection).geojson;
|
||||
|
||||
layer = selection.selectAll('.layer-mvt')
|
||||
.data(_enabled ? [0] : []);
|
||||
|
||||
layer.exit()
|
||||
.remove();
|
||||
|
||||
layer = layer.enter()
|
||||
.append('g')
|
||||
.attr('class', 'layer-mvt')
|
||||
.merge(layer);
|
||||
|
||||
|
||||
var paths = layer
|
||||
.selectAll('path')
|
||||
.data([_geojson]);
|
||||
|
||||
paths.exit()
|
||||
.remove();
|
||||
|
||||
paths = paths.enter()
|
||||
.append('path')
|
||||
.attr('class', 'mvt')
|
||||
.merge(paths);
|
||||
|
||||
paths
|
||||
.attr('d', getPath);
|
||||
|
||||
|
||||
var labelData = _showLabels && _geojson.features ? _geojson.features : [];
|
||||
labelData = labelData.filter(getPath);
|
||||
|
||||
layer
|
||||
.call(drawLabels, 'mvtlabel-halo', labelData)
|
||||
.call(drawLabels, 'mvtlabel', labelData);
|
||||
|
||||
|
||||
function drawLabels(selection, textClass, data) {
|
||||
var labels = selection.selectAll('text.' + textClass)
|
||||
.data(data);
|
||||
|
||||
// exit
|
||||
labels.exit()
|
||||
.remove();
|
||||
|
||||
// enter/update
|
||||
labels = labels.enter()
|
||||
.append('text')
|
||||
.attr('class', textClass)
|
||||
.merge(labels)
|
||||
.text(function(d) {
|
||||
if (d.properties) {
|
||||
return d.properties.desc || d.properties.name;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.attr('x', function(d) {
|
||||
var centroid = getPath.centroid(d);
|
||||
return centroid[0] + 11;
|
||||
})
|
||||
.attr('y', function(d) {
|
||||
var centroid = getPath.centroid(d);
|
||||
return centroid[1];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function vtToGeoJson(bufferdata) {
|
||||
var tile = new vt.VectorTile(new Protobuf(bufferdata.data));
|
||||
var layers = Object.keys(tile.layers);
|
||||
if (!Array.isArray(layers)) { layers = [layers]; }
|
||||
|
||||
var collection = {type: 'FeatureCollection', features: []};
|
||||
|
||||
layers.forEach(function (layerID) {
|
||||
var layer = tile.layers[layerID];
|
||||
if (layer) {
|
||||
for (var i = 0; i < layer.length; i++) {
|
||||
var feature = layer.feature(i).toGeoJSON(bufferdata.zxy[2], bufferdata.zxy[3], bufferdata.zxy[1]);
|
||||
if (layers.length > 1) feature.properties.vt_layer = layerID;
|
||||
collection.features.push(feature);
|
||||
}
|
||||
}
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
function getExtension(fileName) {
|
||||
if (fileName === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex < 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return fileName.substr(lastDotIndex);
|
||||
}
|
||||
|
||||
|
||||
function parseSaveAndZoom(extension, bufferdata) {
|
||||
switch (extension) {
|
||||
case '.pbf':
|
||||
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
|
||||
break;
|
||||
case '.mvt':
|
||||
drawMvt.geojson(vtToGeoJson(bufferdata)).fitZoom();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawMvt.showLabels = function(_) {
|
||||
if (!arguments.length) return _showLabels;
|
||||
_showLabels = _;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.enabled = function(_) {
|
||||
if (!arguments.length) return _enabled;
|
||||
_enabled = _;
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.hasMvt = function() {
|
||||
return (!(_isEmpty(_geojson) || _isEmpty(_geojson.features)));
|
||||
};
|
||||
|
||||
|
||||
drawMvt.geojson = function(gj) {
|
||||
if (!arguments.length) return _geojson;
|
||||
if (_isEmpty(gj) || _isEmpty(gj.features)) return this;
|
||||
_geojson = gj;
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.url = function(url) {
|
||||
d3_request(url)
|
||||
.responseType('arraybuffer')
|
||||
.get(function(err, data) {
|
||||
if (err || !data) return;
|
||||
|
||||
_src = url;
|
||||
var match = url.match(/(pbf|mvt)/i);
|
||||
var extension = match ? ('.' + match[0].toLowerCase()) : '';
|
||||
var zxy = url.match(/\/(\d+)\/(\d+)\/(\d+)/);
|
||||
var bufferdata = {
|
||||
data : data,
|
||||
zxy : zxy
|
||||
};
|
||||
parseSaveAndZoom(extension, bufferdata);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.files = function(fileList) {
|
||||
if (!fileList.length) return this;
|
||||
var f = fileList[0],
|
||||
reader = new FileReader();
|
||||
|
||||
reader.onload = (function(file) {
|
||||
|
||||
return; // todo find x,y,z
|
||||
var data = [];
|
||||
var zxy = [0,0,0];
|
||||
|
||||
_src = file.name;
|
||||
var extension = getExtension(file.name);
|
||||
var bufferdata = {
|
||||
data: data,
|
||||
zxy: zxy
|
||||
};
|
||||
return function (e) {
|
||||
bufferdata.data = e.target.result;
|
||||
parseSaveAndZoom(extension, bufferdata);
|
||||
};
|
||||
})(f);
|
||||
|
||||
reader.readAsArrayBuffer(f);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.getSrc = function () {
|
||||
return _src;
|
||||
};
|
||||
|
||||
|
||||
drawMvt.fitZoom = function() {
|
||||
if (!this.hasMvt()) return this;
|
||||
|
||||
var map = context.map();
|
||||
var viewport = map.trimmedExtent().polygon();
|
||||
var coords = _reduce(_geojson.features, function(coords, feature) {
|
||||
var c = feature.geometry.coordinates;
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (feature.geometry.type) {
|
||||
case 'Point':
|
||||
c = [c];
|
||||
case 'MultiPoint':
|
||||
case 'LineString':
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
c = _flatten(c);
|
||||
case 'Polygon':
|
||||
case 'MultiLineString':
|
||||
c = _flatten(c);
|
||||
break;
|
||||
}
|
||||
/* eslint-enable no-fallthrough */
|
||||
|
||||
return _union(coords, c);
|
||||
}, []);
|
||||
|
||||
if (!geoPolygonIntersectsPolygon(viewport, coords, true)) {
|
||||
var extent = geoExtent(d3_geoBounds({ type: 'LineString', coordinates: coords }));
|
||||
map.centerZoom(extent.center(), map.trimmedExtentZoom(extent));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
init();
|
||||
return drawMvt;
|
||||
}
|
||||
+51
-22
@@ -1,17 +1,26 @@
|
||||
import _throttle from 'lodash-es/throttle';
|
||||
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgPointTransform } from './index';
|
||||
import { services } from '../services';
|
||||
|
||||
|
||||
export function svgNotes(projection, context, dispatch) {
|
||||
if (!dispatch) { dispatch = d3_dispatch('change'); }
|
||||
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
|
||||
var minZoom = 12;
|
||||
var layer = d3_select(null);
|
||||
var _notes;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (svgNotes.initialized) return; // run once
|
||||
@@ -46,22 +55,32 @@ export function svgNotes(projection, context, dispatch) {
|
||||
editOn();
|
||||
|
||||
layer
|
||||
.classed('disabled', false)
|
||||
.style('opacity', 0)
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 1)
|
||||
.on('end', function () { dispatch.call('change'); });
|
||||
.on('end interrupt', function () {
|
||||
dispatch.call('change');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function hideLayer() {
|
||||
editOff();
|
||||
|
||||
throttledRedraw.cancel();
|
||||
layer.interrupt();
|
||||
|
||||
layer
|
||||
.transition()
|
||||
.duration(250)
|
||||
.style('opacity', 0)
|
||||
.on('end', editOff);
|
||||
.on('end interrupt', function () {
|
||||
layer.classed('disabled', true);
|
||||
dispatch.call('change');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -80,37 +99,42 @@ export function svgNotes(projection, context, dispatch) {
|
||||
// enter
|
||||
var notesEnter = notes.enter()
|
||||
.append('g')
|
||||
.attr('class', function(d) { return 'note note-' + d.id + ' ' + d.status; });
|
||||
.attr('class', function(d) { return 'note note-' + d.id + ' ' + d.status; })
|
||||
.classed('new', function(d) { return d.id < 0; });
|
||||
|
||||
// notesEnter
|
||||
// .append('use')
|
||||
// .attr('class', 'note-shadow')
|
||||
// .attr('width', '24px')
|
||||
// .attr('height', '24px')
|
||||
// .attr('x', '-12px')
|
||||
// .attr('y', '-24px')
|
||||
// .attr('xlink:href', '#iD-icon-note');
|
||||
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', '-10px')
|
||||
.attr('x', '-8px')
|
||||
.attr('y', '-22px')
|
||||
.attr('xlink:href', '#iD-icon-note');
|
||||
|
||||
// add dots if there's a comment thread
|
||||
notesEnter.selectAll('.note-annotation')
|
||||
.data(function(d) { return d.comments.length > 1 ? [0] : []; })
|
||||
.data(function(d) { return [d]; })
|
||||
.enter()
|
||||
.append('use')
|
||||
.attr('class', 'note-annotation thread')
|
||||
.attr('width', '14px')
|
||||
.attr('height', '14px')
|
||||
.attr('x', '-7px')
|
||||
.attr('y', '-20px')
|
||||
.attr('xlink:href', '#iD-icon-more');
|
||||
.attr('class', 'note-annotation')
|
||||
.attr('width', '10px')
|
||||
.attr('height', '10px')
|
||||
.attr('x', '-3px')
|
||||
.attr('y', '-19px')
|
||||
.attr('xlink:href', function(d) {
|
||||
return '#iD-icon-' + (d.id < 0 ? 'plus' : (d.status === 'open' ? 'close' : 'apply'));
|
||||
});
|
||||
|
||||
// update
|
||||
notes
|
||||
@@ -156,14 +180,19 @@ export function svgNotes(projection, context, dispatch) {
|
||||
}
|
||||
}
|
||||
|
||||
drawNotes.enabled = function(_) {
|
||||
drawNotes.enabled = function(val) {
|
||||
if (!arguments.length) return svgNotes.enabled;
|
||||
svgNotes.enabled = _;
|
||||
|
||||
svgNotes.enabled = val;
|
||||
if (svgNotes.enabled) {
|
||||
showLayer();
|
||||
} else {
|
||||
hideLayer();
|
||||
if (context.selectedNoteID()) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
};
|
||||
|
||||
+18
-14
@@ -21,6 +21,7 @@ import { uiDisclosure } from './disclosure';
|
||||
import { uiHelp } from './help';
|
||||
import { uiMapData } from './map_data';
|
||||
import { uiMapInMap } from './map_in_map';
|
||||
import { uiSettingsCustomBackground } from './settings/custom_background';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
import { utilCallWhenIdle } from '../util';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
@@ -41,6 +42,9 @@ export function uiBackground(context) {
|
||||
var backgroundDisplayOptions = uiBackgroundDisplayOptions(context);
|
||||
var backgroundOffset = uiBackgroundOffset(context);
|
||||
|
||||
var settingsCustomBackground = uiSettingsCustomBackground(context)
|
||||
.on('change', customChanged);
|
||||
|
||||
|
||||
function setTooltips(selection) {
|
||||
selection.each(function(d, i, nodes) {
|
||||
@@ -100,24 +104,24 @@ export function uiBackground(context) {
|
||||
}
|
||||
|
||||
|
||||
function editCustom() {
|
||||
d3_event.preventDefault();
|
||||
var example = 'https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png';
|
||||
var template = window.prompt(
|
||||
t('background.custom_prompt', { example: example }),
|
||||
_customSource.template() || example
|
||||
);
|
||||
|
||||
if (template) {
|
||||
context.storage('background-custom-template', template);
|
||||
_customSource.template(template);
|
||||
function customChanged(d) {
|
||||
if (d && d.template) {
|
||||
_customSource.template(d.template);
|
||||
chooseBackground(_customSource);
|
||||
} else {
|
||||
_backgroundList.call(updateLayerSelections);
|
||||
_customSource.template('');
|
||||
chooseBackground(context.background().findSource('none'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function editCustom() {
|
||||
d3_event.preventDefault();
|
||||
context.container()
|
||||
.call(settingsCustomBackground);
|
||||
}
|
||||
|
||||
|
||||
function chooseOverlay(d) {
|
||||
d3_event.preventDefault();
|
||||
context.background().toggleOverlayLayer(d);
|
||||
@@ -147,11 +151,11 @@ export function uiBackground(context) {
|
||||
.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(tooltip()
|
||||
.title(t('background.custom_button'))
|
||||
.title(t('settings.custom_background.tooltip'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', editCustom)
|
||||
.call(svgIcon('#iD-icon-edit'));
|
||||
.call(svgIcon('#iD-icon-more'));
|
||||
|
||||
enter.filter(function(d) { return d.best(); })
|
||||
.append('div')
|
||||
|
||||
@@ -23,7 +23,7 @@ export function uiConfirm(selection) {
|
||||
modalSelection.okButton = function() {
|
||||
buttons
|
||||
.append('button')
|
||||
.attr('class', 'action col4')
|
||||
.attr('class', 'button ok-button action col4')
|
||||
.on('click.confirm', function() {
|
||||
modalSelection.remove();
|
||||
})
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { t } from '../util/locale';
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgIcon } from '../svg';
|
||||
|
||||
import {
|
||||
uiDataHeader,
|
||||
uiRawTagEditor
|
||||
} from './index';
|
||||
|
||||
|
||||
export function uiDataEditor(context) {
|
||||
var dataHeader = uiDataHeader();
|
||||
var rawTagEditor = uiRawTagEditor(context);
|
||||
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', 'fr data-editor-close')
|
||||
.on('click', function() {
|
||||
context.enter(modeBrowse(context));
|
||||
})
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
headerEnter
|
||||
.append('h3')
|
||||
.text(t('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]);
|
||||
|
||||
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]);
|
||||
|
||||
rte.enter()
|
||||
.append('div')
|
||||
.attr('class', 'inspector-border raw-tag-editor inspector-inner data-editor')
|
||||
.merge(rte)
|
||||
.call(rawTagEditor
|
||||
.expanded(true)
|
||||
.readOnlyTags([/./])
|
||||
.tags((_datum && _datum.properties) || {})
|
||||
.state('hover')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
dataEditor.datum = function(val) {
|
||||
if (!arguments.length) return _datum;
|
||||
_datum = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
return dataEditor;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { t } from '../util/locale';
|
||||
import { svgIcon } from '../svg';
|
||||
|
||||
|
||||
export 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')
|
||||
.text(t('map_data.layers.custom.title'));
|
||||
}
|
||||
|
||||
|
||||
dataHeader.datum = function(val) {
|
||||
if (!arguments.length) return _datum;
|
||||
_datum = val;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
return dataHeader;
|
||||
}
|
||||
@@ -34,12 +34,15 @@ export function uiField(context, presetField, entity, options) {
|
||||
var _tags = {};
|
||||
|
||||
|
||||
// field implementation
|
||||
field.impl = uiFields[field.type](field, context)
|
||||
.on('change', function(t, onInput) {
|
||||
dispatch.call('change', field, t, onInput);
|
||||
});
|
||||
|
||||
// if this field cares about the entity, pass it along
|
||||
if (entity && field.impl.entity) {
|
||||
field.entityID = entity.id;
|
||||
field.impl.entity(entity);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ export function uiFieldAccess(field, context) {
|
||||
|
||||
|
||||
access.options = function(type) {
|
||||
var options = ['no', 'permissive', 'private', 'destination'];
|
||||
var options = ['no', 'permissive', 'private', 'permit', 'destination'];
|
||||
|
||||
if (type !== 'access') {
|
||||
options.unshift('yes');
|
||||
|
||||
+86
-68
@@ -8,11 +8,17 @@ import _some from 'lodash-es/some';
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { event as d3_event } from 'd3-selection';
|
||||
|
||||
import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
|
||||
import { d3combobox as d3_combobox } from '../../lib/d3.combobox.js';
|
||||
|
||||
import { t } from '../../util/locale';
|
||||
import { services } from '../../services';
|
||||
|
||||
import {
|
||||
utilGetSetValue,
|
||||
utilNoAuto,
|
||||
@@ -28,29 +34,29 @@ export {
|
||||
|
||||
|
||||
export function uiFieldCombo(field, context) {
|
||||
var dispatch = d3_dispatch('change'),
|
||||
nominatim = services.geocoder,
|
||||
taginfo = services.taginfo,
|
||||
isMulti = (field.type === 'multiCombo'),
|
||||
isNetwork = (field.type === 'networkCombo'),
|
||||
isSemi = (field.type === 'semiCombo'),
|
||||
optstrings = field.strings && field.strings.options,
|
||||
optarray = field.options,
|
||||
snake_case = (field.snake_case || (field.snake_case === undefined)),
|
||||
caseSensitive = field.caseSensitive,
|
||||
combobox = d3_combobox()
|
||||
.container(context.container())
|
||||
.caseSensitive(caseSensitive)
|
||||
.minItems(isMulti || isSemi ? 1 : 2),
|
||||
comboData = [],
|
||||
multiData = [],
|
||||
container,
|
||||
input,
|
||||
entity,
|
||||
country;
|
||||
var dispatch = d3_dispatch('change');
|
||||
var nominatim = services.geocoder;
|
||||
var taginfo = services.taginfo;
|
||||
var isMulti = (field.type === 'multiCombo');
|
||||
var isNetwork = (field.type === 'networkCombo');
|
||||
var isSemi = (field.type === 'semiCombo');
|
||||
var optstrings = field.strings && field.strings.options;
|
||||
var optarray = field.options;
|
||||
var snake_case = (field.snake_case || (field.snake_case === undefined));
|
||||
var caseSensitive = field.caseSensitive;
|
||||
var combobox = d3_combobox()
|
||||
.container(context.container())
|
||||
.caseSensitive(caseSensitive)
|
||||
.minItems(isMulti || isSemi ? 1 : 2);
|
||||
var container = d3_select(null);
|
||||
var input = d3_select(null);
|
||||
var _comboData = [];
|
||||
var _multiData = [];
|
||||
var _entity;
|
||||
var _country;
|
||||
|
||||
// ensure multiCombo field.key ends with a ':'
|
||||
if (isMulti && field.key.match(/:$/) === null) {
|
||||
if (isMulti && /[^:]$/.test(field.key)) {
|
||||
field.key += ':';
|
||||
}
|
||||
|
||||
@@ -76,11 +82,11 @@ export function uiFieldCombo(field, context) {
|
||||
dval = clean(dval || '');
|
||||
|
||||
if (optstrings) {
|
||||
var match = _find(comboData, function(o) {
|
||||
var found = _find(_comboData, function(o) {
|
||||
return o.key && clean(o.value) === dval;
|
||||
});
|
||||
if (match) {
|
||||
return match.key;
|
||||
if (found) {
|
||||
return found.key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +104,9 @@ export function uiFieldCombo(field, context) {
|
||||
tval = tval || '';
|
||||
|
||||
if (optstrings) {
|
||||
var match = _find(comboData, function(o) { return o.key === tval && o.value; });
|
||||
if (match) {
|
||||
return match.value;
|
||||
var found = _find(_comboData, function(o) { return o.key === tval && o.value; });
|
||||
if (found) {
|
||||
return found.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +146,7 @@ export function uiFieldCombo(field, context) {
|
||||
if (!(optstrings || optarray)) return;
|
||||
|
||||
if (optstrings) {
|
||||
comboData = Object.keys(optstrings).map(function(k) {
|
||||
_comboData = Object.keys(optstrings).map(function(k) {
|
||||
var v = field.t('options.' + k, { 'default': optstrings[k] });
|
||||
return {
|
||||
key: k,
|
||||
@@ -150,7 +156,7 @@ export function uiFieldCombo(field, context) {
|
||||
});
|
||||
|
||||
} else if (optarray) {
|
||||
comboData = optarray.map(function(k) {
|
||||
_comboData = optarray.map(function(k) {
|
||||
var v = snake_case ? unsnake(k) : k;
|
||||
return {
|
||||
key: k,
|
||||
@@ -160,17 +166,17 @@ export function uiFieldCombo(field, context) {
|
||||
});
|
||||
}
|
||||
|
||||
combobox.data(objectDifference(comboData, multiData));
|
||||
if (callback) callback(comboData);
|
||||
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 && country && country.indexOf(q.toLowerCase()) === 0;
|
||||
var hasCountryPrefix = isNetwork && _country && _country.indexOf(q.toLowerCase()) === 0;
|
||||
if (hasCountryPrefix) {
|
||||
query = country + ':';
|
||||
query = _country + ':';
|
||||
}
|
||||
|
||||
var params = {
|
||||
@@ -179,19 +185,19 @@ export function uiFieldCombo(field, context) {
|
||||
query: query
|
||||
};
|
||||
|
||||
if (entity) {
|
||||
params.geometry = context.geometry(entity.id);
|
||||
if (_entity) {
|
||||
params.geometry = context.geometry(_entity.id);
|
||||
}
|
||||
|
||||
taginfo[fn](params, function(err, data) {
|
||||
if (err) return;
|
||||
if (hasCountryPrefix) {
|
||||
data = _filter(data, function(d) {
|
||||
return d.value.toLowerCase().indexOf(country + ':') === 0;
|
||||
return d.value.toLowerCase().indexOf(_country + ':') === 0;
|
||||
});
|
||||
}
|
||||
|
||||
comboData = _map(data, function(d) {
|
||||
_comboData = _map(data, function(d) {
|
||||
var k = d.value;
|
||||
if (isMulti) k = k.replace(field.key, '');
|
||||
var v = snake_case ? unsnake(k) : k;
|
||||
@@ -202,8 +208,8 @@ export function uiFieldCombo(field, context) {
|
||||
};
|
||||
});
|
||||
|
||||
comboData = objectDifference(comboData, multiData);
|
||||
if (callback) callback(comboData);
|
||||
_comboData = objectDifference(_comboData, _multiData);
|
||||
if (callback) callback(_comboData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,7 +225,7 @@ export function uiFieldCombo(field, context) {
|
||||
ph = field.placeholder() || placeholders.slice(0, 3).join(', ');
|
||||
}
|
||||
|
||||
if (ph.match(/(…|\.\.\.)$/) === null) {
|
||||
if (!/(…|\.\.\.)$/.test(ph)) {
|
||||
ph += '…';
|
||||
}
|
||||
|
||||
@@ -229,21 +235,31 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
|
||||
function change() {
|
||||
var val = tagValue(utilGetSetValue(input)),
|
||||
t = {};
|
||||
var val = tagValue(utilGetSetValue(input));
|
||||
var t = {};
|
||||
|
||||
if (isMulti || isSemi) {
|
||||
if (!val) return;
|
||||
container.classed('active', false);
|
||||
utilGetSetValue(input, '');
|
||||
|
||||
if (isMulti) {
|
||||
field.keys.push(field.key + val);
|
||||
t[field.key + val] = 'yes';
|
||||
var key = field.key + val;
|
||||
if (_entity) {
|
||||
// don't set a multicombo value to 'yes' if it already has a non-'no' value
|
||||
// e.g. `language:de=main`
|
||||
var old = _entity.tags[key] || '';
|
||||
if (old && old.toLowerCase() !== 'no') return;
|
||||
}
|
||||
field.keys.push(key);
|
||||
t[key] = 'yes';
|
||||
|
||||
} else if (isSemi) {
|
||||
var arr = multiData.map(function(d) { return d.key; });
|
||||
var arr = _multiData.map(function(d) { return d.key; });
|
||||
arr.push(val);
|
||||
t[field.key] = _compact(_uniq(arr)).join(';');
|
||||
}
|
||||
|
||||
window.setTimeout(function() { input.node().focus(); }, 10);
|
||||
|
||||
} else {
|
||||
@@ -260,8 +276,8 @@ export function uiFieldCombo(field, context) {
|
||||
if (isMulti) {
|
||||
t[d.key] = undefined;
|
||||
} else if (isSemi) {
|
||||
_remove(multiData, function(md) { return md.key === d.key; });
|
||||
var arr = multiData.map(function(md) { return md.key; });
|
||||
_remove(_multiData, function(md) { return md.key === d.key; });
|
||||
var arr = _multiData.map(function(md) { return md.key; });
|
||||
arr = _compact(_uniq(arr));
|
||||
t[field.key] = arr.length ? arr.join(';') : undefined;
|
||||
}
|
||||
@@ -296,10 +312,10 @@ export function uiFieldCombo(field, context) {
|
||||
.call(initCombo, selection)
|
||||
.merge(input);
|
||||
|
||||
if (isNetwork && nominatim && entity) {
|
||||
var center = entity.extent(context.graph()).center();
|
||||
if (isNetwork && nominatim && _entity) {
|
||||
var center = _entity.extent(context.graph()).center();
|
||||
nominatim.countryCode(center, function (err, code) {
|
||||
country = code;
|
||||
_country = code;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -322,35 +338,37 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
combo.tags = function(tags) {
|
||||
if (isMulti || isSemi) {
|
||||
multiData = [];
|
||||
_multiData = [];
|
||||
|
||||
if (isMulti) {
|
||||
// Build multiData array containing keys already set..
|
||||
Object.keys(tags).forEach(function(key) {
|
||||
if (key.indexOf(field.key) !== 0 || tags[key].toLowerCase() !== 'yes') return;
|
||||
// Build _multiData array containing keys already set..
|
||||
for (var k in tags) {
|
||||
if (k.indexOf(field.key) !== 0) continue;
|
||||
var v = (tags[k] || '').toLowerCase();
|
||||
if (v === '' || v === 'no') continue;
|
||||
|
||||
var suffix = key.substring(field.key.length);
|
||||
multiData.push({
|
||||
key: key,
|
||||
var suffix = k.substring(field.key.length);
|
||||
_multiData.push({
|
||||
key: k,
|
||||
value: displayValue(suffix)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Set keys for form-field modified (needed for undo and reset buttons)..
|
||||
field.keys = _map(multiData, 'key');
|
||||
field.keys = _map(_multiData, 'key');
|
||||
|
||||
} else if (isSemi) {
|
||||
var arr = _compact(_uniq((tags[field.key] || '').split(';')));
|
||||
multiData = arr.map(function(key) {
|
||||
_multiData = arr.map(function(k) {
|
||||
return {
|
||||
key: key,
|
||||
value: displayValue(key)
|
||||
key: k,
|
||||
value: displayValue(k)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Exclude existing multikeys from combo options..
|
||||
var available = objectDifference(comboData, multiData);
|
||||
var available = objectDifference(_comboData, _multiData);
|
||||
combobox.data(available);
|
||||
|
||||
// Hide 'Add' button if this field uses fixed set of
|
||||
@@ -361,7 +379,7 @@ export function uiFieldCombo(field, context) {
|
||||
|
||||
// Render chips
|
||||
var chips = container.selectAll('.chips')
|
||||
.data(multiData);
|
||||
.data(_multiData);
|
||||
|
||||
chips.exit()
|
||||
.remove();
|
||||
@@ -394,9 +412,9 @@ export function uiFieldCombo(field, context) {
|
||||
};
|
||||
|
||||
|
||||
combo.entity = function(_) {
|
||||
if (!arguments.length) return entity;
|
||||
entity = _;
|
||||
combo.entity = function(val) {
|
||||
if (!arguments.length) return _entity;
|
||||
_entity = val;
|
||||
return combo;
|
||||
};
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export function uiFieldText(field, context) {
|
||||
|
||||
// parse as a number
|
||||
function parsed(val) {
|
||||
return parseInt(val || 0, 10) || 0;
|
||||
return parseFloat(val || 0, 10) || 0;
|
||||
}
|
||||
|
||||
// clamp number to min/max
|
||||
|
||||
@@ -6,7 +6,7 @@ import { utilGetSetValue, utilNoAuto } from '../util';
|
||||
|
||||
|
||||
export function uiFormFields(context) {
|
||||
var fieldsArr;
|
||||
var _fieldsArr;
|
||||
|
||||
|
||||
function formFields(selection, klass) {
|
||||
@@ -15,9 +15,8 @@ export function uiFormFields(context) {
|
||||
|
||||
|
||||
function render(selection, klass) {
|
||||
|
||||
var shown = fieldsArr.filter(function(field) { return field.isShown(); }),
|
||||
notShown = fieldsArr.filter(function(field) { return !field.isShown(); });
|
||||
var shown = _fieldsArr.filter(function(field) { return field.isShown(); });
|
||||
var notShown = _fieldsArr.filter(function(field) { return !field.isShown(); });
|
||||
|
||||
var container = selection.selectAll('.form-fields-container')
|
||||
.data([0]);
|
||||
@@ -29,7 +28,7 @@ export function uiFormFields(context) {
|
||||
|
||||
|
||||
var fields = container.selectAll('.wrap-form-field')
|
||||
.data(shown, function(d) { return d.id; });
|
||||
.data(shown, function(d) { return d.id + (d.entityID || ''); });
|
||||
|
||||
fields.exit()
|
||||
.remove();
|
||||
@@ -112,9 +111,9 @@ export function uiFormFields(context) {
|
||||
}
|
||||
|
||||
|
||||
formFields.fieldsArr = function(_) {
|
||||
if (!arguments.length) return fieldsArr;
|
||||
fieldsArr = _;
|
||||
formFields.fieldsArr = function(val) {
|
||||
if (!arguments.length) return _fieldsArr;
|
||||
_fieldsArr = val;
|
||||
return formFields;
|
||||
};
|
||||
|
||||
|
||||
@@ -147,6 +147,17 @@ export function uiHelp(context) {
|
||||
'boundary',
|
||||
'boundary_add'
|
||||
]],
|
||||
['notes', [
|
||||
'intro',
|
||||
'add_note_h',
|
||||
'add_note',
|
||||
'move_note',
|
||||
'update_note_h',
|
||||
'update_note',
|
||||
'save_note_h',
|
||||
'save_note'
|
||||
]],
|
||||
|
||||
['imagery', [
|
||||
'intro',
|
||||
'sources_h',
|
||||
@@ -210,6 +221,9 @@ export function uiHelp(context) {
|
||||
'help.relations.turn_restriction_h': 3,
|
||||
'help.relations.route_h': 3,
|
||||
'help.relations.boundary_h': 3,
|
||||
'help.notes.add_note_h': 3,
|
||||
'help.notes.update_note_h': 3,
|
||||
'help.notes.save_note_h': 3,
|
||||
'help.imagery.sources_h': 3,
|
||||
'help.imagery.offsets_h': 3,
|
||||
'help.streetlevel.using_h': 3,
|
||||
@@ -220,6 +234,7 @@ export function uiHelp(context) {
|
||||
point: icon('#iD-icon-point', 'pre-text'),
|
||||
line: icon('#iD-icon-line', 'pre-text'),
|
||||
area: icon('#iD-icon-area', 'pre-text'),
|
||||
note: icon('#iD-icon-note', 'pre-text add-note'),
|
||||
plus: icon('#iD-icon-plus', 'pre-text'),
|
||||
minus: icon('#iD-icon-minus', 'pre-text'),
|
||||
orthogonalize: icon('#iD-operation-orthogonalize', 'pre-text'),
|
||||
|
||||
@@ -13,6 +13,8 @@ export { uiConfirm } from './confirm';
|
||||
export { uiConflicts } from './conflicts';
|
||||
export { uiContributors } from './contributors';
|
||||
export { uiCurtain } from './curtain';
|
||||
export { uiDataEditor } from './data_editor';
|
||||
export { uiDataHeader } from './data_header';
|
||||
export { uiDisclosure } from './disclosure';
|
||||
export { uiEditMenu } from './edit_menu';
|
||||
export { uiEntityEditor } from './entity_editor';
|
||||
|
||||
+112
-21
@@ -2,6 +2,7 @@ import {
|
||||
event as d3_event,
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
|
||||
@@ -13,6 +14,7 @@ import { modeBrowse } from '../modes';
|
||||
import { services } from '../services';
|
||||
import { svgDefs, svgIcon } from '../svg';
|
||||
import { utilGetDimensions } from '../util/dimensions';
|
||||
import { utilRebind } from '../util';
|
||||
|
||||
import { uiAccount } from './account';
|
||||
import { uiAttribution } from './attribution';
|
||||
@@ -45,6 +47,7 @@ import { uiCmd } from './cmd';
|
||||
|
||||
export function uiInit(context) {
|
||||
var uiInitCounter = 0;
|
||||
var dispatch = d3_dispatch('photoviewerResize');
|
||||
|
||||
|
||||
function render(container) {
|
||||
@@ -256,7 +259,33 @@ export function uiInit(context) {
|
||||
.append('div')
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
photoviewer
|
||||
.append('button')
|
||||
.attr('class', 'resize-handle-xy')
|
||||
.on(
|
||||
'mousedown',
|
||||
buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnX: true, resizeOnY: true })
|
||||
);
|
||||
|
||||
photoviewer
|
||||
.append('button')
|
||||
.attr('class', 'resize-handle-x')
|
||||
.on(
|
||||
'mousedown',
|
||||
buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnX: true })
|
||||
);
|
||||
|
||||
photoviewer
|
||||
.append('button')
|
||||
.attr('class', 'resize-handle-y')
|
||||
.on(
|
||||
'mousedown',
|
||||
buildResizeListener(photoviewer, 'photoviewerResize', dispatch, { resizeOnY: true })
|
||||
);
|
||||
|
||||
var mapDimensions = map.dimensions();
|
||||
|
||||
// bind events
|
||||
window.onbeforeunload = function() {
|
||||
return context.save();
|
||||
};
|
||||
@@ -265,30 +294,13 @@ export function uiInit(context) {
|
||||
context.history().unlock();
|
||||
};
|
||||
|
||||
var mapDimensions = map.dimensions();
|
||||
|
||||
|
||||
function onResize() {
|
||||
mapDimensions = utilGetDimensions(content, true);
|
||||
map.dimensions(mapDimensions);
|
||||
}
|
||||
|
||||
d3_select(window)
|
||||
.on('resize.editor', onResize);
|
||||
|
||||
onResize();
|
||||
|
||||
function pan(d) {
|
||||
return function() {
|
||||
d3_event.preventDefault();
|
||||
context.pan(d, 100);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// pan amount
|
||||
var pa = 80;
|
||||
|
||||
var pa = 80; // pan amount
|
||||
var keybinding = d3_keybinding('main')
|
||||
.on('⌫', function() { d3_event.preventDefault(); })
|
||||
.on('←', pan([pa, 0]))
|
||||
@@ -316,8 +328,8 @@ export function uiInit(context) {
|
||||
.call(uiShortcuts(context));
|
||||
}
|
||||
|
||||
var osm = context.connection(),
|
||||
auth = uiLoading(context).message(t('loading_auth')).blocking(true);
|
||||
var osm = context.connection();
|
||||
var auth = uiLoading(context).message(t('loading_auth')).blocking(true);
|
||||
|
||||
if (osm && auth) {
|
||||
osm
|
||||
@@ -336,6 +348,85 @@ export function uiInit(context) {
|
||||
hash.startWalkthrough = false;
|
||||
context.container().call(uiIntro(context));
|
||||
}
|
||||
|
||||
|
||||
function onResize() {
|
||||
mapDimensions = utilGetDimensions(content, true);
|
||||
map.dimensions(mapDimensions);
|
||||
|
||||
// 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('photoviewerResize', photoviewer, setPhotoDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function pan(d) {
|
||||
return function() {
|
||||
d3_event.preventDefault();
|
||||
context.pan(d, 100);
|
||||
};
|
||||
}
|
||||
|
||||
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 startX;
|
||||
var startY;
|
||||
var startWidth;
|
||||
var startHeight;
|
||||
|
||||
function startResize() {
|
||||
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_select(window)
|
||||
.on('.' + eventName, null);
|
||||
}
|
||||
|
||||
return function initResize() {
|
||||
startX = d3_event.clientX;
|
||||
startY = d3_event.clientY;
|
||||
startWidth = target.node().getBoundingClientRect().width;
|
||||
startHeight = target.node().getBoundingClientRect().height;
|
||||
|
||||
d3_select(window)
|
||||
.on('mousemove.' + eventName, startResize, false)
|
||||
.on('mouseup.' + eventName, stopResize, false);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -370,5 +461,5 @@ export function uiInit(context) {
|
||||
|
||||
ui.sidebar = uiSidebar(context);
|
||||
|
||||
return ui;
|
||||
return utilRebind(ui, dispatch, 'on');
|
||||
}
|
||||
|
||||
+27
-27
@@ -17,12 +17,12 @@ import { icon, pad, transitionTime } from './helper';
|
||||
|
||||
|
||||
export function uiIntroArea(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
playground = [-85.63552, 41.94159],
|
||||
playgroundPreset = context.presets().item('leisure/playground'),
|
||||
descriptionField = context.presets().field('description'),
|
||||
timeouts = [],
|
||||
areaId;
|
||||
var dispatch = d3_dispatch('done');
|
||||
var playground = [-85.63552, 41.94159];
|
||||
var playgroundPreset = context.presets().item('leisure/playground');
|
||||
var descriptionField = context.presets().field('description');
|
||||
var timeouts = [];
|
||||
var _areaID;
|
||||
|
||||
|
||||
var chapter = {
|
||||
@@ -51,7 +51,7 @@ export function uiIntroArea(context, reveal) {
|
||||
function addArea() {
|
||||
context.enter(modeBrowse(context));
|
||||
context.history().reset('initial');
|
||||
areaId = null;
|
||||
_areaID = null;
|
||||
|
||||
var msec = transitionTime(playground, context.map().center());
|
||||
if (msec) { reveal(null, null, { duration: 0 }); }
|
||||
@@ -85,7 +85,7 @@ export function uiIntroArea(context, reveal) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
areaId = null;
|
||||
_areaID = null;
|
||||
context.map().zoomEase(19.5, 500);
|
||||
|
||||
timeout(function() {
|
||||
@@ -120,7 +120,7 @@ export function uiIntroArea(context, reveal) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
areaId = null;
|
||||
_areaID = null;
|
||||
revealPlayground(playground,
|
||||
t('intro.areas.continue_playground', { alt: uiCmd.display('⌥') }),
|
||||
{ duration: 250 }
|
||||
@@ -144,7 +144,7 @@ export function uiIntroArea(context, reveal) {
|
||||
return;
|
||||
}
|
||||
} else if (mode.id === 'select') {
|
||||
areaId = context.selectedIDs()[0];
|
||||
_areaID = context.selectedIDs()[0];
|
||||
return continueTo(searchPresets);
|
||||
} else {
|
||||
return chapter.restart();
|
||||
@@ -164,7 +164,7 @@ export function uiIntroArea(context, reveal) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
areaId = null;
|
||||
_areaID = null;
|
||||
revealPlayground(playground,
|
||||
t('intro.areas.finish_playground'), { duration: 250 }
|
||||
);
|
||||
@@ -181,7 +181,7 @@ export function uiIntroArea(context, reveal) {
|
||||
if (mode.id === 'draw-area') {
|
||||
return;
|
||||
} else if (mode.id === 'select') {
|
||||
areaId = context.selectedIDs()[0];
|
||||
_areaID = context.selectedIDs()[0];
|
||||
return continueTo(searchPresets);
|
||||
} else {
|
||||
return chapter.restart();
|
||||
@@ -197,12 +197,12 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
|
||||
function searchPresets() {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
context.enter(modeSelect(context, [_areaID]));
|
||||
}
|
||||
|
||||
// disallow scrolling
|
||||
@@ -222,14 +222,14 @@ export function uiIntroArea(context, reveal) {
|
||||
}, 400); // after preset list pane visible..
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
if (!_areaID || !context.hasEntity(_areaID)) {
|
||||
return continueTo(addArea);
|
||||
}
|
||||
|
||||
var ids = context.selectedIDs();
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== areaId) {
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
// keep the user's area selected..
|
||||
context.enter(modeSelect(context, [areaId]));
|
||||
context.enter(modeSelect(context, [_areaID]));
|
||||
|
||||
// reset pane, in case user somehow happened to change it..
|
||||
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
|
||||
@@ -278,11 +278,11 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
|
||||
function clickAddField() {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
if (!_areaID || !context.hasEntity(_areaID)) {
|
||||
return addArea();
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
return searchPresets();
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
// 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);
|
||||
var entity = context.entity(_areaID);
|
||||
if (entity.tags.description) {
|
||||
return continueTo(play);
|
||||
}
|
||||
@@ -351,11 +351,11 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
|
||||
function chooseDescriptionField() {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
if (!_areaID || !context.hasEntity(_areaID)) {
|
||||
return addArea();
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
return searchPresets();
|
||||
}
|
||||
|
||||
@@ -400,11 +400,11 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
|
||||
function describePlayground() {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
if (!_areaID || !context.hasEntity(_areaID)) {
|
||||
return addArea();
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
return searchPresets();
|
||||
}
|
||||
|
||||
@@ -432,11 +432,11 @@ export function uiIntroArea(context, reveal) {
|
||||
|
||||
|
||||
function retryChooseDescription() {
|
||||
if (!areaId || !context.hasEntity(areaId)) {
|
||||
if (!_areaID || !context.hasEntity(_areaID)) {
|
||||
return addArea();
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== areaId) {
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _areaID) {
|
||||
return searchPresets();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,15 @@ import { icon, pad, isMostlySquare, selectMenuItem, transitionTime } from './hel
|
||||
|
||||
|
||||
export function uiIntroBuilding(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
house = [-85.62815, 41.95638],
|
||||
tank = [-85.62732, 41.95347],
|
||||
buildingCatetory = context.presets().item('category-building'),
|
||||
housePreset = context.presets().item('building/house'),
|
||||
tankPreset = context.presets().item('man_made/storage_tank'),
|
||||
timeouts = [],
|
||||
houseId = null,
|
||||
tankId = null;
|
||||
var dispatch = d3_dispatch('done');
|
||||
var house = [-85.62815, 41.95638];
|
||||
var tank = [-85.62732, 41.95347];
|
||||
var buildingCatetory = context.presets().item('category-building');
|
||||
var housePreset = context.presets().item('building/house');
|
||||
var tankPreset = context.presets().item('man_made/storage_tank');
|
||||
var timeouts = [];
|
||||
var _houseID = null;
|
||||
var _tankID = null;
|
||||
|
||||
|
||||
var chapter = {
|
||||
@@ -76,7 +76,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
function addHouse() {
|
||||
context.enter(modeBrowse(context));
|
||||
context.history().reset('initial');
|
||||
houseId = null;
|
||||
_houseID = null;
|
||||
|
||||
var msec = transitionTime(house, context.map().center());
|
||||
if (msec) { reveal(null, null, { duration: 0 }); }
|
||||
@@ -110,7 +110,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
return continueTo(addHouse);
|
||||
}
|
||||
|
||||
houseId = null;
|
||||
_houseID = null;
|
||||
context.map().zoomEase(20, 500);
|
||||
|
||||
timeout(function() {
|
||||
@@ -140,7 +140,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
return continueTo(addHouse);
|
||||
}
|
||||
|
||||
houseId = null;
|
||||
_houseID = null;
|
||||
|
||||
revealHouse(house, t('intro.buildings.continue_building'));
|
||||
|
||||
@@ -152,13 +152,13 @@ export function uiIntroBuilding(context, reveal) {
|
||||
if (mode.id === 'draw-area') {
|
||||
return;
|
||||
} else if (mode.id === 'select') {
|
||||
var graph = context.graph(),
|
||||
way = context.entity(context.selectedIDs()[0]),
|
||||
nodes = graph.childNodes(way),
|
||||
points = _uniq(nodes).map(function(n) { return context.projection(n.loc); });
|
||||
var graph = context.graph();
|
||||
var way = context.entity(context.selectedIDs()[0]);
|
||||
var nodes = graph.childNodes(way);
|
||||
var points = _uniq(nodes).map(function(n) { return context.projection(n.loc); });
|
||||
|
||||
if (isMostlySquare(points)) {
|
||||
houseId = way.id;
|
||||
_houseID = way.id;
|
||||
return continueTo(chooseCategoryBuilding);
|
||||
} else {
|
||||
return continueTo(retryHouse);
|
||||
@@ -198,12 +198,12 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function chooseCategoryBuilding() {
|
||||
if (!houseId || !context.hasEntity(houseId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
|
||||
context.enter(modeSelect(context, [_houseID]));
|
||||
}
|
||||
|
||||
// disallow scrolling
|
||||
@@ -228,11 +228,11 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (!houseId || !context.hasEntity(houseId)) {
|
||||
if (!_houseID || !context.hasEntity(_houseID)) {
|
||||
return continueTo(addHouse);
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== houseId) {
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
|
||||
return continueTo(chooseCategoryBuilding);
|
||||
}
|
||||
});
|
||||
@@ -247,12 +247,12 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function choosePresetHouse() {
|
||||
if (!houseId || !context.hasEntity(houseId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
|
||||
context.enter(modeSelect(context, [_houseID]));
|
||||
}
|
||||
|
||||
// disallow scrolling
|
||||
@@ -274,15 +274,14 @@ export function uiIntroBuilding(context, reveal) {
|
||||
continueTo(closeEditorHouse);
|
||||
});
|
||||
|
||||
|
||||
}, 400); // after preset list pane visible..
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (!houseId || !context.hasEntity(houseId)) {
|
||||
if (!_houseID || !context.hasEntity(_houseID)) {
|
||||
return continueTo(addHouse);
|
||||
}
|
||||
var ids = context.selectedIDs();
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== houseId) {
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== _houseID) {
|
||||
return continueTo(chooseCategoryBuilding);
|
||||
}
|
||||
});
|
||||
@@ -297,12 +296,12 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function closeEditorHouse() {
|
||||
if (!houseId || !context.hasEntity(houseId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _houseID) {
|
||||
context.enter(modeSelect(context, [_houseID]));
|
||||
}
|
||||
|
||||
context.history().checkpoint('hasHouse');
|
||||
@@ -325,7 +324,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function rightClickHouse() {
|
||||
if (!houseId) return chapter.restart();
|
||||
if (!_houseID) return chapter.restart();
|
||||
|
||||
context.enter(modeBrowse(context));
|
||||
context.history().reset('hasHouse');
|
||||
@@ -340,7 +339,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') return;
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length !== 1 || ids[0] !== houseId) return;
|
||||
if (ids.length !== 1 || ids[0] !== _houseID) return;
|
||||
|
||||
timeout(function() {
|
||||
var node = selectMenuItem('orthogonalize').node();
|
||||
@@ -367,8 +366,8 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function clickSquare() {
|
||||
if (!houseId) return chapter.restart();
|
||||
var entity = context.hasEntity(houseId);
|
||||
if (!_houseID) return chapter.restart();
|
||||
var entity = context.hasEntity(_houseID);
|
||||
if (!entity) return continueTo(rightClickHouse);
|
||||
|
||||
var node = selectMenuItem('orthogonalize').node();
|
||||
@@ -453,7 +452,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
function addTank() {
|
||||
context.enter(modeBrowse(context));
|
||||
context.history().reset('doneSquare');
|
||||
tankId = null;
|
||||
_tankID = null;
|
||||
|
||||
var msec = transitionTime(tank, context.map().center());
|
||||
if (msec) { reveal(null, null, { duration: 0 }); }
|
||||
@@ -482,7 +481,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
return continueTo(addTank);
|
||||
}
|
||||
|
||||
tankId = null;
|
||||
_tankID = null;
|
||||
|
||||
timeout(function() {
|
||||
revealTank(tank, t('intro.buildings.start_tank'));
|
||||
@@ -511,7 +510,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
return continueTo(addTank);
|
||||
}
|
||||
|
||||
tankId = null;
|
||||
_tankID = null;
|
||||
|
||||
revealTank(tank, t('intro.buildings.continue_tank'));
|
||||
|
||||
@@ -523,7 +522,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
if (mode.id === 'draw-area') {
|
||||
return;
|
||||
} else if (mode.id === 'select') {
|
||||
tankId = context.selectedIDs()[0];
|
||||
_tankID = context.selectedIDs()[0];
|
||||
return continueTo(searchPresetTank);
|
||||
} else {
|
||||
return continueTo(addTank);
|
||||
@@ -539,12 +538,12 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function searchPresetTank() {
|
||||
if (!tankId || !context.hasEntity(tankId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
|
||||
context.enter(modeSelect(context, [_tankID]));
|
||||
}
|
||||
|
||||
// disallow scrolling
|
||||
@@ -564,14 +563,14 @@ export function uiIntroBuilding(context, reveal) {
|
||||
}, 400); // after preset list pane visible..
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (!tankId || !context.hasEntity(tankId)) {
|
||||
if (!_tankID || !context.hasEntity(_tankID)) {
|
||||
return continueTo(addTank);
|
||||
}
|
||||
|
||||
var ids = context.selectedIDs();
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== tankId) {
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== _tankID) {
|
||||
// keep the user's area selected..
|
||||
context.enter(modeSelect(context, [tankId]));
|
||||
context.enter(modeSelect(context, [_tankID]));
|
||||
|
||||
// reset pane, in case user somehow happened to change it..
|
||||
d3_select('.inspector-wrap .panewrap').style('right', '-100%');
|
||||
@@ -620,12 +619,12 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function closeEditorTank() {
|
||||
if (!tankId || !context.hasEntity(tankId)) {
|
||||
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]));
|
||||
if (context.mode().id !== 'select' || !ids.length || ids[0] !== _tankID) {
|
||||
context.enter(modeSelect(context, [_tankID]));
|
||||
}
|
||||
|
||||
context.history().checkpoint('hasTank');
|
||||
@@ -648,7 +647,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function rightClickTank() {
|
||||
if (!tankId) return continueTo(addTank);
|
||||
if (!_tankID) return continueTo(addTank);
|
||||
|
||||
context.enter(modeBrowse(context));
|
||||
context.history().reset('hasTank');
|
||||
@@ -658,7 +657,7 @@ export function uiIntroBuilding(context, reveal) {
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') return;
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length !== 1 || ids[0] !== tankId) return;
|
||||
if (ids.length !== 1 || ids[0] !== _tankID) return;
|
||||
|
||||
timeout(function() {
|
||||
var node = selectMenuItem('circularize').node();
|
||||
@@ -689,8 +688,8 @@ export function uiIntroBuilding(context, reveal) {
|
||||
|
||||
|
||||
function clickCircle() {
|
||||
if (!tankId) return chapter.restart();
|
||||
var entity = context.hasEntity(tankId);
|
||||
if (!_tankID) return chapter.restart();
|
||||
var entity = context.hasEntity(_tankID);
|
||||
if (!entity) return continueTo(rightClickTank);
|
||||
|
||||
var node = selectMenuItem('circularize').node();
|
||||
|
||||
+13
-13
@@ -91,10 +91,10 @@ export function localize(obj) {
|
||||
'postcode', 'province', 'quarter', 'state', 'subdistrict', 'suburb'
|
||||
];
|
||||
addrTags.forEach(function(k) {
|
||||
var key = 'intro.graph.' + k,
|
||||
tag = 'addr:' + k,
|
||||
val = obj.tags && obj.tags[tag],
|
||||
str = t(key, { default: val });
|
||||
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) {
|
||||
@@ -114,10 +114,10 @@ export function localize(obj) {
|
||||
export 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
|
||||
lowerBound = Math.cos((90 - threshold) * Math.PI / 180), // near right
|
||||
upperBound = Math.cos(threshold * Math.PI / 180), // near straight
|
||||
mag;
|
||||
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
|
||||
var mag;
|
||||
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
mag = Math.abs(normalizedDotProduct(i, points));
|
||||
@@ -130,11 +130,11 @@ export function isMostlySquare(points) {
|
||||
|
||||
|
||||
function normalizedDotProduct(i, points) {
|
||||
var a = points[(i - 1 + points.length) % points.length],
|
||||
b = points[i],
|
||||
c = points[(i + 1) % points.length],
|
||||
p = subtractPoints(a, b),
|
||||
q = subtractPoints(c, b);
|
||||
var a = points[(i - 1 + points.length) % points.length];
|
||||
var b = points[i];
|
||||
var c = points[(i + 1) % points.length];
|
||||
var p = subtractPoints(a, b);
|
||||
var q = subtractPoints(c, b);
|
||||
|
||||
p = normalizePoint(p);
|
||||
q = normalizePoint(q);
|
||||
|
||||
+106
-119
@@ -15,29 +15,29 @@ import { icon, pad, selectMenuItem, transitionTime } from './helper';
|
||||
|
||||
|
||||
export function uiIntroLine(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
timeouts = [],
|
||||
tulipRoadId = null,
|
||||
flowerRoadId = 'w646',
|
||||
tulipRoadStart = [-85.6297754121684, 41.95805253325314],
|
||||
tulipRoadMidpoint = [-85.62975395449628, 41.95787501510204],
|
||||
tulipRoadIntersection = [-85.62974496187628, 41.95742515554585],
|
||||
roadCategory = context.presets().item('category-road'),
|
||||
residentialPreset = context.presets().item('highway/residential'),
|
||||
woodRoadId = 'w525',
|
||||
woodRoadEndId = 'n2862',
|
||||
woodRoadAddNode = [-85.62390110349587, 41.95397111462291],
|
||||
woodRoadDragEndpoint = [-85.623867390213, 41.95466987786487],
|
||||
woodRoadDragMidpoint = [-85.62386254803509, 41.95430395953872],
|
||||
washingtonStreetId = 'w522',
|
||||
twelfthAvenueId = 'w1',
|
||||
eleventhAvenueEndId = 'n3550',
|
||||
twelfthAvenueEndId = 'n5',
|
||||
washingtonSegmentId = null,
|
||||
eleventhAvenueEnd = context.entity(eleventhAvenueEndId).loc,
|
||||
twelfthAvenueEnd = context.entity(twelfthAvenueEndId).loc,
|
||||
deleteLinesLoc = [-85.6219395542764, 41.95228033922477],
|
||||
twelfthAvenue = [-85.62219310052491, 41.952505413152956];
|
||||
var dispatch = d3_dispatch('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 = context.presets().item('category-road');
|
||||
var residentialPreset = context.presets().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 = {
|
||||
@@ -106,11 +106,9 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function startLine() {
|
||||
if (context.mode().id !== 'add-line') {
|
||||
return chapter.restart();
|
||||
}
|
||||
if (context.mode().id !== 'add-line') return chapter.restart();
|
||||
|
||||
tulipRoadId = null;
|
||||
_tulipRoadID = null;
|
||||
|
||||
var padding = 70 * Math.pow(2, context.map().zoom() - 18);
|
||||
var box = pad(tulipRoadStart, padding, context);
|
||||
@@ -138,11 +136,9 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function drawLine() {
|
||||
if (context.mode().id !== 'draw-line') {
|
||||
return chapter.restart();
|
||||
}
|
||||
if (context.mode().id !== 'draw-line') return chapter.restart();
|
||||
|
||||
tulipRoadId = context.mode().selectedIDs()[0];
|
||||
_tulipRoadID = context.mode().selectedIDs()[0];
|
||||
context.map().centerEase(tulipRoadMidpoint, 500);
|
||||
|
||||
timeout(function() {
|
||||
@@ -165,23 +161,20 @@ export function uiIntroLine(context, reveal) {
|
||||
}, 550); // after easing..
|
||||
|
||||
context.history().on('change.intro', function() {
|
||||
var entity = tulipRoadId && context.hasEntity(tulipRoadId);
|
||||
if (!entity) return chapter.restart();
|
||||
|
||||
if (isLineConnected()) {
|
||||
continueTo(continueLine);
|
||||
}
|
||||
});
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id === 'draw-line')
|
||||
if (mode.id === 'draw-line') {
|
||||
return;
|
||||
else if (mode.id === 'select') {
|
||||
} else if (mode.id === 'select') {
|
||||
continueTo(retryIntersect);
|
||||
return;
|
||||
}
|
||||
else
|
||||
} else {
|
||||
return chapter.restart();
|
||||
}
|
||||
});
|
||||
|
||||
function continueTo(nextStep) {
|
||||
@@ -194,13 +187,13 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function isLineConnected() {
|
||||
var entity = tulipRoadId && context.hasEntity(tulipRoadId);
|
||||
var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
|
||||
if (!entity) return false;
|
||||
|
||||
var drawNodes = context.graph().childNodes(entity);
|
||||
return _some(drawNodes, function(node) {
|
||||
return _some(context.graph().parentWays(node), function(parent) {
|
||||
return parent.id === flowerRoadId;
|
||||
return parent.id === flowerRoadID;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -220,7 +213,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
function continueLine() {
|
||||
if (context.mode().id !== 'draw-line') return chapter.restart();
|
||||
var entity = tulipRoadId && context.hasEntity(tulipRoadId);
|
||||
var entity = _tulipRoadID && context.hasEntity(_tulipRoadID);
|
||||
if (!entity) return chapter.restart();
|
||||
|
||||
context.map().centerEase(tulipRoadIntersection, 500);
|
||||
@@ -244,9 +237,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function chooseCategoryRoad() {
|
||||
if (context.mode().id !== 'select') {
|
||||
return chapter.restart();
|
||||
}
|
||||
if (context.mode().id !== 'select') return chapter.restart();
|
||||
|
||||
context.on('exit.intro', function() {
|
||||
return chapter.restart();
|
||||
@@ -282,9 +273,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function choosePresetResidential() {
|
||||
if (context.mode().id !== 'select') {
|
||||
return chapter.restart();
|
||||
}
|
||||
if (context.mode().id !== 'select') return chapter.restart();
|
||||
|
||||
context.on('exit.intro', function() {
|
||||
return chapter.restart();
|
||||
@@ -320,9 +309,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
// selected wrong road type
|
||||
function retryPresetResidential() {
|
||||
if (context.mode().id !== 'select') {
|
||||
return chapter.restart();
|
||||
}
|
||||
if (context.mode().id !== 'select') return chapter.restart();
|
||||
|
||||
context.on('exit.intro', function() {
|
||||
return chapter.restart();
|
||||
@@ -390,7 +377,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
function updateLine() {
|
||||
context.history().reset('doneAddLine');
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
@@ -425,7 +412,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
function addNode() {
|
||||
context.history().reset('doneAddLine');
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
@@ -440,7 +427,7 @@ export function uiIntroLine(context, reveal) {
|
||||
});
|
||||
|
||||
context.history().on('change.intro', function(changed) {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
if (changed.created().length === 1) {
|
||||
@@ -464,7 +451,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function startDragEndpoint() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
var padding = 100 * Math.pow(2, context.map().zoom() - 19);
|
||||
@@ -472,14 +459,14 @@ export function uiIntroLine(context, reveal) {
|
||||
reveal(box, t('intro.lines.start_drag_endpoint'));
|
||||
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
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, t('intro.lines.start_drag_endpoint'), { duration: 0 });
|
||||
|
||||
var entity = context.entity(woodRoadEndId);
|
||||
var entity = context.entity(woodRoadEndID);
|
||||
if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) <= 4) {
|
||||
continueTo(finishDragEndpoint);
|
||||
}
|
||||
@@ -493,7 +480,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function finishDragEndpoint() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
|
||||
@@ -502,14 +489,14 @@ export function uiIntroLine(context, reveal) {
|
||||
reveal(box, t('intro.lines.finish_drag_endpoint'));
|
||||
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
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, t('intro.lines.finish_drag_endpoint'), { duration: 0 });
|
||||
|
||||
var entity = context.entity(woodRoadEndId);
|
||||
var entity = context.entity(woodRoadEndID);
|
||||
if (geoSphericalDistance(entity.loc, woodRoadDragEndpoint) > 4) {
|
||||
continueTo(startDragEndpoint);
|
||||
}
|
||||
@@ -528,11 +515,11 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function startDragMidpoint() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
if (context.selectedIDs().indexOf(woodRoadId) === -1) {
|
||||
context.enter(modeSelect(context, [woodRoadId]));
|
||||
if (context.selectedIDs().indexOf(woodRoadID) === -1) {
|
||||
context.enter(modeSelect(context, [woodRoadID]));
|
||||
}
|
||||
|
||||
var padding = 80 * Math.pow(2, context.map().zoom() - 19);
|
||||
@@ -540,7 +527,7 @@ export function uiIntroLine(context, reveal) {
|
||||
reveal(box, t('intro.lines.start_drag_midpoint'));
|
||||
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
var padding = 80 * Math.pow(2, context.map().zoom() - 19);
|
||||
@@ -557,7 +544,7 @@ export function uiIntroLine(context, reveal) {
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') {
|
||||
// keep Wood Road selected so midpoint triangles are drawn..
|
||||
context.enter(modeSelect(context, [woodRoadId]));
|
||||
context.enter(modeSelect(context, [woodRoadID]));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -571,7 +558,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function continueDragMidpoint() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
|
||||
@@ -589,7 +576,7 @@ export function uiIntroLine(context, reveal) {
|
||||
);
|
||||
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
if (!context.hasEntity(woodRoadId) || !context.hasEntity(woodRoadEndId)) {
|
||||
if (!context.hasEntity(woodRoadID) || !context.hasEntity(woodRoadEndID)) {
|
||||
return continueTo(updateLine);
|
||||
}
|
||||
var padding = 100 * Math.pow(2, context.map().zoom() - 19);
|
||||
@@ -611,9 +598,9 @@ export function uiIntroLine(context, reveal) {
|
||||
context.history().reset('doneUpdateLine');
|
||||
context.enter(modeBrowse(context));
|
||||
|
||||
if (!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return chapter.restart();
|
||||
}
|
||||
|
||||
@@ -683,7 +670,7 @@ export function uiIntroLine(context, reveal) {
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') return;
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length !== 1 || ids[0] !== eleventhAvenueEndId) return;
|
||||
if (ids.length !== 1 || ids[0] !== eleventhAvenueEndID) return;
|
||||
|
||||
timeout(function() {
|
||||
var node = selectMenuItem('split').node();
|
||||
@@ -710,9 +697,9 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function splitIntersection() {
|
||||
if (!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(deleteLines);
|
||||
}
|
||||
|
||||
@@ -721,7 +708,7 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
var wasChanged = false;
|
||||
var menuCoords = context.map().mouseCoordinates();
|
||||
washingtonSegmentId = null;
|
||||
_washingtonSegmentID = null;
|
||||
|
||||
revealEditMenu(menuCoords, t('intro.lines.split_intersection',
|
||||
{ button: icon('#iD-operation-split', 'pre-text'), street: t('intro.graph.name.washington-street') })
|
||||
@@ -741,10 +728,10 @@ export function uiIntroLine(context, reveal) {
|
||||
wasChanged = true;
|
||||
timeout(function() {
|
||||
if (context.history().undoAnnotation() === t('operations.split.annotation.line')) {
|
||||
washingtonSegmentId = changed.created()[0].id;
|
||||
_washingtonSegmentID = changed.created()[0].id;
|
||||
continueTo(didSplit);
|
||||
} else {
|
||||
washingtonSegmentId = null;
|
||||
_washingtonSegmentID = null;
|
||||
continueTo(retrySplit);
|
||||
}
|
||||
}, 300); // after any transition (e.g. if user deleted intersection)
|
||||
@@ -785,11 +772,11 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function didSplit() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
|
||||
@@ -819,17 +806,17 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
context.on('enter.intro', function() {
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length === 1 && ids[0] === washingtonSegmentId) {
|
||||
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)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
});
|
||||
@@ -844,17 +831,17 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function multiSelect() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
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;
|
||||
var hasWashington = ids.indexOf(_washingtonSegmentID) !== -1;
|
||||
var hasTwelfth = ids.indexOf(twelfthAvenueID) !== -1;
|
||||
|
||||
if (hasWashington && hasTwelfth) {
|
||||
return continueTo(multiRightClick);
|
||||
@@ -910,11 +897,11 @@ export function uiIntroLine(context, reveal) {
|
||||
});
|
||||
|
||||
context.history().on('change.intro', function() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
});
|
||||
@@ -930,11 +917,11 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function multiRightClick() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
|
||||
@@ -952,13 +939,13 @@ export function uiIntroLine(context, reveal) {
|
||||
timeout(function() {
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length === 2 &&
|
||||
ids.indexOf(twelfthAvenueId) !== -1 &&
|
||||
ids.indexOf(washingtonSegmentId) !== -1) {
|
||||
ids.indexOf(twelfthAvenueID) !== -1 &&
|
||||
ids.indexOf(_washingtonSegmentID) !== -1) {
|
||||
var node = selectMenuItem('delete').node();
|
||||
if (!node) return;
|
||||
continueTo(multiDelete);
|
||||
} else if (ids.length === 1 &&
|
||||
ids.indexOf(washingtonSegmentId) !== -1) {
|
||||
ids.indexOf(_washingtonSegmentID) !== -1) {
|
||||
return continueTo(multiSelect);
|
||||
} else {
|
||||
return continueTo(didSplit);
|
||||
@@ -967,11 +954,11 @@ export function uiIntroLine(context, reveal) {
|
||||
}, true);
|
||||
|
||||
context.history().on('change.intro', function() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
});
|
||||
@@ -986,11 +973,11 @@ export function uiIntroLine(context, reveal) {
|
||||
|
||||
|
||||
function multiDelete() {
|
||||
if (!washingtonSegmentId ||
|
||||
!context.hasEntity(washingtonSegmentId) ||
|
||||
!context.hasEntity(washingtonStreetId) ||
|
||||
!context.hasEntity(twelfthAvenueId) ||
|
||||
!context.hasEntity(eleventhAvenueEndId)) {
|
||||
if (!_washingtonSegmentID ||
|
||||
!context.hasEntity(_washingtonSegmentID) ||
|
||||
!context.hasEntity(washingtonStreetID) ||
|
||||
!context.hasEntity(twelfthAvenueID) ||
|
||||
!context.hasEntity(eleventhAvenueEndID)) {
|
||||
return continueTo(rightClickIntersection);
|
||||
}
|
||||
|
||||
@@ -1010,13 +997,13 @@ export function uiIntroLine(context, reveal) {
|
||||
});
|
||||
|
||||
context.on('exit.intro', function() {
|
||||
if (context.hasEntity(washingtonSegmentId) || context.hasEntity(twelfthAvenueId)) {
|
||||
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)) {
|
||||
if (context.hasEntity(_washingtonSegmentID) || context.hasEntity(twelfthAvenueID)) {
|
||||
continueTo(retryDelete); // changed something but roads still exist
|
||||
} else {
|
||||
continueTo(play);
|
||||
|
||||
@@ -12,15 +12,15 @@ import { icon, pointBox, transitionTime } from './helper';
|
||||
|
||||
|
||||
export function uiIntroNavigation(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
timeouts = [],
|
||||
hallId = 'n2061',
|
||||
townHall = [-85.63591, 41.94285],
|
||||
springStreetId = 'w397',
|
||||
springStreetEndId = 'n1834',
|
||||
springStreet = [-85.63582, 41.94255],
|
||||
onewayField = context.presets().field('oneway'),
|
||||
maxspeedField = context.presets().field('maxspeed');
|
||||
var dispatch = d3_dispatch('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 = context.presets().field('oneway');
|
||||
var maxspeedField = context.presets().field('maxspeed');
|
||||
|
||||
|
||||
var chapter = {
|
||||
@@ -409,9 +409,9 @@ export function uiIntroNavigation(context, reveal) {
|
||||
|
||||
|
||||
function checkSearchResult() {
|
||||
var first = d3_select('.feature-list-item:nth-child(0n+2)'), // skip "No Results" item
|
||||
firstName = first.select('.entity-name'),
|
||||
name = t('intro.graph.name.spring-street');
|
||||
var first = d3_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.text() === name) {
|
||||
reveal(first.node(),
|
||||
|
||||
+29
-29
@@ -12,12 +12,12 @@ import { icon, pointBox, pad, selectMenuItem, transitionTime } from './helper';
|
||||
|
||||
|
||||
export function uiIntroPoint(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
timeouts = [],
|
||||
intersection = [-85.63279, 41.94394],
|
||||
building = [-85.632422, 41.944045],
|
||||
cafePreset = context.presets().item('amenity/cafe'),
|
||||
pointId = null;
|
||||
var dispatch = d3_dispatch('done');
|
||||
var timeouts = [];
|
||||
var intersection = [-85.63279, 41.94394];
|
||||
var building = [-85.632422, 41.944045];
|
||||
var cafePreset = context.presets().item('amenity/cafe');
|
||||
var _pointID = null;
|
||||
|
||||
|
||||
var chapter = {
|
||||
@@ -66,7 +66,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
var tooltip = reveal('button.add-point',
|
||||
t('intro.points.add_point', { button: icon('#iD-icon-point', 'pre-text') }));
|
||||
|
||||
pointId = null;
|
||||
_pointID = null;
|
||||
|
||||
tooltip.selectAll('.tooltip-inner')
|
||||
.insert('svg', 'span')
|
||||
@@ -102,7 +102,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') return chapter.restart();
|
||||
pointId = context.mode().selectedIDs()[0];
|
||||
_pointID = context.mode().selectedIDs()[0];
|
||||
continueTo(searchPreset);
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function searchPreset() {
|
||||
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
|
||||
if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
|
||||
return addPoint();
|
||||
}
|
||||
|
||||
@@ -131,14 +131,14 @@ export function uiIntroPoint(context, reveal) {
|
||||
);
|
||||
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (!pointId || !context.hasEntity(pointId)) {
|
||||
if (!_pointID || !context.hasEntity(_pointID)) {
|
||||
return continueTo(addPoint);
|
||||
}
|
||||
|
||||
var ids = context.selectedIDs();
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== pointId) {
|
||||
if (mode.id !== 'select' || !ids.length || ids[0] !== _pointID) {
|
||||
// keep the user's point selected..
|
||||
context.enter(modeSelect(context, [pointId]));
|
||||
context.enter(modeSelect(context, [_pointID]));
|
||||
|
||||
// disallow scrolling
|
||||
d3_select('.inspector-wrap').on('wheel.intro', eventCancel);
|
||||
@@ -186,7 +186,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function aboutFeatureEditor() {
|
||||
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
|
||||
if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
|
||||
return addPoint();
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function addName() {
|
||||
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
|
||||
if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
|
||||
return addPoint();
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
// 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);
|
||||
var entity = context.entity(_pointID);
|
||||
if (entity.tags.name) {
|
||||
var tooltip = reveal('.entity-editor-pane', t('intro.points.add_name'), {
|
||||
tooltipClass: 'intro-points-describe',
|
||||
@@ -278,13 +278,13 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function reselectPoint() {
|
||||
if (!pointId) return chapter.restart();
|
||||
var entity = context.hasEntity(pointId);
|
||||
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 = context.presets().match(entity, context.graph());
|
||||
context.replace(actionChangePreset(pointId, oldPreset, cafePreset));
|
||||
context.replace(actionChangePreset(_pointID, oldPreset, cafePreset));
|
||||
|
||||
context.enter(modeBrowse(context));
|
||||
|
||||
@@ -298,7 +298,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
timeout(function() {
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
var entity = context.hasEntity(pointId);
|
||||
var entity = context.hasEntity(_pointID);
|
||||
if (!entity) return chapter.restart();
|
||||
var box = pointBox(entity.loc, context);
|
||||
reveal(box, t('intro.points.reselect'), { duration: 0 });
|
||||
@@ -321,7 +321,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function updatePoint() {
|
||||
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
|
||||
if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
|
||||
return continueTo(reselectPoint);
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function updateCloseEditor() {
|
||||
if (context.mode().id !== 'select' || !pointId || !context.hasEntity(pointId)) {
|
||||
if (context.mode().id !== 'select' || !_pointID || !context.hasEntity(_pointID)) {
|
||||
return continueTo(reselectPoint);
|
||||
}
|
||||
|
||||
@@ -376,8 +376,8 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function rightClickPoint() {
|
||||
if (!pointId) return chapter.restart();
|
||||
var entity = context.hasEntity(pointId);
|
||||
if (!_pointID) return chapter.restart();
|
||||
var entity = context.hasEntity(_pointID);
|
||||
if (!entity) return chapter.restart();
|
||||
|
||||
context.enter(modeBrowse(context));
|
||||
@@ -387,7 +387,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
timeout(function() {
|
||||
context.map().on('move.intro drawn.intro', function() {
|
||||
var entity = context.hasEntity(pointId);
|
||||
var entity = context.hasEntity(_pointID);
|
||||
if (!entity) return chapter.restart();
|
||||
var box = pointBox(entity.loc, context);
|
||||
reveal(box, t('intro.points.rightclick'), { duration: 0 });
|
||||
@@ -397,7 +397,7 @@ export function uiIntroPoint(context, reveal) {
|
||||
context.on('enter.intro', function(mode) {
|
||||
if (mode.id !== 'select') return;
|
||||
var ids = context.selectedIDs();
|
||||
if (ids.length !== 1 || ids[0] !== pointId) return;
|
||||
if (ids.length !== 1 || ids[0] !== _pointID) return;
|
||||
|
||||
timeout(function() {
|
||||
var node = selectMenuItem('delete').node();
|
||||
@@ -415,8 +415,8 @@ export function uiIntroPoint(context, reveal) {
|
||||
|
||||
|
||||
function enterDelete() {
|
||||
if (!pointId) return chapter.restart();
|
||||
var entity = context.hasEntity(pointId);
|
||||
if (!_pointID) return chapter.restart();
|
||||
var entity = context.hasEntity(_pointID);
|
||||
if (!entity) return chapter.restart();
|
||||
|
||||
var node = selectMenuItem('delete').node();
|
||||
@@ -436,8 +436,8 @@ export function uiIntroPoint(context, reveal) {
|
||||
}, 300); // after menu visible
|
||||
|
||||
context.on('exit.intro', function() {
|
||||
if (!pointId) return chapter.restart();
|
||||
var entity = context.hasEntity(pointId);
|
||||
if (!_pointID) return chapter.restart();
|
||||
var entity = context.hasEntity(_pointID);
|
||||
if (entity) return continueTo(rightClickPoint); // point still exists
|
||||
});
|
||||
|
||||
|
||||
+17
-17
@@ -9,8 +9,8 @@ import { utilRebind } from '../../util/rebind';
|
||||
|
||||
|
||||
export function uiIntroWelcome(context, reveal) {
|
||||
var dispatch = d3_dispatch('done'),
|
||||
listener = clickListener();
|
||||
var dispatch = d3_dispatch('done');
|
||||
var listener = clickListener();
|
||||
|
||||
var chapter = {
|
||||
title: 'intro.welcome.title'
|
||||
@@ -49,8 +49,8 @@ export function uiIntroWelcome(context, reveal) {
|
||||
|
||||
|
||||
function leftClick() {
|
||||
var counter = 0,
|
||||
times = 5;
|
||||
var counter = 0;
|
||||
var times = 5;
|
||||
|
||||
var tooltip = reveal('.intro-nav-wrap .chapter-welcome',
|
||||
t('intro.welcome.leftclick', { num: times }),
|
||||
@@ -90,8 +90,8 @@ export function uiIntroWelcome(context, reveal) {
|
||||
|
||||
|
||||
function rightClick() {
|
||||
var counter = 0,
|
||||
times = 5;
|
||||
var counter = 0;
|
||||
var times = 5;
|
||||
|
||||
var tooltip = reveal('.intro-nav-wrap .chapter-welcome',
|
||||
t('intro.welcome.rightclick', { num: times }),
|
||||
@@ -163,10 +163,10 @@ export function uiIntroWelcome(context, reveal) {
|
||||
|
||||
|
||||
function clickListener() {
|
||||
var dispatch = d3_dispatch('click'),
|
||||
minTime = 120,
|
||||
tooltip = d3_select(null),
|
||||
down = {};
|
||||
var dispatch = d3_dispatch('click');
|
||||
var minTime = 120;
|
||||
var tooltip = d3_select(null);
|
||||
var down = {};
|
||||
|
||||
// `down` keeps track of which buttons/keys are down.
|
||||
// Setting a property in `down` happens immediately.
|
||||
@@ -187,9 +187,9 @@ function clickListener() {
|
||||
if (d3_event.keyCode === 93) { // context menu
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
var endTime = d3_event.timeStamp,
|
||||
startTime = down.menu || endTime,
|
||||
delay = (endTime - startTime < minTime) ? minTime : 0;
|
||||
var endTime = d3_event.timeStamp;
|
||||
var startTime = down.menu || endTime;
|
||||
var delay = (endTime - startTime < minTime) ? minTime : 0;
|
||||
|
||||
window.setTimeout(function() {
|
||||
tooltip.classed('rightclick', false);
|
||||
@@ -213,10 +213,10 @@ function clickListener() {
|
||||
|
||||
|
||||
function mouseup() {
|
||||
var button = d3_event.button,
|
||||
endTime = d3_event.timeStamp,
|
||||
startTime = down[button] || endTime,
|
||||
delay = (endTime - startTime < minTime) ? minTime : 0;
|
||||
var button = d3_event.button;
|
||||
var endTime = d3_event.timeStamp;
|
||||
var startTime = down[button] || endTime;
|
||||
var delay = (endTime - startTime < minTime) ? minTime : 0;
|
||||
|
||||
if (button === 0 && !d3_event.ctrlKey) {
|
||||
window.setTimeout(function() {
|
||||
|
||||
+171
-123
@@ -8,9 +8,12 @@ import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
import { svgIcon } from '../svg';
|
||||
import { t, textDirection } from '../util/locale';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { geoExtent } from '../geo';
|
||||
import { modeBrowse } from '../modes';
|
||||
import { uiBackground } from './background';
|
||||
import { uiDisclosure } from './disclosure';
|
||||
import { uiHelp } from './help';
|
||||
import { uiSettingsCustomData } from './settings/custom_data';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
|
||||
|
||||
@@ -20,6 +23,9 @@ export function uiMapData(context) {
|
||||
var layers = context.layers();
|
||||
var fills = ['wireframe', 'partial', 'full'];
|
||||
|
||||
var settingsCustomData = uiSettingsCustomData(context)
|
||||
.on('change', customChanged);
|
||||
|
||||
var _fillSelected = context.storage('area-fill') || 'partial';
|
||||
var _shown = false;
|
||||
var _dataLayerContainer = d3_select(null);
|
||||
@@ -75,6 +81,11 @@ export function uiMapData(context) {
|
||||
var layer = layers.layer(which);
|
||||
if (layer) {
|
||||
layer.enabled(enabled);
|
||||
|
||||
if (!enabled && (which === 'osm' || which === 'notes')) {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -137,10 +148,8 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
// Update
|
||||
li = li
|
||||
.merge(liEnter);
|
||||
|
||||
li
|
||||
.merge(liEnter)
|
||||
.classed('active', layerEnabled)
|
||||
.selectAll('input')
|
||||
.property('checked', layerEnabled);
|
||||
@@ -191,24 +200,135 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
// Update
|
||||
li = li
|
||||
.merge(liEnter);
|
||||
|
||||
li
|
||||
.merge(liEnter)
|
||||
.classed('active', function (d) { return d.layer.enabled(); })
|
||||
.selectAll('input')
|
||||
.property('checked', function (d) { return d.layer.enabled(); });
|
||||
}
|
||||
|
||||
|
||||
function drawGpxItem(selection) {
|
||||
var gpx = layers.layer('gpx');
|
||||
var hasGpx = gpx && gpx.hasGpx();
|
||||
var showsGpx = hasGpx && gpx.enabled();
|
||||
// Beta feature - sample vector layers to support Detroit Mapping Challenge
|
||||
// https://github.com/osmus/detroit-mapping-challenge
|
||||
function drawVectorItems(selection) {
|
||||
var dataLayer = layers.layer('data');
|
||||
var vtData = [
|
||||
{
|
||||
name: 'Detroit Neighborhoods/Parks',
|
||||
src: 'neighborhoods-parks',
|
||||
tooltip: 'Neighborhood boundaries and parks as compiled by City of Detroit in concert with community groups.',
|
||||
template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmur6x34562qp9iv1u3ksf-54hev,jonahadkins.cjksmqxdx33jj2wp90xd9x2md-4e5y2/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA'
|
||||
}, {
|
||||
name: 'Detroit Composite POIs',
|
||||
src: 'composite-poi',
|
||||
tooltip: 'Fire Inspections, Business Licenses, and other public location data collated from the City of Detroit.',
|
||||
template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmm6a02sli31myxhsr7zf3-2sw8h/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA'
|
||||
}, {
|
||||
name: 'Detroit All-The-Places POIs',
|
||||
src: 'alltheplaces-poi',
|
||||
tooltip: 'Public domain business location data created by web scrapers.',
|
||||
template: 'https://{switch:a,b,c,d}.tiles.mapbox.com/v4/jonahadkins.cjksmswgk340g2vo06p1w9w0j-8fjjc/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1Ijoiam9uYWhhZGtpbnMiLCJhIjoiRlVVVkx3VSJ9.9sdVEK_B_VkEXPjssU5MqA'
|
||||
}
|
||||
];
|
||||
|
||||
// Only show this if the map is around Detroit..
|
||||
var detroit = geoExtent([-83.5, 42.1], [-82.8, 42.5]);
|
||||
var showVectorItems = (context.map().zoom() > 9 && detroit.contains(context.map().center()));
|
||||
|
||||
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')
|
||||
.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')
|
||||
.attr('tabindex', -1)
|
||||
.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);
|
||||
|
||||
li.exit()
|
||||
.remove();
|
||||
|
||||
var liEnter = li.enter()
|
||||
.append('li')
|
||||
.attr('class', function(d) { return 'list-item list-item-' + d.src; });
|
||||
|
||||
var labelEnter = liEnter
|
||||
.append('label')
|
||||
.each(function(d) {
|
||||
d3_select(this).call(
|
||||
tooltip().title(d.tooltip).placement('top')
|
||||
);
|
||||
});
|
||||
|
||||
labelEnter
|
||||
.append('input')
|
||||
.attr('type', 'radio')
|
||||
.attr('name', 'vectortile')
|
||||
.on('change', selectVTLayer);
|
||||
|
||||
labelEnter
|
||||
.append('span')
|
||||
.text(function(d) { return d.name; });
|
||||
|
||||
// Update
|
||||
li
|
||||
.merge(liEnter)
|
||||
.classed('active', isVTLayerSelected)
|
||||
.selectAll('input')
|
||||
.property('checked', isVTLayerSelected);
|
||||
|
||||
|
||||
function isVTLayerSelected(d) {
|
||||
return dataLayer && dataLayer.template() === d.template;
|
||||
}
|
||||
|
||||
function selectVTLayer(d) {
|
||||
context.storage('settings-custom-data-url', d.template);
|
||||
if (dataLayer) {
|
||||
dataLayer.template(d.template, d.src);
|
||||
dataLayer.enabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawCustomDataItems(selection) {
|
||||
var dataLayer = layers.layer('data');
|
||||
var hasData = dataLayer && dataLayer.hasData();
|
||||
var showsData = hasData && dataLayer.enabled();
|
||||
|
||||
var ul = selection
|
||||
.selectAll('.layer-list-gpx')
|
||||
.data(gpx ? [0] : []);
|
||||
.selectAll('.layer-list-data')
|
||||
.data(dataLayer ? [0] : []);
|
||||
|
||||
// Exit
|
||||
ul.exit()
|
||||
@@ -217,154 +337,82 @@ export function uiMapData(context) {
|
||||
// Enter
|
||||
var ulEnter = ul.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-list-gpx');
|
||||
.attr('class', 'layer-list layer-list-data');
|
||||
|
||||
var liEnter = ulEnter
|
||||
.append('li')
|
||||
.attr('class', 'list-item-gpx');
|
||||
.attr('class', 'list-item-data');
|
||||
|
||||
liEnter
|
||||
.append('button')
|
||||
.attr('class', 'list-item-gpx-extent')
|
||||
.call(tooltip()
|
||||
.title(t('gpx.zoom'))
|
||||
.title(t('settings.custom_data.tooltip'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', editCustom)
|
||||
.call(svgIcon('#iD-icon-more'));
|
||||
|
||||
liEnter
|
||||
.append('button')
|
||||
.call(tooltip()
|
||||
.title(t('map_data.layers.custom.zoom'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', function() {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
gpx.fitZoom();
|
||||
dataLayer.fitZoom();
|
||||
})
|
||||
.call(svgIcon('#iD-icon-search'));
|
||||
|
||||
liEnter
|
||||
.append('button')
|
||||
.attr('class', 'list-item-gpx-browse')
|
||||
.call(tooltip()
|
||||
.title(t('gpx.browse'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', function() {
|
||||
d3_select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
gpx.files(d3_event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.call(svgIcon('#iD-icon-geolocate'));
|
||||
|
||||
var labelEnter = liEnter
|
||||
.append('label')
|
||||
.call(tooltip()
|
||||
.title(t('gpx.drag_drop'))
|
||||
.title(t('map_data.layers.custom.tooltip'))
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
labelEnter
|
||||
.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', function() { toggleLayer('gpx'); });
|
||||
.on('change', function() { toggleLayer('data'); });
|
||||
|
||||
labelEnter
|
||||
.append('span')
|
||||
.text(t('gpx.local_layer'));
|
||||
.text(t('map_data.layers.custom.title'));
|
||||
|
||||
// Update
|
||||
ul = ul
|
||||
.merge(ulEnter);
|
||||
|
||||
ul.selectAll('.list-item-gpx')
|
||||
.classed('active', showsGpx)
|
||||
ul.selectAll('.list-item-data')
|
||||
.classed('active', showsData)
|
||||
.selectAll('label')
|
||||
.classed('deemphasize', !hasGpx)
|
||||
.classed('deemphasize', !hasData)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasGpx)
|
||||
.property('checked', showsGpx);
|
||||
.property('disabled', !hasData)
|
||||
.property('checked', showsData);
|
||||
}
|
||||
|
||||
function drawMvtItem(selection) {
|
||||
var mvt = layers.layer('mvt'),
|
||||
hasMvt = mvt && mvt.hasMvt(),
|
||||
showsMvt = hasMvt && mvt.enabled();
|
||||
|
||||
var ul = selection
|
||||
.selectAll('.layer-list-mvt')
|
||||
.data(mvt ? [0] : []);
|
||||
|
||||
// Exit
|
||||
ul.exit()
|
||||
.remove();
|
||||
|
||||
// Enter
|
||||
var ulEnter = ul.enter()
|
||||
.append('ul')
|
||||
.attr('class', 'layer-list layer-list-mvt');
|
||||
|
||||
var liEnter = ulEnter
|
||||
.append('li')
|
||||
.attr('class', 'list-item-mvt');
|
||||
|
||||
liEnter
|
||||
.append('button')
|
||||
.attr('class', 'list-item-mvt-extent')
|
||||
.call(tooltip()
|
||||
.title(t('mvt.zoom'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', function() {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopPropagation();
|
||||
mvt.fitZoom();
|
||||
})
|
||||
.call(svgIcon('#iD-icon-search'));
|
||||
|
||||
liEnter
|
||||
.append('button')
|
||||
.attr('class', 'list-item-mvt-browse')
|
||||
.call(tooltip()
|
||||
.title(t('mvt.browse'))
|
||||
.placement((textDirection === 'rtl') ? 'right' : 'left')
|
||||
)
|
||||
.on('click', function() {
|
||||
d3_select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
mvt.files(d3_event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.call(svgIcon('#iD-icon-geolocate'));
|
||||
|
||||
var labelEnter = liEnter
|
||||
.append('label')
|
||||
.call(tooltip()
|
||||
.title(t('mvt.drag_drop'))
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
labelEnter
|
||||
.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', function() { toggleLayer('mvt'); });
|
||||
|
||||
labelEnter
|
||||
.append('span')
|
||||
.text(t('mvt.local_layer'));
|
||||
|
||||
// Update
|
||||
ul = ul
|
||||
.merge(ulEnter);
|
||||
|
||||
ul.selectAll('.list-item-mvt')
|
||||
.classed('active', showsMvt)
|
||||
.selectAll('label')
|
||||
.classed('deemphasize', !hasMvt)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasMvt)
|
||||
.property('checked', showsMvt);
|
||||
function editCustom() {
|
||||
d3_event.preventDefault();
|
||||
context.container()
|
||||
.call(settingsCustomData);
|
||||
}
|
||||
|
||||
|
||||
function customChanged(d) {
|
||||
var dataLayer = layers.layer('data');
|
||||
|
||||
if (d && d.url) {
|
||||
dataLayer.url(d.url);
|
||||
} else if (d && d.fileList) {
|
||||
dataLayer.fileList(d.fileList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawListItems(selection, data, type, name, change, active) {
|
||||
var items = selection.selectAll('li')
|
||||
.data(data);
|
||||
@@ -456,8 +504,8 @@ export function uiMapData(context) {
|
||||
_dataLayerContainer
|
||||
.call(drawOsmItems)
|
||||
.call(drawPhotoItems)
|
||||
.call(drawGpxItem);
|
||||
// .call(drawMvtItem);
|
||||
.call(drawCustomDataItems)
|
||||
.call(drawVectorItems); // Beta - Detroit mapping challenge
|
||||
|
||||
_fillList
|
||||
.call(drawListItems, fills, 'radio', 'area_fill', setFill, showsFill);
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '../geo';
|
||||
|
||||
import { rendererTileLayer } from '../renderer';
|
||||
import { svgDebug, svgGpx } from '../svg';
|
||||
import { svgDebug, svgData } from '../svg';
|
||||
import { utilSetTransform } from '../util';
|
||||
import { utilGetDimensions } from '../util/dimensions';
|
||||
|
||||
@@ -33,7 +33,7 @@ export function uiMapInMap(context) {
|
||||
var backgroundLayer = rendererTileLayer(context);
|
||||
var overlayLayers = {};
|
||||
var projection = geoRawMercator();
|
||||
var gpxLayer = svgGpx(projection, context).showLabels(false);
|
||||
var dataLayer = svgData(projection, context).showLabels(false);
|
||||
var debugLayer = svgDebug(projection, context);
|
||||
var zoom = d3_zoom()
|
||||
.scaleExtent([geoZoomToScale(0.5), geoZoomToScale(24)])
|
||||
@@ -242,7 +242,7 @@ export function uiMapInMap(context) {
|
||||
.append('svg')
|
||||
.attr('class', 'map-in-map-data')
|
||||
.merge(dataLayers)
|
||||
.call(gpxLayer)
|
||||
.call(dataLayer)
|
||||
.call(debugLayer);
|
||||
|
||||
|
||||
|
||||
+77
-57
@@ -1,8 +1,7 @@
|
||||
import _debounce from 'lodash-es/debounce';
|
||||
|
||||
import {
|
||||
select as d3_select,
|
||||
selectAll as d3_selectAll
|
||||
select as d3_select
|
||||
} from 'd3-selection';
|
||||
import { d3keybinding as d3_keybinding } from '../lib/d3.keybinding.js';
|
||||
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
modeAddArea,
|
||||
modeAddLine,
|
||||
modeAddPoint,
|
||||
modeAddNote,
|
||||
modeBrowse
|
||||
} from '../modes';
|
||||
|
||||
@@ -17,65 +17,32 @@ import { svgIcon } from '../svg';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
|
||||
import _includes from 'lodash-es/includes';
|
||||
|
||||
export function uiModes(context) {
|
||||
var modes = [
|
||||
modeAddPoint(context),
|
||||
modeAddLine(context),
|
||||
modeAddArea(context)
|
||||
modeAddArea(context),
|
||||
modeAddNote(context)
|
||||
];
|
||||
|
||||
|
||||
function editable() {
|
||||
var mode = context.mode();
|
||||
return context.editable() && mode && mode.id !== 'save';
|
||||
}
|
||||
|
||||
function difference(mode) {
|
||||
var match = mode.attr('class');
|
||||
var defaultIndex = context.presets().defaultTypes().findIndex(function(d) { return new RegExp(d).test(match); });
|
||||
return defaultIndex === -1;
|
||||
function notesEnabled() {
|
||||
var noteLayer = context.layers().layer('notes');
|
||||
return noteLayer && noteLayer.enabled();
|
||||
}
|
||||
|
||||
function notesEditable() {
|
||||
var mode = context.mode();
|
||||
return context.map().notesEditable() && mode && mode.id !== 'save';
|
||||
}
|
||||
|
||||
return function(selection) {
|
||||
var buttons = selection.selectAll('button.add-button')
|
||||
.data(modes);
|
||||
|
||||
buttons = buttons.enter()
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('class', function(mode) { return mode.id + ' add-button col4'; })
|
||||
.on('click.mode-buttons', function(mode) {
|
||||
// When drawing, ignore accidental clicks on mode buttons - #4042
|
||||
var currMode = context.mode().id;
|
||||
if (currMode.match(/^draw/) !== null) return;
|
||||
|
||||
if (mode.id === currMode) {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
context.enter(mode);
|
||||
}
|
||||
})
|
||||
.call(tooltip()
|
||||
.placement('bottom')
|
||||
.html(true)
|
||||
.title(function(mode) {
|
||||
return uiTooltipHtml(mode.description, mode.key);
|
||||
})
|
||||
);
|
||||
|
||||
buttons
|
||||
.each(function(d) {
|
||||
d3_select(this)
|
||||
.call(svgIcon('#iD-icon-' + d.button, 'pre-text'));
|
||||
});
|
||||
|
||||
buttons
|
||||
.append('span')
|
||||
.attr('class', 'label')
|
||||
.text(function(mode) { return mode.title; });
|
||||
|
||||
context
|
||||
.on('enter.editor', function(entered) {
|
||||
selection.selectAll('button.add-button')
|
||||
@@ -94,12 +61,13 @@ export function uiModes(context) {
|
||||
|
||||
modes.forEach(function(mode) {
|
||||
keybinding.on(mode.key, function() {
|
||||
if (editable()) {
|
||||
if (mode.id === context.mode().id) {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
context.enter(mode);
|
||||
}
|
||||
if (mode.id === 'add-note' && !(notesEnabled() && notesEditable())) return;
|
||||
if (mode.id !== 'add-note' && !editable()) return;
|
||||
|
||||
if (mode.id === context.mode().id) {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
context.enter(mode);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -117,15 +85,67 @@ export function uiModes(context) {
|
||||
context
|
||||
.on('enter.modes', update);
|
||||
|
||||
update();
|
||||
|
||||
|
||||
function update() {
|
||||
var modes = selection.selectAll('button.add-button');
|
||||
modes.property('disabled', !editable());
|
||||
d3_selectAll('button.add-button').each(function (d) {
|
||||
var mode = d3_select(this);
|
||||
mode.property('disabled', difference(mode));
|
||||
});
|
||||
var showNotes = notesEnabled();
|
||||
var data = showNotes ? modes : modes.slice(0, 3);
|
||||
|
||||
selection
|
||||
.classed('col3', !showNotes) // 25%
|
||||
.classed('col4', showNotes); // 33%
|
||||
|
||||
var buttons = selection.selectAll('button.add-button')
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
buttons.exit()
|
||||
.remove();
|
||||
|
||||
// enter
|
||||
var buttonsEnter = buttons.enter()
|
||||
.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.attr('class', function(d) { return d.id + ' add-button'; })
|
||||
.on('click.mode-buttons', function(mode) {
|
||||
// When drawing, ignore accidental clicks on mode buttons - #4042
|
||||
var currMode = context.mode().id;
|
||||
if (currMode.match(/^draw/) !== null) return;
|
||||
|
||||
if (mode.id === currMode) {
|
||||
context.enter(modeBrowse(context));
|
||||
} else {
|
||||
context.enter(mode);
|
||||
}
|
||||
})
|
||||
.call(tooltip()
|
||||
.placement('bottom')
|
||||
.html(true)
|
||||
.title(function(mode) {
|
||||
return uiTooltipHtml(mode.description, mode.key);
|
||||
})
|
||||
);
|
||||
|
||||
buttonsEnter
|
||||
.each(function(d) {
|
||||
d3_select(this)
|
||||
.call(svgIcon('#iD-icon-' + d.button, 'pre-text'));
|
||||
});
|
||||
|
||||
buttonsEnter
|
||||
.append('span')
|
||||
.attr('class', 'label')
|
||||
.text(function(mode) { return mode.title; });
|
||||
|
||||
// update
|
||||
buttons = buttons
|
||||
.merge(buttonsEnter)
|
||||
.classed('col3', showNotes) // 25%
|
||||
.classed('col4', !showNotes) // 33%
|
||||
.property('disabled', function(d) {
|
||||
return d.id === 'add-note' ? !notesEditable() : !editable();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ export function uiNoteComments() {
|
||||
|
||||
|
||||
function noteComments(selection) {
|
||||
if (_note.isNew()) return; // don't draw .comments-container
|
||||
|
||||
var comments = selection.selectAll('.comments-container')
|
||||
.data([0]);
|
||||
|
||||
@@ -59,12 +61,14 @@ export function uiNoteComments() {
|
||||
metadataEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-date')
|
||||
.text(function(d) { return d.action + ' ' + localeDateString(d.date); });
|
||||
.text(function(d) {
|
||||
return t('note.status.' + d.action, { when: localeDateString(d.date) });
|
||||
});
|
||||
|
||||
mainEnter
|
||||
.append('div')
|
||||
.attr('class', 'comment-text')
|
||||
.text(function(d) { return d.text; });
|
||||
.html(function(d) { return d.html; });
|
||||
|
||||
comments
|
||||
.call(replaceAvatars);
|
||||
@@ -99,6 +103,7 @@ export function uiNoteComments() {
|
||||
if (!s) return null;
|
||||
var detected = utilDetect();
|
||||
var options = { day: 'numeric', month: 'short', year: 'numeric' };
|
||||
s = s.replace(/-/g, '/'); // fix browser-specific Date() issues
|
||||
var d = new Date(s);
|
||||
if (isNaN(d.getTime())) return null;
|
||||
return d.toLocaleDateString(detected.locale, options);
|
||||
|
||||
+164
-40
@@ -9,6 +9,9 @@ import { services } from '../services';
|
||||
import { modeBrowse } from '../modes';
|
||||
import { svgIcon } from '../svg';
|
||||
|
||||
// import { uiField } from './field';
|
||||
// import { uiFormFields } from './form_fields';
|
||||
|
||||
import {
|
||||
uiNoteComments,
|
||||
uiNoteHeader,
|
||||
@@ -26,7 +29,11 @@ export function uiNoteEditor(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var noteComments = uiNoteComments();
|
||||
var noteHeader = uiNoteHeader();
|
||||
|
||||
// var formFields = uiFormFields(context);
|
||||
|
||||
var _note;
|
||||
// var _fieldsArr;
|
||||
|
||||
|
||||
function noteEditor(selection) {
|
||||
@@ -40,7 +47,9 @@ export function uiNoteEditor(context) {
|
||||
headerEnter
|
||||
.append('button')
|
||||
.attr('class', 'fr note-editor-close')
|
||||
.on('click', function() { context.enter(modeBrowse(context)); })
|
||||
.on('click', function() {
|
||||
context.enter(modeBrowse(context));
|
||||
})
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
headerEnter
|
||||
@@ -59,7 +68,7 @@ export function uiNoteEditor(context) {
|
||||
var editor = body.selectAll('.note-editor')
|
||||
.data([0]);
|
||||
|
||||
editor = editor.enter()
|
||||
editor.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section note-editor')
|
||||
.merge(editor)
|
||||
@@ -71,7 +80,7 @@ export function uiNoteEditor(context) {
|
||||
var footer = selection.selectAll('.footer')
|
||||
.data([0]);
|
||||
|
||||
footer = footer.enter()
|
||||
footer.enter()
|
||||
.append('div')
|
||||
.attr('class', 'footer')
|
||||
.merge(footer)
|
||||
@@ -103,10 +112,46 @@ export function uiNoteEditor(context) {
|
||||
.append('div')
|
||||
.attr('class', 'note-save save-section cf');
|
||||
|
||||
// // if new note, show categories to pick from
|
||||
// if (_note.isNew()) {
|
||||
// var presets = context.presets();
|
||||
|
||||
// // NOTE: this key isn't a age and therefore there is no documentation (yet)
|
||||
// _fieldsArr = [
|
||||
// uiField(context, presets.field('category'), null, { show: true, revert: false }),
|
||||
// ];
|
||||
|
||||
// _fieldsArr.forEach(function(field) {
|
||||
// field
|
||||
// .on('change', changeCategory);
|
||||
// });
|
||||
|
||||
// noteSaveEnter
|
||||
// .append('div')
|
||||
// .attr('class', 'note-category')
|
||||
// .call(formFields.fieldsArr(_fieldsArr));
|
||||
// }
|
||||
|
||||
// function changeCategory() {
|
||||
// // NOTE: perhaps there is a better way to get value
|
||||
// var val = d3_select('input[name=\'category\']:checked').property('__data__') || undefined;
|
||||
|
||||
// // store the unsaved category with the note itself
|
||||
// _note = _note.update({ newCategory: val });
|
||||
// var osm = services.osm;
|
||||
// if (osm) {
|
||||
// osm.replaceNote(_note); // update note cache
|
||||
// }
|
||||
// noteSave
|
||||
// .call(noteSaveButtons);
|
||||
// }
|
||||
|
||||
noteSaveEnter
|
||||
.append('h4')
|
||||
.attr('class', 'note-save-header')
|
||||
.text(t('note.newComment'));
|
||||
.attr('class', '.note-save-header')
|
||||
.text(function() {
|
||||
return _note.isNew() ? t('note.newDescription') : t('note.newComment');
|
||||
});
|
||||
|
||||
noteSaveEnter
|
||||
.append('textarea')
|
||||
@@ -115,8 +160,9 @@ export function uiNoteEditor(context) {
|
||||
.attr('maxlength', 1000)
|
||||
.property('value', function(d) { return d.newComment; })
|
||||
.call(utilNoAuto)
|
||||
.on('input', change)
|
||||
.on('blur', change);
|
||||
.on('keydown.note-input', keydown)
|
||||
.on('input.note-input', changeInput)
|
||||
.on('blur.note-input', changeInput);
|
||||
|
||||
// update
|
||||
noteSave = noteSaveEnter
|
||||
@@ -125,7 +171,37 @@ export function uiNoteEditor(context) {
|
||||
.call(noteSaveButtons);
|
||||
|
||||
|
||||
function change() {
|
||||
// fast submit if user presses cmd+enter
|
||||
function keydown() {
|
||||
if (!(d3_event.keyCode === 13 && d3_event.metaKey)) return;
|
||||
|
||||
var osm = services.osm;
|
||||
if (!osm) return;
|
||||
|
||||
var hasAuth = osm.authenticated();
|
||||
if (!hasAuth) return;
|
||||
|
||||
if (!_note.newComment) return;
|
||||
|
||||
d3_event.preventDefault();
|
||||
|
||||
d3_select(this)
|
||||
.on('keydown.note-input', null);
|
||||
|
||||
// focus on button and submit
|
||||
window.setTimeout(function() {
|
||||
if (_note.isNew()) {
|
||||
noteSave.selectAll('.save-button').node().focus();
|
||||
clickSave(_note);
|
||||
} else {
|
||||
noteSave.selectAll('.comment-button').node().focus();
|
||||
clickComment(_note);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
|
||||
|
||||
function changeInput() {
|
||||
var input = d3_select(this);
|
||||
var val = input.property('value').trim() || undefined;
|
||||
|
||||
@@ -250,23 +326,40 @@ export function uiNoteEditor(context) {
|
||||
.append('div')
|
||||
.attr('class', 'buttons');
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button status-button action')
|
||||
.append('span')
|
||||
.attr('class', 'label');
|
||||
if (_note.isNew()) {
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button cancel-button secondary-action')
|
||||
.text(t('confirm.cancel'));
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button save-button action')
|
||||
.text(t('note.save'));
|
||||
|
||||
} else {
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button status-button action');
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button comment-button action')
|
||||
.text(t('note.comment'));
|
||||
}
|
||||
|
||||
buttonEnter
|
||||
.append('button')
|
||||
.attr('class', 'button comment-button action')
|
||||
.append('span')
|
||||
.attr('class', 'label')
|
||||
.text(t('note.comment'));
|
||||
|
||||
// update
|
||||
buttonSection = buttonSection
|
||||
.merge(buttonEnter);
|
||||
|
||||
buttonSection.select('.cancel-button') // select and propagate data
|
||||
.on('click.cancel', clickCancel);
|
||||
|
||||
buttonSection.select('.save-button') // select and propagate data
|
||||
.attr('disabled', isSaveDisabled)
|
||||
.on('click.save', clickSave);
|
||||
|
||||
buttonSection.select('.status-button') // select and propagate data
|
||||
.attr('disabled', (hasAuth ? null : true))
|
||||
.text(function(d) {
|
||||
@@ -274,30 +367,61 @@ export function uiNoteEditor(context) {
|
||||
var andComment = (d.newComment ? '_comment' : '');
|
||||
return t('note.' + action + andComment);
|
||||
})
|
||||
.on('click.status', function(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);
|
||||
});
|
||||
}
|
||||
});
|
||||
.on('click.status', clickStatus);
|
||||
|
||||
buttonSection.select('.comment-button') // select and propagate data
|
||||
.attr('disabled', function(d) {
|
||||
return (hasAuth && d.status === 'open' && d.newComment) ? null : true;
|
||||
})
|
||||
.on('click.save', function(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);
|
||||
});
|
||||
}
|
||||
.attr('disabled', isSaveDisabled)
|
||||
.on('click.comment', clickComment);
|
||||
|
||||
|
||||
function isSaveDisabled(d) {
|
||||
return (hasAuth && d.status === 'open' && d.newComment) ? null : true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function clickCancel(d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
osm.removeNote(d);
|
||||
}
|
||||
context.enter(modeBrowse(context));
|
||||
dispatch.call('change');
|
||||
}
|
||||
|
||||
|
||||
function clickSave(d) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
var osm = services.osm;
|
||||
if (osm) {
|
||||
osm.postNoteCreate(d, function(err, note) {
|
||||
dispatch.call('change', note);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function clickStatus(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(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ export function uiNoteHeader() {
|
||||
|
||||
var iconEnter = headerEnter
|
||||
.append('div')
|
||||
.attr('class', function(d) { return 'note-header-icon ' + d.status; });
|
||||
.attr('class', function(d) { return 'note-header-icon ' + d.status; })
|
||||
.classed('new', function(d) { return d.id < 0; });
|
||||
|
||||
iconEnter
|
||||
.append('div')
|
||||
@@ -30,18 +31,18 @@ export function uiNoteHeader() {
|
||||
.call(svgIcon('#iD-icon-note', 'note-fill'));
|
||||
|
||||
iconEnter.each(function(d) {
|
||||
if (d.comments.length > 1) {
|
||||
iconEnter
|
||||
.append('div')
|
||||
.attr('class', 'note-icon-annotation')
|
||||
.call(svgIcon('#iD-icon-more', 'note-annotation'));
|
||||
}
|
||||
var statusIcon = '#iD-icon-' + (d.id < 0 ? 'plus' : (d.status === 'open' ? 'close' : 'apply'));
|
||||
iconEnter
|
||||
.append('div')
|
||||
.attr('class', 'note-icon-annotation')
|
||||
.call(svgIcon(statusIcon, 'note-annotation'));
|
||||
});
|
||||
|
||||
headerEnter
|
||||
.append('div')
|
||||
.attr('class', 'note-header-label')
|
||||
.text(function(d) {
|
||||
if (_note.isNew()) { return t('note.new'); }
|
||||
return t('note.note') + ' ' + d.id + ' ' +
|
||||
(d.status === 'closed' ? t('note.closed') : '');
|
||||
});
|
||||
|
||||
@@ -8,21 +8,21 @@ import { utilDetect } from '../../util/detect';
|
||||
export function uiPanelHistory(context) {
|
||||
var osm;
|
||||
|
||||
function displayTimestamp(entity) {
|
||||
if (!entity.timestamp) return t('info_panels.history.unknown');
|
||||
function displayTimestamp(timestamp) {
|
||||
if (!timestamp) return t('info_panels.history.unknown');
|
||||
var detected = utilDetect();
|
||||
var options = {
|
||||
day: 'numeric', month: 'short', year: 'numeric',
|
||||
hour: 'numeric', minute: 'numeric', second: 'numeric'
|
||||
};
|
||||
var d = new Date(entity.timestamp);
|
||||
var d = new Date(timestamp);
|
||||
if (isNaN(d.getTime())) return t('info_panels.history.unknown');
|
||||
return d.toLocaleString(detected.locale, options);
|
||||
}
|
||||
|
||||
|
||||
function displayUser(selection, entity) {
|
||||
if (!entity.user) {
|
||||
function displayUser(selection, userName) {
|
||||
if (!userName) {
|
||||
selection
|
||||
.append('span')
|
||||
.text(t('info_panels.history.unknown'));
|
||||
@@ -32,7 +32,7 @@ export function uiPanelHistory(context) {
|
||||
selection
|
||||
.append('span')
|
||||
.attr('class', 'user-name')
|
||||
.text(entity.user);
|
||||
.text(userName);
|
||||
|
||||
var links = selection
|
||||
.append('div')
|
||||
@@ -42,7 +42,7 @@ export function uiPanelHistory(context) {
|
||||
links
|
||||
.append('a')
|
||||
.attr('class', 'user-osm-link')
|
||||
.attr('href', osm.userURL(entity.user))
|
||||
.attr('href', osm.userURL(userName))
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.text('OSM');
|
||||
@@ -51,15 +51,15 @@ export function uiPanelHistory(context) {
|
||||
links
|
||||
.append('a')
|
||||
.attr('class', 'user-hdyc-link')
|
||||
.attr('href', 'https://hdyc.neis-one.org/?' + entity.user)
|
||||
.attr('href', 'https://hdyc.neis-one.org/?' + userName)
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.text('HDYC');
|
||||
}
|
||||
|
||||
|
||||
function displayChangeset(selection, entity) {
|
||||
if (!entity.changeset) {
|
||||
function displayChangeset(selection, changeset) {
|
||||
if (!changeset) {
|
||||
selection
|
||||
.append('span')
|
||||
.text(t('info_panels.history.unknown'));
|
||||
@@ -69,7 +69,7 @@ export function uiPanelHistory(context) {
|
||||
selection
|
||||
.append('span')
|
||||
.attr('class', 'changeset-id')
|
||||
.text(entity.changeset);
|
||||
.text(changeset);
|
||||
|
||||
var links = selection
|
||||
.append('div')
|
||||
@@ -79,7 +79,7 @@ export function uiPanelHistory(context) {
|
||||
links
|
||||
.append('a')
|
||||
.attr('class', 'changeset-osm-link')
|
||||
.attr('href', osm.changesetURL(entity.changeset))
|
||||
.attr('href', osm.changesetURL(changeset))
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.text('OSM');
|
||||
@@ -88,7 +88,7 @@ export function uiPanelHistory(context) {
|
||||
links
|
||||
.append('a')
|
||||
.attr('class', 'changeset-osmcha-link')
|
||||
.attr('href', 'https://osmcha.mapbox.com/changesets/' + entity.changeset)
|
||||
.attr('href', 'https://osmcha.mapbox.com/changesets/' + changeset)
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.text('OSMCha');
|
||||
@@ -96,11 +96,22 @@ export function uiPanelHistory(context) {
|
||||
|
||||
|
||||
function redraw(selection) {
|
||||
var selected = _filter(context.selectedIDs(), function(e) { return context.hasEntity(e); });
|
||||
var singular = selected.length === 1 ? selected[0] : null;
|
||||
|
||||
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);
|
||||
} else { // selected 1..n entities
|
||||
selected = _filter(context.selectedIDs(), function(e) { return context.hasEntity(e); });
|
||||
if (selected.length) {
|
||||
entity = context.entity(selected[0]);
|
||||
}
|
||||
}
|
||||
|
||||
var singular = selected.length === 1 ? selected[0] : null;
|
||||
|
||||
selection.html('');
|
||||
|
||||
selection
|
||||
@@ -110,9 +121,60 @@ export function uiPanelHistory(context) {
|
||||
|
||||
if (!singular) return;
|
||||
|
||||
var entity = context.entity(singular);
|
||||
if (entity) {
|
||||
selection.call(redrawEntity, entity);
|
||||
} else if (note) {
|
||||
selection.call(redrawNote, note);
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity.version) {
|
||||
|
||||
function redrawNote(selection, note) {
|
||||
if (!note || note.isNew()) {
|
||||
selection
|
||||
.append('div')
|
||||
.text(t('info_panels.history.note_no_history'));
|
||||
return;
|
||||
}
|
||||
|
||||
var list = selection
|
||||
.append('ul');
|
||||
|
||||
list
|
||||
.append('li')
|
||||
.text(t('info_panels.history.note_comments') + ':')
|
||||
.append('span')
|
||||
.text(note.comments.length);
|
||||
|
||||
if (note.comments.length) {
|
||||
list
|
||||
.append('li')
|
||||
.text(t('info_panels.history.note_created_date') + ':')
|
||||
.append('span')
|
||||
.text(displayTimestamp(note.comments[0].date));
|
||||
|
||||
list
|
||||
.append('li')
|
||||
.text(t('info_panels.history.note_created_user') + ':')
|
||||
.call(displayUser, note.comments[0].user);
|
||||
}
|
||||
|
||||
if (osm) {
|
||||
selection
|
||||
.append('a')
|
||||
.attr('class', 'view-history-on-osm')
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.attr('href', osm.noteURL(note))
|
||||
.call(svgIcon('#iD-icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('info_panels.history.note_link_text'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function redrawEntity(selection, entity) {
|
||||
if (!entity || entity.isNew()) {
|
||||
selection
|
||||
.append('div')
|
||||
.text(t('info_panels.history.no_history'));
|
||||
@@ -132,17 +194,17 @@ export function uiPanelHistory(context) {
|
||||
.append('li')
|
||||
.text(t('info_panels.history.last_edit') + ':')
|
||||
.append('span')
|
||||
.text(displayTimestamp(entity));
|
||||
.text(displayTimestamp(entity.timestamp));
|
||||
|
||||
list
|
||||
.append('li')
|
||||
.text(t('info_panels.history.edited_by') + ':')
|
||||
.call(displayUser, entity);
|
||||
.call(displayUser, entity.user);
|
||||
|
||||
list
|
||||
.append('li')
|
||||
.text(t('info_panels.history.changeset') + ':')
|
||||
.call(displayChangeset, entity);
|
||||
.call(displayChangeset, entity.changeset);
|
||||
|
||||
if (osm) {
|
||||
selection
|
||||
@@ -165,11 +227,16 @@ export function uiPanelHistory(context) {
|
||||
.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.map().on('drawn.info-history', null);
|
||||
context.on('enter.info-history', null);
|
||||
};
|
||||
|
||||
panel.id = 'history';
|
||||
|
||||
@@ -11,7 +11,7 @@ import { t } from '../../util/locale';
|
||||
import { displayArea, displayLength, decimalCoordinatePair, dmsCoordinatePair } from '../../util/units';
|
||||
import { geoExtent } from '../../geo';
|
||||
import { utilDetect } from '../../util/detect';
|
||||
|
||||
import { services } from '../../services';
|
||||
|
||||
|
||||
export function uiPanelMeasurement(context) {
|
||||
@@ -43,21 +43,40 @@ export function uiPanelMeasurement(context) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function nodeCount(feature) {
|
||||
if (feature.type === 'LineString') return feature.coordinates.length;
|
||||
|
||||
if (feature.type === 'Polygon') {
|
||||
return feature.coordinates[0].length - 1;
|
||||
}
|
||||
if (feature.type === 'Polygon') return feature.coordinates[0].length - 1;
|
||||
}
|
||||
|
||||
|
||||
function redraw(selection) {
|
||||
var resolver = context.graph();
|
||||
var selected = _filter(context.selectedIDs(), function(e) { return context.hasEntity(e); });
|
||||
var selectedNoteID = context.selectedNoteID();
|
||||
var osm = services.osm;
|
||||
|
||||
var selected, center, entity, note, geometry;
|
||||
|
||||
if (selectedNoteID && osm) { // selected 1 note
|
||||
selected = [ t('note.note') + ' ' + selectedNoteID ];
|
||||
note = osm.getNote(selectedNoteID);
|
||||
center = note.loc;
|
||||
geometry = 'note';
|
||||
|
||||
} else { // selected 1..n entities
|
||||
var extent = geoExtent();
|
||||
selected = _filter(context.selectedIDs(), function(e) { return context.hasEntity(e); });
|
||||
if (selected.length) {
|
||||
for (var i = 0; i < selected.length; i++) {
|
||||
entity = context.entity(selected[i]);
|
||||
extent._extend(entity.extent(resolver));
|
||||
}
|
||||
center = extent.center();
|
||||
geometry = entity.geometry(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
var singular = selected.length === 1 ? selected[0] : null;
|
||||
var extent = geoExtent();
|
||||
var entity;
|
||||
|
||||
selection.html('');
|
||||
|
||||
@@ -68,19 +87,12 @@ export function uiPanelMeasurement(context) {
|
||||
|
||||
if (!selected.length) return;
|
||||
|
||||
var center;
|
||||
for (var i = 0; i < selected.length; i++) {
|
||||
entity = context.entity(selected[i]);
|
||||
extent._extend(entity.extent(resolver));
|
||||
}
|
||||
center = extent.center();
|
||||
|
||||
|
||||
var list = selection
|
||||
.append('ul');
|
||||
var coordItem;
|
||||
|
||||
// multiple features, just display extent center..
|
||||
// multiple selected features, just display extent center..
|
||||
if (!singular) {
|
||||
coordItem = list
|
||||
.append('li')
|
||||
@@ -92,16 +104,13 @@ export function uiPanelMeasurement(context) {
|
||||
return;
|
||||
}
|
||||
|
||||
// single feature, display details..
|
||||
if (!entity) return;
|
||||
var geometry = entity.geometry(resolver);
|
||||
|
||||
// single selected feature, display details..
|
||||
if (geometry === 'line' || geometry === 'area') {
|
||||
var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()),
|
||||
feature = entity.asGeoJSON(resolver),
|
||||
length = radiansToMeters(d3_geoLength(toLineString(feature))),
|
||||
lengthLabel = t('info_panels.measurement.' + (closed ? 'perimeter' : 'length')),
|
||||
centroid = d3_geoCentroid(feature);
|
||||
var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate());
|
||||
var feature = entity.asGeoJSON(resolver);
|
||||
var length = radiansToMeters(d3_geoLength(toLineString(feature)));
|
||||
var lengthLabel = t('info_panels.measurement.' + (closed ? 'perimeter' : 'length'));
|
||||
var centroid = d3_geoCentroid(feature);
|
||||
|
||||
list
|
||||
.append('li')
|
||||
@@ -157,7 +166,8 @@ export function uiPanelMeasurement(context) {
|
||||
});
|
||||
|
||||
} else {
|
||||
var centerLabel = t('info_panels.measurement.' + (entity.type === 'node' ? 'location' : 'center'));
|
||||
var centerLabel = t('info_panels.measurement.' +
|
||||
(note || entity.type === 'node' ? 'location' : 'center'));
|
||||
|
||||
list
|
||||
.append('li')
|
||||
@@ -183,11 +193,16 @@ export function uiPanelMeasurement(context) {
|
||||
.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.map().on('drawn.info-measurement', null);
|
||||
context.on('enter.info-measurement', null);
|
||||
};
|
||||
|
||||
panel.id = 'measurement';
|
||||
|
||||
+39
-39
@@ -14,13 +14,13 @@ import { utilRebind } from '../util';
|
||||
|
||||
|
||||
export function uiPresetEditor(context) {
|
||||
var dispatch = d3_dispatch('change'),
|
||||
formFields = uiFormFields(context),
|
||||
state,
|
||||
fieldsArr,
|
||||
preset,
|
||||
tags,
|
||||
entityId;
|
||||
var dispatch = d3_dispatch('change');
|
||||
var formFields = uiFormFields(context);
|
||||
var _state;
|
||||
var _fieldsArr;
|
||||
var _preset;
|
||||
var _tags;
|
||||
var _entityID;
|
||||
|
||||
|
||||
function presetEditor(selection) {
|
||||
@@ -32,36 +32,36 @@ export function uiPresetEditor(context) {
|
||||
|
||||
|
||||
function render(selection) {
|
||||
if (!fieldsArr) {
|
||||
var entity = context.entity(entityId),
|
||||
geometry = context.geometry(entityId),
|
||||
presets = context.presets();
|
||||
if (!_fieldsArr) {
|
||||
var entity = context.entity(_entityID);
|
||||
var geometry = context.geometry(_entityID);
|
||||
var presets = context.presets();
|
||||
|
||||
fieldsArr = [];
|
||||
_fieldsArr = [];
|
||||
|
||||
preset.fields.forEach(function(field) {
|
||||
_preset.fields.forEach(function(field) {
|
||||
if (field.matchGeometry(geometry)) {
|
||||
fieldsArr.push(
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (entity.isHighwayIntersection(context.graph()) && presets.field('restrictions')) {
|
||||
fieldsArr.push(
|
||||
_fieldsArr.push(
|
||||
uiField(context, presets.field('restrictions'), entity)
|
||||
);
|
||||
}
|
||||
|
||||
presets.universal().forEach(function(field) {
|
||||
if (preset.fields.indexOf(field) === -1) {
|
||||
fieldsArr.push(
|
||||
if (_preset.fields.indexOf(field) === -1) {
|
||||
_fieldsArr.push(
|
||||
uiField(context, field, entity, { show: false })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
fieldsArr.forEach(function(field) {
|
||||
_fieldsArr.forEach(function(field) {
|
||||
field
|
||||
.on('change', function(t, onInput) {
|
||||
dispatch.call('change', field, t, onInput);
|
||||
@@ -69,15 +69,15 @@ export function uiPresetEditor(context) {
|
||||
});
|
||||
}
|
||||
|
||||
fieldsArr.forEach(function(field) {
|
||||
_fieldsArr.forEach(function(field) {
|
||||
field
|
||||
.state(state)
|
||||
.tags(tags);
|
||||
.state(_state)
|
||||
.tags(_tags);
|
||||
});
|
||||
|
||||
|
||||
selection
|
||||
.call(formFields.fieldsArr(fieldsArr), 'inspector-inner fillL3');
|
||||
.call(formFields.fieldsArr(_fieldsArr), 'inspector-inner fillL3');
|
||||
|
||||
|
||||
selection.selectAll('.wrap-form-field input')
|
||||
@@ -90,35 +90,35 @@ export function uiPresetEditor(context) {
|
||||
}
|
||||
|
||||
|
||||
presetEditor.preset = function(_) {
|
||||
if (!arguments.length) return preset;
|
||||
if (preset && preset.id === _.id) return presetEditor;
|
||||
preset = _;
|
||||
fieldsArr = null;
|
||||
presetEditor.preset = function(val) {
|
||||
if (!arguments.length) return _preset;
|
||||
if (_preset && _preset.id === val.id) return presetEditor;
|
||||
_preset = val;
|
||||
_fieldsArr = null;
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
|
||||
presetEditor.state = function(_) {
|
||||
if (!arguments.length) return state;
|
||||
state = _;
|
||||
presetEditor.state = function(val) {
|
||||
if (!arguments.length) return _state;
|
||||
_state = val;
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
|
||||
presetEditor.tags = function(_) {
|
||||
if (!arguments.length) return tags;
|
||||
tags = _;
|
||||
// Don't reset fieldsArr here.
|
||||
presetEditor.tags = function(val) {
|
||||
if (!arguments.length) return _tags;
|
||||
_tags = val;
|
||||
// Don't reset _fieldsArr here.
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
|
||||
presetEditor.entityID = function(_) {
|
||||
if (!arguments.length) return entityId;
|
||||
if (entityId === _) return presetEditor;
|
||||
entityId = _;
|
||||
fieldsArr = null;
|
||||
presetEditor.entityID = function(val) {
|
||||
if (!arguments.length) return _entityID;
|
||||
if (_entityID === val) return presetEditor;
|
||||
_entityID = val;
|
||||
_fieldsArr = null;
|
||||
return presetEditor;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,17 +24,17 @@ import {
|
||||
|
||||
|
||||
export function uiRawTagEditor(context) {
|
||||
var taginfo = services.taginfo,
|
||||
dispatch = d3_dispatch('change'),
|
||||
_readOnlyTags = [],
|
||||
_showBlank = false,
|
||||
_updatePreference = true,
|
||||
_expanded = false,
|
||||
_newRow,
|
||||
_state,
|
||||
_preset,
|
||||
_tags,
|
||||
_entityID;
|
||||
var taginfo = services.taginfo;
|
||||
var dispatch = d3_dispatch('change');
|
||||
var _readOnlyTags = [];
|
||||
var _showBlank = false;
|
||||
var _updatePreference = true;
|
||||
var _expanded = false;
|
||||
var _newRow;
|
||||
var _state;
|
||||
var _preset;
|
||||
var _tags;
|
||||
var _entityID;
|
||||
|
||||
|
||||
function rawTagEditor(selection) {
|
||||
@@ -148,16 +148,16 @@ export function uiRawTagEditor(context) {
|
||||
|
||||
items
|
||||
.each(function(tag) {
|
||||
var row = d3_select(this),
|
||||
key = row.select('input.key'), // propagate bound data to child
|
||||
value = row.select('input.value'); // propagate bound data to child
|
||||
var row = d3_select(this);
|
||||
var key = row.select('input.key'); // propagate bound data to child
|
||||
var value = row.select('input.value'); // propagate bound data to child
|
||||
|
||||
if (_entityID && taginfo) {
|
||||
bindTypeahead(key, value);
|
||||
}
|
||||
|
||||
var isRelation = (_entityID && context.entity(_entityID).type === 'relation'),
|
||||
reference;
|
||||
var isRelation = (_entityID && context.entity(_entityID).type === 'relation');
|
||||
var reference;
|
||||
|
||||
if (isRelation && tag.key === 'type') {
|
||||
reference = uiTagReference({ rtype: tag.value }, context);
|
||||
@@ -239,8 +239,8 @@ export function uiRawTagEditor(context) {
|
||||
|
||||
|
||||
function sort(value, data) {
|
||||
var sameletter = [],
|
||||
other = [];
|
||||
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]);
|
||||
@@ -265,10 +265,9 @@ export function uiRawTagEditor(context) {
|
||||
|
||||
|
||||
function keyChange(d) {
|
||||
var kOld = d.key,
|
||||
kNew = this.value.trim(),
|
||||
tag = {};
|
||||
|
||||
var kOld = d.key;
|
||||
var kNew = this.value.trim();
|
||||
var tag = {};
|
||||
|
||||
if (isReadOnly({ key: kNew })) {
|
||||
this.value = kOld;
|
||||
@@ -276,17 +275,17 @@ export function uiRawTagEditor(context) {
|
||||
}
|
||||
|
||||
if (kNew && kNew !== kOld) {
|
||||
var match = kNew.match(/^(.*?)(?:_(\d+))?$/),
|
||||
base = match[1],
|
||||
suffix = +(match[2] || 1);
|
||||
var match = kNew.match(/^(.*?)(?:_(\d+))?$/);
|
||||
var base = match[1];
|
||||
var suffix = +(match[2] || 1);
|
||||
while (_tags[kNew]) { // rename key if already in use
|
||||
kNew = base + '_' + suffix++;
|
||||
}
|
||||
|
||||
if (_includes(kNew, '=')) {
|
||||
var splitStr = kNew.split('=').map(function(str) { return str.trim(); }),
|
||||
key = splitStr[0],
|
||||
value = splitStr[1];
|
||||
var splitStr = kNew.split('=').map(function(str) { return str.trim(); });
|
||||
var key = splitStr[0];
|
||||
var value = splitStr[1];
|
||||
|
||||
kNew = key;
|
||||
d.value = value;
|
||||
@@ -295,9 +294,9 @@ export function uiRawTagEditor(context) {
|
||||
tag[kOld] = undefined;
|
||||
tag[kNew] = d.value;
|
||||
|
||||
d.key = kNew; // Maintain DOM identity through the subsequent update.
|
||||
d.key = kNew; // Maintain DOM identity through the subsequent update.
|
||||
|
||||
if (_newRow === kOld) { // see if this row is still a new row
|
||||
if (_newRow === kOld) { // see if this row is still a new row
|
||||
_newRow = ((d.value === '' || kNew === '') ? kNew : undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import _cloneDeep from 'lodash-es/cloneDeep';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import { t } from '../../util/locale';
|
||||
import { uiConfirm } from '../confirm';
|
||||
import { utilNoAuto, utilRebind } from '../../util';
|
||||
|
||||
|
||||
export function uiSettingsCustomBackground(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
|
||||
function render(selection) {
|
||||
var _origSettings = {
|
||||
template: context.storage('background-custom-template')
|
||||
};
|
||||
var _currSettings = _cloneDeep(_origSettings);
|
||||
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')
|
||||
.text(t('settings.custom_background.header'));
|
||||
|
||||
|
||||
var textSection = modal.select('.modal-section.message-text');
|
||||
|
||||
textSection
|
||||
.append('pre')
|
||||
.attr('class', 'instructions-template')
|
||||
.text(t('settings.custom_background.instructions', { example: example }));
|
||||
|
||||
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, and adjust the button widths
|
||||
var buttonSection = modal.select('.modal-section.buttons');
|
||||
|
||||
buttonSection
|
||||
.insert('button', '.ok-button')
|
||||
.attr('class', 'button col3 cancel-button secondary-action')
|
||||
.text(t('confirm.cancel'));
|
||||
|
||||
|
||||
buttonSection.select('.cancel-button')
|
||||
.on('click.cancel', clickCancel);
|
||||
|
||||
buttonSection.select('.ok-button')
|
||||
.classed('col3', true)
|
||||
.classed('col4', false)
|
||||
.attr('disabled', isSaveDisabled)
|
||||
.on('click.save', clickSave);
|
||||
|
||||
|
||||
function isSaveDisabled() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// restore the original template
|
||||
function clickCancel() {
|
||||
textSection.select('.field-template').property('value', _origSettings.template);
|
||||
context.storage('background-custom-template', _origSettings.template);
|
||||
this.blur();
|
||||
modal.close();
|
||||
}
|
||||
|
||||
// accept the current template
|
||||
function clickSave() {
|
||||
_currSettings.template = textSection.select('.field-template').property('value');
|
||||
context.storage('background-custom-template', _currSettings.template);
|
||||
this.blur();
|
||||
modal.close();
|
||||
dispatch.call('change', this, _currSettings);
|
||||
}
|
||||
}
|
||||
|
||||
return utilRebind(render, dispatch, 'on');
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import _cloneDeep from 'lodash-es/cloneDeep';
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { event as d3_event } from 'd3-selection';
|
||||
|
||||
import { t } from '../../util/locale';
|
||||
import { uiConfirm } from '../confirm';
|
||||
import { utilNoAuto, utilRebind } from '../../util';
|
||||
|
||||
|
||||
export function uiSettingsCustomData(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
|
||||
function render(selection) {
|
||||
var dataLayer = context.layers().layer('data');
|
||||
var _origSettings = {
|
||||
fileList: (dataLayer && dataLayer.fileList()) || null,
|
||||
url: context.storage('settings-custom-data-url')
|
||||
};
|
||||
var _currSettings = _cloneDeep(_origSettings);
|
||||
|
||||
// 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-data', true);
|
||||
|
||||
modal.select('.modal-section.header')
|
||||
.append('h3')
|
||||
.text(t('settings.custom_data.header'));
|
||||
|
||||
|
||||
var textSection = modal.select('.modal-section.message-text');
|
||||
|
||||
textSection
|
||||
.append('pre')
|
||||
.attr('class', 'instructions-file')
|
||||
.text(t('settings.custom_data.file.instructions'));
|
||||
|
||||
textSection
|
||||
.append('input')
|
||||
.attr('class', 'field-file')
|
||||
.attr('type', 'file')
|
||||
.property('files', _currSettings.fileList) // works for all except IE11
|
||||
.on('change', function() {
|
||||
var files = d3_event.target.files;
|
||||
if (files && files.length) {
|
||||
_currSettings.url = '';
|
||||
textSection.select('.field-url').property('value', '');
|
||||
_currSettings.fileList = files;
|
||||
} else {
|
||||
_currSettings.fileList = null;
|
||||
}
|
||||
});
|
||||
|
||||
textSection
|
||||
.append('h4')
|
||||
.text(t('settings.custom_data.or'));
|
||||
|
||||
textSection
|
||||
.append('pre')
|
||||
.attr('class', 'instructions-url')
|
||||
.text(t('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, and adjust the button widths
|
||||
var buttonSection = modal.select('.modal-section.buttons');
|
||||
|
||||
buttonSection
|
||||
.insert('button', '.ok-button')
|
||||
.attr('class', 'button col3 cancel-button secondary-action')
|
||||
.text(t('confirm.cancel'));
|
||||
|
||||
|
||||
buttonSection.select('.cancel-button')
|
||||
.on('click.cancel', clickCancel);
|
||||
|
||||
buttonSection.select('.ok-button')
|
||||
.classed('col3', true)
|
||||
.classed('col4', false)
|
||||
.attr('disabled', isSaveDisabled)
|
||||
.on('click.save', clickSave);
|
||||
|
||||
|
||||
function isSaveDisabled() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// restore the original url
|
||||
function clickCancel() {
|
||||
textSection.select('.field-url').property('value', _origSettings.url);
|
||||
context.storage('settings-custom-data-url', _origSettings.url);
|
||||
this.blur();
|
||||
modal.close();
|
||||
}
|
||||
|
||||
// accept the current url
|
||||
function clickSave() {
|
||||
_currSettings.url = textSection.select('.field-url').property('value').trim();
|
||||
|
||||
// one or the other but not both
|
||||
if (_currSettings.url) { _currSettings.fileList = null; }
|
||||
if (_currSettings.fileList) { _currSettings.url = ''; }
|
||||
|
||||
context.storage('settings-custom-data-url', _currSettings.url);
|
||||
this.blur();
|
||||
modal.close();
|
||||
dispatch.call('change', this, _currSettings);
|
||||
}
|
||||
}
|
||||
|
||||
return utilRebind(render, dispatch, 'on');
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { uiSettingsCustomBackground } from './custom_background';
|
||||
export { uiSettingsCustomData } from './custom_data';
|
||||
+34
-20
@@ -2,18 +2,26 @@ import _throttle from 'lodash-es/throttle';
|
||||
|
||||
import { selectAll as d3_selectAll } from 'd3-selection';
|
||||
|
||||
import { osmNote } from '../osm';
|
||||
import { uiFeatureList } from './feature_list';
|
||||
import { uiInspector } from './inspector';
|
||||
import { uiNoteEditor } from './note_editor';
|
||||
import {
|
||||
osmEntity,
|
||||
osmNote
|
||||
} from '../osm';
|
||||
|
||||
import {
|
||||
uiDataEditor,
|
||||
uiFeatureList,
|
||||
uiInspector,
|
||||
uiNoteEditor
|
||||
} from './index';
|
||||
|
||||
|
||||
export function uiSidebar(context) {
|
||||
var inspector = uiInspector(context);
|
||||
var dataEditor = uiDataEditor(context);
|
||||
var noteEditor = uiNoteEditor(context);
|
||||
var _current;
|
||||
var _wasData = false;
|
||||
var _wasNote = false;
|
||||
// var layer = d3_select(null);
|
||||
|
||||
|
||||
function sidebar(selection) {
|
||||
@@ -22,25 +30,31 @@ export function uiSidebar(context) {
|
||||
.attr('class', 'feature-list-pane')
|
||||
.call(uiFeatureList(context));
|
||||
|
||||
|
||||
var inspectorWrap = selection
|
||||
.append('div')
|
||||
.attr('class', 'inspector-hidden inspector-wrap fr');
|
||||
|
||||
|
||||
function hover(what) {
|
||||
if ((what instanceof osmNote)) {
|
||||
_wasNote = true;
|
||||
var notes = d3_selectAll('.note');
|
||||
notes
|
||||
.classed('hovered', function(d) { return d === what; });
|
||||
|
||||
sidebar.show(noteEditor.note(what));
|
||||
function hover(datum) {
|
||||
if (datum && datum.__featurehash__) { // hovering on data
|
||||
_wasData = true;
|
||||
sidebar
|
||||
.show(dataEditor.datum(datum));
|
||||
|
||||
selection.selectAll('.sidebar-component')
|
||||
.classed('inspector-hover', true);
|
||||
|
||||
} else if (!_current && context.hasEntity(what)) {
|
||||
} else if (datum instanceof osmNote) {
|
||||
if (context.mode().id === 'drag-note') return;
|
||||
_wasNote = true;
|
||||
|
||||
sidebar
|
||||
.show(noteEditor.note(datum));
|
||||
|
||||
selection.selectAll('.sidebar-component')
|
||||
.classed('inspector-hover', true);
|
||||
|
||||
} else if (!_current && (datum instanceof osmEntity)) {
|
||||
featureListWrap
|
||||
.classed('inspector-hidden', true);
|
||||
|
||||
@@ -48,10 +62,10 @@ export function uiSidebar(context) {
|
||||
.classed('inspector-hidden', false)
|
||||
.classed('inspector-hover', true);
|
||||
|
||||
if (inspector.entityID() !== what || inspector.state() !== 'hover') {
|
||||
if (inspector.entityID() !== datum.id || inspector.state() !== 'hover') {
|
||||
inspector
|
||||
.state('hover')
|
||||
.entityID(what);
|
||||
.entityID(datum.id);
|
||||
|
||||
inspectorWrap
|
||||
.call(inspector);
|
||||
@@ -65,10 +79,10 @@ export function uiSidebar(context) {
|
||||
inspector
|
||||
.state('hide');
|
||||
|
||||
} else if (_wasNote) {
|
||||
} else if (_wasData || _wasNote) {
|
||||
_wasNote = false;
|
||||
d3_selectAll('.note')
|
||||
.classed('hovered', false);
|
||||
_wasData = false;
|
||||
d3_selectAll('.note').classed('hover', false);
|
||||
sidebar.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ import { tooltip } from '../util/tooltip';
|
||||
|
||||
|
||||
// these are module variables so they are preserved through a ui.restart()
|
||||
var sawVersion = null,
|
||||
isNewVersion = false,
|
||||
isNewUser = false;
|
||||
var sawVersion = null;
|
||||
var isNewVersion = false;
|
||||
var isNewUser = false;
|
||||
|
||||
|
||||
export function uiVersion(context) {
|
||||
|
||||
var currVersion = context.version,
|
||||
matchedVersion = currVersion.match(/\d\.\d\.\d.*/);
|
||||
var currVersion = context.version;
|
||||
var matchedVersion = currVersion.match(/\d+\.\d+\.\d+.*/);
|
||||
|
||||
if (sawVersion === null && matchedVersion !== null) {
|
||||
isNewVersion = (context.storage('sawVersion') !== currVersion);
|
||||
|
||||
@@ -14,6 +14,7 @@ export { utilFunctor } from './util';
|
||||
export { utilGetAllNodes } from './util';
|
||||
export { utilGetPrototypeOf } from './util';
|
||||
export { utilGetSetValue } from './get_set_value';
|
||||
export { utilHashcode } from './util';
|
||||
export { utilIdleWorker} from './idle_worker';
|
||||
export { utilMapCSSRule } from './mapcss_rule';
|
||||
export { utilNoAuto } from './util';
|
||||
@@ -26,5 +27,6 @@ export { utilSessionMutex } from './session_mutex';
|
||||
export { utilStringQs } from './util';
|
||||
export { utilSuggestNames } from './suggest_names';
|
||||
export { utilTagText } from './util';
|
||||
export { utilTiler } from './tiler';
|
||||
export { utilTriggerEvent } from './trigger_event';
|
||||
export { utilWrap } from './util';
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { range as d3_range } from 'd3-array';
|
||||
import { geoExtent, geoScaleToZoom } from '../geo';
|
||||
|
||||
|
||||
export function utilTiler() {
|
||||
var _size = [256, 256];
|
||||
var _scale = 256;
|
||||
var _tileSize = 256;
|
||||
var _zoomExtent = [0, 20];
|
||||
var _translate = [_size[0] / 2, _size[1] / 2];
|
||||
var _margin = 0;
|
||||
var _skipNullIsland = false;
|
||||
|
||||
|
||||
function bound(val) {
|
||||
return Math.min(_zoomExtent[1], Math.max(_zoomExtent[0], val));
|
||||
}
|
||||
|
||||
|
||||
function nearNullIsland(tile) {
|
||||
var x = tile[0];
|
||||
var y = tile[1];
|
||||
var z = tile[2];
|
||||
if (z >= 7) {
|
||||
var center = Math.pow(2, z - 1);
|
||||
var width = Math.pow(2, z - 6);
|
||||
var min = center - (width / 2);
|
||||
var max = center + (width / 2) - 1;
|
||||
return x >= min && x <= max && y >= min && y <= max;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function tiler() {
|
||||
var z = geoScaleToZoom(_scale / (2 * Math.PI), _tileSize);
|
||||
var z0 = bound(Math.round(z));
|
||||
var log2ts = Math.log(_tileSize) * Math.LOG2E;
|
||||
var k = Math.pow(2, z - z0 + log2ts);
|
||||
var origin = [
|
||||
(_translate[0] - _scale / 2) / k,
|
||||
(_translate[1] - _scale / 2) / k
|
||||
];
|
||||
|
||||
var cols = d3_range(
|
||||
Math.max(0, Math.floor(-origin[0]) - _margin),
|
||||
Math.max(0, Math.ceil(_size[0] / k - origin[0]) + _margin)
|
||||
);
|
||||
var rows = d3_range(
|
||||
Math.max(0, Math.floor(-origin[1]) - _margin),
|
||||
Math.max(0, Math.ceil(_size[1] / k - origin[1]) + _margin)
|
||||
);
|
||||
|
||||
var tiles = [];
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var y = rows[i];
|
||||
for (var j = 0; j < cols.length; j++) {
|
||||
var x = cols[j];
|
||||
|
||||
if (i >= _margin && i <= rows.length - _margin &&
|
||||
j >= _margin && j <= cols.length - _margin) {
|
||||
tiles.unshift([x, y, z0]); // tiles in view at beginning
|
||||
} else {
|
||||
tiles.push([x, y, z0]); // tiles in margin at the end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tiles.translate = origin;
|
||||
tiles.scale = k;
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getTiles() returns an array of tiles that cover the map view
|
||||
*/
|
||||
tiler.getTiles = function(projection) {
|
||||
var origin = [
|
||||
projection.scale() * Math.PI - projection.translate()[0],
|
||||
projection.scale() * Math.PI - projection.translate()[1]
|
||||
];
|
||||
|
||||
this
|
||||
.size(projection.clipExtent()[1])
|
||||
.scale(projection.scale() * 2 * Math.PI)
|
||||
.translate(projection.translate());
|
||||
|
||||
var tiles = tiler();
|
||||
var ts = tiles.scale;
|
||||
|
||||
return tiles
|
||||
.map(function(tile) {
|
||||
if (_skipNullIsland && nearNullIsland(tile)) {
|
||||
return false;
|
||||
}
|
||||
var x = tile[0] * ts - origin[0];
|
||||
var y = tile[1] * ts - origin[1];
|
||||
return {
|
||||
id: tile.toString(),
|
||||
xyz: tile,
|
||||
extent: geoExtent(
|
||||
projection.invert([x, y + ts]),
|
||||
projection.invert([x + ts, y])
|
||||
)
|
||||
};
|
||||
}).filter(Boolean);
|
||||
};
|
||||
|
||||
|
||||
tiler.tileSize = function(val) {
|
||||
if (!arguments.length) return _tileSize;
|
||||
_tileSize = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
tiler.zoomExtent = function(val) {
|
||||
if (!arguments.length) return _zoomExtent;
|
||||
_zoomExtent = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
tiler.size = function(val) {
|
||||
if (!arguments.length) return _size;
|
||||
_size = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
tiler.scale = function(val) {
|
||||
if (!arguments.length) return _scale;
|
||||
_scale = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
tiler.translate = function(val) {
|
||||
if (!arguments.length) return _translate;
|
||||
_translate = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
// number to extend the rows/columns beyond those covering the viewport
|
||||
tiler.margin = function(val) {
|
||||
if (!arguments.length) return _margin;
|
||||
_margin = +val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
tiler.skipNullIsland = function(val) {
|
||||
if (!arguments.length) return _skipNullIsland;
|
||||
_skipNullIsland = val;
|
||||
return tiler;
|
||||
};
|
||||
|
||||
|
||||
return tiler;
|
||||
}
|
||||
+16
-1
@@ -273,4 +273,19 @@ export function utilExternalPresets() {
|
||||
|
||||
export function utilExternalValidationRules() {
|
||||
return utilStringQs(window.location.hash).hasOwnProperty('validations');
|
||||
}
|
||||
}
|
||||
|
||||
// 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/
|
||||
export function utilHashcode(str) {
|
||||
var hash = 0;
|
||||
if (str.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
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
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { serviceMapRules } from '../services';
|
||||
|
||||
export function validationMapCSSChecks() {
|
||||
var validation = function(changes, graph, rules) {
|
||||
var validation = function(changes, graph) {
|
||||
var rules = serviceMapRules.validationRules();
|
||||
var warnings = [];
|
||||
var createdModified = ['created', 'modified'];
|
||||
for (var i = 0; i < createdModified.length; i++) {
|
||||
var entities = changes[createdModified[i]];
|
||||
for (var j = 0; j < entities.length; j++) {
|
||||
var entity = entities[j];
|
||||
for (var k = 0; k < rules.length; k++) {
|
||||
var rule = rules[k];
|
||||
rule.findWarnings(entity, graph, warnings);
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
for (var j = 0; j < createdModified.length; j++) {
|
||||
var type = createdModified[j];
|
||||
var entities = changes[type];
|
||||
for (var k = 0; k < entities.length; k++) {
|
||||
rule.findWarnings(entities[k], graph, warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
};
|
||||
return validation;
|
||||
|
||||
@@ -12,7 +12,11 @@ export function validationTagSuggestsArea() {
|
||||
var presence = ['landuse', 'amenities', 'tourism', 'shop'];
|
||||
for (var i = 0; i < presence.length; i++) {
|
||||
if (tags[presence[i]] !== undefined) {
|
||||
return presence[i] + '=' + tags[presence[i]];
|
||||
if (presence[i] === 'tourism' && tags[presence[i]] === 'artwork') {
|
||||
continue; // exception for tourism=artwork - #5206
|
||||
} else {
|
||||
return presence[i] + '=' + tags[presence[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user