mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
decoupled graph from context for better testing
fixed feature matching tests for vertices and relations
This commit is contained in:
@@ -207,8 +207,9 @@ window.iD = function () {
|
||||
var features = iD.Features(context);
|
||||
context.features = function() { return features; };
|
||||
context.hasHiddenConnections = function(id) {
|
||||
var entity = history.graph().entity(id);
|
||||
return features.hasHiddenConnections(entity);
|
||||
var graph = history.graph(),
|
||||
entity = graph.entity(id);
|
||||
return features.hasHiddenConnections(entity, graph);
|
||||
};
|
||||
|
||||
/* Map */
|
||||
|
||||
@@ -42,6 +42,7 @@ iD.Features = function(context) {
|
||||
};
|
||||
|
||||
var dispatch = d3.dispatch('change', 'redraw'),
|
||||
resolver = context.graph(),
|
||||
feature = {},
|
||||
cullFactor = 1;
|
||||
|
||||
@@ -59,31 +60,31 @@ iD.Features = function(context) {
|
||||
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; },
|
||||
hidden: function() { return this.count > this.currentMax * cullFactor; },
|
||||
autoHidden: function() { return this.hidden() && this.currentMax > 0; }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
defineFeature('points', function(entity) {
|
||||
return entity.geometry(context.graph()) === 'point';
|
||||
return entity.geometry(resolver) === 'point';
|
||||
}, 200);
|
||||
|
||||
defineFeature('major_roads', function(entity) {
|
||||
return entity.geometry(context.graph()) === 'line' && major_roads[entity.tags.highway];
|
||||
return entity.geometry(resolver) === 'line' && major_roads[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('minor_roads', function(entity) {
|
||||
return entity.geometry(context.graph()) === 'line' && minor_roads[entity.tags.highway];
|
||||
return entity.geometry(resolver) === 'line' && minor_roads[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('paths', function(entity) {
|
||||
return entity.geometry(context.graph()) === 'line' && paths[entity.tags.highway];
|
||||
return entity.geometry(resolver) === 'line' && paths[entity.tags.highway];
|
||||
});
|
||||
|
||||
defineFeature('buildings', function(entity) {
|
||||
return (
|
||||
entity.geometry(context.graph()) === 'area' && (
|
||||
entity.geometry(resolver) === 'area' && (
|
||||
(!!entity.tags.building && entity.tags.building !== 'no') ||
|
||||
entity.tags.amenity === 'shelter' ||
|
||||
entity.tags.parking === 'multi-storey' ||
|
||||
@@ -95,7 +96,7 @@ iD.Features = function(context) {
|
||||
}, 250);
|
||||
|
||||
defineFeature('landuse', function(entity) {
|
||||
return entity.geometry(context.graph()) === 'area' &&
|
||||
return entity.geometry(resolver) === 'area' &&
|
||||
!feature.buildings.filter(entity) &&
|
||||
!feature.water.filter(entity);
|
||||
});
|
||||
@@ -145,8 +146,8 @@ iD.Features = function(context) {
|
||||
// lines or areas that don't match another feature filter.
|
||||
defineFeature('others', function(entity) {
|
||||
return (
|
||||
entity.geometry(context.graph()) === 'line' ||
|
||||
entity.geometry(context.graph()) === 'area'
|
||||
entity.geometry(resolver) === 'line' ||
|
||||
entity.geometry(resolver) === 'area'
|
||||
) &&
|
||||
_.reduce(_.omit(feature, 'others'), function(result, v) {
|
||||
return result && !v.filter(entity);
|
||||
@@ -213,20 +214,18 @@ iD.Features = function(context) {
|
||||
return feature[k] && feature[k].count;
|
||||
};
|
||||
|
||||
features.resetStats = function() {
|
||||
var dimensions = context.map().dimensions();
|
||||
|
||||
features.gatherStats = function(d, graph, dimensions) {
|
||||
var hidden = features.hidden(),
|
||||
keys = features.keys();
|
||||
resolver = graph || resolver;
|
||||
|
||||
_.each(feature, 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;
|
||||
};
|
||||
|
||||
features.gatherStats = function(d) {
|
||||
var hidden = features.hidden(),
|
||||
keys = features.keys();
|
||||
|
||||
features.resetStats();
|
||||
_.each(d, function(entity) {
|
||||
_.each(keys, function(k) {
|
||||
if (feature[k].filter(entity)) {
|
||||
@@ -248,43 +247,54 @@ iD.Features = function(context) {
|
||||
return stats;
|
||||
};
|
||||
|
||||
features.isHiddenFeature = function(entity) {
|
||||
features.isHiddenFeature = function(entity, graph) {
|
||||
resolver = graph || resolver;
|
||||
return _.any(features.hidden(), function(k) { return feature[k].filter(entity); });
|
||||
};
|
||||
|
||||
features.isHiddenChild = function(entity) {
|
||||
var g = context.graph(),
|
||||
parents = _.union(g.parentWays(entity), g.parentRelations(entity));
|
||||
return parents.length ? _.all(parents, features.isHidden) : false;
|
||||
features.isHiddenChild = function(entity, graph) {
|
||||
var parents;
|
||||
resolver = graph || resolver;
|
||||
|
||||
parents = _.union(resolver.parentWays(entity), resolver.parentRelations(entity));
|
||||
return parents.length ? _.all(parents, function(e) {
|
||||
return features.isHidden(e, resolver);
|
||||
}) : false;
|
||||
};
|
||||
|
||||
features.hasHiddenConnections = function(entity) {
|
||||
var g = context.graph(),
|
||||
childNodes, connections;
|
||||
features.hasHiddenConnections = function(entity, graph) {
|
||||
var childNodes, connections;
|
||||
resolver = graph || resolver;
|
||||
|
||||
if (entity.type === 'midpoint') {
|
||||
childNodes = [context.entity(entity.edge[0]), context.entity(entity.edge[1])];
|
||||
childNodes = [resolver.entity(entity.edge[0]), resolver.entity(entity.edge[1])];
|
||||
} else {
|
||||
childNodes = g.childNodes(entity);
|
||||
childNodes = resolver.childNodes(entity);
|
||||
}
|
||||
|
||||
// gather parents..
|
||||
connections = _.union(g.parentWays(entity), g.parentRelations(entity));
|
||||
connections = _.union(resolver.parentWays(entity), resolver.parentRelations(entity));
|
||||
// gather ways connected to child nodes..
|
||||
connections = _.reduce(childNodes, function(result, e) {
|
||||
return g.isShared(e) ? _.union(result, g.parentWays(e)) : result;
|
||||
return resolver.isShared(e) ? _.union(result, resolver.parentWays(e)) : result;
|
||||
}, connections);
|
||||
|
||||
return connections.length ? _.any(connections, features.isHidden) : false;
|
||||
return connections.length ? _.any(connections, function(e) {
|
||||
return features.isHidden(e, resolver);
|
||||
}) : false;
|
||||
};
|
||||
|
||||
features.isHidden = function(entity) {
|
||||
features.isHidden = function(entity, graph) {
|
||||
resolver = graph || resolver;
|
||||
return !!entity.version &&
|
||||
(features.isHiddenFeature(entity) || features.isHiddenChild(entity));
|
||||
(features.isHiddenFeature(entity, resolver) || features.isHiddenChild(entity, resolver));
|
||||
};
|
||||
|
||||
features.filter = function(d) {
|
||||
return features.hidden().length ? _.reject(d, features.isHidden) : d;
|
||||
features.filter = function(d, graph) {
|
||||
resolver = graph || resolver;
|
||||
return features.hidden().length ? _.reject(d, function(e) {
|
||||
return features.isHidden(e, resolver);
|
||||
}) : d;
|
||||
};
|
||||
|
||||
return d3.rebind(features, dispatch, 'on');
|
||||
|
||||
@@ -95,34 +95,36 @@ 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'));
|
||||
data = context.intersects(map.extent().intersection(extent));
|
||||
var set = d3.set(_.pluck(data, 'id'));
|
||||
filter = function(d) { return set.has(d.id); };
|
||||
|
||||
} else {
|
||||
all = context.intersects(map.extent());
|
||||
data = all;
|
||||
filter = d3.functor(true);
|
||||
}
|
||||
|
||||
context.features().gatherStats(context.intersects(map.extent()));
|
||||
all = context.features().filter(all);
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
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)
|
||||
.call(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});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
describe('iD.Features', function() {
|
||||
var context, features;
|
||||
var dimensions = [1000, 1000],
|
||||
features;
|
||||
|
||||
beforeEach(function() {
|
||||
context = iD();
|
||||
features = context.features();
|
||||
features = iD().features();
|
||||
});
|
||||
|
||||
it('returns feature keys', function() {
|
||||
@@ -121,23 +121,26 @@ describe('iD.Features', function() {
|
||||
// Others
|
||||
iD.Way({id: 'fence', tags: {barrier: 'fence'}, version: 1}),
|
||||
iD.Way({id: 'pipeline', tags: {man_made: 'pipeline'}, version: 1})
|
||||
]);
|
||||
]),
|
||||
all = _.values(graph.base().entities);
|
||||
|
||||
|
||||
function doMatch(ids) {
|
||||
_.each(ids, function(id) {
|
||||
expect(features.isHidden(graph.entity(id)), id).to.be.true;
|
||||
expect(features.isHidden(graph.entity(id), graph), 'doMatch: ' + id).to.be.true;
|
||||
});
|
||||
}
|
||||
|
||||
function dontMatch(ids) {
|
||||
_.each(ids, function(id) {
|
||||
expect(features.isHidden(graph.entity(id)), id).to.be.false;
|
||||
expect(features.isHidden(graph.entity(id), graph), 'dontMatch: ' + id).to.be.false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
it("matches points", function () {
|
||||
features.disable('points');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'point_bar', 'point_dock', 'point_rail_station',
|
||||
@@ -156,6 +159,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches major roads", function () {
|
||||
features.disable('major_roads');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'motorway', 'motorway_link', 'trunk', 'trunk_link',
|
||||
@@ -175,6 +179,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches minor roads", function () {
|
||||
features.disable('minor_roads');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'service', 'living_street', 'road', 'unclassified', 'track'
|
||||
@@ -192,6 +197,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches paths", function () {
|
||||
features.disable('paths');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'path', 'footway', 'cycleway', 'bridleway',
|
||||
@@ -210,6 +216,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches buildings", function () {
|
||||
features.disable('buildings');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'building_yes', 'building_part', 'shelter',
|
||||
@@ -228,6 +235,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches landuse", function () {
|
||||
features.disable('landuse');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'forest', 'scrub', 'industrial', 'parkinglot', 'building_no',
|
||||
@@ -246,6 +254,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches boundaries", function () {
|
||||
features.disable('boundaries');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'boundary'
|
||||
@@ -263,6 +272,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches water", function () {
|
||||
features.disable('water');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'point_dock', 'water', 'coastline', 'bay', 'pond',
|
||||
@@ -281,6 +291,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches rail", function () {
|
||||
features.disable('rail');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'point_rail_station', 'point_old_rail_station',
|
||||
@@ -300,6 +311,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches power", function () {
|
||||
features.disable('power');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'point_generator', 'power_line'
|
||||
@@ -317,6 +329,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches past/future", function () {
|
||||
features.disable('past_future');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'point_old_rail_station', 'rail_disused',
|
||||
@@ -335,6 +348,7 @@ describe('iD.Features', function() {
|
||||
|
||||
it("matches others", function () {
|
||||
features.disable('others');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
doMatch([
|
||||
'fence', 'pipeline'
|
||||
@@ -355,32 +369,44 @@ describe('iD.Features', function() {
|
||||
var a = iD.Node({id: 'a', version: 1}),
|
||||
b = iD.Node({id: 'b', version: 1}),
|
||||
w = iD.Way({id: 'w', nodes: [a.id, b.id], tags: {highway: 'path'}, version: 1}),
|
||||
graph = iD.Graph([a, b, w]);
|
||||
graph = iD.Graph([a, b, w]),
|
||||
all = _.values(graph.base().entities);
|
||||
|
||||
features.disable('paths');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
expect(features.isHiddenChild(graph.entity('a'))).to.be.true;
|
||||
expect(features.isHidden(graph.entity('a'))).to.be.true;
|
||||
expect(features.isHiddenChild(graph.entity('a'), graph)).to.be.true;
|
||||
expect(features.isHidden(graph.entity('a'), graph)).to.be.true;
|
||||
|
||||
features.enable('paths');
|
||||
});
|
||||
|
||||
it('hides child ways on a hidden relation', function() {
|
||||
var a = iD.Node({id: 'a'}),
|
||||
b = iD.Node({id: 'b'}),
|
||||
c = iD.Node({id: 'c'}),
|
||||
d = iD.Node({id: 'd'}),
|
||||
e = iD.Node({id: 'e'}),
|
||||
f = iD.Node({id: 'f'}),
|
||||
outer = iD.Way({id: 'outer', nodes: [a.id, b.id, c.id, a.id], tags: {natural: 'wood'}}),
|
||||
inner = iD.Way({id: 'inner', nodes: [d.id, e.id, f.id, d.id]}),
|
||||
r = iD.Relation({members: [{id: outer.id, type: 'way'}, {id: inner.id, role: 'inner', type: 'way'}]}),
|
||||
graph = iD.Graph([a, b, c, d, e, f, outer, inner, r]);
|
||||
var a = iD.Node({id: 'a', version: 1}),
|
||||
b = iD.Node({id: 'b', version: 1}),
|
||||
c = iD.Node({id: 'c', version: 1}),
|
||||
d = iD.Node({id: 'd', version: 1}),
|
||||
e = iD.Node({id: 'e', version: 1}),
|
||||
f = iD.Node({id: 'f', version: 1}),
|
||||
outer = iD.Way({id: 'outer', nodes: [a.id, b.id, c.id, a.id], tags: {area: 'yes', natural: 'wood'}, version: 1}),
|
||||
inner = iD.Way({id: 'inner', nodes: [d.id, e.id, f.id, d.id], version: 1}),
|
||||
r = iD.Relation({
|
||||
id: 'r',
|
||||
tags: {type: 'multipolygon'},
|
||||
members: [
|
||||
{id: outer.id, role: 'outer', type: 'way'},
|
||||
{id: inner.id, role: 'inner', type: 'way'}
|
||||
],
|
||||
version: 1
|
||||
}),
|
||||
graph = iD.Graph([a, b, c, d, e, f, outer, inner, r]),
|
||||
all = _.values(graph.base().entities);
|
||||
|
||||
features.disable('landuse');
|
||||
features.gatherStats(all, graph, dimensions);
|
||||
|
||||
expect(features.isHiddenChild(graph.entity('inner'))).to.be.true;
|
||||
expect(features.isHidden(graph.entity('inner'))).to.be.true;
|
||||
expect(features.isHiddenChild(graph.entity('inner'), graph)).to.be.true;
|
||||
expect(features.isHidden(graph.entity('inner'), graph)).to.be.true;
|
||||
|
||||
features.enable('landuse');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user