move maprules/mapcss to a service class

ref #remote-presets
This commit is contained in:
Max Grossman
2018-09-23 18:38:01 -04:00
327 changed files with 21654 additions and 7363 deletions
+48
View File
@@ -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;
}
+1
View File
@@ -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';
+1 -5
View File
@@ -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',
+2 -2
View File
@@ -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
View File
@@ -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);
};
+9 -8
View File
@@ -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];
+6 -1
View File
@@ -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);
}
-1
View File
@@ -138,7 +138,6 @@ export function behaviorDraw(context) {
return;
}
}
dispatch.call('click', this, context.map().mouseCoordinates(), d);
}
+1 -1
View File
@@ -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
View File
@@ -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);
}
}
};
+3 -3
View File
@@ -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' &&
+2 -1
View File
@@ -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
View File
@@ -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));
+6 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}));
},
+1
View File
@@ -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';
-93
View File
@@ -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
View File
@@ -1,3 +1,2 @@
export { d3combobox } from './d3.combobox';
export { d3geoTile } from './d3.geo.tile';
export { d3keybinding } from './d3.keybinding';
+14 -13
View File
@@ -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),
+8 -8
View File
@@ -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),
+56
View File
@@ -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;
}
+5 -7
View File
@@ -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();
-3
View File
@@ -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()
+126
View File
@@ -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;
}
+6 -6
View File
@@ -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];
};
+3
View File
@@ -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';
+2 -6
View File
@@ -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)
+5 -8
View File
@@ -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;
+97
View File
@@ -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;
}
+50 -38
View File
@@ -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);
};
+85
View File
@@ -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;
}
+1
View File
@@ -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
View File
@@ -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 });
}
});
+1 -2
View File
@@ -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
View File
@@ -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; })
+1 -1
View File
@@ -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});
};
+42 -30
View File
@@ -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');
}
}
+12 -26
View File
@@ -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
View File
@@ -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 = _;
+17 -32
View File
@@ -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;
};
+7
View File
@@ -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
};
+28 -56
View File
@@ -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);
}
},
+213
View File
@@ -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; }
};
+49 -80
View File
@@ -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
View File
@@ -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;
}
};
+27 -63
View File
@@ -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');
+1 -1
View File
@@ -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);
+215
View File
@@ -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;
}
};
+2 -2
View File
@@ -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
+541
View File
@@ -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
View File
@@ -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();
-266
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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';
+2 -4
View File
@@ -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) },
+19 -1
View File
@@ -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);
-301
View File
@@ -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
View File
@@ -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
View File
@@ -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')
+1 -1
View File
@@ -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();
})
+79
View File
@@ -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;
}
+47
View File
@@ -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;
}
+3
View File
@@ -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);
}
+1 -1
View File
@@ -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
View File
@@ -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;
};
+1 -1
View File
@@ -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
+7 -8
View File
@@ -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;
};
+15
View File
@@ -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'),
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
+51 -52
View File
@@ -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
View File
@@ -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
View File
@@ -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 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+3 -3
View File
@@ -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
View File
@@ -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();
});
}
};
}
+7 -2
View File
@@ -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
View File
@@ -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);
});
}
}
+8 -7
View File
@@ -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') : '');
});
+90 -23
View File
@@ -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';
+43 -28
View File
@@ -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
View File
@@ -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;
};
+29 -30
View File
@@ -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);
}
+87
View File
@@ -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');
}
+121
View File
@@ -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');
}
+2
View File
@@ -0,0 +1,2 @@
export { uiSettingsCustomBackground } from './custom_background';
export { uiSettingsCustomData } from './custom_data';
+34 -20
View File
@@ -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();
}
}
+5 -5
View File
@@ -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);
+2
View File
@@ -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';
+163
View File
@@ -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
View File
@@ -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;
}
+13 -8
View File
@@ -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;
+5 -1
View File
@@ -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]];
}
}
}