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