mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 21:48:20 +02:00
Clip lines with Cohen-Sutherland algorithm
This yields a 10x paint performance increase at #map=18.88/38.85208/-76.72632, as measured with Chrome Canary's "Continuous Page Repainting" mode. Fixes #885.
This commit is contained in:
@@ -13,6 +13,7 @@ all: \
|
||||
.INTERMEDIATE iD.js: \
|
||||
js/lib/bootstrap-tooltip.js \
|
||||
js/lib/d3.v3.js \
|
||||
js/lib/d3.clip.js \
|
||||
js/lib/d3.combobox.js \
|
||||
js/lib/d3.geo.tile.js \
|
||||
js/lib/d3.keybinding.js \
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<script src='js/lib/d3.size.js'></script>
|
||||
<script src='js/lib/d3.trigger.js'></script>
|
||||
<script src='js/lib/d3.keybinding.js'></script>
|
||||
<script src='js/lib/d3.clip.js'></script>
|
||||
<script src='js/lib/d3-compat.js'></script>
|
||||
<script src='js/lib/bootstrap-tooltip.js'></script>
|
||||
<script src='js/lib/rtree.js'></script>
|
||||
|
||||
@@ -89,7 +89,7 @@ iD.Map = function(context) {
|
||||
surface
|
||||
.call(points, graph, all, filter)
|
||||
.call(vertices, graph, all, filter)
|
||||
.call(lines, graph, all, filter)
|
||||
.call(lines, graph, all, filter, dimensions)
|
||||
.call(areas, graph, all, filter)
|
||||
.call(midpoints, graph, all, filter, extent)
|
||||
.call(labels, graph, all, filter, dimensions, !difference);
|
||||
|
||||
+14
-6
@@ -13,22 +13,30 @@ iD.svg = {
|
||||
};
|
||||
},
|
||||
|
||||
LineString: function(projection, graph) {
|
||||
LineString: function(projection, graph, dimensions) {
|
||||
var cache = {};
|
||||
return function(entity) {
|
||||
if (cache[entity.id] !== undefined) {
|
||||
return cache[entity.id];
|
||||
}
|
||||
|
||||
if (entity.nodes.length === 0) {
|
||||
var clip = d3.clip.cohenSutherland()
|
||||
.bounds([0, 0, dimensions[0], dimensions[1]]);
|
||||
|
||||
var segments = clip(graph.childNodes(entity).map(function(n) {
|
||||
return projection(n.loc);
|
||||
}));
|
||||
|
||||
if (segments.length === 0) {
|
||||
return (cache[entity.id] = null);
|
||||
}
|
||||
|
||||
return (cache[entity.id] =
|
||||
'M' + graph.childNodes(entity).map(function(n) {
|
||||
var pt = projection(n.loc);
|
||||
return pt[0] + ',' + pt[1];
|
||||
}).join('L'));
|
||||
segments.map(function(points) {
|
||||
return 'M' + points.map(function(p) {
|
||||
return p[0] + ',' + p[1];
|
||||
}).join('L');
|
||||
}).join(''));
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
+6
-2
@@ -33,8 +33,12 @@ iD.svg.Lines = function(projection) {
|
||||
return as - bs;
|
||||
}
|
||||
|
||||
return function drawLines(surface, graph, entities, filter) {
|
||||
return function drawLines(surface, graph, entities, filter, dimensions) {
|
||||
function drawPaths(group, lines, filter, klass, lineString) {
|
||||
lines = lines.filter(function(line) {
|
||||
return lineString(line);
|
||||
});
|
||||
|
||||
var tagClasses = iD.svg.TagClasses();
|
||||
|
||||
if (klass === 'stroke') {
|
||||
@@ -82,7 +86,7 @@ iD.svg.Lines = function(projection) {
|
||||
|
||||
lines.sort(waystack);
|
||||
|
||||
var lineString = iD.svg.LineString(projection, graph);
|
||||
var lineString = iD.svg.LineString(projection, graph, dimensions);
|
||||
|
||||
var shadow = surface.select('.layer-shadow'),
|
||||
casing = surface.select('.layer-casing'),
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
d3.clip = {};
|
||||
|
||||
d3.clip.cohenSutherland = function() {
|
||||
var xmin = 0, xmax = 0, ymin = 0, ymax = 0;
|
||||
|
||||
var x = function(d) {
|
||||
return d[0];
|
||||
};
|
||||
|
||||
var y = function(d) {
|
||||
return d[1];
|
||||
};
|
||||
|
||||
var INSIDE = 0; // 0000
|
||||
var LEFT = 1; // 0001
|
||||
var RIGHT = 2; // 0010
|
||||
var BOTTOM = 4; // 0100
|
||||
var TOP = 8; // 1000
|
||||
|
||||
function outCode(x, y) {
|
||||
var code = INSIDE;
|
||||
|
||||
if (x < xmin)
|
||||
code |= LEFT;
|
||||
else if (x > xmax)
|
||||
code |= RIGHT;
|
||||
|
||||
if (y < ymin)
|
||||
code |= BOTTOM;
|
||||
else if (y > ymax)
|
||||
code |= TOP;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
function clip(data) {
|
||||
var segments = [],
|
||||
points = [],
|
||||
i = 0,
|
||||
n = data.length,
|
||||
x0, y0, x1, y1, c0, c1, _x0, _y0, _x1, _y1, _c0, _c1,
|
||||
fx = d3.functor(x),
|
||||
fy = d3.functor(y);
|
||||
|
||||
function segment() {
|
||||
segments.push(points);
|
||||
points = [];
|
||||
}
|
||||
|
||||
if (n) {
|
||||
x0 = +fx.call(this, data[0], 0);
|
||||
y0 = +fy.call(this, data[0], 0);
|
||||
c0 = outCode(x0, y0);
|
||||
if (!c0) points.push([x0, y0]);
|
||||
}
|
||||
|
||||
while (++i < n) {
|
||||
x1 = +fx.call(this, data[i], i);
|
||||
y1 = +fy.call(this, data[i], i);
|
||||
c1 = outCode(x1, y1);
|
||||
|
||||
_x0 = x0;
|
||||
_y0 = y0;
|
||||
_x1 = x1;
|
||||
_y1 = y1;
|
||||
_c0 = c0;
|
||||
_c1 = c1;
|
||||
|
||||
while (true) {
|
||||
if (!(_c0 | _c1)) {
|
||||
if (c0) points.push([_x0, _y0]);
|
||||
points.push([_x1, _y1]);
|
||||
if (c1) segment();
|
||||
break;
|
||||
} else if (_c0 & _c1) {
|
||||
break;
|
||||
} else {
|
||||
var _x, _y, outcodeOut = _c0 ? _c0 : _c1;
|
||||
|
||||
if (outcodeOut & TOP) {
|
||||
_x = _x0 + (_x1 - _x0) * (ymax - _y0) / (_y1 - _y0);
|
||||
_y = ymax;
|
||||
} else if (outcodeOut & BOTTOM) {
|
||||
_x = _x0 + (_x1 - _x0) * (ymin - _y0) / (_y1 - _y0);
|
||||
_y = ymin;
|
||||
} else if (outcodeOut & RIGHT) {
|
||||
_y = _y0 + (_y1 - _y0) * (xmax - _x0) / (_x1 - _x0);
|
||||
_x = xmax;
|
||||
} else if (outcodeOut & LEFT) {
|
||||
_y = _y0 + (_y1 - _y0) * (xmin - _x0) / (_x1 - _x0);
|
||||
_x = xmin;
|
||||
}
|
||||
|
||||
if (outcodeOut == _c0) {
|
||||
_x0 = _x;
|
||||
_y0 = _y;
|
||||
_c0 = outCode(_x0, _y0);
|
||||
} else {
|
||||
_x1 = _x;
|
||||
_y1 = _y;
|
||||
_c1 = outCode(_x1, _y1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x0 = x1;
|
||||
y0 = y1;
|
||||
c0 = c1;
|
||||
}
|
||||
|
||||
if (points.length) segment();
|
||||
return segments;
|
||||
}
|
||||
|
||||
clip.x = function(_) {
|
||||
if (!arguments.length) return x;
|
||||
x = _;
|
||||
return clip;
|
||||
};
|
||||
|
||||
clip.y = function(_) {
|
||||
if (!arguments.length) return y;
|
||||
y = _;
|
||||
return clip;
|
||||
};
|
||||
|
||||
clip.bounds = function(_) {
|
||||
if (!arguments.length) return [xmin, ymin, xmax, ymax];
|
||||
xmin = _[0];
|
||||
ymin = _[1];
|
||||
xmax = _[2];
|
||||
ymax = _[3];
|
||||
return clip;
|
||||
};
|
||||
|
||||
return clip;
|
||||
};
|
||||
@@ -19,6 +19,7 @@
|
||||
<script src='../js/lib/lodash.js'></script>
|
||||
<script src='../js/lib/d3.v3.js'></script>
|
||||
<script src='../js/lib/sha.js'></script>
|
||||
<script src='../js/lib/d3.clip.js'></script>
|
||||
<script src='../js/lib/d3.combobox.js'></script>
|
||||
<script src='../js/lib/d3.geo.tile.js'></script>
|
||||
<script src='../js/lib/d3.keybinding.js'></script>
|
||||
@@ -168,6 +169,7 @@
|
||||
<script src="spec/spec_helpers.js"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script src="spec/lib/d3.clip.js"></script>
|
||||
<script src="spec/lib/d3.keybinding.js"></script>
|
||||
<script src="spec/lib/locale.js"></script>
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
describe('d3.clip.cohenSutherland', function() {
|
||||
var clip;
|
||||
|
||||
beforeEach(function() {
|
||||
clip = d3.clip.cohenSutherland()
|
||||
.bounds([0, 0, 10, 10]);
|
||||
});
|
||||
|
||||
it('clips an empty array', function() {
|
||||
expect(clip([])).to.eql([]);
|
||||
});
|
||||
|
||||
it('clips a point inside bounds', function() {
|
||||
expect(clip([[0, 0]])).to.eql([[[0, 0]]]);
|
||||
});
|
||||
|
||||
it('clips a point outside bounds', function() {
|
||||
expect(clip([[-1, -1]])).to.eql([]);
|
||||
});
|
||||
|
||||
it('clips a single segment inside bounds', function() {
|
||||
expect(clip([[0, 0], [10, 10]])).to.eql([[[0, 0], [10, 10]]]);
|
||||
});
|
||||
|
||||
it('clips a single segment leaving bounds', function() {
|
||||
expect(clip([[5, 5], [15, 15]])).to.eql([[[5, 5], [10, 10]]]);
|
||||
});
|
||||
|
||||
it('clips a single segment entering bounds', function() {
|
||||
expect(clip([[15, 15], [5, 5]])).to.eql([[[10, 10], [5, 5]]]);
|
||||
});
|
||||
|
||||
it('clips a single segment entering and leaving bounds', function() {
|
||||
expect(clip([[0, 15], [15, 0]])).to.eql([[[5, 10], [10, 5]]]);
|
||||
});
|
||||
|
||||
it('clips multiple segments', function() {
|
||||
expect(clip([[15, 15], [5, 5], [15, 15], [5, 5]])).to.
|
||||
eql([[[10, 10], [5, 5], [10, 10]], [[10, 10], [5, 5]]]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user