conrec.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /**
  2. * Copyright (c) 2010, Jason Davies.
  3. *
  4. * All rights reserved. This code is based on Bradley White's Java version,
  5. * which is in turn based on Nicholas Yue's C++ version, which in turn is based
  6. * on Paul D. Bourke's original Fortran version. See below for the respective
  7. * copyright notices.
  8. *
  9. * See http://paulbourke.net/papers/conrec/ for the original paper by Paul D.
  10. * Bourke.
  11. *
  12. * The vector conversion code is based on http://apptree.net/conrec.htm by
  13. * Graham Cox.
  14. *
  15. * Redistribution and use in source and binary forms, with or without
  16. * modification, are permitted provided that the following conditions are met:
  17. * * Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. * * Redistributions in binary form must reproduce the above copyright
  20. * notice, this list of conditions and the following disclaimer in the
  21. * documentation and/or other materials provided with the distribution.
  22. * * Neither the name of the <organization> nor the
  23. * names of its contributors may be used to endorse or promote products
  24. * derived from this software without specific prior written permission.
  25. *
  26. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  27. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  28. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  29. * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  30. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  31. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  32. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  33. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  34. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  35. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. */
  37. /*
  38. * Copyright (c) 1996-1997 Nicholas Yue
  39. *
  40. * This software is copyrighted by Nicholas Yue. This code is based on Paul D.
  41. * Bourke's CONREC.F routine.
  42. *
  43. * The authors hereby grant permission to use, copy, and distribute this
  44. * software and its documentation for any purpose, provided that existing
  45. * copyright notices are retained in all copies and that this notice is
  46. * included verbatim in any distributions. Additionally, the authors grant
  47. * permission to modify this software and its documentation for any purpose,
  48. * provided that such modifications are not distributed without the explicit
  49. * consent of the authors and that existing copyright notices are retained in
  50. * all copies. Some of the algorithms implemented by this software are
  51. * patented, observe all applicable patent law.
  52. *
  53. * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
  54. * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
  55. * OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
  56. * EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  57. *
  58. * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
  59. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  60. * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS
  61. * PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO
  62. * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
  63. * MODIFICATIONS.
  64. */
  65. (function(exports) {
  66. exports.Conrec = Conrec;
  67. var EPSILON = 1e-10;
  68. function pointsEqual(a, b) {
  69. var x = a.x - b.x, y = a.y - b.y;
  70. return x * x + y * y < EPSILON;
  71. }
  72. function reverseList(list) {
  73. var pp = list.head;
  74. while (pp) {
  75. // swap prev/next pointers
  76. var temp = pp.next;
  77. pp.next = pp.prev;
  78. pp.prev = temp;
  79. // continue through the list
  80. pp = temp;
  81. }
  82. // swap head/tail pointers
  83. var temp = list.head;
  84. list.head = list.tail;
  85. list.tail = temp;
  86. }
  87. function ContourBuilder(level) {
  88. this.level = level;
  89. this.s = null;
  90. this.count = 0;
  91. }
  92. ContourBuilder.prototype.remove_seq = function(list) {
  93. // if list is the first item, static ptr s is updated
  94. if (list.prev) {
  95. list.prev.next = list.next;
  96. } else {
  97. this.s = list.next;
  98. }
  99. if (list.next) {
  100. list.next.prev = list.prev;
  101. }
  102. --this.count;
  103. }
  104. ContourBuilder.prototype.addSegment = function(a, b) {
  105. var ss = this.s;
  106. var ma = null;
  107. var mb = null;
  108. var prependA = false;
  109. var prependB = false;
  110. while (ss) {
  111. if (ma == null) {
  112. // no match for a yet
  113. if (pointsEqual(a, ss.head.p)) {
  114. ma = ss;
  115. prependA = true;
  116. } else if (pointsEqual(a, ss.tail.p)) {
  117. ma = ss;
  118. }
  119. }
  120. if (mb == null) {
  121. // no match for b yet
  122. if (pointsEqual(b, ss.head.p)) {
  123. mb = ss;
  124. prependB = true;
  125. } else if (pointsEqual(b, ss.tail.p)) {
  126. mb = ss;
  127. }
  128. }
  129. // if we matched both no need to continue searching
  130. if (mb != null && ma != null) {
  131. break;
  132. } else {
  133. ss = ss.next;
  134. }
  135. }
  136. // c is the case selector based on which of ma and/or mb are set
  137. var c = ((ma != null) ? 1 : 0) | ((mb != null) ? 2 : 0);
  138. switch(c) {
  139. case 0: // both unmatched, add as new sequence
  140. var aa = {p: a, prev: null};
  141. var bb = {p: b, next: null};
  142. aa.next = bb;
  143. bb.prev = aa;
  144. // create sequence element and push onto head of main list. The order
  145. // of items in this list is unimportant
  146. ma = {head: aa, tail: bb, next: this.s, prev: null, closed: false};
  147. if (this.s) {
  148. this.s.prev = ma;
  149. }
  150. this.s = ma;
  151. ++this.count; // not essential - tracks number of unmerged sequences
  152. break;
  153. case 1: // a matched, b did not - thus b extends sequence ma
  154. var pp = {p: b};
  155. if (prependA) {
  156. pp.next = ma.head;
  157. pp.prev = null;
  158. ma.head.prev = pp;
  159. ma.head = pp;
  160. } else {
  161. pp.next = null;
  162. pp.prev = ma.tail;
  163. ma.tail.next = pp;
  164. ma.tail = pp;
  165. }
  166. break;
  167. case 2: // b matched, a did not - thus a extends sequence mb
  168. var pp = {p: a};
  169. if (prependB) {
  170. pp.next = mb.head;
  171. pp.prev = null;
  172. mb.head.prev = pp;
  173. mb.head = pp;
  174. } else {
  175. pp.next = null;
  176. pp.prev = mb.tail;
  177. mb.tail.next = pp;
  178. mb.tail = pp;
  179. }
  180. break;
  181. case 3: // both matched, can merge sequences
  182. // if the sequences are the same, do nothing, as we are simply closing this path (could set a flag)
  183. if (ma === mb) {
  184. var pp = {p: ma.tail.p, next: ma.head, prev: null};
  185. ma.head.prev = pp;
  186. ma.head = pp;
  187. ma.closed = true;
  188. break;
  189. }
  190. // there are 4 ways the sequence pair can be joined. The current setting of prependA and
  191. // prependB will tell us which type of join is needed. For head/head and tail/tail joins
  192. // one sequence needs to be reversed
  193. switch((prependA ? 1 : 0) | (prependB ? 2 : 0)) {
  194. case 0: // tail-tail
  195. // reverse ma and append to mb
  196. reverseList(ma);
  197. // fall through to head/tail case
  198. case 1: // head-tail
  199. // ma is appended to mb and ma discarded
  200. mb.tail.next = ma.head;
  201. ma.head.prev = mb.tail;
  202. mb.tail = ma.tail;
  203. //discard ma sequence record
  204. this.remove_seq(ma);
  205. break;
  206. case 3: // head-head
  207. // reverse ma and append mb to it
  208. reverseList(ma);
  209. // fall through to tail/head case
  210. case 2: // tail-head
  211. // mb is appended to ma and mb is discarded
  212. ma.tail.next = mb.head;
  213. mb.head.prev = ma.tail;
  214. ma.tail = mb.tail;
  215. //discard mb sequence record
  216. this.remove_seq(mb);
  217. break;
  218. }
  219. }
  220. }
  221. /**
  222. * Implements CONREC.
  223. *
  224. * @param {function} drawContour function for drawing contour. Defaults to a
  225. * custom "contour builder", which populates the
  226. * contours property.
  227. */
  228. function Conrec(drawContour) {
  229. if (!drawContour) {
  230. var c = this;
  231. c.contours = {};
  232. /**
  233. * drawContour - interface for implementing the user supplied method to
  234. * render the countours.
  235. *
  236. * Draws a line between the start and end coordinates.
  237. *
  238. * @param startX - start coordinate for X
  239. * @param startY - start coordinate for Y
  240. * @param endX - end coordinate for X
  241. * @param endY - end coordinate for Y
  242. * @param contourLevel - Contour level for line.
  243. */
  244. this.drawContour = function(startX, startY, endX, endY, contourLevel, k) {
  245. var cb = c.contours[k];
  246. if (!cb) {
  247. cb = c.contours[k] = new ContourBuilder(contourLevel);
  248. }
  249. cb.addSegment({x: startX, y: startY}, {x: endX, y: endY});
  250. }
  251. this.contourList = function() {
  252. var l = [];
  253. var a = c.contours;
  254. for (var k in a) {
  255. var s = a[k].s;
  256. var level = a[k].level;
  257. while (s) {
  258. var h = s.head;
  259. var l2 = [];
  260. l2.level = level;
  261. l2.k = k;
  262. while (h && h.p) {
  263. l2.push(h.p);
  264. h = h.next;
  265. }
  266. l.push(l2);
  267. s = s.next;
  268. }
  269. }
  270. l.sort(function(a, b) { return a.k - b.k });
  271. return l;
  272. }
  273. } else {
  274. this.drawContour = drawContour;
  275. }
  276. this.h = new Array(5);
  277. this.sh = new Array(5);
  278. this.xh = new Array(5);
  279. this.yh = new Array(5);
  280. }
  281. /**
  282. * contour is a contouring subroutine for rectangularily spaced data
  283. *
  284. * It emits calls to a line drawing subroutine supplied by the user which
  285. * draws a contour map corresponding to real*4data on a randomly spaced
  286. * rectangular grid. The coordinates emitted are in the same units given in
  287. * the x() and y() arrays.
  288. *
  289. * Any number of contour levels may be specified but they must be in order of
  290. * increasing value.
  291. *
  292. *
  293. * @param {number[][]} d - matrix of data to contour
  294. * @param {number} ilb,iub,jlb,jub - index bounds of data matrix
  295. *
  296. * The following two, one dimensional arrays (x and y) contain
  297. * the horizontal and vertical coordinates of each sample points.
  298. * @param {number[]} x - data matrix column coordinates
  299. * @param {number[]} y - data matrix row coordinates
  300. * @param {number} nc - number of contour levels
  301. * @param {number[]} z - contour levels in increasing order.
  302. */
  303. Conrec.prototype.contour = function(d, ilb, iub, jlb, jub, x, y, nc, z) {
  304. var h = this.h, sh = this.sh, xh = this.xh, yh = this.yh;
  305. var drawContour = this.drawContour;
  306. this.contours = {};
  307. /** private */
  308. var xsect = function(p1, p2){
  309. return (h[p2]*xh[p1]-h[p1]*xh[p2])/(h[p2]-h[p1]);
  310. }
  311. var ysect = function(p1, p2){
  312. return (h[p2]*yh[p1]-h[p1]*yh[p2])/(h[p2]-h[p1]);
  313. }
  314. var m1;
  315. var m2;
  316. var m3;
  317. var case_value;
  318. var dmin;
  319. var dmax;
  320. var x1 = 0.0;
  321. var x2 = 0.0;
  322. var y1 = 0.0;
  323. var y2 = 0.0;
  324. // The indexing of im and jm should be noted as it has to start from zero
  325. // unlike the fortran counter part
  326. var im = [0, 1, 1, 0];
  327. var jm = [0, 0, 1, 1];
  328. // Note that castab is arranged differently from the FORTRAN code because
  329. // Fortran and C/C++ arrays are transposed of each other, in this case
  330. // it is more tricky as castab is in 3 dimensions
  331. var castab = [
  332. [
  333. [0, 0, 8], [0, 2, 5], [7, 6, 9]
  334. ],
  335. [
  336. [0, 3, 4], [1, 3, 1], [4, 3, 0]
  337. ],
  338. [
  339. [9, 6, 7], [5, 2, 0], [8, 0, 0]
  340. ]
  341. ];
  342. for (var j=(jub-1);j>=jlb;j--) {
  343. for (var i=ilb;i<=iub-1;i++) {
  344. var temp1, temp2;
  345. temp1 = Math.min(d[i][j],d[i][j+1]);
  346. temp2 = Math.min(d[i+1][j],d[i+1][j+1]);
  347. dmin = Math.min(temp1,temp2);
  348. temp1 = Math.max(d[i][j],d[i][j+1]);
  349. temp2 = Math.max(d[i+1][j],d[i+1][j+1]);
  350. dmax = Math.max(temp1,temp2);
  351. if (dmax>=z[0]&&dmin<=z[nc-1]) {
  352. for (var k=0;k<nc;k++) {
  353. if (z[k]>=dmin&&z[k]<=dmax) {
  354. for (var m=4;m>=0;m--) {
  355. if (m>0) {
  356. // The indexing of im and jm should be noted as it has to
  357. // start from zero
  358. h[m] = d[i+im[m-1]][j+jm[m-1]]-z[k];
  359. xh[m] = x[i+im[m-1]];
  360. yh[m] = y[j+jm[m-1]];
  361. } else {
  362. h[0] = 0.25*(h[1]+h[2]+h[3]+h[4]);
  363. xh[0]=0.5*(x[i]+x[i+1]);
  364. yh[0]=0.5*(y[j]+y[j+1]);
  365. }
  366. if (h[m]>EPSILON) {
  367. sh[m] = 1;
  368. } else if (h[m]<-EPSILON) {
  369. sh[m] = -1;
  370. } else
  371. sh[m] = 0;
  372. }
  373. //
  374. // Note: at this stage the relative heights of the corners and the
  375. // centre are in the h array, and the corresponding coordinates are
  376. // in the xh and yh arrays. The centre of the box is indexed by 0
  377. // and the 4 corners by 1 to 4 as shown below.
  378. // Each triangle is then indexed by the parameter m, and the 3
  379. // vertices of each triangle are indexed by parameters m1,m2,and
  380. // m3.
  381. // It is assumed that the centre of the box is always vertex 2
  382. // though this isimportant only when all 3 vertices lie exactly on
  383. // the same contour level, in which case only the side of the box
  384. // is drawn.
  385. //
  386. //
  387. // vertex 4 +-------------------+ vertex 3
  388. // | \ / |
  389. // | \ m-3 / |
  390. // | \ / |
  391. // | \ / |
  392. // | m=2 X m=2 | the centre is vertex 0
  393. // | / \ |
  394. // | / \ |
  395. // | / m=1 \ |
  396. // | / \ |
  397. // vertex 1 +-------------------+ vertex 2
  398. //
  399. //
  400. //
  401. // Scan each triangle in the box
  402. //
  403. for (m=1;m<=4;m++) {
  404. m1 = m;
  405. m2 = 0;
  406. if (m!=4) {
  407. m3 = m+1;
  408. } else {
  409. m3 = 1;
  410. }
  411. case_value = castab[sh[m1]+1][sh[m2]+1][sh[m3]+1];
  412. if (case_value!=0) {
  413. switch (case_value) {
  414. case 1: // Line between vertices 1 and 2
  415. x1=xh[m1];
  416. y1=yh[m1];
  417. x2=xh[m2];
  418. y2=yh[m2];
  419. break;
  420. case 2: // Line between vertices 2 and 3
  421. x1=xh[m2];
  422. y1=yh[m2];
  423. x2=xh[m3];
  424. y2=yh[m3];
  425. break;
  426. case 3: // Line between vertices 3 and 1
  427. x1=xh[m3];
  428. y1=yh[m3];
  429. x2=xh[m1];
  430. y2=yh[m1];
  431. break;
  432. case 4: // Line between vertex 1 and side 2-3
  433. x1=xh[m1];
  434. y1=yh[m1];
  435. x2=xsect(m2,m3);
  436. y2=ysect(m2,m3);
  437. break;
  438. case 5: // Line between vertex 2 and side 3-1
  439. x1=xh[m2];
  440. y1=yh[m2];
  441. x2=xsect(m3,m1);
  442. y2=ysect(m3,m1);
  443. break;
  444. case 6: // Line between vertex 3 and side 1-2
  445. x1=xh[m3];
  446. y1=yh[m3];
  447. x2=xsect(m1,m2);
  448. y2=ysect(m1,m2);
  449. break;
  450. case 7: // Line between sides 1-2 and 2-3
  451. x1=xsect(m1,m2);
  452. y1=ysect(m1,m2);
  453. x2=xsect(m2,m3);
  454. y2=ysect(m2,m3);
  455. break;
  456. case 8: // Line between sides 2-3 and 3-1
  457. x1=xsect(m2,m3);
  458. y1=ysect(m2,m3);
  459. x2=xsect(m3,m1);
  460. y2=ysect(m3,m1);
  461. break;
  462. case 9: // Line between sides 3-1 and 1-2
  463. x1=xsect(m3,m1);
  464. y1=ysect(m3,m1);
  465. x2=xsect(m1,m2);
  466. y2=ysect(m1,m2);
  467. break;
  468. default:
  469. break;
  470. }
  471. // Put your processing code here and comment out the printf
  472. //printf("%f %f %f %f %f\n",x1,y1,x2,y2,z[k]);
  473. drawContour(x1,y1,x2,y2,z[k],k);
  474. }
  475. }
  476. }
  477. }
  478. }
  479. }
  480. }
  481. }
  482. })(typeof exports !== "undefined" ? exports : window);