问题
I'm making a terrain editor and I need to find the perimeter polygon of a set of points. If I just needed a convex hull then the speed would be no issue. To make a concave hull, I must go through a few hoops. I've figured out that I can triangulate the points and then throw away any triangles with a side longer than the known distance between the points.
The next step is the problem: Combining the triangles (as mini polygons) into one large polygon using the JSTS geometry library (http://github.com/bjornharrtell/jsts) is really slow.
See the full code: http://codepen.io/anon/pen/oCfDh
I've got an array (polys) that gets merged to form the final polygon. The problem is that with 552 points (I want to support 15k+), it takes ~3500ms to run. Look at the console in the codepen link for your speed.
var reader = new jsts.io.WKTReader(),
merged = reader.read(polys[0]).union(reader.read(polys[1]));
console.time('jsts mergization');
for(var i = 2; i<polys.length; i++){
try{
merged = merged.union(reader.read(polys[i]));
}catch(err){
console.log('Error triangulating points!');
};
};
console.timeEnd('jsts mergization');
Does anybody know of any faster way to either merge triangles into a polygon or to go even wider and build a concave polygon from a set a points in a whole different way?
回答1:
Thanks simonzack!
I've rewritten the algorithm using your suggestion and it's much faster!
Reworked codepen: http://codepen.io/anon/pen/Btdyj
The same example now runs in ~15ms!
function pointsToPolygon(points, triangles, maxEdgeLength){
console.time('homebrewed mergization');
var dist = function(a, b){
if(typeof a === "number"){
a = points[a];
};
if(typeof b === "number"){
b = points[b];
};
return Math.sqrt(Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2));
};
if(!points.length){
return undefined;
};
var pointFreq = [];
points.forEach(function(v){
pointFreq.push(0);
});
for(var i = triangles.length; i; i-=3){
if(dist(triangles[i-1], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-3], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-1], triangles[i-3]) < maxEdgeLength){
pointFreq[triangles[i-1]]++;
pointFreq[triangles[i-2]]++;
pointFreq[triangles[i-3]]++;
};
};
// Keep points that are used in 3 or fewer triangles
var output =[];
pointFreq.forEach(function(freq, i){
if(freq<4){
output.push(points[i]);
};
});
// Sort points by looping around by each next closest point
var sorted = [];
while(output.length>0){
sorted.push(output.pop());
output=output.sort(function(a,b){
var distA =dist(sorted[sorted.length-1], a),
distB =dist(sorted[sorted.length-1], b);
if(distA < distB){
return 1;
}else if(distA === distB){
return 0;
};
return -1;
});
};
sorted=simplifyPath(sorted,0.1);
console.timeEnd('homebrewed mergization');
return sorted;
};
I can find the boundary by filtering the points that are used in 3 or fewer triangles then sort points by looping around by each next closest point from any arbitrary point.
Maybe not 100% as accurate due to the Douglas-Peucker simplification algorithm (adapted from https://gist.github.com/adammiller/826148) but it seems good enough for me.
来源:https://stackoverflow.com/questions/25521146/find-polygon-perimeter-of-points-quickly-in-javascript