make-geo-picking-texture.js 6.3 KB

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