mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-11 13:46:10 +00:00
Merge pull request #2357 from bhousel/feature-filtering
Feature filtering
This commit is contained in:
@@ -50,7 +50,15 @@ iD.Graph.prototype = {
|
||||
},
|
||||
|
||||
parentWays: function(entity) {
|
||||
return _.map(this._parentWays[entity.id], this.entity, this);
|
||||
var parents = this._parentWays[entity.id],
|
||||
result = [];
|
||||
|
||||
if (parents) {
|
||||
for (var i = 0, imax = parents.length; i !== imax; i++) {
|
||||
result.push(this.entity(parents[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
isPoi: function(entity) {
|
||||
@@ -64,7 +72,15 @@ iD.Graph.prototype = {
|
||||
},
|
||||
|
||||
parentRelations: function(entity) {
|
||||
return _.map(this._parentRels[entity.id], this.entity, this);
|
||||
var parents = this._parentRels[entity.id],
|
||||
result = [];
|
||||
|
||||
if (parents) {
|
||||
for (var i = 0, imax = parents.length; i !== imax; i++) {
|
||||
result.push(this.entity(parents[i]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
childNodes: function(entity) {
|
||||
@@ -72,8 +88,10 @@ iD.Graph.prototype = {
|
||||
return this._childNodes[entity.id];
|
||||
|
||||
var nodes = [];
|
||||
for (var i = 0, l = entity.nodes.length; i < l; i++) {
|
||||
nodes[i] = this.entity(entity.nodes[i]);
|
||||
if (entity.nodes) {
|
||||
for (var i = 0, l = entity.nodes.length; i < l; i++) {
|
||||
nodes[i] = this.entity(entity.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (iD.debug) Object.freeze(nodes);
|
||||
|
||||
@@ -14,6 +14,13 @@ iD.geo.Extent = function geoExtent(min, max) {
|
||||
iD.geo.Extent.prototype = new Array(2);
|
||||
|
||||
_.extend(iD.geo.Extent.prototype, {
|
||||
equals: function (obj) {
|
||||
return this[0][0] === obj[0][0] &&
|
||||
this[0][1] === obj[0][1] &&
|
||||
this[1][0] === obj[1][0] &&
|
||||
this[1][1] === obj[1][1];
|
||||
},
|
||||
|
||||
extend: function(obj) {
|
||||
if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj);
|
||||
return iD.geo.Extent([Math.min(obj[0][0], this[0][0]),
|
||||
|
||||
10
js/id/id.js
10
js/id/id.js
@@ -97,6 +97,7 @@ window.iD = function () {
|
||||
|
||||
context.flush = function() {
|
||||
connection.flush();
|
||||
features.reset();
|
||||
history.reset();
|
||||
return context;
|
||||
};
|
||||
@@ -203,6 +204,15 @@ window.iD = function () {
|
||||
var background = iD.Background(context);
|
||||
context.background = function() { return background; };
|
||||
|
||||
/* Features */
|
||||
var features = iD.Features();
|
||||
context.features = function() { return features; };
|
||||
context.hasHiddenConnections = function(id) {
|
||||
var graph = history.graph(),
|
||||
entity = graph.entity(id);
|
||||
return features.hasHiddenConnections(entity, graph);
|
||||
};
|
||||
|
||||
/* Map */
|
||||
var map = iD.Map(context);
|
||||
context.map = function() { return map; };
|
||||
|
||||
@@ -48,7 +48,9 @@ iD.modes.DragNode = function(context) {
|
||||
}
|
||||
|
||||
function start(entity) {
|
||||
cancelled = d3.event.sourceEvent.shiftKey;
|
||||
cancelled = d3.event.sourceEvent.shiftKey ||
|
||||
context.features().hasHiddenConnections(entity, context.graph());
|
||||
|
||||
if (cancelled) return behavior.cancel();
|
||||
|
||||
wasMidpoint = entity.type === 'midpoint';
|
||||
|
||||
@@ -137,9 +137,16 @@ iD.modes.Select = function(context, selectedIDs) {
|
||||
.call(keybinding);
|
||||
|
||||
function selectElements() {
|
||||
context.surface()
|
||||
.selectAll(iD.util.entityOrMemberSelector(selectedIDs, context.graph()))
|
||||
.classed('selected', true);
|
||||
var selection = context.surface()
|
||||
.selectAll(iD.util.entityOrMemberSelector(selectedIDs, context.graph()));
|
||||
|
||||
if (selection.empty()) {
|
||||
// Exit mode if selected DOM elements have disappeared..
|
||||
context.enter(iD.modes.Browse(context));
|
||||
} else {
|
||||
selection
|
||||
.classed('selected', true);
|
||||
}
|
||||
}
|
||||
|
||||
context.map().on('drawn.select', selectElements);
|
||||
|
||||
@@ -20,6 +20,8 @@ iD.operations.Circularize = function(selectedIDs, context) {
|
||||
var reason;
|
||||
if (extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
reason = 'too_large';
|
||||
} else if (context.hasHiddenConnections(entityId)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,8 @@ iD.operations.Continue = function(selectedIDs, context) {
|
||||
};
|
||||
|
||||
operation.available = function() {
|
||||
return geometries.vertex.length === 1 && geometries.line.length <= 1;
|
||||
return geometries.vertex.length === 1 && geometries.line.length <= 1 &&
|
||||
!context.features().hasHiddenConnections(vertex, context.graph());
|
||||
};
|
||||
|
||||
operation.disabled = function() {
|
||||
|
||||
@@ -52,7 +52,11 @@ iD.operations.Delete = function(selectedIDs, context) {
|
||||
};
|
||||
|
||||
operation.disabled = function() {
|
||||
return action.disabled(context.graph());
|
||||
var reason;
|
||||
if (_.any(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
operation.tooltip = function() {
|
||||
|
||||
@@ -19,7 +19,11 @@ iD.operations.Disconnect = function(selectedIDs, context) {
|
||||
};
|
||||
|
||||
operation.disabled = function() {
|
||||
return action.disabled(context.graph());
|
||||
var reason;
|
||||
if (_.any(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
operation.tooltip = function() {
|
||||
|
||||
@@ -16,6 +16,8 @@ iD.operations.Move = function(selectedIDs, context) {
|
||||
var reason;
|
||||
if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
reason = 'too_large';
|
||||
} else if (_.any(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return iD.actions.Move(selectedIDs).disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ iD.operations.Orthogonalize = function(selectedIDs, context) {
|
||||
var reason;
|
||||
if (extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
reason = 'too_large';
|
||||
} else if (context.hasHiddenConnections(entityId)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ iD.operations.Rotate = function(selectedIDs, context) {
|
||||
operation.disabled = function() {
|
||||
if (extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
return 'too_large';
|
||||
} else if (context.hasHiddenConnections(entityId)) {
|
||||
return 'connected_to_hidden';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@ iD.operations.Split = function(selectedIDs, context) {
|
||||
};
|
||||
|
||||
operation.disabled = function() {
|
||||
return action.disabled(context.graph());
|
||||
var reason;
|
||||
if (_.any(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
operation.tooltip = function() {
|
||||
|
||||
@@ -16,7 +16,11 @@ iD.operations.Straighten = function(selectedIDs, context) {
|
||||
};
|
||||
|
||||
operation.disabled = function() {
|
||||
return action.disabled(context.graph());
|
||||
var reason;
|
||||
if (context.hasHiddenConnections(entityId)) {
|
||||
reason = 'connected_to_hidden';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
};
|
||||
|
||||
operation.tooltip = function() {
|
||||
|
||||
@@ -64,19 +64,11 @@ iD.Background = function(context) {
|
||||
|
||||
base.call(baseLayer);
|
||||
|
||||
var gpx = selection.selectAll('.gpx-layer')
|
||||
.data([0]);
|
||||
|
||||
gpx.enter().insert('div', '.layer-data')
|
||||
.attr('class', 'layer-layer gpx-layer');
|
||||
|
||||
gpx.call(gpxLayer);
|
||||
|
||||
var overlays = selection.selectAll('.overlay-layer')
|
||||
var overlays = selection.selectAll('.layer-overlay')
|
||||
.data(overlayLayers, function(d) { return d.source().name(); });
|
||||
|
||||
overlays.enter().insert('div', '.layer-data')
|
||||
.attr('class', 'layer-layer overlay-layer');
|
||||
.attr('class', 'layer-layer layer-overlay');
|
||||
|
||||
overlays.each(function(layer) {
|
||||
d3.select(this).call(layer);
|
||||
@@ -85,6 +77,14 @@ iD.Background = function(context) {
|
||||
overlays.exit()
|
||||
.remove();
|
||||
|
||||
var gpx = selection.selectAll('.layer-gpx')
|
||||
.data([0]);
|
||||
|
||||
gpx.enter().insert('div')
|
||||
.attr('class', 'layer-layer layer-gpx');
|
||||
|
||||
gpx.call(gpxLayer);
|
||||
|
||||
var mapillary = selection.selectAll('.layer-mapillary')
|
||||
.data([0]);
|
||||
|
||||
|
||||
396
js/id/renderer/features.js
Normal file
396
js/id/renderer/features.js
Normal file
@@ -0,0 +1,396 @@
|
||||
iD.Features = function() {
|
||||
var major_roads = {
|
||||
'motorway': true,
|
||||
'motorway_link': true,
|
||||
'trunk': true,
|
||||
'trunk_link': true,
|
||||
'primary': true,
|
||||
'primary_link': true,
|
||||
'secondary': true,
|
||||
'secondary_link': true,
|
||||
'tertiary': true,
|
||||
'tertiary_link': true,
|
||||
'residential': true
|
||||
};
|
||||
|
||||
var minor_roads = {
|
||||
'service': true,
|
||||
'living_street': true,
|
||||
'road': true,
|
||||
'unclassified': true,
|
||||
'track': true
|
||||
};
|
||||
|
||||
var paths = {
|
||||
'path': true,
|
||||
'footway': true,
|
||||
'cycleway': true,
|
||||
'bridleway': true,
|
||||
'steps': true,
|
||||
'pedestrian': true
|
||||
};
|
||||
|
||||
var past_futures = {
|
||||
'proposed': true,
|
||||
'construction': true,
|
||||
'abandoned': true,
|
||||
'dismantled': true,
|
||||
'disused': true,
|
||||
'razed': true,
|
||||
'demolished': true,
|
||||
'obliterated': true
|
||||
};
|
||||
|
||||
var dispatch = d3.dispatch('change', 'redraw'),
|
||||
_cullFactor = 1,
|
||||
_cache = {},
|
||||
_features = {},
|
||||
_stats = {},
|
||||
_keys = [],
|
||||
_hidden = [];
|
||||
|
||||
function update() {
|
||||
_hidden = features.hidden();
|
||||
dispatch.change();
|
||||
dispatch.redraw();
|
||||
}
|
||||
|
||||
function defineFeature(k, filter, max) {
|
||||
_keys.push(k);
|
||||
_features[k] = {
|
||||
filter: filter,
|
||||
enabled: true, // whether the user wants it enabled..
|
||||
count: 0,
|
||||
currentMax: (max || Infinity),
|
||||
defaultMax: (max || Infinity),
|
||||
enable: function() { this.enabled = true; this.currentMax = this.defaultMax; },
|
||||
disable: function() { this.enabled = false; this.currentMax = 0; },
|
||||
hidden: function() { return this.count > this.currentMax * _cullFactor; },
|
||||
autoHidden: function() { return this.hidden() && this.currentMax > 0; }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
defineFeature('points', function isPoint(entity, resolver) {
|
||||
return entity.geometry(resolver) === 'point';
|
||||
}, 200);
|
||||
|
||||
defineFeature('major_roads', function isMajorRoad(entity) {
|
||||
return major_roads[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('minor_roads', function isMinorRoad(entity) {
|
||||
return minor_roads[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('paths', function isPath(entity) {
|
||||
return paths[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('buildings', function isBuilding(entity) {
|
||||
return (
|
||||
!!entity.tags['building:part'] ||
|
||||
(!!entity.tags.building && entity.tags.building !== 'no') ||
|
||||
entity.tags.amenity === 'shelter' ||
|
||||
entity.tags.parking === 'multi-storey' ||
|
||||
entity.tags.parking === 'sheds' ||
|
||||
entity.tags.parking === 'carports' ||
|
||||
entity.tags.parking === 'garage_boxes'
|
||||
);
|
||||
}, 250);
|
||||
|
||||
defineFeature('landuse', function isLanduse(entity, resolver) {
|
||||
return entity.geometry(resolver) === 'area' &&
|
||||
!_features.buildings.filter(entity) &&
|
||||
!_features.water.filter(entity);
|
||||
});
|
||||
|
||||
defineFeature('boundaries', function isBoundary(entity) {
|
||||
return !!entity.tags.boundary;
|
||||
});
|
||||
|
||||
defineFeature('water', function isWater(entity) {
|
||||
return (
|
||||
!!entity.tags.waterway ||
|
||||
entity.tags.natural === 'water' ||
|
||||
entity.tags.natural === 'coastline' ||
|
||||
entity.tags.natural === 'bay' ||
|
||||
entity.tags.landuse === 'pond' ||
|
||||
entity.tags.landuse === 'basin' ||
|
||||
entity.tags.landuse === 'reservoir' ||
|
||||
entity.tags.landuse === 'salt_pond'
|
||||
);
|
||||
});
|
||||
|
||||
defineFeature('rail', function isRail(entity) {
|
||||
return (
|
||||
!!entity.tags.railway ||
|
||||
entity.tags.landuse === 'railway'
|
||||
) && !(
|
||||
major_roads[entity.tags.highway] ||
|
||||
minor_roads[entity.tags.highway] ||
|
||||
paths[entity.tags.highway]
|
||||
);
|
||||
});
|
||||
|
||||
defineFeature('power', function isPower(entity) {
|
||||
return !!entity.tags.power;
|
||||
});
|
||||
|
||||
// contains a past/future tag, but not in active use as a road/path/cycleway/etc..
|
||||
defineFeature('past_future', function isPastFuture(entity) {
|
||||
if (
|
||||
major_roads[entity.tags.highway] ||
|
||||
minor_roads[entity.tags.highway] ||
|
||||
paths[entity.tags.highway]
|
||||
) { return false; }
|
||||
|
||||
var strings = Object.keys(entity.tags);
|
||||
|
||||
for (var i = 0, imax = strings.length; i !== imax; i++) {
|
||||
var s = strings[i];
|
||||
if (past_futures[s] || past_futures[entity.tags[s]]) { return true; }
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// lines or areas that don't match another feature filter.
|
||||
defineFeature('others', function isOther(entity, resolver) {
|
||||
var geom = entity.geometry(resolver);
|
||||
return (geom === 'line' || geom === 'area') && !(
|
||||
_features.major_roads.filter(entity, resolver) ||
|
||||
_features.minor_roads.filter(entity, resolver) ||
|
||||
_features.paths.filter(entity, resolver) ||
|
||||
_features.buildings.filter(entity, resolver) ||
|
||||
_features.landuse.filter(entity, resolver) ||
|
||||
_features.boundaries.filter(entity, resolver) ||
|
||||
_features.water.filter(entity, resolver) ||
|
||||
_features.rail.filter(entity, resolver) ||
|
||||
_features.power.filter(entity, resolver) ||
|
||||
_features.past_future.filter(entity, resolver)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
function features() {}
|
||||
|
||||
features.keys = function() {
|
||||
return _keys;
|
||||
};
|
||||
|
||||
features.enabled = function(k) {
|
||||
if (!arguments.length) {
|
||||
return _.filter(_keys, function(k) { return _features[k].enabled; });
|
||||
}
|
||||
return _features[k] && _features[k].enabled;
|
||||
};
|
||||
|
||||
features.disabled = function(k) {
|
||||
if (!arguments.length) {
|
||||
return _.reject(_keys, function(k) { return _features[k].enabled; });
|
||||
}
|
||||
return _features[k] && !_features[k].enabled;
|
||||
};
|
||||
|
||||
features.hidden = function(k) {
|
||||
if (!arguments.length) {
|
||||
return _.filter(_keys, function(k) { return _features[k].hidden(); });
|
||||
}
|
||||
return _features[k] && _features[k].hidden();
|
||||
};
|
||||
|
||||
features.autoHidden = function(k) {
|
||||
if (!arguments.length) {
|
||||
return _.filter(_keys, function(k) { return _features[k].autoHidden(); });
|
||||
}
|
||||
return _features[k] && _features[k].autoHidden();
|
||||
};
|
||||
|
||||
features.enable = function(k) {
|
||||
if (_features[k] && !_features[k].enabled) {
|
||||
_features[k].enable();
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
features.disable = function(k) {
|
||||
if (_features[k] && _features[k].enabled) {
|
||||
_features[k].disable();
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
features.toggle = function(k) {
|
||||
if (_features[k]) {
|
||||
(function(f) { return f.enabled ? f.disable() : f.enable(); }(_features[k]));
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
features.resetStats = function() {
|
||||
_.each(_features, function(f) { f.count = 0; });
|
||||
dispatch.change();
|
||||
};
|
||||
|
||||
features.gatherStats = function(d, resolver, dimensions) {
|
||||
var needsRedraw = false,
|
||||
currHidden, geometry, feats;
|
||||
|
||||
_.each(_features, function(f) { f.count = 0; });
|
||||
|
||||
// adjust the threshold for point/building culling based on viewport size..
|
||||
// a _cullFactor of 1 corresponds to a 1000x1000px viewport..
|
||||
_cullFactor = dimensions[0] * dimensions[1] / 1000000;
|
||||
|
||||
for (var i = 0, imax = d.length; i !== imax; i++) {
|
||||
geometry = d[i].geometry(resolver);
|
||||
if (!(geometry === 'vertex' || geometry === 'relation')) {
|
||||
feats = Object.keys(features.matchEntity(d[i], resolver));
|
||||
for (var j = 0, jmax = feats.length; j !== jmax; j++) {
|
||||
_features[feats[j]].count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currHidden = features.hidden();
|
||||
if (currHidden !== _hidden) {
|
||||
_hidden = currHidden;
|
||||
needsRedraw = true;
|
||||
dispatch.change();
|
||||
}
|
||||
|
||||
return needsRedraw;
|
||||
};
|
||||
|
||||
features.stats = function() {
|
||||
_.each(_keys, function(k) { _stats[k] = _features[k].count; });
|
||||
return _stats;
|
||||
};
|
||||
|
||||
features.clear = function(d) {
|
||||
for (var i = 0, imax = d.length; i !== imax; i++) {
|
||||
features.clearEntity(d[i]);
|
||||
}
|
||||
};
|
||||
|
||||
features.clearEntity = function(entity) {
|
||||
delete _cache[iD.Entity.key(entity)];
|
||||
};
|
||||
|
||||
features.reset = function() {
|
||||
_cache = {};
|
||||
};
|
||||
|
||||
features.match = function(d) {
|
||||
for (var i = 0, imax = d.length; i !== imax; i++) {
|
||||
features.matchEntity(d[i]);
|
||||
}
|
||||
};
|
||||
|
||||
features.matchEntity = function(entity, resolver) {
|
||||
var ent = iD.Entity.key(entity);
|
||||
|
||||
if (!_cache[ent]) {
|
||||
var geometry = entity.geometry(resolver),
|
||||
matches = {},
|
||||
hasMatch = false;
|
||||
|
||||
if (!(geometry === 'vertex' || geometry === 'relation')) {
|
||||
for (var i = 0, imax = _keys.length; i !== imax; i++) {
|
||||
if (hasMatch && _keys[i] === 'others') {
|
||||
continue;
|
||||
}
|
||||
if (_features[_keys[i]].filter(entity, resolver)) {
|
||||
matches[_keys[i]] = hasMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_cache[ent] = matches;
|
||||
}
|
||||
return _cache[ent];
|
||||
};
|
||||
|
||||
features.isHiddenFeature = function(entity, resolver) {
|
||||
var matches = features.matchEntity(entity, resolver);
|
||||
|
||||
if (!entity.version) return false;
|
||||
|
||||
for (var i = 0, imax = _hidden.length; i !== imax; i++) {
|
||||
if (matches[_hidden[i]]) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
features.isHiddenChild = function(entity, resolver, geom) {
|
||||
var geometry = geom || entity.geometry(resolver),
|
||||
parents;
|
||||
|
||||
if (!entity.version || geometry === 'point') { return false; }
|
||||
|
||||
if (geometry === 'vertex') {
|
||||
parents = resolver.parentWays(entity);
|
||||
} else { // 'line', 'area', 'relation'
|
||||
parents = resolver.parentRelations(entity);
|
||||
}
|
||||
|
||||
if (!parents.length) { return false; }
|
||||
|
||||
for (var i = 0, imax = parents.length; i !== imax; i++) {
|
||||
if (!features.isHidden(parents[i], resolver)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
features.hasHiddenConnections = function(entity, resolver) {
|
||||
var childNodes, connections;
|
||||
|
||||
if (entity.type === 'midpoint') {
|
||||
childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
|
||||
} else {
|
||||
childNodes = resolver.childNodes(entity);
|
||||
}
|
||||
|
||||
// gather parents..
|
||||
connections = _.union(resolver.parentWays(entity), resolver.parentRelations(entity));
|
||||
// gather ways connected to child nodes..
|
||||
connections = _.reduce(childNodes, function(result, e) {
|
||||
return resolver.isShared(e) ? _.union(result, resolver.parentWays(e)) : result;
|
||||
}, connections);
|
||||
|
||||
return connections.length ? _.any(connections, function(e) {
|
||||
return features.isHidden(e, resolver);
|
||||
}) : false;
|
||||
};
|
||||
|
||||
features.isHidden = function(entity, resolver) {
|
||||
var geometry;
|
||||
|
||||
if (!entity.version) return false;
|
||||
|
||||
geometry = entity.geometry(resolver);
|
||||
if (geometry === 'vertex') return features.isHiddenChild(entity, resolver, geometry);
|
||||
if (geometry === 'point') return features.isHiddenFeature(entity, resolver);
|
||||
|
||||
return (features.isHiddenFeature(entity, resolver) ||
|
||||
features.isHiddenChild(entity, resolver, geometry));
|
||||
};
|
||||
|
||||
features.filter = function(d, resolver) {
|
||||
var result = [];
|
||||
|
||||
if (!_hidden.length) {
|
||||
return d;
|
||||
} else {
|
||||
for (var i = 0, imax = d.length; i !== imax; i++) {
|
||||
if (!features.isHidden(d[i], resolver)) {
|
||||
result.push(d[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return d3.rebind(features, dispatch, 'on');
|
||||
};
|
||||
@@ -27,6 +27,8 @@ iD.Map = function(context) {
|
||||
.on('change.map', redraw);
|
||||
context.background()
|
||||
.on('change.map', redraw);
|
||||
context.features()
|
||||
.on('redraw.map', redraw);
|
||||
|
||||
selection.call(zoom);
|
||||
|
||||
@@ -77,6 +79,8 @@ iD.Map = function(context) {
|
||||
var all = context.intersects(map.extent()),
|
||||
filter = d3.functor(true),
|
||||
graph = context.graph();
|
||||
|
||||
all = context.features().filter(all, graph);
|
||||
surface.call(vertices, graph, all, filter, map.extent(), map.zoom());
|
||||
surface.call(midpoints, graph, all, filter, map.trimmedExtent());
|
||||
dispatch.drawn({full: false});
|
||||
@@ -91,47 +95,58 @@ iD.Map = function(context) {
|
||||
function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
|
||||
|
||||
function drawVector(difference, extent) {
|
||||
var filter, all,
|
||||
graph = context.graph();
|
||||
var graph = context.graph(),
|
||||
features = context.features(),
|
||||
all = context.intersects(map.extent()),
|
||||
data, filter;
|
||||
|
||||
if (difference) {
|
||||
var complete = difference.complete(map.extent());
|
||||
all = _.compact(_.values(complete));
|
||||
data = _.compact(_.values(complete));
|
||||
filter = function(d) { return d.id in complete; };
|
||||
|
||||
} else if (extent) {
|
||||
all = context.intersects(map.extent().intersection(extent));
|
||||
var set = d3.set(_.pluck(all, 'id'));
|
||||
filter = function(d) { return set.has(d.id); };
|
||||
features.clear(data);
|
||||
|
||||
} else {
|
||||
all = context.intersects(map.extent());
|
||||
filter = d3.functor(true);
|
||||
// force a full redraw if gatherStats detects that a feature
|
||||
// should be auto-hidden (e.g. points or buildings)..
|
||||
if (features.gatherStats(all, graph, dimensions)) {
|
||||
extent = undefined;
|
||||
}
|
||||
|
||||
if (extent) {
|
||||
data = context.intersects(map.extent().intersection(extent));
|
||||
var set = d3.set(_.pluck(data, 'id'));
|
||||
filter = function(d) { return set.has(d.id); };
|
||||
|
||||
} else {
|
||||
data = all;
|
||||
filter = d3.functor(true);
|
||||
}
|
||||
}
|
||||
|
||||
data = features.filter(data, graph);
|
||||
|
||||
surface
|
||||
.call(vertices, graph, all, filter, map.extent(), map.zoom())
|
||||
.call(lines, graph, all, filter)
|
||||
.call(areas, graph, all, filter)
|
||||
.call(midpoints, graph, all, filter, map.trimmedExtent())
|
||||
.call(labels, graph, all, filter, dimensions, !difference && !extent);
|
||||
|
||||
if (points.points(context.intersects(map.extent()), 100).length >= 100) {
|
||||
surface.select('.layer-hit').selectAll('g.point').remove();
|
||||
} else {
|
||||
surface.call(points, points.points(all), filter);
|
||||
}
|
||||
.call(vertices, graph, data, filter, map.extent(), map.zoom())
|
||||
.call(lines, graph, data, filter)
|
||||
.call(areas, graph, data, filter)
|
||||
.call(midpoints, graph, data, filter, map.trimmedExtent())
|
||||
.call(labels, graph, data, filter, dimensions, !difference && !extent)
|
||||
.call(points, data, filter);
|
||||
|
||||
dispatch.drawn({full: true});
|
||||
}
|
||||
|
||||
function editOff() {
|
||||
var mode = context.mode();
|
||||
|
||||
context.features().resetStats();
|
||||
surface.selectAll('.layer *').remove();
|
||||
dispatch.drawn({full: true});
|
||||
if (!(mode && mode.id === 'browse')) {
|
||||
context.enter(iD.modes.Browse(context));
|
||||
}
|
||||
|
||||
dispatch.drawn({full: true});
|
||||
}
|
||||
|
||||
function zoomPan() {
|
||||
|
||||
@@ -17,20 +17,14 @@ iD.svg.Areas = function(projection) {
|
||||
|
||||
var patternKeys = ['landuse', 'natural', 'amenity'];
|
||||
|
||||
var clipped = ['residential', 'commercial', 'retail', 'industrial'];
|
||||
|
||||
function clip(entity) {
|
||||
return clipped.indexOf(entity.tags.landuse) !== -1;
|
||||
}
|
||||
|
||||
function setPattern(d) {
|
||||
for (var i = 0; i < patternKeys.length; i++) {
|
||||
if (patterns.hasOwnProperty(d.tags[patternKeys[i]])) {
|
||||
this.style.fill = 'url("#pattern-' + patterns[d.tags[patternKeys[i]]] + '")';
|
||||
this.style.fill = this.style.stroke = 'url("#pattern-' + patterns[d.tags[patternKeys[i]]] + '")';
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.style.fill = '';
|
||||
this.style.fill = this.style.stroke = '';
|
||||
}
|
||||
|
||||
return function drawAreas(surface, graph, entities, filter) {
|
||||
@@ -65,7 +59,7 @@ iD.svg.Areas = function(projection) {
|
||||
});
|
||||
|
||||
var data = {
|
||||
clip: areas.filter(clip),
|
||||
clip: areas,
|
||||
shadow: strokes,
|
||||
stroke: strokes,
|
||||
fill: areas
|
||||
@@ -125,11 +119,8 @@ iD.svg.Areas = function(projection) {
|
||||
|
||||
this.setAttribute('class', entity.type + ' area ' + layer + ' ' + entity.id);
|
||||
|
||||
if (layer === 'fill' && clip(entity)) {
|
||||
this.setAttribute('clip-path', 'url(#' + entity.id + '-clippath)');
|
||||
}
|
||||
|
||||
if (layer === 'fill') {
|
||||
this.setAttribute('clip-path', 'url(#' + entity.id + '-clippath)');
|
||||
setPattern.apply(this, arguments);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10,7 +10,10 @@ iD.svg.Points = function(projection, context) {
|
||||
return b.loc[1] - a.loc[1];
|
||||
}
|
||||
|
||||
function drawPoints(surface, points, filter) {
|
||||
return function drawPoints(surface, entities, filter) {
|
||||
var graph = context.graph(),
|
||||
points = _.filter(entities, function(e) { return e.geometry(graph) === 'point'; });
|
||||
|
||||
points.sort(sortY);
|
||||
|
||||
var groups = surface.select('.layer-hit').selectAll('g.point')
|
||||
@@ -48,22 +51,5 @@ iD.svg.Points = function(projection, context) {
|
||||
|
||||
groups.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
drawPoints.points = function(entities, limit) {
|
||||
var graph = context.graph(),
|
||||
points = [];
|
||||
|
||||
for (var i = 0; i < entities.length; i++) {
|
||||
var entity = entities[i];
|
||||
if (entity.geometry(graph) === 'point') {
|
||||
points.push(entity);
|
||||
if (limit && points.length >= limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
};
|
||||
|
||||
return drawPoints;
|
||||
};
|
||||
|
||||
@@ -12,20 +12,22 @@ iD.svg.Vertices = function(projection, context) {
|
||||
var vertices = {};
|
||||
|
||||
function addChildVertices(entity) {
|
||||
var i;
|
||||
if (entity.type === 'way') {
|
||||
for (i = 0; i < entity.nodes.length; i++) {
|
||||
addChildVertices(graph.entity(entity.nodes[i]));
|
||||
}
|
||||
} else if (entity.type === 'relation') {
|
||||
for (i = 0; i < entity.members.length; i++) {
|
||||
var member = context.hasEntity(entity.members[i].id);
|
||||
if (member) {
|
||||
addChildVertices(member);
|
||||
if (!context.features().isHiddenFeature(entity, graph)) {
|
||||
var i;
|
||||
if (entity.type === 'way') {
|
||||
for (i = 0; i < entity.nodes.length; i++) {
|
||||
addChildVertices(graph.entity(entity.nodes[i]));
|
||||
}
|
||||
} else if (entity.type === 'relation') {
|
||||
for (i = 0; i < entity.members.length; i++) {
|
||||
var member = context.hasEntity(entity.members[i].id);
|
||||
if (member) {
|
||||
addChildVertices(member);
|
||||
}
|
||||
}
|
||||
} else if (entity.intersects(extent, graph)) {
|
||||
vertices[entity.id] = entity;
|
||||
}
|
||||
} else if (entity.intersects(extent, graph)) {
|
||||
vertices[entity.id] = entity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
42
js/id/ui.js
42
js/id/ui.js
@@ -54,9 +54,6 @@ iD.ui = function(context) {
|
||||
.attr('class', 'spinner')
|
||||
.call(iD.ui.Spinner(context));
|
||||
|
||||
content
|
||||
.call(iD.ui.Attribution(context));
|
||||
|
||||
content.append('div')
|
||||
.style('display', 'none')
|
||||
.attr('class', 'help-wrap map-overlay fillL col5 content');
|
||||
@@ -76,11 +73,22 @@ iD.ui = function(context) {
|
||||
.attr('class', 'map-control background-control')
|
||||
.call(iD.ui.Background(context));
|
||||
|
||||
controls.append('div')
|
||||
.attr('class', 'map-control map-data-control')
|
||||
.call(iD.ui.MapData(context));
|
||||
|
||||
controls.append('div')
|
||||
.attr('class', 'map-control help-control')
|
||||
.call(iD.ui.Help(context));
|
||||
|
||||
var footer = content.append('div')
|
||||
var about = content.append('div')
|
||||
.attr('id', 'about');
|
||||
|
||||
about.append('div')
|
||||
.attr('id', 'attrib')
|
||||
.call(iD.ui.Attribution(context));
|
||||
|
||||
var footer = about.append('div')
|
||||
.attr('id', 'footer')
|
||||
.attr('class', 'fillD');
|
||||
|
||||
@@ -88,24 +96,23 @@ iD.ui = function(context) {
|
||||
.attr('id', 'scale-block')
|
||||
.call(iD.ui.Scale(context));
|
||||
|
||||
var linkList = footer.append('div')
|
||||
var aboutList = footer.append('div')
|
||||
.attr('id', 'info-block')
|
||||
.append('ul')
|
||||
.attr('id', 'about-list')
|
||||
.attr('class', 'link-list');
|
||||
.attr('id', 'about-list');
|
||||
|
||||
if (!context.embed()) {
|
||||
linkList.call(iD.ui.Account(context));
|
||||
aboutList.call(iD.ui.Account(context));
|
||||
}
|
||||
|
||||
linkList.append('li')
|
||||
aboutList.append('li')
|
||||
.append('a')
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
.attr('href', 'http://github.com/openstreetmap/iD')
|
||||
.text(iD.version);
|
||||
|
||||
var bugReport = linkList.append('li')
|
||||
var bugReport = aboutList.append('li')
|
||||
.append('a')
|
||||
.attr('target', '_blank')
|
||||
.attr('tabindex', -1)
|
||||
@@ -119,7 +126,12 @@ iD.ui = function(context) {
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
linkList.append('li')
|
||||
aboutList.append('li')
|
||||
.attr('class', 'feature-warning')
|
||||
.attr('tabindex', -1)
|
||||
.call(iD.ui.FeatureInfo(context));
|
||||
|
||||
aboutList.append('li')
|
||||
.attr('class', 'user-list')
|
||||
.attr('tabindex', -1)
|
||||
.call(iD.ui.Contributors(context));
|
||||
@@ -191,5 +203,11 @@ iD.ui = function(context) {
|
||||
};
|
||||
|
||||
iD.ui.tooltipHtml = function(text, key) {
|
||||
return '<span>' + text + '</span>' + '<div class="keyhint-wrap">' + '<span> ' + (t('tooltip_keyhint')) + ' </span>' + '<span class="keyhint"> ' + key + '</span></div>';
|
||||
var s = '<span>' + text + '</span>';
|
||||
if (key) {
|
||||
s += '<div class="keyhint-wrap">' +
|
||||
'<span> ' + (t('tooltip_keyhint')) + ' </span>' +
|
||||
'<span class="keyhint"> ' + key + '</span></div>';
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ iD.ui.Account = function(context) {
|
||||
function update(selection) {
|
||||
if (!connection.authenticated()) {
|
||||
selection.selectAll('#userLink, #logoutLink')
|
||||
.style('display', 'none');
|
||||
.classed('hide', true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ iD.ui.Account = function(context) {
|
||||
if (err) return;
|
||||
|
||||
selection.selectAll('#userLink, #logoutLink')
|
||||
.style('display', 'list-item');
|
||||
.classed('hide', false);
|
||||
|
||||
// Link
|
||||
userLink.append('a')
|
||||
@@ -54,11 +54,11 @@ iD.ui.Account = function(context) {
|
||||
return function(selection) {
|
||||
selection.append('li')
|
||||
.attr('id', 'logoutLink')
|
||||
.style('display', 'none');
|
||||
.classed('hide', true);
|
||||
|
||||
selection.append('li')
|
||||
.attr('id', 'userLink')
|
||||
.style('display', 'none');
|
||||
.classed('hide', true);
|
||||
|
||||
connection.on('auth.account', function() { update(selection); });
|
||||
update(selection);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
iD.ui.Background = function(context) {
|
||||
var key = 'b',
|
||||
var key = 'B',
|
||||
opacities = [1, 0.75, 0.5, 0.25],
|
||||
directions = [
|
||||
['left', [1, 0]],
|
||||
@@ -72,16 +72,6 @@ iD.ui.Background = function(context) {
|
||||
selectLayer();
|
||||
}
|
||||
|
||||
function clickGpx() {
|
||||
context.background().toggleGpxLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
function clickMapillary() {
|
||||
context.background().toggleMapillaryLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
function drawList(layerList, type, change, filter) {
|
||||
var sources = context.background()
|
||||
.sources(context.map().extent())
|
||||
@@ -120,22 +110,6 @@ iD.ui.Background = function(context) {
|
||||
backgroundList.call(drawList, 'radio', clickSetSource, function(d) { return !d.overlay; });
|
||||
overlayList.call(drawList, 'checkbox', clickSetOverlay, function(d) { return d.overlay; });
|
||||
|
||||
var hasGpx = context.background().hasGpxLayer(),
|
||||
showsGpx = context.background().showsGpxLayer();
|
||||
|
||||
gpxLayerItem
|
||||
.classed('active', showsGpx)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasGpx)
|
||||
.property('checked', showsGpx);
|
||||
|
||||
var showsMapillary = context.background().showsMapillaryLayer();
|
||||
|
||||
mapillaryLayerItem
|
||||
.classed('active', showsMapillary)
|
||||
.selectAll('input')
|
||||
.property('checked', showsMapillary);
|
||||
|
||||
selectLayer();
|
||||
|
||||
var source = context.background().baseLayerSource();
|
||||
@@ -276,68 +250,6 @@ iD.ui.Background = function(context) {
|
||||
var overlayList = content.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
|
||||
var mapillaryLayerItem = overlayList.append('li');
|
||||
|
||||
label = mapillaryLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('mapillary.tooltip'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickMapillary);
|
||||
|
||||
label.append('span')
|
||||
.text(t('mapillary.title'));
|
||||
|
||||
var gpxLayerItem = content.append('ul')
|
||||
.style('display', iD.detect().filedrop ? 'block' : 'none')
|
||||
.attr('class', 'layer-list')
|
||||
.append('li')
|
||||
.classed('layer-toggle-gpx', true);
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-extent')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.zoom'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
context.background().zoomToGpxLayer();
|
||||
})
|
||||
.append('span')
|
||||
.attr('class', 'icon geolocate');
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.browse'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
context.background().gpxLayerFiles(d3.event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.append('span')
|
||||
.attr('class', 'icon geocode');
|
||||
|
||||
label = gpxLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.drag_drop'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.property('disabled', true)
|
||||
.on('change', clickGpx);
|
||||
|
||||
label.append('span')
|
||||
.text(t('gpx.local_layer'));
|
||||
|
||||
var adjustments = content.append('div')
|
||||
.attr('class', 'adjustments');
|
||||
|
||||
@@ -382,11 +294,10 @@ iD.ui.Background = function(context) {
|
||||
update();
|
||||
setOpacity(opacityDefault);
|
||||
|
||||
var keybinding = d3.keybinding('background');
|
||||
keybinding.on(key, toggle);
|
||||
keybinding.on('m', function() {
|
||||
context.enter(iD.modes.SelectImage(context));
|
||||
});
|
||||
var keybinding = d3.keybinding('background')
|
||||
.on(key, toggle)
|
||||
.on('F', hide)
|
||||
.on('H', hide);
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
|
||||
45
js/id/ui/feature_info.js
Normal file
45
js/id/ui/feature_info.js
Normal file
@@ -0,0 +1,45 @@
|
||||
iD.ui.FeatureInfo = function(context) {
|
||||
function update(selection) {
|
||||
var features = context.features(),
|
||||
hidden = features.hidden();
|
||||
|
||||
selection.html('');
|
||||
|
||||
if (hidden.length) {
|
||||
var stats = features.stats(),
|
||||
count = 0,
|
||||
hiddenList = _.map(hidden, function(k) {
|
||||
count += stats[k];
|
||||
return String(stats[k]) + ' ' + t('feature.' + k + '.description');
|
||||
}),
|
||||
tooltip = bootstrap.tooltip()
|
||||
.placement('top')
|
||||
.html(true)
|
||||
.title(function() {
|
||||
return iD.ui.tooltipHtml(hiddenList.join('<br/>'));
|
||||
});
|
||||
|
||||
var warning = selection.append('a')
|
||||
.attr('href', '#')
|
||||
.attr('tabindex', -1)
|
||||
.html(t('feature_info.hidden_warning', { count: count }))
|
||||
.call(tooltip)
|
||||
.on('click', function() {
|
||||
tooltip.hide(warning);
|
||||
// open map data panel?
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
selection
|
||||
.classed('hide', !hidden.length);
|
||||
}
|
||||
|
||||
return function(selection) {
|
||||
update(selection);
|
||||
|
||||
context.features().on('change.feature_info', function() {
|
||||
update(selection);
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
iD.ui.Help = function(context) {
|
||||
var key = 'h';
|
||||
var key = 'H';
|
||||
|
||||
var docKeys = [
|
||||
'help.help',
|
||||
@@ -140,7 +140,9 @@ iD.ui.Help = function(context) {
|
||||
clickHelp(docs[0], 0);
|
||||
|
||||
var keybinding = d3.keybinding('help')
|
||||
.on(key, toggle);
|
||||
.on(key, toggle)
|
||||
.on('B', hide)
|
||||
.on('F', hide);
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
|
||||
329
js/id/ui/map_data.js
Normal file
329
js/id/ui/map_data.js
Normal file
@@ -0,0 +1,329 @@
|
||||
iD.ui.MapData = function(context) {
|
||||
var key = 'F',
|
||||
features = context.features().keys(),
|
||||
fills = ['wireframe', 'partial', 'full'],
|
||||
fillDefault = context.storage('area-fill') || 'partial',
|
||||
fillSelected = fillDefault;
|
||||
|
||||
function map_data(selection) {
|
||||
|
||||
function showsFeature(d) {
|
||||
return autoHiddenFeature(d) ? null : context.features().enabled(d);
|
||||
}
|
||||
|
||||
function autoHiddenFeature(d) {
|
||||
return context.features().autoHidden(d);
|
||||
}
|
||||
|
||||
function clickFeature(d) {
|
||||
context.features().toggle(d);
|
||||
update();
|
||||
}
|
||||
|
||||
function showsFill(d) {
|
||||
return fillSelected === d;
|
||||
}
|
||||
|
||||
function setFill(d) {
|
||||
_.each(fills, function(opt) {
|
||||
context.surface().classed('fill-' + opt, Boolean(opt === d));
|
||||
});
|
||||
|
||||
fillSelected = d;
|
||||
if (d !== 'wireframe') {
|
||||
fillDefault = d;
|
||||
context.storage('area-fill', d);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
function clickGpx() {
|
||||
context.background().toggleGpxLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
function clickMapillary() {
|
||||
context.background().toggleMapillaryLayer();
|
||||
update();
|
||||
}
|
||||
|
||||
function drawList(selection, data, type, name, change, active) {
|
||||
var items = selection.selectAll('li')
|
||||
.data(data);
|
||||
|
||||
//enter
|
||||
var enter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', 'layer')
|
||||
.call(bootstrap.tooltip()
|
||||
.html(true)
|
||||
.title(function(d) {
|
||||
var tip = t(name + '.' + d + '.tooltip'),
|
||||
key = (d === 'wireframe' ? 'W' : null);
|
||||
|
||||
if (name === 'feature' && autoHiddenFeature(d)) {
|
||||
tip += '<div>' + t('map_data.autohidden') + '</div>';
|
||||
}
|
||||
return iD.ui.tooltipHtml(tip, key);
|
||||
})
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
var label = enter.append('label');
|
||||
|
||||
label.append('input')
|
||||
.attr('type', type)
|
||||
.attr('name', name)
|
||||
.on('change', change);
|
||||
|
||||
label.append('span')
|
||||
.text(function(d) { return t(name + '.' + d + '.description'); });
|
||||
|
||||
//update
|
||||
items
|
||||
.classed('active', active)
|
||||
.selectAll('input')
|
||||
.property('checked', active);
|
||||
|
||||
if (name === 'feature') {
|
||||
items
|
||||
.selectAll('input')
|
||||
.property('indeterminate', autoHiddenFeature);
|
||||
}
|
||||
|
||||
//exit
|
||||
items.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
function update() {
|
||||
featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature);
|
||||
fillList.call(drawList, fills, 'radio', 'area_fill', setFill, showsFill);
|
||||
|
||||
var hasGpx = context.background().hasGpxLayer(),
|
||||
showsGpx = context.background().showsGpxLayer(),
|
||||
showsMapillary = context.background().showsMapillaryLayer();
|
||||
|
||||
gpxLayerItem
|
||||
.classed('active', showsGpx)
|
||||
.selectAll('input')
|
||||
.property('disabled', !hasGpx)
|
||||
.property('checked', showsGpx);
|
||||
|
||||
mapillaryLayerItem
|
||||
.classed('active', showsMapillary)
|
||||
.selectAll('input')
|
||||
.property('checked', showsMapillary);
|
||||
}
|
||||
|
||||
var content = selection.append('div')
|
||||
.attr('class', 'fillL map-overlay col3 content hide'),
|
||||
tooltip = bootstrap.tooltip()
|
||||
.placement('left')
|
||||
.html(true)
|
||||
.title(iD.ui.tooltipHtml(t('map_data.description'), key));
|
||||
|
||||
function hidePanel() { setVisible(false); }
|
||||
|
||||
function togglePanel() {
|
||||
if (d3.event) d3.event.preventDefault();
|
||||
tooltip.hide(button);
|
||||
setVisible(!button.classed('active'));
|
||||
}
|
||||
|
||||
function toggleWireframe() {
|
||||
if (d3.event) {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
}
|
||||
setFill((fillSelected === 'wireframe' ? fillDefault : 'wireframe'));
|
||||
}
|
||||
|
||||
function setVisible(show) {
|
||||
if (show !== shown) {
|
||||
button.classed('active', show);
|
||||
shown = show;
|
||||
|
||||
if (show) {
|
||||
selection.on('mousedown.map_data-inside', function() {
|
||||
return d3.event.stopPropagation();
|
||||
});
|
||||
content.style('display', 'block')
|
||||
.style('right', '-300px')
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('right', '0px');
|
||||
} else {
|
||||
content.style('display', 'block')
|
||||
.style('right', '0px')
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('right', '-300px')
|
||||
.each('end', function() {
|
||||
d3.select(this).style('display', 'none');
|
||||
});
|
||||
selection.on('mousedown.map_data-inside', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var button = selection.append('button')
|
||||
.attr('tabindex', -1)
|
||||
.on('click', togglePanel)
|
||||
.call(tooltip),
|
||||
shown = false;
|
||||
|
||||
button.append('span')
|
||||
.attr('class', 'icon data light');
|
||||
|
||||
content.append('h4')
|
||||
.text(t('map_data.title'));
|
||||
|
||||
|
||||
// data layers
|
||||
content.append('a')
|
||||
.text(t('map_data.data_layers'))
|
||||
.attr('href', '#')
|
||||
.classed('hide-toggle', true)
|
||||
.classed('expanded', true)
|
||||
.on('click', function() {
|
||||
var exp = d3.select(this).classed('expanded');
|
||||
layerContainer.style('display', exp ? 'none' : 'block');
|
||||
d3.select(this).classed('expanded', !exp);
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
var layerContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
.style('display', 'block');
|
||||
|
||||
// mapillary
|
||||
var mapillaryLayerItem = layerContainer.append('ul')
|
||||
.attr('class', 'layer-list')
|
||||
.append('li');
|
||||
|
||||
var label = mapillaryLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('mapillary.tooltip'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.on('change', clickMapillary);
|
||||
|
||||
label.append('span')
|
||||
.text(t('mapillary.title'));
|
||||
|
||||
// gpx
|
||||
var gpxLayerItem = layerContainer.append('ul')
|
||||
.style('display', iD.detect().filedrop ? 'block' : 'none')
|
||||
.attr('class', 'layer-list')
|
||||
.append('li')
|
||||
.classed('layer-toggle-gpx', true);
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-extent')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.zoom'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.event.preventDefault();
|
||||
d3.event.stopPropagation();
|
||||
context.background().zoomToGpxLayer();
|
||||
})
|
||||
.append('span')
|
||||
.attr('class', 'icon geolocate');
|
||||
|
||||
gpxLayerItem.append('button')
|
||||
.attr('class', 'layer-browse')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.browse'))
|
||||
.placement('left'))
|
||||
.on('click', function() {
|
||||
d3.select(document.createElement('input'))
|
||||
.attr('type', 'file')
|
||||
.on('change', function() {
|
||||
context.background().gpxLayerFiles(d3.event.target.files);
|
||||
})
|
||||
.node().click();
|
||||
})
|
||||
.append('span')
|
||||
.attr('class', 'icon geocode');
|
||||
|
||||
label = gpxLayerItem.append('label')
|
||||
.call(bootstrap.tooltip()
|
||||
.title(t('gpx.drag_drop'))
|
||||
.placement('top'));
|
||||
|
||||
label.append('input')
|
||||
.attr('type', 'checkbox')
|
||||
.property('disabled', true)
|
||||
.on('change', clickGpx);
|
||||
|
||||
label.append('span')
|
||||
.text(t('gpx.local_layer'));
|
||||
|
||||
|
||||
// area fills
|
||||
content.append('a')
|
||||
.text(t('map_data.fill_area'))
|
||||
.attr('href', '#')
|
||||
.classed('hide-toggle', true)
|
||||
.classed('expanded', false)
|
||||
.on('click', function() {
|
||||
var exp = d3.select(this).classed('expanded');
|
||||
fillContainer.style('display', exp ? 'none' : 'block');
|
||||
d3.select(this).classed('expanded', !exp);
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
var fillContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
.style('display', 'none');
|
||||
|
||||
var fillList = fillContainer.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
|
||||
|
||||
// feature filters
|
||||
content.append('a')
|
||||
.text(t('map_data.map_features'))
|
||||
.attr('href', '#')
|
||||
.classed('hide-toggle', true)
|
||||
.classed('expanded', false)
|
||||
.on('click', function() {
|
||||
var exp = d3.select(this).classed('expanded');
|
||||
featureContainer.style('display', exp ? 'none' : 'block');
|
||||
d3.select(this).classed('expanded', !exp);
|
||||
d3.event.preventDefault();
|
||||
});
|
||||
|
||||
var featureContainer = content.append('div')
|
||||
.attr('class', 'filters')
|
||||
.style('display', 'none');
|
||||
|
||||
var featureList = featureContainer.append('ul')
|
||||
.attr('class', 'layer-list');
|
||||
|
||||
|
||||
context.features()
|
||||
.on('change.map_data-update', update);
|
||||
|
||||
update();
|
||||
setFill(fillDefault);
|
||||
|
||||
var keybinding = d3.keybinding('features')
|
||||
.on(key, togglePanel)
|
||||
.on('W', toggleWireframe)
|
||||
.on('B', hidePanel)
|
||||
.on('H', hidePanel);
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
|
||||
context.surface().on('mousedown.map_data-outside', hidePanel);
|
||||
context.container().on('mousedown.map_data-outside', hidePanel);
|
||||
}
|
||||
|
||||
return map_data;
|
||||
};
|
||||
Reference in New Issue
Block a user