geogrid 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.min.js +24 -0
- package/package.json +63 -0
- package/src/grid/diamond.js +50 -0
- package/src/grid/dot.js +36 -0
- package/src/grid/h3.js +48 -0
- package/src/grid/hexbin.js +52 -0
- package/src/grid/random.js +44 -0
- package/src/grid/square.js +45 -0
- package/src/grid/triangle.js +66 -0
- package/src/helpers/rewind.js +45 -0
- package/src/helpers/rewind2.ts +159 -0
- package/src/index.js +21 -0
- package/src/operator/pointstogrid.js +49 -0
- package/src/operator/polygonstogrid.js +68 -0
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "geogrid",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "xxx",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"module": "src/index.js",
|
|
7
|
+
"jsdelivr": "dist/index.min.js",
|
|
8
|
+
"unpkg": "dist/index.min.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
"umd": "./dist/index.min.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rollup --config",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"docs": "jsdoc --configure jsdoc.json --verbose"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/neocarto/geogrid.git"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"xxx",
|
|
28
|
+
"xxx",
|
|
29
|
+
"d3xxx"
|
|
30
|
+
],
|
|
31
|
+
"author": "Nicolas Lambert",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/neocarto/geogrid/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/neocarto/geogrid#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@turf/boolean-point-in-polygon": "^7.1.0",
|
|
39
|
+
"@turf/helpers": "^7.1.0",
|
|
40
|
+
"@turf/intersect": "^7.1.0",
|
|
41
|
+
"d3-array": "^3.2.4",
|
|
42
|
+
"d3-delaunay": "^6.0.4",
|
|
43
|
+
"d3-geo": "^3.1.1",
|
|
44
|
+
"docs": "^0.3.2-canary.0",
|
|
45
|
+
"documentation": "^14.0.3",
|
|
46
|
+
"geojson2h3": "^1.2.0",
|
|
47
|
+
"h3-js": "^4.1.0",
|
|
48
|
+
"rollup": "^4.10.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@jood/jsdoc-theme": "^0.1.14",
|
|
52
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
53
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
54
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
55
|
+
"@rollup/plugin-terser": "^0.4.3",
|
|
56
|
+
"clean-jsdoc-theme": "^4.2.17",
|
|
57
|
+
"docdash": "^2.0.1",
|
|
58
|
+
"jsdoc": "^4.0.2",
|
|
59
|
+
"jsdoc-to-markdown": "^8.0.0",
|
|
60
|
+
"modern-jsdoc-template": "^1.0.0",
|
|
61
|
+
"postman-jsdoc-theme": "^0.0.3"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { range } from "d3-array";
|
|
2
|
+
const d3 = Object.assign({}, { range });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.diamond
|
|
6
|
+
* @description The `make.diamond()` function allows to create a diamond geoJSON grid in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
export function diamond({ step = 50, width = 1000, height = 500 } = {}) {
|
|
12
|
+
let size = step * Math.sqrt(2);
|
|
13
|
+
|
|
14
|
+
// build grid
|
|
15
|
+
let x = d3.range(0, width + size, size);
|
|
16
|
+
let y = d3.range(0, height + size, size / 2).reverse();
|
|
17
|
+
let grid = x.map((x) => y.map((y, j) => [x, y, j % 2])).flat();
|
|
18
|
+
grid = grid.map((d) => {
|
|
19
|
+
return d[2] == 1 ? [d[0] + size / 2, d[1]] : [d[0], d[1]];
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let s = size / 2;
|
|
23
|
+
// build object
|
|
24
|
+
let result = grid.map((d, i) => {
|
|
25
|
+
return {
|
|
26
|
+
type: "Feature",
|
|
27
|
+
geometry: {
|
|
28
|
+
type: "Polygon",
|
|
29
|
+
coordinates: [
|
|
30
|
+
[
|
|
31
|
+
[d[0] - s, d[1]],
|
|
32
|
+
[d[0], d[1] + s],
|
|
33
|
+
[d[0] + s, d[1]],
|
|
34
|
+
[d[0], d[1] - s],
|
|
35
|
+
[d[0] - s, d[1]],
|
|
36
|
+
],
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
properties: {
|
|
40
|
+
index: i,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
type: "FeatureCollection",
|
|
46
|
+
grid: "diamond",
|
|
47
|
+
geo: false,
|
|
48
|
+
features: result,
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/grid/dot.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { range } from "d3-array";
|
|
2
|
+
const d3 = Object.assign({}, { range });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.dot
|
|
6
|
+
* @description The `make.dot()` function allows to create a geoJSON vith regular dots in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
export function dot({ step = 30, width = 1000, height = 500 } = {}) {
|
|
12
|
+
// build grid
|
|
13
|
+
let y = d3.range(0 + step / 2, height, step).reverse();
|
|
14
|
+
let x = d3.range(0 + step / 2, width, step);
|
|
15
|
+
let grid = x.map((x) => y.map((y) => [x, y])).flat();
|
|
16
|
+
let s = step / 2;
|
|
17
|
+
// build object
|
|
18
|
+
let result = grid.map((d, i) => {
|
|
19
|
+
return {
|
|
20
|
+
type: "Feature",
|
|
21
|
+
geometry: {
|
|
22
|
+
type: "Point",
|
|
23
|
+
coordinates: d,
|
|
24
|
+
},
|
|
25
|
+
properties: {
|
|
26
|
+
index: i,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
type: "FeatureCollection",
|
|
32
|
+
grid: "dot",
|
|
33
|
+
geo: false,
|
|
34
|
+
features: result,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/src/grid/h3.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRes0Cells,
|
|
3
|
+
cellToChildren,
|
|
4
|
+
isPentagon,
|
|
5
|
+
cellToBoundary,
|
|
6
|
+
} from "h3-js";
|
|
7
|
+
|
|
8
|
+
import { featureToH3Set, h3SetToFeatureCollection } from "geojson2h3";
|
|
9
|
+
import { rewind as rrewind } from "../helpers/rewind";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @function make.h3
|
|
13
|
+
* @description The `make.h3()` function allows to create a hexbin geoJSON grid in geographical coordinates.
|
|
14
|
+
* @property {number} [level = 0] - level of the grid. Form 0 (large hexagons) to 4 (small hexagons). See: https://h3geo.org
|
|
15
|
+
* @property {object} [domain] - a geoJSON to define an extent
|
|
16
|
+
* @property {boolen} [rewind] - to rewind the output
|
|
17
|
+
*/
|
|
18
|
+
export function h3({ level = 0, domain = undefined, rewind = undefined } = {}) {
|
|
19
|
+
let output;
|
|
20
|
+
if (domain) {
|
|
21
|
+
rewind = rewind !== undefined ? rewind : true;
|
|
22
|
+
const hexagons = featureToH3Set(domain, level);
|
|
23
|
+
output = h3SetToFeatureCollection(hexagons, (hex) => ({
|
|
24
|
+
value: hex,
|
|
25
|
+
}));
|
|
26
|
+
} else {
|
|
27
|
+
rewind = rewind !== undefined ? rewind : false;
|
|
28
|
+
output = {
|
|
29
|
+
type: "FeatureCollection",
|
|
30
|
+
grid: "h3",
|
|
31
|
+
level: level,
|
|
32
|
+
geo: true,
|
|
33
|
+
features: getRes0Cells()
|
|
34
|
+
.map((i) => cellToChildren(i, level))
|
|
35
|
+
.flat()
|
|
36
|
+
.map((d) => ({
|
|
37
|
+
type: "Feature",
|
|
38
|
+
properties: { id: d, pentagon: isPentagon(d) },
|
|
39
|
+
geometry: {
|
|
40
|
+
type: "Polygon",
|
|
41
|
+
coordinates: [cellToBoundary(d, true).reverse()],
|
|
42
|
+
},
|
|
43
|
+
})),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return rewind ? rrewind(output) : output;
|
|
48
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { range, max } from "d3-array";
|
|
2
|
+
const d3 = Object.assign({}, { range, max });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.hexbin
|
|
6
|
+
* @description The `make.hexbin()` function allows to create a hexbin geoJSON grid in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
export function hexbin({ step = 50, width = 1000, height = 500 } = {}) {
|
|
12
|
+
let w = step;
|
|
13
|
+
let size = w / Math.sqrt(3);
|
|
14
|
+
let h = 2 * size * (3 / 4);
|
|
15
|
+
|
|
16
|
+
// build grid
|
|
17
|
+
let y = d3.range(0, height + size, h).reverse();
|
|
18
|
+
if (y.length % 2) {
|
|
19
|
+
y.unshift(d3.max(y) + h);
|
|
20
|
+
}
|
|
21
|
+
let x = d3.range(0, width + size, w);
|
|
22
|
+
let grid = x.map((x) => y.map((y) => [x, y])).flat();
|
|
23
|
+
grid = grid.map((d, i) => {
|
|
24
|
+
return i % 2 == 1 ? [d[0] + w / 2, d[1]] : d;
|
|
25
|
+
});
|
|
26
|
+
let s = step / 2;
|
|
27
|
+
// build object
|
|
28
|
+
let result = grid.map((d, i) => {
|
|
29
|
+
let hex = [];
|
|
30
|
+
for (let i = 0; i < 6; i++) {
|
|
31
|
+
let ang = (Math.PI / 180) * (60 * i - 30);
|
|
32
|
+
hex.push([d[0] + size * Math.cos(ang), d[1] + size * Math.sin(ang)]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
type: "Feature",
|
|
37
|
+
geometry: {
|
|
38
|
+
type: "Polygon",
|
|
39
|
+
coordinates: [[hex[0], hex[1], hex[2], hex[3], hex[4], hex[5], hex[0]]],
|
|
40
|
+
},
|
|
41
|
+
properties: {
|
|
42
|
+
index: i,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
type: "FeatureCollection",
|
|
48
|
+
grid: "hexbin",
|
|
49
|
+
geo: false,
|
|
50
|
+
features: result,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Delaunay } from "d3-delaunay";
|
|
2
|
+
const d3 = Object.assign({}, { Delaunay });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.random
|
|
6
|
+
* @description The `make.random()` function allows to create an arbitrary geoJSON grid in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function random({ step = 50, width = 1000, height = 500 } = {}) {
|
|
13
|
+
let grid = [];
|
|
14
|
+
let nb = Math.round((width / step) * (height / step));
|
|
15
|
+
for (let i = 0; i < nb; i++) {
|
|
16
|
+
grid.push([Math.random() * width, Math.random() * height]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let voronoi = d3.Delaunay.from(
|
|
20
|
+
grid,
|
|
21
|
+
(d) => d[0],
|
|
22
|
+
(d) => d[1]
|
|
23
|
+
).voronoi([0, 0, width, height]);
|
|
24
|
+
|
|
25
|
+
// build object
|
|
26
|
+
let result = grid.map((d, i) => {
|
|
27
|
+
return {
|
|
28
|
+
type: "Feature",
|
|
29
|
+
geometry: {
|
|
30
|
+
type: "Polygon",
|
|
31
|
+
coordinates: [voronoi.cellPolygon(i)],
|
|
32
|
+
},
|
|
33
|
+
properties: {
|
|
34
|
+
index: i,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
type: "FeatureCollection",
|
|
40
|
+
grid: "random",
|
|
41
|
+
geo: false,
|
|
42
|
+
features: result,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { range } from "d3-array";
|
|
2
|
+
const d3 = Object.assign({}, { range });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.square
|
|
6
|
+
* @description The `make.square()` function allows to create a square geoJSON grid in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
export function square({ step = 50, width = 1000, height = 500 } = {}) {
|
|
12
|
+
// build grid
|
|
13
|
+
let y = d3.range(0 + step / 2, height, step).reverse();
|
|
14
|
+
let x = d3.range(0 + step / 2, width, step);
|
|
15
|
+
let grid = x.map((x) => y.map((y) => [x, y])).flat();
|
|
16
|
+
|
|
17
|
+
let s = step / 2;
|
|
18
|
+
// build object
|
|
19
|
+
let result = grid.map((d, i) => {
|
|
20
|
+
return {
|
|
21
|
+
type: "Feature",
|
|
22
|
+
geometry: {
|
|
23
|
+
type: "Polygon",
|
|
24
|
+
coordinates: [
|
|
25
|
+
[
|
|
26
|
+
[d[0] - s, d[1] + s],
|
|
27
|
+
[d[0] + s, d[1] + s],
|
|
28
|
+
[d[0] + s, d[1] - s],
|
|
29
|
+
[d[0] - s, d[1] - s],
|
|
30
|
+
[d[0] - s, d[1] + s],
|
|
31
|
+
],
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
properties: {
|
|
35
|
+
index: i,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
type: "FeatureCollection",
|
|
41
|
+
grid: "square",
|
|
42
|
+
geo: false,
|
|
43
|
+
features: result,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { range, max } from "d3-array";
|
|
2
|
+
const d3 = Object.assign({}, { range, max });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function make.triangle
|
|
6
|
+
* @description The `make.triangle()` function allows to create a triangle geoJSON grid in SVG coordinates.
|
|
7
|
+
* @property {number} [step = 50] - step of the grid
|
|
8
|
+
* @property {number} [width = 1000] - width of the grid
|
|
9
|
+
* @property {number} [height = 500] - height of the grid
|
|
10
|
+
*/
|
|
11
|
+
export function triangle({ step = 50, width = 1000, height = 500 } = {}) {
|
|
12
|
+
let triangletop = (p, size) => {
|
|
13
|
+
let h = (Math.sqrt(3) / 2) * size;
|
|
14
|
+
let p1 = [p[0] + size / 2, p[1]];
|
|
15
|
+
let p2 = [p[0], p[1] - h];
|
|
16
|
+
let p3 = [p[0] - size / 2, p[1]];
|
|
17
|
+
return [p1, p2, p3, p1];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let trianglebottom = (p, size) => {
|
|
21
|
+
let h = (Math.sqrt(3) / 2) * size;
|
|
22
|
+
let p1 = [p[0] + size / 2, p[1]];
|
|
23
|
+
let p2 = [p[0], p[1] + h];
|
|
24
|
+
let p3 = [p[0] - size / 2, p[1]];
|
|
25
|
+
return [p1, p2, p3, p1];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let size = step / Math.sqrt(3);
|
|
29
|
+
let h = (Math.sqrt(3) / 2) * step;
|
|
30
|
+
|
|
31
|
+
// build grid
|
|
32
|
+
|
|
33
|
+
let y = d3.range(0, height + size, h).reverse();
|
|
34
|
+
if (y.length % 2) {
|
|
35
|
+
y.unshift(d3.max(y) + h);
|
|
36
|
+
}
|
|
37
|
+
let x = d3.range(0, width + size, step);
|
|
38
|
+
let grid = x.map((x, i) => y.map((y) => [x, y])).flat();
|
|
39
|
+
grid = grid.map((d, i) => {
|
|
40
|
+
return i % 2 == 1 ? [d[0] + step / 2, d[1]] : d;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let nb = grid.length;
|
|
44
|
+
grid = grid.concat(grid);
|
|
45
|
+
|
|
46
|
+
// build object
|
|
47
|
+
let result = grid.map((d, i) => {
|
|
48
|
+
return {
|
|
49
|
+
type: "Feature",
|
|
50
|
+
geometry: {
|
|
51
|
+
type: "Polygon",
|
|
52
|
+
coordinates:
|
|
53
|
+
i < nb ? [triangletop(d, step)] : [trianglebottom(d, step)],
|
|
54
|
+
},
|
|
55
|
+
properties: {
|
|
56
|
+
index: i,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
type: "FeatureCollection",
|
|
62
|
+
grid: "triangle",
|
|
63
|
+
geo: false,
|
|
64
|
+
features: result,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @function tool/rewind
|
|
3
|
+
* @description The `tool.rewind` function allows to generate compliant Polygon and MultiPolygon geometries. Adapted from MapBox geojson-rewind code (https://github.com/mapbox/grojson-rewind) under ISC license
|
|
4
|
+
* @see {@link https://observablehq.com/@neocartocnrs/handle-geometries}
|
|
5
|
+
* @property {object} data - a GeoJSON FeatureCollection
|
|
6
|
+
* @property {boolean} [options.outer = false] - rewind Rings Outer
|
|
7
|
+
* @property {boolean} [options.mutate = false] - mutate the Input geoJSON
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function rewind(data, options = {}) {
|
|
11
|
+
data = JSON.parse(JSON.stringify(data));
|
|
12
|
+
let outer = options.outer === false ? false : true;
|
|
13
|
+
let mutate = options.mutate === false ? false : true;
|
|
14
|
+
let geo = mutate === true ? data : JSON.parse(JSON.stringify(x));
|
|
15
|
+
for (let i = 0; i < geo.features.length; i++) {
|
|
16
|
+
if (geo.features[i].geometry.type === "Polygon") {
|
|
17
|
+
rewindRings(geo.features[i].geometry.coordinates, outer);
|
|
18
|
+
} else if (geo.features[i].geometry.type === "MultiPolygon") {
|
|
19
|
+
for (let j = 0; j < geo.features[i].geometry.coordinates.length; j++) {
|
|
20
|
+
rewindRings(geo.features[i].geometry.coordinates[j], outer);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return geo;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rewindRings(rings, outer) {
|
|
28
|
+
if (rings.length === 0) return;
|
|
29
|
+
rewindRing(rings[0], outer);
|
|
30
|
+
for (let i = 1; i < rings.length; i++) {
|
|
31
|
+
rewindRing(rings[i], !outer);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function rewindRing(ring, dir) {
|
|
36
|
+
let tArea = 0;
|
|
37
|
+
let err = 0;
|
|
38
|
+
for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
|
|
39
|
+
const k = (ring[i][0] - ring[j][0]) * (ring[j][1] + ring[i][1]);
|
|
40
|
+
const m = tArea + k;
|
|
41
|
+
err += Math.abs(tArea) >= Math.abs(k) ? tArea - m + k : k - m + tArea;
|
|
42
|
+
tArea = m;
|
|
43
|
+
}
|
|
44
|
+
if (tArea + err >= 0 !== !!dir) ring.reverse();
|
|
45
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code in this file rewinds GeoJSON polygons and multipolygons to fit d3's expectations.
|
|
3
|
+
* This is adapted by Matthieu Viry from a notebook of Philippe Rivière: https://observablehq.com/@fil/rewind
|
|
4
|
+
* which is licensed under the ISC license.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import d3 from './d3-custom';
|
|
8
|
+
import { GeoJSONFeature, GeoJSONFeatureCollection } from '../global';
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
geoTransform,
|
|
12
|
+
geoStream,
|
|
13
|
+
geoContains,
|
|
14
|
+
geoArea,
|
|
15
|
+
} = d3;
|
|
16
|
+
|
|
17
|
+
function projectPolygons(o, stream) {
|
|
18
|
+
let coordinates = [];
|
|
19
|
+
let polygon; let
|
|
20
|
+
line;
|
|
21
|
+
geoStream(
|
|
22
|
+
o,
|
|
23
|
+
stream({
|
|
24
|
+
polygonStart() {
|
|
25
|
+
coordinates.push((polygon = []));
|
|
26
|
+
},
|
|
27
|
+
polygonEnd() {},
|
|
28
|
+
lineStart() {
|
|
29
|
+
polygon.push((line = []));
|
|
30
|
+
},
|
|
31
|
+
lineEnd() {
|
|
32
|
+
line.push(line[0].slice());
|
|
33
|
+
},
|
|
34
|
+
point(x, y) {
|
|
35
|
+
line.push([x, y]);
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
if (o.type === 'Polygon') {
|
|
40
|
+
// eslint-disable-next-line prefer-destructuring
|
|
41
|
+
coordinates = coordinates[0];
|
|
42
|
+
}
|
|
43
|
+
return { ...o, coordinates, rewind: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function projectGeometry(o, stream) {
|
|
47
|
+
// eslint-disable-next-line no-nested-ternary
|
|
48
|
+
return !o
|
|
49
|
+
? null
|
|
50
|
+
: o.type === 'GeometryCollection' // eslint-disable-line no-nested-ternary
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
52
|
+
? projectGeometryCollection(o, stream)
|
|
53
|
+
: o.type === 'Polygon' || o.type === 'MultiPolygon'
|
|
54
|
+
? projectPolygons(o, stream)
|
|
55
|
+
: o;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function projectFeature(o, stream) {
|
|
59
|
+
return { ...o, geometry: projectGeometry(o.geometry, stream) };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function projectFeatureCollection(o, stream) {
|
|
63
|
+
return { ...o, features: o.features.map((f) => projectFeature(f, stream)) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function projectGeometryCollection(obj, stream) {
|
|
67
|
+
return {
|
|
68
|
+
...obj,
|
|
69
|
+
geometries: obj.geometries.map((o) => projectGeometry(o, stream)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const geoProjectSimple = function (object, projection) {
|
|
74
|
+
const { stream } = projection;
|
|
75
|
+
let project;
|
|
76
|
+
if (!stream) throw new Error('invalid projection');
|
|
77
|
+
switch (object && object.type) {
|
|
78
|
+
case 'Feature':
|
|
79
|
+
project = projectFeature;
|
|
80
|
+
break;
|
|
81
|
+
case 'FeatureCollection':
|
|
82
|
+
project = projectFeatureCollection;
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
project = projectGeometry;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return project(object, stream);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
function geoRewindStream(simple = true) {
|
|
92
|
+
let ring;
|
|
93
|
+
let polygon;
|
|
94
|
+
return geoTransform({
|
|
95
|
+
polygonStart() {
|
|
96
|
+
this.stream.polygonStart();
|
|
97
|
+
polygon = [];
|
|
98
|
+
},
|
|
99
|
+
lineStart() {
|
|
100
|
+
if (polygon) polygon.push((ring = []));
|
|
101
|
+
else this.stream.lineStart();
|
|
102
|
+
},
|
|
103
|
+
lineEnd() {
|
|
104
|
+
if (!polygon) this.stream.lineEnd();
|
|
105
|
+
},
|
|
106
|
+
point(x, y) {
|
|
107
|
+
if (polygon) ring.push([x, y]);
|
|
108
|
+
else this.stream.point(x, y);
|
|
109
|
+
},
|
|
110
|
+
polygonEnd() {
|
|
111
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
112
|
+
for (const [i, rring] of polygon.entries()) {
|
|
113
|
+
rring.push(rring[0].slice());
|
|
114
|
+
if (
|
|
115
|
+
i // eslint-disable-line no-nested-ternary
|
|
116
|
+
// a hole must contain the first point of the polygon
|
|
117
|
+
? !geoContains(
|
|
118
|
+
{ type: 'Polygon', coordinates: [rring] },
|
|
119
|
+
polygon[0][0],
|
|
120
|
+
)
|
|
121
|
+
: polygon[1]
|
|
122
|
+
// the outer ring must contain the first point of its first hole (if any)
|
|
123
|
+
? !geoContains(
|
|
124
|
+
{ type: 'Polygon', coordinates: [rring] },
|
|
125
|
+
polygon[1][0],
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
127
|
+
) && !rring.some((p) => p[0] === polygon[1][0][0] && p[1] === polygon[1][0][1])
|
|
128
|
+
// a single ring polygon must be smaller than a hemisphere (optional)
|
|
129
|
+
: simple && geoArea({ type: 'Polygon', coordinates: [rring] }) > 2 * Math.PI
|
|
130
|
+
) {
|
|
131
|
+
rring.reverse();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.stream.lineStart();
|
|
135
|
+
rring.pop();
|
|
136
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
137
|
+
for (const [x, y] of rring) this.stream.point(x, y);
|
|
138
|
+
this.stream.lineEnd();
|
|
139
|
+
}
|
|
140
|
+
this.stream.polygonEnd();
|
|
141
|
+
polygon = null;
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const rewindFeature = (
|
|
147
|
+
feature: GeoJSONFeature,
|
|
148
|
+
simple: boolean,
|
|
149
|
+
) => geoProjectSimple(feature, geoRewindStream(simple));
|
|
150
|
+
|
|
151
|
+
const rewindLayer = (
|
|
152
|
+
layer: GeoJSONFeatureCollection,
|
|
153
|
+
simple: boolean = true,
|
|
154
|
+
): GeoJSONFeatureCollection => {
|
|
155
|
+
const features = layer.features.map((feature) => rewindFeature(feature, simple));
|
|
156
|
+
return { ...layer, features };
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export default rewindLayer;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { square } from "./grid/square.js";
|
|
2
|
+
import { triangle } from "./grid/triangle.js";
|
|
3
|
+
import { dot } from "./grid/dot.js";
|
|
4
|
+
import { random } from "./grid/random.js";
|
|
5
|
+
import { diamond } from "./grid/diamond.js";
|
|
6
|
+
import { hexbin } from "./grid/hexbin.js";
|
|
7
|
+
import { h3 } from "./grid/h3.js";
|
|
8
|
+
|
|
9
|
+
export let make = {
|
|
10
|
+
square,
|
|
11
|
+
triangle,
|
|
12
|
+
dot,
|
|
13
|
+
diamond,
|
|
14
|
+
random,
|
|
15
|
+
hexbin,
|
|
16
|
+
h3,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
import { pointstogrid } from "./operator/pointstogrid.js";
|
|
20
|
+
import { polygonstogrid } from "./operator/polygonstogrid.js";
|
|
21
|
+
export let op = { pointstogrid, polygonstogrid };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @function op.pointstogrid
|
|
5
|
+
* @description The `op.pointstogrid()` function allows to count dots in polygons (e.g. grid cells)
|
|
6
|
+
* @property {object} [points] - dots geoJSON
|
|
7
|
+
* @property {object} [grid] - grid
|
|
8
|
+
* @property {string} [var = undefined] - field (absolute quantitative data only)
|
|
9
|
+
*/
|
|
10
|
+
export function pointstogrid(
|
|
11
|
+
opts = {
|
|
12
|
+
points: undefined,
|
|
13
|
+
grid: undefined,
|
|
14
|
+
var: undefined,
|
|
15
|
+
}
|
|
16
|
+
) {
|
|
17
|
+
let polys = opts.grid.features;
|
|
18
|
+
let points = opts.dots.features;
|
|
19
|
+
let count = new Array(polys.length).fill(0);
|
|
20
|
+
let nb = points.length;
|
|
21
|
+
let test = new Array(nb).fill(true);
|
|
22
|
+
polys.forEach((p, i) => {
|
|
23
|
+
points.forEach((d, j) => {
|
|
24
|
+
if (test[j]) {
|
|
25
|
+
if (booleanPointInPolygon(d, p)) {
|
|
26
|
+
if (opts.var == undefined) {
|
|
27
|
+
count[i] = count[i] + 1;
|
|
28
|
+
} else {
|
|
29
|
+
count[i] = count[i] + parseFloat(d.properties[opts.var]);
|
|
30
|
+
}
|
|
31
|
+
test[j] = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Rebuild grid
|
|
38
|
+
let output = polys
|
|
39
|
+
.map((d, i) => ({
|
|
40
|
+
type: d.type,
|
|
41
|
+
geometry: d.geometry,
|
|
42
|
+
properties: { ...d.properties, count: count[i] },
|
|
43
|
+
}))
|
|
44
|
+
.filter((d) => d.properties.count !== 0);
|
|
45
|
+
|
|
46
|
+
//const endTime = performance.now();
|
|
47
|
+
//const elapsedTime = endTime - startTime;
|
|
48
|
+
return { type: "FeatureCollection", features: output };
|
|
49
|
+
}
|