'use strict'; /* global shapefile */ /* eslint no-console: off */ /* eslint no-unused-vars: off */ async function main() { const size = 4096; const pickCtx = document.querySelector( '#pick' ).getContext( '2d' ); pickCtx.canvas.width = size; pickCtx.canvas.height = size; const outlineCtx = document.querySelector( '#outline' ).getContext( '2d' ); outlineCtx.canvas.width = size; outlineCtx.canvas.height = size; outlineCtx.translate( outlineCtx.canvas.width / 2, outlineCtx.canvas.height / 2 ); outlineCtx.scale( outlineCtx.canvas.width / 360, outlineCtx.canvas.height / - 180 ); outlineCtx.strokeStyle = '#FFF'; const workCtx = document.createElement( 'canvas' ).getContext( '2d' ); workCtx.canvas.width = size; workCtx.canvas.height = size; let id = 1; const countryData = {}; const countriesById = []; let min; let max; function resetMinMax() { min = [ 10000, 10000 ]; max = [ - 10000, - 10000 ]; } function minMax( p ) { min[ 0 ] = Math.min( min[ 0 ], p[ 0 ] ); min[ 1 ] = Math.min( min[ 1 ], p[ 1 ] ); max[ 0 ] = Math.max( max[ 0 ], p[ 0 ] ); max[ 1 ] = Math.max( max[ 1 ], p[ 1 ] ); } const geoHandlers = { 'MultiPolygon': multiPolygonArea, 'Polygon': polygonArea, }; function multiPolygonArea( ctx, geo, drawFn ) { const { coordinates } = geo; for ( const polygon of coordinates ) { ctx.beginPath(); for ( const ring of polygon ) { ring.forEach( minMax ); ctx.moveTo( ...ring[ 0 ] ); for ( let i = 0; i < ring.length; ++ i ) { ctx.lineTo( ...ring[ i ] ); } ctx.closePath(); } drawFn( ctx ); } } function polygonArea( ctx, geo, drawFn ) { const { coordinates } = geo; ctx.beginPath(); for ( const ring of coordinates ) { ring.forEach( minMax ); ctx.moveTo( ...ring[ 0 ] ); for ( let i = 0; i < ring.length; ++ i ) { ctx.lineTo( ...ring[ i ] ); } ctx.closePath(); } drawFn( ctx ); } function fill( ctx ) { ctx.fill( 'evenodd' ); } // function stroke(ctx) { // ctx.save(); // ctx.setTransform(1, 0, 0, 1, 0, 0); // ctx.stroke(); // ctx.restore(); // } function draw( area ) { const { properties, geometry } = area; const { type } = geometry; const name = properties.NAME; console.log( name ); if ( ! countryData[ name ] ) { const r = ( id >> 0 ) & 0xFF; const g = ( id >> 8 ) & 0xFF; const b = ( id >> 16 ) & 0xFF; countryData[ name ] = { color: [ r, g, b ], id: id ++, }; countriesById.push( { name } ); } const countryInfo = countriesById[ countryData[ name ].id - 1 ]; const handler = geoHandlers[ type ]; if ( ! handler ) { throw new Error( 'unknown geometry type.' ); } resetMinMax(); workCtx.save(); workCtx.clearRect( 0, 0, workCtx.canvas.width, workCtx.canvas.height ); workCtx.fillStyle = '#000'; workCtx.strokeStyle = '#000'; workCtx.translate( workCtx.canvas.width / 2, workCtx.canvas.height / 2 ); workCtx.scale( workCtx.canvas.width / 360, workCtx.canvas.height / - 180 ); handler( workCtx, geometry, fill ); workCtx.restore(); countryInfo.min = min; countryInfo.max = max; countryInfo.area = properties.AREA; countryInfo.lat = properties.LAT; countryInfo.lon = properties.LON; countryInfo.population = { '2005': properties.POP2005, }; // const left = Math.floor( ( min[ 0 ] + 180 ) * workCtx.canvas.width / 360 ); const bottom = Math.floor( ( - min[ 1 ] + 90 ) * workCtx.canvas.height / 180 ); const right = Math.ceil( ( max[ 0 ] + 180 ) * workCtx.canvas.width / 360 ); const top = Math.ceil( ( - max[ 1 ] + 90 ) * workCtx.canvas.height / 180 ); const width = right - left + 1; const height = Math.max( 1, bottom - top + 1 ); const color = countryData[ name ].color; const src = workCtx.getImageData( left, top, width, height ); for ( let y = 0; y < height; ++ y ) { for ( let x = 0; x < width; ++ x ) { const off = ( y * width + x ) * 4; if ( src.data[ off + 3 ] ) { src.data[ off + 0 ] = color[ 0 ]; src.data[ off + 1 ] = color[ 1 ]; src.data[ off + 2 ] = color[ 2 ]; src.data[ off + 3 ] = 255; } } } workCtx.putImageData( src, left, top ); pickCtx.drawImage( workCtx.canvas, 0, 0 ); // handler(outlineCtx, geometry, stroke); } const source = await shapefile.open( 'TM_WORLD_BORDERS-0.3.shp' ); const areas = []; for ( let i = 0; ; ++ i ) { const { done, value } = await source.read(); if ( done ) { break; } areas.push( value ); draw( value ); if ( i % 20 === 19 ) { await wait(); } } console.log( JSON.stringify( areas ) ); console.log( 'min', min ); console.log( 'max', max ); console.log( JSON.stringify( countriesById, null, 2 ) ); const pick = pickCtx.getImageData( 0, 0, pickCtx.canvas.width, pickCtx.canvas.height ); const outline = outlineCtx.getImageData( 0, 0, outlineCtx.canvas.width, outlineCtx.canvas.height ); function getId( imageData, x, y ) { const off = ( ( ( y + imageData.height ) % imageData.height ) * imageData.width + ( ( x + imageData.width ) % imageData.width ) ) * 4; return imageData.data[ off + 0 ] + imageData.data[ off + 1 ] * 256 + imageData.data[ off + 2 ] * 256 * 256 + imageData.data[ off + 3 ] * 256 * 256 * 256; } function putPixel( imageData, x, y, color ) { const off = ( y * imageData.width + x ) * 4; imageData.data.set( color, off ); } for ( let y = 0; y < pick.height; ++ y ) { for ( let x = 0; x < pick.width; ++ x ) { const s = getId( pick, x, y ); const r = getId( pick, x + 1, y ); const d = getId( pick, x, y + 1 ); let v = 0; if ( s !== r || s !== d ) { v = 255; } putPixel( outline, x, y, [ v, v, v, v ] ); } } for ( let y = 0; y < outline.height; ++ y ) { for ( let x = 0; x < outline.width; ++ x ) { const s = getId( outline, x, y ); const l = getId( outline, x - 1, y ); const u = getId( outline, x, y - 1 ); const r = getId( outline, x + 1, y ); const d = getId( outline, x, y + 1 ); //const rd = getId(outline, x + 1, y + 1); let v = s; if ( ( s && r && d ) || ( s && l && d ) || ( s && r && u ) || ( s && l && u ) ) { v = 0; } putPixel( outline, x, y, [ v, v, v, v ] ); } } outlineCtx.putImageData( outline, 0, 0 ); } function wait( ms = 0 ) { return new Promise( ( resolve ) => { setTimeout( resolve, ms ); } ); } main();