// https://github.com/Fil/d3-geo-voronoi Version 0.0.5. Copyright 2017 Philippe Riviere. (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-geo'), require('d3-voronoi')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-geo', 'd3-voronoi'], factory) : (factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3)); }(this, (function (exports,d3Array,d3Collection,d3Geo,d3Voronoi) { 'use strict'; // // Copyright (c) 2016, by Loren Petrich // // Distributed under the terms of the MIT License // // http://lpetrich.org/ // // Calculates the spherical Delaunay triangulation of a set of points // These points are entered as an array of arrays of coordinates: 0, 1, 2 // Any extra members are ignored // FindDelaunayTriangulation(Positions) and // FindDelaunayTriangulationIndexed(Positions, Indices) // work from an array of points as specified above, // the second one working from a set of indices into the array, // and return an object with these members: // positions -- vectors on a unit sphere // indices -- of all the vertices // triangles -- array of TriangleObject // edges -- array of EdgeObject // hull -- array of vertex indices -- the convex hull // vor_positions -- positions of triangles' circumcircle centers (Voronoi vertices) // vor_edges -- pair of indices in vor_positions (empty one: [-1,-1]) // vor_polygons -- object indexed by vertex index, // and containing edges (EdgeObject), triangles (TriangleObject), // and boundary (vertices in vor_positions) // Open ones have a -1 at each end. // Warning: ImproveTriangulation() is mysteriously buggy // and is effectively disabled for now function dotprd(x,y) { var sum = 0.0; for (var ic=0; ic<3; ic++) sum += x[ic]*y[ic]; return sum; } function crsprd(x,y) { var prod = new Array(3); for (var ic=0; ic<3; ic++) { var ic1 = ic + 1; if (ic1 >= 3) ic1 -= 3; var ic2 = ic + 2; if (ic2 >= 3) ic2 -= 3; prod[ic] = x[ic1]*y[ic2] - x[ic2]*y[ic1]; } return prod; } function triple_prd(x,y,z) { return dotprd(crsprd(x,y),z); } // This distance formula has some nice properties: // distance and not square of distance; // the square roots give better numerical resolution // distance of antipode(p) to p' = - (distance of p to p') // Range: -2 to +2 function ptdist(x,y) { var dst1 = 0.0; var dst2 = 0.0; for (var ic=0; ic<3; ic++) { var diff1 = y[ic] - x[ic]; dst1 += diff1*diff1; var diff2 = y[ic] + x[ic]; dst2 += diff2*diff2; } return Math.sqrt(dst1) - Math.sqrt(dst2); } function Normalize(vec) { var vecres = new Array(3); var sum = 0.0, nrmult; for (var ic=0; ic<3; ic++) { var val = vec[ic]; sum += val*val; } if (sum > 0) nrmult = 1/Math.sqrt(sum); else nrmult = 0; for (var ic=0; ic<3; ic++) { vecres[ic] = nrmult*vec[ic]; } return vecres; } function crsprd_ix(Positions,ix,iy) { return crsprd(Positions[ix],Positions[iy]); } function triple_prd_ix(Positions,ix,iy,iz) { return triple_prd(Positions[ix],Positions[iy],Positions[iz]); } function ptdist_ix(Positions,ix,iy) { return ptdist(Positions[ix],Positions[iy]); } // Returns a zero 3-vector function zerovec() { var vec = new Array(3); for (var ic=0; ic<3; ic++) vec[ic] = 0.0; return vec; } // Implements copying function vec_copy(x) { var vec = new Array(3); for (var ic=0; ic<3; ic++) vec[ic] = x[ic]; return vec; } // Implements x += y function vec_add_to(x,y) { for (var ic=0; ic<3; ic++) x[ic] += y[ic]; } // Implements x *= y function vec_mult_scalar_to(x,y) { for (var ic=0; ic<3; ic++) x[ic] *= y; } // Implements x - y function vec_difference(x,y) { var diff = zerovec(); for (var ic=0; ic<3; ic++) diff[ic] = x[ic] - y[ic]; return diff; } // JavaScript's counterpart of "null" / "None": function IsNull(x) { return (typeof(x) == 'undefined'); } function TrianglesEqual(tr1, tr2) { if (IsNull(tr1)) return false; if (IsNull(tr2)) return false; for (var iv=0; iv<3; iv++) if (tr1.verts[iv] != tr2.verts[iv]) return false; return true; } function EdgesEqual(ed1, ed2) { if (IsNull(ed1)) return false; if (IsNull(ed2)) return false; for (var iv=0; iv<2; iv++) if (ed1.verts[iv] != ed2.verts[iv]) return false; return true; } function max(x,y) { return (y > x) ? y : x; } function TriangleObject(Positions, verts) { this.verts = verts; this.edges = new Array(3); // Find directions for testing whether a point is inside this.dirs = new Array(3); for (var ic=0; ic<3; ic++) { var ic1 = ic + 1; if (ic1 >= 3) ic1 -= 3; var ic2 = ic + 2; if (ic2 >= 3) ic2 -= 3; this.dirs[ic] = crsprd_ix(Positions,verts[ic1],verts[ic2]); } // Tetrahedral volume factor this.vol = triple_prd_ix(Positions,verts[0],verts[1],verts[2]); // Adjust to get the signs correct for the point-inside test; // the vertex opposite the edges' vertices ought to give a dot product of 1 for (var ic=0; ic<3; ic++) vec_mult_scalar_to(this.dirs[ic],1/this.vol); // Circumcircle test var ccdir = zerovec(); for (var ic=0; ic<3; ic++) vec_add_to(ccdir,this.dirs[ic]); this.ccdir = Normalize(ccdir); var ccdsq = 0; for (var ic=0; ic<3; ic++) ccdsq += ptdist(this.ccdir,Positions[verts[ic]]); ccdsq /= 3; this.ccdsq = ccdsq; } // For copying in vertex info from another triangle TriangleObject.prototype.copy_vert_info = function(src) { this.verts = src.verts; this.dirs = src.dirs; this.vol = src.vol; this.ccdir = src.ccdir; this.ccdsq = src.ccdsq; }; TriangleObject.prototype.IsVertexOrderCorrect = function() { return this.vol >= 0; }; TriangleObject.prototype.IsPointInside = function(p) { for (var ic=0; ic<3; ic++) if (dotprd(p,this.dirs[ic]) < 0) return false; return true; }; TriangleObject.prototype.IsPointInCircumcircle = function(p) { return (ptdist(this.ccdir,p) < this.ccdsq); }; TriangleObject.prototype.IsVertex = function(ix) { for (var ic=0; ic<3; ic++) if (ix == this.verts[ic]) return true; return false; }; TriangleObject.prototype.VertexIndexIn = function(ix) { for (var ic=0; ic<3; ic++) if (ix == this.verts[ic]) return ic; return -1; }; TriangleObject.prototype.EdgeIndexIn = function(ed) { for (var ic=0; ic<3; ic++) if (EdgesEqual(this.edges[ic], ed)) return ic; return -1; }; function EdgeObject(verts) { this.verts = verts; this.polys = new Array(2); } EdgeObject.prototype.IsVertex = function(ix) { for (var ic=0; ic<2; ic++) if (ix == this.verts[ic]) return true; return false; }; EdgeObject.prototype.VertexIndexIn = function(ix) { for (var ic=0; ic<2; ic++) if (ix == this.verts[ic]) return ic; return -1; }; EdgeObject.prototype.PolyIndexIn = function(pl) { for (var ic=0; ic<2; ic++) if (TrianglesEqual(this.polys[ic],pl)) return ic; return -1; }; function EdgeCheckObject(Positions,verts) { this.verts = verts; this.pdst = ptdist_ix(Positions,verts[0],verts[1]); this.direc = Normalize(crsprd_ix(Positions,verts[0],verts[1])); var midpnt = zerovec(); vec_add_to(midpnt,Positions[verts[0]]); vec_add_to(midpnt,Positions[verts[1]]); this.midpnt = Normalize(midpnt); } // Check on the possible intersection with another edge-check object // return a boolean of whether or not it does EdgeCheckObject.prototype.intersects = function(Positions,other) { // Assume that sharing a vertex means non-intersecting for (var ic=0; ic<2; ic++) for (var ict=0; ict<2; ict++) if (this.verts[ic] == other.verts[ict]) return false; // Find intersection point; will test it and its antipode var itsc = Normalize(crsprd(this.direc, other.direc)); // Find dot product with midpoints to test if the intersection // is in the near hemispheres of the lines' midpoints. // If it is in both near hemispheres or both far hemispheres, it's OK // In both far hemispheres: antipode is in both near hemispheres var near0 = dotprd(itsc,this.midpnt) > 0; var near1 = dotprd(itsc,other.midpnt) > 0; if (near1 != near0) return false; var pd0 = []; for (var ic=0; ic<2; ic++) { var pd = ptdist(itsc, Positions[this.verts[ic]]); pd0.push(pd); } var pd1 = []; for (var ic=0; ic<2; ic++) { var pd = ptdist(itsc, Positions[other.verts[ic]]); pd1.push(pd); } var mxpd0 = max(pd0[0],pd0[1]); var mxpd1 = max(pd1[0],pd1[1]); if ((mxpd0 <= this.pdst) && (mxpd1 <= other.pdst) && near0) return true; // Handle its antipode; use antipode-related shortcuts // like reversing the distance value and the hemisphere-presence value vec_mult_scalar_to(itsc, -1); near0 = !near0; for (var ic=0; ic<2; ic++) { pd0[ic] = - pd0[ic]; pd1[ic] = - pd1[ic]; } mxpd0 = max(pd0[0],pd0[1]); mxpd1 = max(pd1[0],pd1[1]); if ((mxpd0 <= this.pdst) && (mxpd1 <= other.pdst) && near0) return true; return false; }; // Adds to an array if it was not already present; // Must resort to this kludge because JavaScript doesn't have sets function AddUnique(arr, x) { for (var i=0; i= 3) ic1 -= 3; newtris[ic] = new TriangleObject(Positions,[tri.verts[ic],tri.verts[ic1],ix]); neweds[ic] = new EdgeObject([tri.verts[ic],ix]); } // Connect those triangles and edges for (var ic=0; ic<3; ic++) { var ic1 = ic + 1; if (ic1 >= 3) ic1 -= 3; newtris[ic].edges[0] = neweds[ic1]; newtris[ic].edges[1] = neweds[ic]; neweds[ic].polys[0] = newtris[ic]; neweds[ic1].polys[1] = newtris[ic]; } // Find which external edges go with which triangles for (var ic=0; ic<3; ic++) { var ed = eds[ic]; var trix = trixs[ic]; for (var ict=0; ict<3; ict++) { var newtri = newtris[ict]; var numverts = 0; for (var iv=0; iv<2; iv++) { if (newtri.IsVertex(ed.verts[iv])) numverts++; if (numverts == 2) { ed.polys[trix] = newtri; newtri.edges[2] = ed; break; } } } } // Insert those triangles and edges into the lists TriSet.triangles[j] = newtris[0]; for (var ic=1; ic<3; ic++) TriSet.triangles.push(newtris[ic]); for (var ic=0; ic<3; ic++) TriSet.edges.push(neweds[ic]); // All done; indicate that the point was added return true; } } // The point was inside no triangle, and thus was not added return false; } function ImproveTriangulation(TriSet) { var Positions = TriSet.positions; var quad_verts = new Array(4); for (var itr=0; itr<100; itr++) { var numflips = 0; for (var i=0; i= 3) ic1 -= 3; var ic2 = ic + 2; if (ic2 >= 3) ic2 -= 3; quad_verts[0] = ix; quad_verts[1] = tris[0].verts[ic1]; quad_verts[3] = tris[0].verts[ic2]; for (var ic=0; ic<3; ic++) { var ix = tris[1].verts[ic]; if (!edge.IsVertex(ix)) break; } quad_verts[2] = ix; // Are the non-edge points in the other triangles' circumcircles? var incc0 = tris[0].IsPointInCircumcircle(Positions[quad_verts[2]]); var incc1 = tris[1].IsPointInCircumcircle(Positions[quad_verts[0]]); if ((!incc0) && (!incc1)) continue; // Are the would-be triangles properly oriented? var newtri0 = new TriangleObject(Positions, [quad_verts[0],quad_verts[1],quad_verts[2]]); if (!newtri0.IsVertexOrderCorrect()) continue; var newtri1 = new TriangleObject(Positions, [quad_verts[0],quad_verts[2],quad_verts[3]]); if (!newtri1.IsVertexOrderCorrect()) continue; // If so, then flip numflips++; // Adjust the edge and triangle memberships: // 0-3 goes from 0 to 1, 1-2 goes from 1 to 0 for (var ic=0; ic<3; ic++) { var ed = tris[0].edges[ic]; if (EdgesEqual(ed,edge)) continue; else if (ed.IsVertex(quad_verts[3])) { var ed03 = ed; var edix03 = ic; break; } } for (var ic=0; ic<3; ic++) { var ed = tris[1].edges[ic]; if (EdgesEqual(ed,edge)) continue; else if (ed.IsVertex(quad_verts[1])) { var ed12 = ed; var edix12 = ic; break; } } var trix0 = ed03.PolyIndexIn(tris[0]); var trix1 = ed12.PolyIndexIn(tris[1]); ed03.polys[trix0] = tris[1]; ed12.polys[trix1] = tris[0]; tris[0].edges[edix03] = ed12; tris[1].edges[edix12] = ed03; // Add the vertices tris[0].copy_vert_info(newtri0); tris[1].copy_vert_info(newtri1); edge.verts = [quad_verts[0], quad_verts[2]]; } if (numflips == 0) break; } } function FindConvexHull(TriSet) { // var Positions = TriSet.positions; // Find boundary loop -- use as convex hull var NextVertex = new Object; var VtxStart = -1; for (var i=0; i= 0) { var ix = VtxStart; var hull = [ix]; while(true) { var ixnext = NextVertex[ix]; if (ixnext == VtxStart) break; hull.push(ixnext); ix = ixnext; } TriSet.hull = hull; } } // Finds the dual of the Delaunay triangulation // Won't bother to do the sort of connectivity // that was necessary for the Delaunay triangulation function FindVoronoiDiagram(TriSet) { // Special cases: 3 or fewer points if (TriSet.triangles.length == 1) { // A single triangle if (TriSet.hull.length == 3) { var tri = TriSet.triangles[0]; TriSet.vor_positions.push(tri.ccdir); for (var k=0; k<3; k++) { var kx = k + 1; if (kx >= 3) kx = 0; var ky = k - 1; if (ky < 0) ky = 2; var v1 = TriSet.positions[TriSet.hull[k]]; var v2 = TriSet.positions[TriSet.hull[kx]]; var posdiff = vec_difference(v2,v1); TriSet.vor_positions.push(Normalize(crsprd(posdiff,tri.ccdir))); TriSet.vor_edges.push([0, k+1, 4]); var ix = TriSet.hull[k]; TriSet.vor_polygons[ix] = new Object; var vor_poly = TriSet.vor_polygons[ix]; var iy = TriSet.hull[ky]; for (var l=0; l<3; l++) { var edge = TriSet.edges[l]; var shrd = FindShared([iy,ix],edge.verts); if (shrd.length == 2) break; } vor_poly.edges = [edge]; vor_poly.triangles = [tri]; vor_poly.boundary = [0, ky+1, 4, k+1]; } var ept = vec_copy(tri.ccdir); vec_mult_scalar_to(ept,-1); TriSet.vor_positions.push(ept); } return; } else if (TriSet.triangles.length == 0) { // A biangle if (TriSet.hull.length == 2) { var v0 = TriSet.positions[TriSet.hull[0]]; var v1 = TriSet.positions[TriSet.hull[1]]; var vt0 = zerovec(); vec_add_to(vt0,v0); vec_add_to(vt0,v1); vt0 = Normalize(vt0); TriSet.vor_positions.push(vt0); var vt1 = Normalize(crsprd(v0,v1)); TriSet.vor_positions.push(vt1); var vt2 = vec_copy(vt0); vec_mult_scalar_to(vt2,-1); TriSet.vor_positions.push(vt2); var vt3 = vec_copy(vt1); vec_mult_scalar_to(vt3,-1); TriSet.vor_positions.push(vt3); TriSet.vor_edges.push([0, 1, 2, 3, 0]); edge = TriSet.edges[0]; for (var k=0; k<2; k++) { var ix = TriSet.hull[k]; TriSet.vor_polygons[ix] = new Object; var vor_poly = TriSet.vor_polygons[ix]; vor_poly.edges = [edge]; vor_poly.triangles = [0]; if (k == 0) vor_poly.boundary = [0, 1, 2, 3]; else if (k == 1) vor_poly.boundary = [0, 3, 2, 1]; } } return; } // Create the array of Voronoi-vertex positions: // Add indices to the triangle objects for convenience for (var i=0; i= 3) { // Set up the initial boundary lines // The boundary lines contain: // Index of Voronoi vertex / triangle center / intersection (in VorPos) // Indices of original vertices on each side of the line var VorBdLns = new Array(); var Positions = TriSet.positions; var hlen = TriSet.hull.length; for (var ic=0; ic= hlen) icx = 0; var ixa = TriSet.hull[icx]; var edset1 = TriSet.vor_polygons[ix].edges; var edset2 = TriSet.vor_polygons[ixa].edges; var edsetshr = FindSharedEdges(edset1,edset2); var edge = edsetshr[0]; var tvrt = edge.polys[0].index; var vt0 = Positions[ix]; var vt1 = Positions[ixa]; var vtdf = vec_difference(vt1,vt0); // Contains: triangle index (Voronoi vertex), // vertex index 1 (Voronoi region), position // vertex index 2 (Voronoi region), position, // great-circle normal var VorBdLn = [tvrt, TriSet.vor_positions[tvrt], ix, vt0, ixa, vt1, vtdf]; VorBdLns.push(VorBdLn); } // Find intersections while (VorBdLns.length > 3) { // Check all combinations of neighbors var n = VorBdLns.length; var itscpts = new Array(); var ptitscs = new Array(); for (var k=0; k= n) kx = 0; var itscpt = Normalize(crsprd(VorBdLns[k][6],VorBdLns[kx][6])); vec_mult_scalar_to(itscpt,-1); ptitscs[k].push(itscpts.length); ptitscs[kx].push(itscpts.length); itscpts.push(itscpt); } // Find the intersection points that are the closest to their parent points for (var k=0; k= 2) { var dists = new Array(); for (var kp=0; kp= n) kx = 0; var ky = k - 1; if (ky < 0) ky = n - 1; // 0 is lone, 1 is leading, 2 is trailing // vorvtidx is the index of the Voronoi vertex var pstat = 0; var ptitsc = ptitscs[k], ptitsc_next; if (ptitsc != -1) { var ptitsc_prev = ptitscs[ky]; if (ptitsc == ptitsc_prev) pstat = 2; else { ptitsc_next = ptitscs[kx]; if (ptitsc == ptitsc_next) pstat = 1; } } if (pstat == 0) { // Keep the Voronoi line without merging NewVorBdLns.push(VorBdLns[k]); } else if (pstat == 1) { // Merge the Voronoi lines and create a new one var VorBdLn0 = VorBdLns[k]; var VorBdLn1 = VorBdLns[kx]; var itscpt = itscpts[k]; var tvrt0 = VorBdLn0[0]; var tvrt1 = VorBdLn1[0]; var PointOK = (tvrt1 != tvrt0); if (PointOK) { var nitx = TriSet.vor_positions.length; var ix0 = VorBdLn0[2]; var vt0 = VorBdLn0[3]; var ix1 = VorBdLn1[4]; var vt1 = VorBdLn1[5]; var dst_in = undefined; var dst_out = undefined; for (var m=0; m= n) mrl -= n; if (mrl <= 2) { if (dst_in == undefined) dst_in = ptstm; else if (ptstm < dst_in) dst_in = ptstm; } else { if (dst_out == undefined) dst_out = ptstm; else if (ptstm < dst_out) dst_out = ptstm; } } PointOK = (dst_in < dst_out); } if (PointOK) { var vtdf = vec_difference(vt1,vt0); var VorBdLn = [nitx, itscpt, ix0, vt0, ix1, vt1, vtdf]; NewVorBdLns.push(VorBdLn); TriSet.vor_positions.push(itscpt); var ixi = VorBdLn0[4]; // Should be equal: // ixi = VorBdLn2[2]; TriSet.vor_edges.push([tvrt0, nitx]); TriSet.vor_edges.push([tvrt1, nitx]); // Add the point to the center Voronoi region and close it TriSet.vor_polygons[ixi].boundary.shift(); var vpln = TriSet.vor_polygons[ixi].boundary.length; TriSet.vor_polygons[ixi].boundary[vpln-1] = nitx; // Add the point to the left Voronoi region if (TriSet.vor_polygons[ix0].boundary[1] == tvrt0) { TriSet.vor_polygons[ix0].boundary.unshift(-1); TriSet.vor_polygons[ix0].boundary[1] = nitx; } else { vpln = TriSet.vor_polygons[ix0].boundary.length; if (TriSet.vor_polygons[ix0].boundary[vpln-2] == tvrt0) { TriSet.vor_polygons[ix0].boundary.push(-1); vpln = TriSet.vor_polygons[ix0].boundary.length; TriSet.vor_polygons[ix0].boundary[vpln-2] = nitx; } } // Add the point to the right Voronoi region if (TriSet.vor_polygons[ix1].boundary[1] == tvrt1) { TriSet.vor_polygons[ix1].boundary.unshift(-1); TriSet.vor_polygons[ix1].boundary[1] = nitx; } else { vpln = TriSet.vor_polygons[ix1].boundary.length; if (TriSet.vor_polygons[ix1].boundary[vpln-2] == tvrt1) { TriSet.vor_polygons[ix1].boundary.push(-1); vpln = TriSet.vor_polygons[ix1].boundary.length; TriSet.vor_polygons[ix1].boundary[vpln-2] = nitx; } } } else { NewVorBdLns.push(VorBdLn0); NewVorBdLns.push(VorBdLn1); } } /* else if (pstat == 2) { // Do nothing } */ } if (NewVorBdLns.length == VorBdLns.length) break; VorBdLns = NewVorBdLns; } // Special cases: only two or three points left if (VorBdLns.length == 2) { if (VorBdLns[0][0] != VorBdLns[1][0]) { var VorLn = []; for (var k=0; k<2; k++) { // Connecting line var kx = VorBdLns[k][0]; VorLn.push(kx); // Close the Voronoi region by deleting the end -1's kx = VorBdLns[k][2]; TriSet.vor_polygons[kx].boundary.shift(); TriSet.vor_polygons[kx].boundary.pop(); } TriSet.vor_edges.push(VorLn); } } else if (VorBdLns.length == 3) { var ic0 = VorBdLns[0][0]; var ic1 = VorBdLns[1][0]; var ic2 = VorBdLns[2][0]; if (ic0 != ic1 && ic0 != ic2 && ic1 != ic2) { var nitx = TriSet.vor_positions.length; var v0 = VorBdLns[0][3]; var v1 = VorBdLns[1][3]; var v2 = VorBdLns[2][3]; var itscpt = zerovec(); vec_add_to(itscpt,crsprd(v0,v1)); vec_add_to(itscpt,crsprd(v1,v2)); vec_add_to(itscpt,crsprd(v2,v0)); itscpt = Normalize(itscpt); vec_mult_scalar_to(itscpt,-1); TriSet.vor_positions.push(itscpt); for (var k=0; k<3; k++) { // Connecting line var VorBdLn = VorBdLns[k]; TriSet.vor_edges.push([VorBdLn[0], nitx]); // Add the point to the Voronoi region and close it var ix = VorBdLn[2]; TriSet.vor_polygons[ix].boundary.shift(); var vpln = TriSet.vor_polygons[ix].boundary.length; TriSet.vor_polygons[ix].boundary[vpln-1] = nitx; } } } } // Adjust the orientations for (var k=0; k= 3 && vor_poly.boundary[0] >= 0) { tri = new TriangleObject(TriSet.vor_positions,vor_poly.boundary.slice(0,3)); if (!tri.IsVertexOrderCorrect()) vor_poly.boundary.reverse(); } } } function FindDelaunayTriangulationIndexed(Positions, Indices) { // Create the triangle-set object var TriSet = new Object; TriSet.positions = Positions; TriSet.indices = Indices; TriSet.triangles = []; TriSet.edges = []; TriSet.hull = []; TriSet.vor_positions = []; TriSet.vor_edges = []; TriSet.vor_polygons = new Object; // Create the first triangle, if it is possible to create any if (Indices.length < 3) { if (Indices.length == 2) { TriSet.edges.push(new EdgeObject(Indices)); TriSet.hull = Indices; } FindVoronoiDiagram(TriSet); return TriSet; } var tri = new TriangleObject(Positions, Indices.slice(0,3)); if (!tri.IsVertexOrderCorrect()) tri = new TriangleObject(Positions, [Indices[0],Indices[2],Indices[1]]); TriSet.triangles.push(tri); var echs = new Array(3); for (var ic=0; ic<3; ic++) { var ic1 = ic + 1; if (ic1 >= 3) ic1 -= 3; var ix = Indices[ic]; var ix1 = Indices[ic1]; var vts = [ix, ix1]; var edge = new EdgeObject(vts); var echeck = new EdgeCheckObject(Positions,vts); echeck.edge = edge; echs[ic] = echeck; tri.edges[ic] = edge; edge.polys[0] = tri; TriSet.edges.push(edge); } // Place those crossing checkers in a boundary object; // will have to use various kludges since JavaScript doesn't have sets var BoundaryVerts = Indices.slice(0,3); var BoundaryEdges = echs; var Verts = Object; for (var ic=0; ic<3; ic++) { var ic1 = ic + 2; if (ic1 >= 3) ic1 -= 3; var ix = Indices[ic]; Verts[ix] = [echs[ic],echs[ic+1]]; } // Add points until it is no longer possible for (var i=3; i l) { l = length; remove = n; } } features[remove].properties.urquhart = false; }); return diagram._links = { type: "FeatureCollection", features: features }; }; diagram.triangles = voro.triangles = function (s) { if (s) voro(s); if (diagram._triangles) return diagram._triangles; var features = DT.triangles .map(function (t) { t.spherical = t.verts.map(function (v) { return DT.positions[v]; }) .map(spherical); // correct winding order if (t.ccdsq < 0) { t.spherical = t.spherical.reverse(); t.ccdsq *= -1; } return t; }) // make geojson .map(function (t) { return { type: "Feature", geometry: { type: "Polygon", coordinates: [t.spherical.concat([t.spherical[0]])] }, properties: { sites: t.verts.map(function (i) { return sites[i]; }), area: t.vol, // steradians circumcenter: spherical(t.ccdir), // ccdsq is *not* the geodesic distance /* circumradius: (2-t.ccdsq) * 53 */ } } }); return diagram._triangles = { type: "FeatureCollection", features: features }; }; diagram.polygons = voro.polygons = function (s) { if (s) voro(s); if (diagram._polygons) return diagram._polygons; var features = DT.indices.map(function (i, n) { var vor_poly = DT.vor_polygons[DT.indices[i]]; var geometry = {}; if (vor_poly == undefined) { geometry.type = "Sphere"; } else { var line = mapline(DT.vor_positions, vor_poly.boundary.concat([vor_poly.boundary[0]]) ); // correct winding order var b = { type: "Polygon", coordinates: [[pos[i], line[0], line[1], pos[i]]] }; if (d3Geo.geoArea(b) > 2 * Math.PI + 1e-10) { line = line.reverse(); } geometry.type = "Polygon"; geometry.coordinates = [line]; } return { type: "Feature", geometry: geometry, properties: { site: sites[i], sitecoordinates: pos[i], neighbours: vor_poly.edges.map(function (e) { return e.verts.filter(function (j) { return j !== i; })[0]; }) } }; }); return diagram._polygons = { type: "FeatureCollection", features: features }; }; diagram.hull = voro.hull = function (s) { if (s) voro(s); if (diagram._hull) return diagram._hull; if (!DT.hull.length) { return null; // What is a null GeoJSON? } // seems that DT.hull is always clockwise var hull = DT.hull.reverse(); // make GeoJSON return diagram._hull = { type: "Feature", geometry: { type: "Polygon", coordinates: [hull.concat([hull[0]]).map(function (i) { return pos[i]; })] }, properties: { sites: hull.map(function (i) { return sites[i]; }) } }; }; diagram.find = function (x, y, radius) { var features = diagram.polygons().features; // optimization: start from most recent result var i, next = diagram.find.found || 0; var cell = features[next] || features[next = 0]; var dist = d3Geo.geoDistance([x, y], cell.properties.sitecoordinates); do { cell = features[i = next]; next = null; cell.properties.neighbours.forEach(function (e) { var ndist = d3Geo.geoDistance([x, y], features[e].properties.sitecoordinates); if (ndist < dist) { dist = ndist; next = e; return; } }); } while (next !== null); diagram.find.found = i; if (!radius || dist < radius * radius) return cell.properties.site; }; voro.x = function (f) { if (!f) return x; x = f; return voro; }; voro.y = function (f) { if (!f) return y; y = f; return voro; }; voro.extent = function (f) { if (!f) return null; return voro; }; voro.size = function (f) { if (!f) return null; return voro; }; return voro; }; exports.geoVoronoi = geoVoronoi; Object.defineProperty(exports, '__esModule', { value: true }); })));