You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
6.3 KiB
JavaScript
312 lines
6.3 KiB
JavaScript
'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();
|