polylabel.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.polylabel = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
  2. 'use strict';
  3. module.exports = TinyQueue;
  4. module.exports.default = TinyQueue;
  5. function TinyQueue(data, compare) {
  6. if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
  7. this.data = data || [];
  8. this.length = this.data.length;
  9. this.compare = compare || defaultCompare;
  10. if (this.length > 0) {
  11. for (var i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
  12. }
  13. }
  14. function defaultCompare(a, b) {
  15. return a < b ? -1 : a > b ? 1 : 0;
  16. }
  17. TinyQueue.prototype = {
  18. push: function (item) {
  19. this.data.push(item);
  20. this.length++;
  21. this._up(this.length - 1);
  22. },
  23. pop: function () {
  24. if (this.length === 0) return undefined;
  25. var top = this.data[0];
  26. this.length--;
  27. if (this.length > 0) {
  28. this.data[0] = this.data[this.length];
  29. this._down(0);
  30. }
  31. this.data.pop();
  32. return top;
  33. },
  34. peek: function () {
  35. return this.data[0];
  36. },
  37. _up: function (pos) {
  38. var data = this.data;
  39. var compare = this.compare;
  40. var item = data[pos];
  41. while (pos > 0) {
  42. var parent = (pos - 1) >> 1;
  43. var current = data[parent];
  44. if (compare(item, current) >= 0) break;
  45. data[pos] = current;
  46. pos = parent;
  47. }
  48. data[pos] = item;
  49. },
  50. _down: function (pos) {
  51. var data = this.data;
  52. var compare = this.compare;
  53. var halfLength = this.length >> 1;
  54. var item = data[pos];
  55. while (pos < halfLength) {
  56. var left = (pos << 1) + 1;
  57. var right = left + 1;
  58. var best = data[left];
  59. if (right < this.length && compare(data[right], best) < 0) {
  60. left = right;
  61. best = data[right];
  62. }
  63. if (compare(best, item) >= 0) break;
  64. data[pos] = best;
  65. pos = left;
  66. }
  67. data[pos] = item;
  68. }
  69. };
  70. },{}],2:[function(require,module,exports){
  71. 'use strict';
  72. var Queue = require('tinyqueue');
  73. module.exports = polylabel;
  74. module.exports.default = polylabel;
  75. function polylabel(polygon, precision, debug) {
  76. precision = precision || 1.0;
  77. // find the bounding box of the outer ring
  78. var minX, minY, maxX, maxY;
  79. for (var i = 0; i < polygon[0].length; i++) {
  80. var p = polygon[0][i];
  81. if (!i || p[0] < minX) minX = p[0];
  82. if (!i || p[1] < minY) minY = p[1];
  83. if (!i || p[0] > maxX) maxX = p[0];
  84. if (!i || p[1] > maxY) maxY = p[1];
  85. }
  86. var width = maxX - minX;
  87. var height = maxY - minY;
  88. var cellSize = Math.min(width, height);
  89. var h = cellSize / 2;
  90. if (cellSize === 0) return [minX, minY];
  91. // a priority queue of cells in order of their "potential" (max distance to polygon)
  92. var cellQueue = new Queue(null, compareMax);
  93. // cover polygon with initial cells
  94. for (var x = minX; x < maxX; x += cellSize) {
  95. for (var y = minY; y < maxY; y += cellSize) {
  96. cellQueue.push(new Cell(x + h, y + h, h, polygon));
  97. }
  98. }
  99. // take centroid as the first best guess
  100. var bestCell = getCentroidCell(polygon);
  101. // special case for rectangular polygons
  102. var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
  103. if (bboxCell.d > bestCell.d) bestCell = bboxCell;
  104. var numProbes = cellQueue.length;
  105. while (cellQueue.length) {
  106. // pick the most promising cell from the queue
  107. var cell = cellQueue.pop();
  108. // update the best cell if we found a better one
  109. if (cell.d > bestCell.d) {
  110. bestCell = cell;
  111. if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
  112. }
  113. // do not drill down further if there's no chance of a better solution
  114. if (cell.max - bestCell.d <= precision) continue;
  115. // split the cell into four cells
  116. h = cell.h / 2;
  117. cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
  118. cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
  119. cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
  120. cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
  121. numProbes += 4;
  122. }
  123. if (debug) {
  124. console.log('num probes: ' + numProbes);
  125. console.log('best distance: ' + bestCell.d);
  126. }
  127. return [bestCell.x, bestCell.y];
  128. }
  129. function compareMax(a, b) {
  130. return b.max - a.max;
  131. }
  132. function Cell(x, y, h, polygon) {
  133. this.x = x; // cell center x
  134. this.y = y; // cell center y
  135. this.h = h; // half the cell size
  136. this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
  137. this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
  138. }
  139. // signed distance from point to polygon outline (negative if point is outside)
  140. function pointToPolygonDist(x, y, polygon) {
  141. var inside = false;
  142. var minDistSq = Infinity;
  143. for (var k = 0; k < polygon.length; k++) {
  144. var ring = polygon[k];
  145. for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
  146. var a = ring[i];
  147. var b = ring[j];
  148. if ((a[1] > y !== b[1] > y) &&
  149. (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0])) inside = !inside;
  150. minDistSq = Math.min(minDistSq, getSegDistSq(x, y, a, b));
  151. }
  152. }
  153. return (inside ? 1 : -1) * Math.sqrt(minDistSq);
  154. }
  155. // get polygon centroid
  156. function getCentroidCell(polygon) {
  157. var area = 0;
  158. var x = 0;
  159. var y = 0;
  160. var points = polygon[0];
  161. for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) {
  162. var a = points[i];
  163. var b = points[j];
  164. var f = a[0] * b[1] - b[0] * a[1];
  165. x += (a[0] + b[0]) * f;
  166. y += (a[1] + b[1]) * f;
  167. area += f * 3;
  168. }
  169. if (area === 0) return new Cell(points[0][0], points[0][1], 0, polygon);
  170. return new Cell(x / area, y / area, 0, polygon);
  171. }
  172. // get squared distance from a point to a segment
  173. function getSegDistSq(px, py, a, b) {
  174. var x = a[0];
  175. var y = a[1];
  176. var dx = b[0] - x;
  177. var dy = b[1] - y;
  178. if (dx !== 0 || dy !== 0) {
  179. var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
  180. if (t > 1) {
  181. x = b[0];
  182. y = b[1];
  183. } else if (t > 0) {
  184. x += dx * t;
  185. y += dy * t;
  186. }
  187. }
  188. dx = px - x;
  189. dy = py - y;
  190. return dx * dx + dy * dy;
  191. }
  192. },{"tinyqueue":1}]},{},[2])(2)
  193. });