make-geo-picking-texture.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. 'use strict';
  2. /* global shapefile */
  3. /* eslint no-console: off */
  4. /* eslint no-unused-vars: off */
  5. async function main() {
  6. const size = 4096;
  7. const pickCtx = document.querySelector('#pick').getContext('2d');
  8. pickCtx.canvas.width = size;
  9. pickCtx.canvas.height = size;
  10. const outlineCtx = document.querySelector('#outline').getContext('2d');
  11. outlineCtx.canvas.width = size;
  12. outlineCtx.canvas.height = size;
  13. outlineCtx.translate(outlineCtx.canvas.width / 2, outlineCtx.canvas.height / 2);
  14. outlineCtx.scale(outlineCtx.canvas.width / 360, outlineCtx.canvas.height / -180);
  15. outlineCtx.strokeStyle = '#FFF';
  16. const workCtx = document.createElement('canvas').getContext('2d');
  17. workCtx.canvas.width = size;
  18. workCtx.canvas.height = size;
  19. let id = 1;
  20. const countryData = {};
  21. const countriesById = [];
  22. let min;
  23. let max;
  24. function resetMinMax() {
  25. min = [ 10000, 10000];
  26. max = [-10000, -10000];
  27. }
  28. function minMax(p) {
  29. min[0] = Math.min(min[0], p[0]);
  30. min[1] = Math.min(min[1], p[1]);
  31. max[0] = Math.max(max[0], p[0]);
  32. max[1] = Math.max(max[1], p[1]);
  33. }
  34. const geoHandlers = {
  35. 'MultiPolygon': multiPolygonArea,
  36. 'Polygon': polygonArea,
  37. };
  38. function multiPolygonArea(ctx, geo, drawFn) {
  39. const {coordinates} = geo;
  40. for (const polygon of coordinates) {
  41. ctx.beginPath();
  42. for (const ring of polygon) {
  43. ring.forEach(minMax);
  44. ctx.moveTo(...ring[0]);
  45. for (let i = 0; i < ring.length; ++i) {
  46. ctx.lineTo(...ring[i]);
  47. }
  48. ctx.closePath();
  49. }
  50. drawFn(ctx);
  51. }
  52. }
  53. function polygonArea(ctx, geo, drawFn) {
  54. const {coordinates} = geo;
  55. ctx.beginPath();
  56. for (const ring of coordinates) {
  57. ring.forEach(minMax);
  58. ctx.moveTo(...ring[0]);
  59. for (let i = 0; i < ring.length; ++i) {
  60. ctx.lineTo(...ring[i]);
  61. }
  62. ctx.closePath();
  63. }
  64. drawFn(ctx);
  65. }
  66. function fill(ctx) {
  67. ctx.fill('evenodd');
  68. }
  69. // function stroke(ctx) {
  70. // ctx.save();
  71. // ctx.setTransform(1, 0, 0, 1, 0, 0);
  72. // ctx.stroke();
  73. // ctx.restore();
  74. // }
  75. function draw(area) {
  76. const {properties, geometry} = area;
  77. const {type} = geometry;
  78. const name = properties.NAME;
  79. console.log(name);
  80. if (!countryData[name]) {
  81. const r = (id >> 0) & 0xFF;
  82. const g = (id >> 8) & 0xFF;
  83. const b = (id >> 16) & 0xFF;
  84. countryData[name] = {
  85. color: [r, g, b],
  86. id: id++,
  87. };
  88. countriesById.push({name});
  89. }
  90. const countryInfo = countriesById[countryData[name].id - 1];
  91. const handler = geoHandlers[type];
  92. if (!handler) {
  93. throw new Error('unknown geometry type.');
  94. }
  95. resetMinMax();
  96. workCtx.save();
  97. workCtx.clearRect(0, 0, workCtx.canvas.width, workCtx.canvas.height);
  98. workCtx.fillStyle = '#000';
  99. workCtx.strokeStyle = '#000';
  100. workCtx.translate(workCtx.canvas.width / 2, workCtx.canvas.height / 2);
  101. workCtx.scale(workCtx.canvas.width / 360, workCtx.canvas.height / -180);
  102. handler(workCtx, geometry, fill);
  103. workCtx.restore();
  104. countryInfo.min = min;
  105. countryInfo.max = max;
  106. countryInfo.area = properties.AREA;
  107. countryInfo.lat = properties.LAT;
  108. countryInfo.lon = properties.LON;
  109. countryInfo.population = {
  110. '2005': properties.POP2005,
  111. };
  112. //
  113. const left = Math.floor(( min[0] + 180) * workCtx.canvas.width / 360);
  114. const bottom = Math.floor((-min[1] + 90) * workCtx.canvas.height / 180);
  115. const right = Math.ceil( ( max[0] + 180) * workCtx.canvas.width / 360);
  116. const top = Math.ceil( (-max[1] + 90) * workCtx.canvas.height / 180);
  117. const width = right - left + 1;
  118. const height = Math.max(1, bottom - top + 1);
  119. const color = countryData[name].color;
  120. const src = workCtx.getImageData(left, top, width, height);
  121. for (let y = 0; y < height; ++y) {
  122. for (let x = 0; x < width; ++x) {
  123. const off = (y * width + x) * 4;
  124. if (src.data[off + 3]) {
  125. src.data[off + 0] = color[0];
  126. src.data[off + 1] = color[1];
  127. src.data[off + 2] = color[2];
  128. src.data[off + 3] = 255;
  129. }
  130. }
  131. }
  132. workCtx.putImageData(src, left, top);
  133. pickCtx.drawImage(workCtx.canvas, 0, 0);
  134. // handler(outlineCtx, geometry, stroke);
  135. }
  136. const source = await shapefile.open('TM_WORLD_BORDERS-0.3.shp');
  137. const areas = [];
  138. for (let i = 0; ; ++i) {
  139. const {done, value} = await source.read();
  140. if (done) {
  141. break;
  142. }
  143. areas.push(value);
  144. draw(value);
  145. if (i % 20 === 19) {
  146. await wait();
  147. }
  148. }
  149. console.log(JSON.stringify(areas));
  150. console.log('min', min);
  151. console.log('max', max);
  152. console.log(JSON.stringify(countriesById, null, 2));
  153. const pick = pickCtx.getImageData(0, 0, pickCtx.canvas.width, pickCtx.canvas.height);
  154. const outline = outlineCtx.getImageData(0, 0, outlineCtx.canvas.width, outlineCtx.canvas.height);
  155. function getId(imageData, x, y) {
  156. const off = (((y + imageData.height) % imageData.height) * imageData.width + ((x + imageData.width) % imageData.width)) * 4;
  157. return imageData.data[off + 0] +
  158. imageData.data[off + 1] * 256 +
  159. imageData.data[off + 2] * 256 * 256 +
  160. imageData.data[off + 3] * 256 * 256 * 256;
  161. }
  162. function putPixel(imageData, x, y, color) {
  163. const off = (y * imageData.width + x) * 4;
  164. imageData.data.set(color, off);
  165. }
  166. for (let y = 0; y < pick.height; ++y) {
  167. for (let x = 0; x < pick.width; ++x) {
  168. const s = getId(pick, x, y);
  169. const r = getId(pick, x + 1, y);
  170. const d = getId(pick, x, y + 1);
  171. let v = 0;
  172. if (s !== r || s !== d) {
  173. v = 255;
  174. }
  175. putPixel(outline, x, y, [v, v, v, v]);
  176. }
  177. }
  178. for (let y = 0; y < outline.height; ++y) {
  179. for (let x = 0; x < outline.width; ++x) {
  180. const s = getId(outline, x, y);
  181. const l = getId(outline, x - 1, y);
  182. const u = getId(outline, x, y - 1);
  183. const r = getId(outline, x + 1, y);
  184. const d = getId(outline, x, y + 1);
  185. //const rd = getId(outline, x + 1, y + 1);
  186. let v = s;
  187. if ((s && r && d) ||
  188. (s && l && d) ||
  189. (s && r && u) ||
  190. (s && l && u)) {
  191. v = 0;
  192. }
  193. putPixel(outline, x, y, [v, v, v, v]);
  194. }
  195. }
  196. outlineCtx.putImageData(outline, 0, 0);
  197. }
  198. function wait(ms = 0) {
  199. return new Promise((resolve) => {
  200. setTimeout(resolve, ms);
  201. });
  202. }
  203. main();