topojson-server.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. // https://github.com/topojson/topojson-server v3.0.1 Copyright 2019 Mike Bostock
  2. (function (global, factory) {
  3. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  4. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  5. (global = global || self, factory(global.topojson = global.topojson || {}));
  6. }(this, function (exports) { 'use strict';
  7. var hasOwnProperty = Object.prototype.hasOwnProperty;
  8. // Computes the bounding box of the specified hash of GeoJSON objects.
  9. function bounds(objects) {
  10. var x0 = Infinity,
  11. y0 = Infinity,
  12. x1 = -Infinity,
  13. y1 = -Infinity;
  14. function boundGeometry(geometry) {
  15. if (geometry != null && hasOwnProperty.call(boundGeometryType, geometry.type)) boundGeometryType[geometry.type](geometry);
  16. }
  17. var boundGeometryType = {
  18. GeometryCollection: function(o) { o.geometries.forEach(boundGeometry); },
  19. Point: function(o) { boundPoint(o.coordinates); },
  20. MultiPoint: function(o) { o.coordinates.forEach(boundPoint); },
  21. LineString: function(o) { boundLine(o.arcs); },
  22. MultiLineString: function(o) { o.arcs.forEach(boundLine); },
  23. Polygon: function(o) { o.arcs.forEach(boundLine); },
  24. MultiPolygon: function(o) { o.arcs.forEach(boundMultiLine); }
  25. };
  26. function boundPoint(coordinates) {
  27. var x = coordinates[0],
  28. y = coordinates[1];
  29. if (x < x0) x0 = x;
  30. if (x > x1) x1 = x;
  31. if (y < y0) y0 = y;
  32. if (y > y1) y1 = y;
  33. }
  34. function boundLine(coordinates) {
  35. coordinates.forEach(boundPoint);
  36. }
  37. function boundMultiLine(coordinates) {
  38. coordinates.forEach(boundLine);
  39. }
  40. for (var key in objects) {
  41. boundGeometry(objects[key]);
  42. }
  43. return x1 >= x0 && y1 >= y0 ? [x0, y0, x1, y1] : undefined;
  44. }
  45. function hashset(size, hash, equal, type, empty) {
  46. if (arguments.length === 3) {
  47. type = Array;
  48. empty = null;
  49. }
  50. var store = new type(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
  51. mask = size - 1;
  52. for (var i = 0; i < size; ++i) {
  53. store[i] = empty;
  54. }
  55. function add(value) {
  56. var index = hash(value) & mask,
  57. match = store[index],
  58. collisions = 0;
  59. while (match != empty) {
  60. if (equal(match, value)) return true;
  61. if (++collisions >= size) throw new Error("full hashset");
  62. match = store[index = (index + 1) & mask];
  63. }
  64. store[index] = value;
  65. return true;
  66. }
  67. function has(value) {
  68. var index = hash(value) & mask,
  69. match = store[index],
  70. collisions = 0;
  71. while (match != empty) {
  72. if (equal(match, value)) return true;
  73. if (++collisions >= size) break;
  74. match = store[index = (index + 1) & mask];
  75. }
  76. return false;
  77. }
  78. function values() {
  79. var values = [];
  80. for (var i = 0, n = store.length; i < n; ++i) {
  81. var match = store[i];
  82. if (match != empty) values.push(match);
  83. }
  84. return values;
  85. }
  86. return {
  87. add: add,
  88. has: has,
  89. values: values
  90. };
  91. }
  92. function hashmap(size, hash, equal, keyType, keyEmpty, valueType) {
  93. if (arguments.length === 3) {
  94. keyType = valueType = Array;
  95. keyEmpty = null;
  96. }
  97. var keystore = new keyType(size = 1 << Math.max(4, Math.ceil(Math.log(size) / Math.LN2))),
  98. valstore = new valueType(size),
  99. mask = size - 1;
  100. for (var i = 0; i < size; ++i) {
  101. keystore[i] = keyEmpty;
  102. }
  103. function set(key, value) {
  104. var index = hash(key) & mask,
  105. matchKey = keystore[index],
  106. collisions = 0;
  107. while (matchKey != keyEmpty) {
  108. if (equal(matchKey, key)) return valstore[index] = value;
  109. if (++collisions >= size) throw new Error("full hashmap");
  110. matchKey = keystore[index = (index + 1) & mask];
  111. }
  112. keystore[index] = key;
  113. valstore[index] = value;
  114. return value;
  115. }
  116. function maybeSet(key, value) {
  117. var index = hash(key) & mask,
  118. matchKey = keystore[index],
  119. collisions = 0;
  120. while (matchKey != keyEmpty) {
  121. if (equal(matchKey, key)) return valstore[index];
  122. if (++collisions >= size) throw new Error("full hashmap");
  123. matchKey = keystore[index = (index + 1) & mask];
  124. }
  125. keystore[index] = key;
  126. valstore[index] = value;
  127. return value;
  128. }
  129. function get(key, missingValue) {
  130. var index = hash(key) & mask,
  131. matchKey = keystore[index],
  132. collisions = 0;
  133. while (matchKey != keyEmpty) {
  134. if (equal(matchKey, key)) return valstore[index];
  135. if (++collisions >= size) break;
  136. matchKey = keystore[index = (index + 1) & mask];
  137. }
  138. return missingValue;
  139. }
  140. function keys() {
  141. var keys = [];
  142. for (var i = 0, n = keystore.length; i < n; ++i) {
  143. var matchKey = keystore[i];
  144. if (matchKey != keyEmpty) keys.push(matchKey);
  145. }
  146. return keys;
  147. }
  148. return {
  149. set: set,
  150. maybeSet: maybeSet, // set if unset
  151. get: get,
  152. keys: keys
  153. };
  154. }
  155. function equalPoint(pointA, pointB) {
  156. return pointA[0] === pointB[0] && pointA[1] === pointB[1];
  157. }
  158. // TODO if quantized, use simpler Int32 hashing?
  159. var buffer = new ArrayBuffer(16),
  160. floats = new Float64Array(buffer),
  161. uints = new Uint32Array(buffer);
  162. function hashPoint(point) {
  163. floats[0] = point[0];
  164. floats[1] = point[1];
  165. var hash = uints[0] ^ uints[1];
  166. hash = hash << 5 ^ hash >> 7 ^ uints[2] ^ uints[3];
  167. return hash & 0x7fffffff;
  168. }
  169. // Given an extracted (pre-)topology, identifies all of the junctions. These are
  170. // the points at which arcs (lines or rings) will need to be cut so that each
  171. // arc is represented uniquely.
  172. //
  173. // A junction is a point where at least one arc deviates from another arc going
  174. // through the same point. For example, consider the point B. If there is a arc
  175. // through ABC and another arc through CBA, then B is not a junction because in
  176. // both cases the adjacent point pairs are {A,C}. However, if there is an
  177. // additional arc ABD, then {A,D} != {A,C}, and thus B becomes a junction.
  178. //
  179. // For a closed ring ABCA, the first point A’s adjacent points are the second
  180. // and last point {B,C}. For a line, the first and last point are always
  181. // considered junctions, even if the line is closed; this ensures that a closed
  182. // line is never rotated.
  183. function join(topology) {
  184. var coordinates = topology.coordinates,
  185. lines = topology.lines,
  186. rings = topology.rings,
  187. indexes = index(),
  188. visitedByIndex = new Int32Array(coordinates.length),
  189. leftByIndex = new Int32Array(coordinates.length),
  190. rightByIndex = new Int32Array(coordinates.length),
  191. junctionByIndex = new Int8Array(coordinates.length),
  192. junctionCount = 0, // upper bound on number of junctions
  193. i, n,
  194. previousIndex,
  195. currentIndex,
  196. nextIndex;
  197. for (i = 0, n = coordinates.length; i < n; ++i) {
  198. visitedByIndex[i] = leftByIndex[i] = rightByIndex[i] = -1;
  199. }
  200. for (i = 0, n = lines.length; i < n; ++i) {
  201. var line = lines[i],
  202. lineStart = line[0],
  203. lineEnd = line[1];
  204. currentIndex = indexes[lineStart];
  205. nextIndex = indexes[++lineStart];
  206. ++junctionCount, junctionByIndex[currentIndex] = 1; // start
  207. while (++lineStart <= lineEnd) {
  208. sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[lineStart]);
  209. }
  210. ++junctionCount, junctionByIndex[nextIndex] = 1; // end
  211. }
  212. for (i = 0, n = coordinates.length; i < n; ++i) {
  213. visitedByIndex[i] = -1;
  214. }
  215. for (i = 0, n = rings.length; i < n; ++i) {
  216. var ring = rings[i],
  217. ringStart = ring[0] + 1,
  218. ringEnd = ring[1];
  219. previousIndex = indexes[ringEnd - 1];
  220. currentIndex = indexes[ringStart - 1];
  221. nextIndex = indexes[ringStart];
  222. sequence(i, previousIndex, currentIndex, nextIndex);
  223. while (++ringStart <= ringEnd) {
  224. sequence(i, previousIndex = currentIndex, currentIndex = nextIndex, nextIndex = indexes[ringStart]);
  225. }
  226. }
  227. function sequence(i, previousIndex, currentIndex, nextIndex) {
  228. if (visitedByIndex[currentIndex] === i) return; // ignore self-intersection
  229. visitedByIndex[currentIndex] = i;
  230. var leftIndex = leftByIndex[currentIndex];
  231. if (leftIndex >= 0) {
  232. var rightIndex = rightByIndex[currentIndex];
  233. if ((leftIndex !== previousIndex || rightIndex !== nextIndex)
  234. && (leftIndex !== nextIndex || rightIndex !== previousIndex)) {
  235. ++junctionCount, junctionByIndex[currentIndex] = 1;
  236. }
  237. } else {
  238. leftByIndex[currentIndex] = previousIndex;
  239. rightByIndex[currentIndex] = nextIndex;
  240. }
  241. }
  242. function index() {
  243. var indexByPoint = hashmap(coordinates.length * 1.4, hashIndex, equalIndex, Int32Array, -1, Int32Array),
  244. indexes = new Int32Array(coordinates.length);
  245. for (var i = 0, n = coordinates.length; i < n; ++i) {
  246. indexes[i] = indexByPoint.maybeSet(i, i);
  247. }
  248. return indexes;
  249. }
  250. function hashIndex(i) {
  251. return hashPoint(coordinates[i]);
  252. }
  253. function equalIndex(i, j) {
  254. return equalPoint(coordinates[i], coordinates[j]);
  255. }
  256. visitedByIndex = leftByIndex = rightByIndex = null;
  257. var junctionByPoint = hashset(junctionCount * 1.4, hashPoint, equalPoint), j;
  258. // Convert back to a standard hashset by point for caller convenience.
  259. for (i = 0, n = coordinates.length; i < n; ++i) {
  260. if (junctionByIndex[j = indexes[i]]) {
  261. junctionByPoint.add(coordinates[j]);
  262. }
  263. }
  264. return junctionByPoint;
  265. }
  266. // Given an extracted (pre-)topology, cuts (or rotates) arcs so that all shared
  267. // point sequences are identified. The topology can then be subsequently deduped
  268. // to remove exact duplicate arcs.
  269. function cut(topology) {
  270. var junctions = join(topology),
  271. coordinates = topology.coordinates,
  272. lines = topology.lines,
  273. rings = topology.rings,
  274. next,
  275. i, n;
  276. for (i = 0, n = lines.length; i < n; ++i) {
  277. var line = lines[i],
  278. lineMid = line[0],
  279. lineEnd = line[1];
  280. while (++lineMid < lineEnd) {
  281. if (junctions.has(coordinates[lineMid])) {
  282. next = {0: lineMid, 1: line[1]};
  283. line[1] = lineMid;
  284. line = line.next = next;
  285. }
  286. }
  287. }
  288. for (i = 0, n = rings.length; i < n; ++i) {
  289. var ring = rings[i],
  290. ringStart = ring[0],
  291. ringMid = ringStart,
  292. ringEnd = ring[1],
  293. ringFixed = junctions.has(coordinates[ringStart]);
  294. while (++ringMid < ringEnd) {
  295. if (junctions.has(coordinates[ringMid])) {
  296. if (ringFixed) {
  297. next = {0: ringMid, 1: ring[1]};
  298. ring[1] = ringMid;
  299. ring = ring.next = next;
  300. } else { // For the first junction, we can rotate rather than cut.
  301. rotateArray(coordinates, ringStart, ringEnd, ringEnd - ringMid);
  302. coordinates[ringEnd] = coordinates[ringStart];
  303. ringFixed = true;
  304. ringMid = ringStart; // restart; we may have skipped junctions
  305. }
  306. }
  307. }
  308. }
  309. return topology;
  310. }
  311. function rotateArray(array, start, end, offset) {
  312. reverse(array, start, end);
  313. reverse(array, start, start + offset);
  314. reverse(array, start + offset, end);
  315. }
  316. function reverse(array, start, end) {
  317. for (var mid = start + ((end-- - start) >> 1), t; start < mid; ++start, --end) {
  318. t = array[start], array[start] = array[end], array[end] = t;
  319. }
  320. }
  321. // Given a cut topology, combines duplicate arcs.
  322. function dedup(topology) {
  323. var coordinates = topology.coordinates,
  324. lines = topology.lines, line,
  325. rings = topology.rings, ring,
  326. arcCount = lines.length + rings.length,
  327. i, n;
  328. delete topology.lines;
  329. delete topology.rings;
  330. // Count the number of (non-unique) arcs to initialize the hashmap safely.
  331. for (i = 0, n = lines.length; i < n; ++i) {
  332. line = lines[i]; while (line = line.next) ++arcCount;
  333. }
  334. for (i = 0, n = rings.length; i < n; ++i) {
  335. ring = rings[i]; while (ring = ring.next) ++arcCount;
  336. }
  337. var arcsByEnd = hashmap(arcCount * 2 * 1.4, hashPoint, equalPoint),
  338. arcs = topology.arcs = [];
  339. for (i = 0, n = lines.length; i < n; ++i) {
  340. line = lines[i];
  341. do {
  342. dedupLine(line);
  343. } while (line = line.next);
  344. }
  345. for (i = 0, n = rings.length; i < n; ++i) {
  346. ring = rings[i];
  347. if (ring.next) { // arc is no longer closed
  348. do {
  349. dedupLine(ring);
  350. } while (ring = ring.next);
  351. } else {
  352. dedupRing(ring);
  353. }
  354. }
  355. function dedupLine(arc) {
  356. var startPoint,
  357. endPoint,
  358. startArcs, startArc,
  359. endArcs, endArc,
  360. i, n;
  361. // Does this arc match an existing arc in order?
  362. if (startArcs = arcsByEnd.get(startPoint = coordinates[arc[0]])) {
  363. for (i = 0, n = startArcs.length; i < n; ++i) {
  364. startArc = startArcs[i];
  365. if (equalLine(startArc, arc)) {
  366. arc[0] = startArc[0];
  367. arc[1] = startArc[1];
  368. return;
  369. }
  370. }
  371. }
  372. // Does this arc match an existing arc in reverse order?
  373. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[1]])) {
  374. for (i = 0, n = endArcs.length; i < n; ++i) {
  375. endArc = endArcs[i];
  376. if (reverseEqualLine(endArc, arc)) {
  377. arc[1] = endArc[0];
  378. arc[0] = endArc[1];
  379. return;
  380. }
  381. }
  382. }
  383. if (startArcs) startArcs.push(arc); else arcsByEnd.set(startPoint, [arc]);
  384. if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
  385. arcs.push(arc);
  386. }
  387. function dedupRing(arc) {
  388. var endPoint,
  389. endArcs,
  390. endArc,
  391. i, n;
  392. // Does this arc match an existing line in order, or reverse order?
  393. // Rings are closed, so their start point and end point is the same.
  394. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0]])) {
  395. for (i = 0, n = endArcs.length; i < n; ++i) {
  396. endArc = endArcs[i];
  397. if (equalRing(endArc, arc)) {
  398. arc[0] = endArc[0];
  399. arc[1] = endArc[1];
  400. return;
  401. }
  402. if (reverseEqualRing(endArc, arc)) {
  403. arc[0] = endArc[1];
  404. arc[1] = endArc[0];
  405. return;
  406. }
  407. }
  408. }
  409. // Otherwise, does this arc match an existing ring in order, or reverse order?
  410. if (endArcs = arcsByEnd.get(endPoint = coordinates[arc[0] + findMinimumOffset(arc)])) {
  411. for (i = 0, n = endArcs.length; i < n; ++i) {
  412. endArc = endArcs[i];
  413. if (equalRing(endArc, arc)) {
  414. arc[0] = endArc[0];
  415. arc[1] = endArc[1];
  416. return;
  417. }
  418. if (reverseEqualRing(endArc, arc)) {
  419. arc[0] = endArc[1];
  420. arc[1] = endArc[0];
  421. return;
  422. }
  423. }
  424. }
  425. if (endArcs) endArcs.push(arc); else arcsByEnd.set(endPoint, [arc]);
  426. arcs.push(arc);
  427. }
  428. function equalLine(arcA, arcB) {
  429. var ia = arcA[0], ib = arcB[0],
  430. ja = arcA[1], jb = arcB[1];
  431. if (ia - ja !== ib - jb) return false;
  432. for (; ia <= ja; ++ia, ++ib) if (!equalPoint(coordinates[ia], coordinates[ib])) return false;
  433. return true;
  434. }
  435. function reverseEqualLine(arcA, arcB) {
  436. var ia = arcA[0], ib = arcB[0],
  437. ja = arcA[1], jb = arcB[1];
  438. if (ia - ja !== ib - jb) return false;
  439. for (; ia <= ja; ++ia, --jb) if (!equalPoint(coordinates[ia], coordinates[jb])) return false;
  440. return true;
  441. }
  442. function equalRing(arcA, arcB) {
  443. var ia = arcA[0], ib = arcB[0],
  444. ja = arcA[1], jb = arcB[1],
  445. n = ja - ia;
  446. if (n !== jb - ib) return false;
  447. var ka = findMinimumOffset(arcA),
  448. kb = findMinimumOffset(arcB);
  449. for (var i = 0; i < n; ++i) {
  450. if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[ib + (i + kb) % n])) return false;
  451. }
  452. return true;
  453. }
  454. function reverseEqualRing(arcA, arcB) {
  455. var ia = arcA[0], ib = arcB[0],
  456. ja = arcA[1], jb = arcB[1],
  457. n = ja - ia;
  458. if (n !== jb - ib) return false;
  459. var ka = findMinimumOffset(arcA),
  460. kb = n - findMinimumOffset(arcB);
  461. for (var i = 0; i < n; ++i) {
  462. if (!equalPoint(coordinates[ia + (i + ka) % n], coordinates[jb - (i + kb) % n])) return false;
  463. }
  464. return true;
  465. }
  466. // Rings are rotated to a consistent, but arbitrary, start point.
  467. // This is necessary to detect when a ring and a rotated copy are dupes.
  468. function findMinimumOffset(arc) {
  469. var start = arc[0],
  470. end = arc[1],
  471. mid = start,
  472. minimum = mid,
  473. minimumPoint = coordinates[mid];
  474. while (++mid < end) {
  475. var point = coordinates[mid];
  476. if (point[0] < minimumPoint[0] || point[0] === minimumPoint[0] && point[1] < minimumPoint[1]) {
  477. minimum = mid;
  478. minimumPoint = point;
  479. }
  480. }
  481. return minimum - start;
  482. }
  483. return topology;
  484. }
  485. // Given an array of arcs in absolute (but already quantized!) coordinates,
  486. // converts to fixed-point delta encoding.
  487. // This is a destructive operation that modifies the given arcs!
  488. function delta(arcs) {
  489. var i = -1,
  490. n = arcs.length;
  491. while (++i < n) {
  492. var arc = arcs[i],
  493. j = 0,
  494. k = 1,
  495. m = arc.length,
  496. point = arc[0],
  497. x0 = point[0],
  498. y0 = point[1],
  499. x1,
  500. y1;
  501. while (++j < m) {
  502. point = arc[j], x1 = point[0], y1 = point[1];
  503. if (x1 !== x0 || y1 !== y0) arc[k++] = [x1 - x0, y1 - y0], x0 = x1, y0 = y1;
  504. }
  505. if (k === 1) arc[k++] = [0, 0]; // Each arc must be an array of two or more positions.
  506. arc.length = k;
  507. }
  508. return arcs;
  509. }
  510. // Extracts the lines and rings from the specified hash of geometry objects.
  511. //
  512. // Returns an object with three properties:
  513. //
  514. // * coordinates - shared buffer of [x, y] coordinates
  515. // * lines - lines extracted from the hash, of the form [start, end]
  516. // * rings - rings extracted from the hash, of the form [start, end]
  517. //
  518. // For each ring or line, start and end represent inclusive indexes into the
  519. // coordinates buffer. For rings (and closed lines), coordinates[start] equals
  520. // coordinates[end].
  521. //
  522. // For each line or polygon geometry in the input hash, including nested
  523. // geometries as in geometry collections, the `coordinates` array is replaced
  524. // with an equivalent `arcs` array that, for each line (for line string
  525. // geometries) or ring (for polygon geometries), points to one of the above
  526. // lines or rings.
  527. function extract(objects) {
  528. var index = -1,
  529. lines = [],
  530. rings = [],
  531. coordinates = [];
  532. function extractGeometry(geometry) {
  533. if (geometry && hasOwnProperty.call(extractGeometryType, geometry.type)) extractGeometryType[geometry.type](geometry);
  534. }
  535. var extractGeometryType = {
  536. GeometryCollection: function(o) { o.geometries.forEach(extractGeometry); },
  537. LineString: function(o) { o.arcs = extractLine(o.arcs); },
  538. MultiLineString: function(o) { o.arcs = o.arcs.map(extractLine); },
  539. Polygon: function(o) { o.arcs = o.arcs.map(extractRing); },
  540. MultiPolygon: function(o) { o.arcs = o.arcs.map(extractMultiRing); }
  541. };
  542. function extractLine(line) {
  543. for (var i = 0, n = line.length; i < n; ++i) coordinates[++index] = line[i];
  544. var arc = {0: index - n + 1, 1: index};
  545. lines.push(arc);
  546. return arc;
  547. }
  548. function extractRing(ring) {
  549. for (var i = 0, n = ring.length; i < n; ++i) coordinates[++index] = ring[i];
  550. var arc = {0: index - n + 1, 1: index};
  551. rings.push(arc);
  552. return arc;
  553. }
  554. function extractMultiRing(rings) {
  555. return rings.map(extractRing);
  556. }
  557. for (var key in objects) {
  558. extractGeometry(objects[key]);
  559. }
  560. return {
  561. type: "Topology",
  562. coordinates: coordinates,
  563. lines: lines,
  564. rings: rings,
  565. objects: objects
  566. };
  567. }
  568. // Given a hash of GeoJSON objects, returns a hash of GeoJSON geometry objects.
  569. // Any null input geometry objects are represented as {type: null} in the output.
  570. // Any feature.{id,properties,bbox} are transferred to the output geometry object.
  571. // Each output geometry object is a shallow copy of the input (e.g., properties, coordinates)!
  572. function geometry(inputs) {
  573. var outputs = {}, key;
  574. for (key in inputs) outputs[key] = geomifyObject(inputs[key]);
  575. return outputs;
  576. }
  577. function geomifyObject(input) {
  578. return input == null ? {type: null}
  579. : (input.type === "FeatureCollection" ? geomifyFeatureCollection
  580. : input.type === "Feature" ? geomifyFeature
  581. : geomifyGeometry)(input);
  582. }
  583. function geomifyFeatureCollection(input) {
  584. var output = {type: "GeometryCollection", geometries: input.features.map(geomifyFeature)};
  585. if (input.bbox != null) output.bbox = input.bbox;
  586. return output;
  587. }
  588. function geomifyFeature(input) {
  589. var output = geomifyGeometry(input.geometry), key; // eslint-disable-line no-unused-vars
  590. if (input.id != null) output.id = input.id;
  591. if (input.bbox != null) output.bbox = input.bbox;
  592. for (key in input.properties) { output.properties = input.properties; break; }
  593. return output;
  594. }
  595. function geomifyGeometry(input) {
  596. if (input == null) return {type: null};
  597. var output = input.type === "GeometryCollection" ? {type: "GeometryCollection", geometries: input.geometries.map(geomifyGeometry)}
  598. : input.type === "Point" || input.type === "MultiPoint" ? {type: input.type, coordinates: input.coordinates}
  599. : {type: input.type, arcs: input.coordinates}; // TODO Check for unknown types?
  600. if (input.bbox != null) output.bbox = input.bbox;
  601. return output;
  602. }
  603. function prequantize(objects, bbox, n) {
  604. var x0 = bbox[0],
  605. y0 = bbox[1],
  606. x1 = bbox[2],
  607. y1 = bbox[3],
  608. kx = x1 - x0 ? (n - 1) / (x1 - x0) : 1,
  609. ky = y1 - y0 ? (n - 1) / (y1 - y0) : 1;
  610. function quantizePoint(input) {
  611. return [Math.round((input[0] - x0) * kx), Math.round((input[1] - y0) * ky)];
  612. }
  613. function quantizePoints(input, m) {
  614. var i = -1,
  615. j = 0,
  616. n = input.length,
  617. output = new Array(n), // pessimistic
  618. pi,
  619. px,
  620. py,
  621. x,
  622. y;
  623. while (++i < n) {
  624. pi = input[i];
  625. x = Math.round((pi[0] - x0) * kx);
  626. y = Math.round((pi[1] - y0) * ky);
  627. if (x !== px || y !== py) output[j++] = [px = x, py = y]; // non-coincident points
  628. }
  629. output.length = j;
  630. while (j < m) j = output.push([output[0][0], output[0][1]]);
  631. return output;
  632. }
  633. function quantizeLine(input) {
  634. return quantizePoints(input, 2);
  635. }
  636. function quantizeRing(input) {
  637. return quantizePoints(input, 4);
  638. }
  639. function quantizePolygon(input) {
  640. return input.map(quantizeRing);
  641. }
  642. function quantizeGeometry(o) {
  643. if (o != null && hasOwnProperty.call(quantizeGeometryType, o.type)) quantizeGeometryType[o.type](o);
  644. }
  645. var quantizeGeometryType = {
  646. GeometryCollection: function(o) { o.geometries.forEach(quantizeGeometry); },
  647. Point: function(o) { o.coordinates = quantizePoint(o.coordinates); },
  648. MultiPoint: function(o) { o.coordinates = o.coordinates.map(quantizePoint); },
  649. LineString: function(o) { o.arcs = quantizeLine(o.arcs); },
  650. MultiLineString: function(o) { o.arcs = o.arcs.map(quantizeLine); },
  651. Polygon: function(o) { o.arcs = quantizePolygon(o.arcs); },
  652. MultiPolygon: function(o) { o.arcs = o.arcs.map(quantizePolygon); }
  653. };
  654. for (var key in objects) {
  655. quantizeGeometry(objects[key]);
  656. }
  657. return {
  658. scale: [1 / kx, 1 / ky],
  659. translate: [x0, y0]
  660. };
  661. }
  662. // Constructs the TopoJSON Topology for the specified hash of features.
  663. // Each object in the specified hash must be a GeoJSON object,
  664. // meaning FeatureCollection, a Feature or a geometry object.
  665. function topology(objects, quantization) {
  666. var bbox = bounds(objects = geometry(objects)),
  667. transform = quantization > 0 && bbox && prequantize(objects, bbox, quantization),
  668. topology = dedup(cut(extract(objects))),
  669. coordinates = topology.coordinates,
  670. indexByArc = hashmap(topology.arcs.length * 1.4, hashArc, equalArc);
  671. objects = topology.objects; // for garbage collection
  672. topology.bbox = bbox;
  673. topology.arcs = topology.arcs.map(function(arc, i) {
  674. indexByArc.set(arc, i);
  675. return coordinates.slice(arc[0], arc[1] + 1);
  676. });
  677. delete topology.coordinates;
  678. coordinates = null;
  679. function indexGeometry(geometry) {
  680. if (geometry && hasOwnProperty.call(indexGeometryType, geometry.type)) indexGeometryType[geometry.type](geometry);
  681. }
  682. var indexGeometryType = {
  683. GeometryCollection: function(o) { o.geometries.forEach(indexGeometry); },
  684. LineString: function(o) { o.arcs = indexArcs(o.arcs); },
  685. MultiLineString: function(o) { o.arcs = o.arcs.map(indexArcs); },
  686. Polygon: function(o) { o.arcs = o.arcs.map(indexArcs); },
  687. MultiPolygon: function(o) { o.arcs = o.arcs.map(indexMultiArcs); }
  688. };
  689. function indexArcs(arc) {
  690. var indexes = [];
  691. do {
  692. var index = indexByArc.get(arc);
  693. indexes.push(arc[0] < arc[1] ? index : ~index);
  694. } while (arc = arc.next);
  695. return indexes;
  696. }
  697. function indexMultiArcs(arcs) {
  698. return arcs.map(indexArcs);
  699. }
  700. for (var key in objects) {
  701. indexGeometry(objects[key]);
  702. }
  703. if (transform) {
  704. topology.transform = transform;
  705. topology.arcs = delta(topology.arcs);
  706. }
  707. return topology;
  708. }
  709. function hashArc(arc) {
  710. var i = arc[0], j = arc[1], t;
  711. if (j < i) t = i, i = j, j = t;
  712. return i + 31 * j;
  713. }
  714. function equalArc(arcA, arcB) {
  715. var ia = arcA[0], ja = arcA[1],
  716. ib = arcB[0], jb = arcB[1], t;
  717. if (ja < ia) t = ia, ia = ja, ja = t;
  718. if (jb < ib) t = ib, ib = jb, jb = t;
  719. return ia === ib && ja === jb;
  720. }
  721. exports.topology = topology;
  722. Object.defineProperty(exports, '__esModule', { value: true });
  723. }));