| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 | 'use strict';var RBush = require('rbush');var Queue = require('tinyqueue');var pointInPolygon = require('point-in-polygon');var orient = require('robust-predicates/umd/orient2d.min.js').orient2d;// Fix for require issue in webpack https://github.com/mapbox/concaveman/issues/18if (Queue.default) {    Queue = Queue.default;}module.exports = concaveman;module.exports.default = concaveman;function concaveman(points, concavity, lengthThreshold) {    // a relative measure of concavity; higher value means simpler hull    concavity = Math.max(0, concavity === undefined ? 2 : concavity);    // when a segment goes below this length threshold, it won't be drilled down further    lengthThreshold = lengthThreshold || 0;    // start with a convex hull of the points    var hull = fastConvexHull(points);    // index the points with an R-tree    var tree = new RBush(16);    tree.toBBox = function (a) {        return {            minX: a[0],            minY: a[1],            maxX: a[0],            maxY: a[1]        };    };    tree.compareMinX = function (a, b) { return a[0] - b[0]; };    tree.compareMinY = function (a, b) { return a[1] - b[1]; };    tree.load(points);    // turn the convex hull into a linked list and populate the initial edge queue with the nodes    var queue = [];    for (var i = 0, last; i < hull.length; i++) {        var p = hull[i];        tree.remove(p);        last = insertNode(p, last);        queue.push(last);    }    // index the segments with an R-tree (for intersection checks)    var segTree = new RBush(16);    for (i = 0; i < queue.length; i++) segTree.insert(updateBBox(queue[i]));    var sqConcavity = concavity * concavity;    var sqLenThreshold = lengthThreshold * lengthThreshold;    // process edges one by one    while (queue.length) {        var node = queue.shift();        var a = node.p;        var b = node.next.p;        // skip the edge if it's already short enough        var sqLen = getSqDist(a, b);        if (sqLen < sqLenThreshold) continue;        var maxSqLen = sqLen / sqConcavity;        // find the best connection point for the current edge to flex inward to        p = findCandidate(tree, node.prev.p, a, b, node.next.next.p, maxSqLen, segTree);        // if we found a connection and it satisfies our concavity measure        if (p && Math.min(getSqDist(p, a), getSqDist(p, b)) <= maxSqLen) {            // connect the edge endpoints through this point and add 2 new edges to the queue            queue.push(node);            queue.push(insertNode(p, node));            // update point and segment indexes            tree.remove(p);            segTree.remove(node);            segTree.insert(updateBBox(node));            segTree.insert(updateBBox(node.next));        }    }    // convert the resulting hull linked list to an array of points    node = last;    var concave = [];    do {        concave.push(node.p);        node = node.next;    } while (node !== last);    concave.push(node.p);    return concave;}function findCandidate(tree, a, b, c, d, maxDist, segTree) {    var queue = new Queue([], compareDist);    var node = tree.data;    // search through the point R-tree with a depth-first search using a priority queue    // in the order of distance to the edge (b, c)    while (node) {        for (var i = 0; i < node.children.length; i++) {            var child = node.children[i];            var dist = node.leaf ? sqSegDist(child, b, c) : sqSegBoxDist(b, c, child);            if (dist > maxDist) continue; // skip the node if it's farther than we ever need            queue.push({                node: child,                dist: dist            });        }        while (queue.length && !queue.peek().node.children) {            var item = queue.pop();            var p = item.node;            // skip all points that are as close to adjacent edges (a,b) and (c,d),            // and points that would introduce self-intersections when connected            var d0 = sqSegDist(p, a, b);            var d1 = sqSegDist(p, c, d);            if (item.dist < d0 && item.dist < d1 &&                noIntersections(b, p, segTree) &&                noIntersections(c, p, segTree)) return p;        }        node = queue.pop();        if (node) node = node.node;    }    return null;}function compareDist(a, b) {    return a.dist - b.dist;}// square distance from a segment bounding box to the given onefunction sqSegBoxDist(a, b, bbox) {    if (inside(a, bbox) || inside(b, bbox)) return 0;    var d1 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.minY, bbox.maxX, bbox.minY);    if (d1 === 0) return 0;    var d2 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.minY, bbox.minX, bbox.maxY);    if (d2 === 0) return 0;    var d3 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.maxX, bbox.minY, bbox.maxX, bbox.maxY);    if (d3 === 0) return 0;    var d4 = sqSegSegDist(a[0], a[1], b[0], b[1], bbox.minX, bbox.maxY, bbox.maxX, bbox.maxY);    if (d4 === 0) return 0;    return Math.min(d1, d2, d3, d4);}function inside(a, bbox) {    return a[0] >= bbox.minX &&           a[0] <= bbox.maxX &&           a[1] >= bbox.minY &&           a[1] <= bbox.maxY;}// check if the edge (a,b) doesn't intersect any other edgesfunction noIntersections(a, b, segTree) {    var minX = Math.min(a[0], b[0]);    var minY = Math.min(a[1], b[1]);    var maxX = Math.max(a[0], b[0]);    var maxY = Math.max(a[1], b[1]);    var edges = segTree.search({minX: minX, minY: minY, maxX: maxX, maxY: maxY});    for (var i = 0; i < edges.length; i++) {        if (intersects(edges[i].p, edges[i].next.p, a, b)) return false;    }    return true;}function cross(p1, p2, p3) {    return orient(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1]);}// check if the edges (p1,q1) and (p2,q2) intersectfunction intersects(p1, q1, p2, q2) {    return p1 !== q2 && q1 !== p2 &&        cross(p1, q1, p2) > 0 !== cross(p1, q1, q2) > 0 &&        cross(p2, q2, p1) > 0 !== cross(p2, q2, q1) > 0;}// update the bounding box of a node's edgefunction updateBBox(node) {    var p1 = node.p;    var p2 = node.next.p;    node.minX = Math.min(p1[0], p2[0]);    node.minY = Math.min(p1[1], p2[1]);    node.maxX = Math.max(p1[0], p2[0]);    node.maxY = Math.max(p1[1], p2[1]);    return node;}// speed up convex hull by filtering out points inside quadrilateral formed by 4 extreme pointsfunction fastConvexHull(points) {    var left = points[0];    var top = points[0];    var right = points[0];    var bottom = points[0];    // find the leftmost, rightmost, topmost and bottommost points    for (var i = 0; i < points.length; i++) {        var p = points[i];        if (p[0] < left[0]) left = p;        if (p[0] > right[0]) right = p;        if (p[1] < top[1]) top = p;        if (p[1] > bottom[1]) bottom = p;    }    // filter out points that are inside the resulting quadrilateral    var cull = [left, top, right, bottom];    var filtered = cull.slice();    for (i = 0; i < points.length; i++) {        if (!pointInPolygon(points[i], cull)) filtered.push(points[i]);    }    // get convex hull around the filtered points    return convexHull(filtered);}// create a new node in a doubly linked listfunction insertNode(p, prev) {    var node = {        p: p,        prev: null,        next: null,        minX: 0,        minY: 0,        maxX: 0,        maxY: 0    };    if (!prev) {        node.prev = node;        node.next = node;    } else {        node.next = prev.next;        node.prev = prev;        prev.next.prev = node;        prev.next = node;    }    return node;}// square distance between 2 pointsfunction getSqDist(p1, p2) {    var dx = p1[0] - p2[0],        dy = p1[1] - p2[1];    return dx * dx + dy * dy;}// square distance from a point to a segmentfunction sqSegDist(p, p1, p2) {    var x = p1[0],        y = p1[1],        dx = p2[0] - x,        dy = p2[1] - y;    if (dx !== 0 || dy !== 0) {        var t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);        if (t > 1) {            x = p2[0];            y = p2[1];        } else if (t > 0) {            x += dx * t;            y += dy * t;        }    }    dx = p[0] - x;    dy = p[1] - y;    return dx * dx + dy * dy;}// segment to segment distance, ported from http://geomalgorithms.com/a07-_distance.html by Dan Sundayfunction sqSegSegDist(x0, y0, x1, y1, x2, y2, x3, y3) {    var ux = x1 - x0;    var uy = y1 - y0;    var vx = x3 - x2;    var vy = y3 - y2;    var wx = x0 - x2;    var wy = y0 - y2;    var a = ux * ux + uy * uy;    var b = ux * vx + uy * vy;    var c = vx * vx + vy * vy;    var d = ux * wx + uy * wy;    var e = vx * wx + vy * wy;    var D = a * c - b * b;    var sc, sN, tc, tN;    var sD = D;    var tD = D;    if (D === 0) {        sN = 0;        sD = 1;        tN = e;        tD = c;    } else {        sN = b * e - c * d;        tN = a * e - b * d;        if (sN < 0) {            sN = 0;            tN = e;            tD = c;        } else if (sN > sD) {            sN = sD;            tN = e + b;            tD = c;        }    }    if (tN < 0.0) {        tN = 0.0;        if (-d < 0.0) sN = 0.0;        else if (-d > a) sN = sD;        else {            sN = -d;            sD = a;        }    } else if (tN > tD) {        tN = tD;        if ((-d + b) < 0.0) sN = 0;        else if (-d + b > a) sN = sD;        else {            sN = -d + b;            sD = a;        }    }    sc = sN === 0 ? 0 : sN / sD;    tc = tN === 0 ? 0 : tN / tD;    var cx = (1 - sc) * x0 + sc * x1;    var cy = (1 - sc) * y0 + sc * y1;    var cx2 = (1 - tc) * x2 + tc * x3;    var cy2 = (1 - tc) * y2 + tc * y3;    var dx = cx2 - cx;    var dy = cy2 - cy;    return dx * dx + dy * dy;}function compareByX(a, b) {    return a[0] === b[0] ? a[1] - b[1] : a[0] - b[0];}function convexHull(points) {    points.sort(compareByX);    var lower = [];    for (var i = 0; i < points.length; i++) {        while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {            lower.pop();        }        lower.push(points[i]);    }    var upper = [];    for (var ii = points.length - 1; ii >= 0; ii--) {        while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[ii]) <= 0) {            upper.pop();        }        upper.push(points[ii]);    }    upper.pop();    lower.pop();    return lower.concat(upper);}
 |