You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

241 lines
6.3 KiB

'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();