geogrid 0.0.1 → 0.0.3
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/README.md +6 -0
- package/dist/index.min.js +2 -2
- package/package.json +10 -5
- package/src/grid/diamond.js +37 -13
- package/src/grid/dot.js +26 -10
- package/src/grid/h3.js +3 -3
- package/src/grid/hexbin.js +58 -31
- package/src/grid/random.js +29 -8
- package/src/grid/square.js +35 -10
- package/src/grid/triangle.js +92 -24
- package/src/helpers/createSteppedArray.js +7 -0
- package/src/index.js +9 -21
- package/src/operator/pointstogrid.js +44 -21
- package/src/operator/polygonstogrid.js +56 -34
- package/src/operator/project.js +14 -0
- package/src/helpers/rewind.js +0 -45
- package/src/helpers/rewind2.ts +0 -159
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import RBush from "rbush";
|
|
2
|
+
import { bbox } from "@turf/bbox";
|
|
1
3
|
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
|
|
4
|
+
import { geoPath } from "d3-geo";
|
|
5
|
+
import { groups } from "d3-array";
|
|
6
|
+
const d3 = Object.assign({}, { geoPath, groups });
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* @function op.pointstogrid
|
|
@@ -14,28 +19,46 @@ export function pointstogrid(
|
|
|
14
19
|
var: undefined,
|
|
15
20
|
}
|
|
16
21
|
) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
const t0 = performance.now();
|
|
23
|
+
|
|
24
|
+
const polys = opts.grid.features;
|
|
25
|
+
const points = opts.points.features;
|
|
26
|
+
const count = new Array(polys.length).fill(0);
|
|
27
|
+
|
|
28
|
+
// ---- 1. Construire l’index spatial des polygones ----
|
|
29
|
+
const tree = new RBush();
|
|
30
|
+
const items = polys.map((p, i) => {
|
|
31
|
+
const [minX, minY, maxX, maxY] = bbox(p);
|
|
32
|
+
return { minX, minY, maxX, maxY, i };
|
|
33
|
+
});
|
|
34
|
+
tree.load(items);
|
|
35
|
+
|
|
36
|
+
// ---- 2. Boucle sur les points ----
|
|
37
|
+
points.forEach((pt) => {
|
|
38
|
+
const [x, y] = pt.geometry.coordinates;
|
|
39
|
+
|
|
40
|
+
// Trouver les polygones candidats dont la bbox contient le point
|
|
41
|
+
const candidates = tree.search({
|
|
42
|
+
minX: x,
|
|
43
|
+
minY: y,
|
|
44
|
+
maxX: x,
|
|
45
|
+
maxY: y,
|
|
34
46
|
});
|
|
47
|
+
|
|
48
|
+
// Tester seulement ces candidats
|
|
49
|
+
for (const cand of candidates) {
|
|
50
|
+
const poly = polys[cand.i];
|
|
51
|
+
if (booleanPointInPolygon(pt, poly)) {
|
|
52
|
+
const value =
|
|
53
|
+
opts.var === undefined ? 1 : parseFloat(pt.properties[opts.var]) || 0;
|
|
54
|
+
count[cand.i] += value;
|
|
55
|
+
break; // Si un point ne doit appartenir qu'à une cellule
|
|
56
|
+
}
|
|
57
|
+
}
|
|
35
58
|
});
|
|
36
59
|
|
|
37
|
-
//
|
|
38
|
-
|
|
60
|
+
// ---- 3. Reconstituer la grille ----
|
|
61
|
+
const output = polys
|
|
39
62
|
.map((d, i) => ({
|
|
40
63
|
type: d.type,
|
|
41
64
|
geometry: d.geometry,
|
|
@@ -43,7 +66,7 @@ export function pointstogrid(
|
|
|
43
66
|
}))
|
|
44
67
|
.filter((d) => d.properties.count !== 0);
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
const t1 = performance.now();
|
|
70
|
+
console.log(`Temps d'exécution: ${(t1 - t0).toFixed(2)} ms`);
|
|
48
71
|
return { type: "FeatureCollection", features: output };
|
|
49
72
|
}
|
|
@@ -1,68 +1,90 @@
|
|
|
1
1
|
import { featureCollection } from "@turf/helpers";
|
|
2
|
+
import { bbox } from "@turf/bbox";
|
|
2
3
|
import { intersect } from "@turf/intersect";
|
|
4
|
+
import RBush from "rbush";
|
|
3
5
|
import { geoPath } from "d3-geo";
|
|
4
6
|
import { groups } from "d3-array";
|
|
7
|
+
|
|
5
8
|
const d3 = Object.assign({}, { geoPath, groups });
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
|
-
* @function op.
|
|
9
|
-
* @description
|
|
10
|
-
* @property {object} [
|
|
11
|
+
* @function op.pointstogrid
|
|
12
|
+
* @description Count points (optionally weighted) within polygons (e.g. grid cells)
|
|
13
|
+
* @property {object} [points] - points geoJSON
|
|
11
14
|
* @property {object} [grid] - grid geoJSON
|
|
12
|
-
* @property {string} [var = undefined] - field
|
|
15
|
+
* @property {string} [var = undefined] - field for point weight
|
|
13
16
|
*/
|
|
14
|
-
|
|
15
17
|
export function polygonstogrid(
|
|
16
|
-
opts = {
|
|
17
|
-
grid: undefined,
|
|
18
|
-
polygons: undefined,
|
|
19
|
-
var: undefined,
|
|
20
|
-
}
|
|
18
|
+
opts = { grid: undefined, polygons: undefined, var: undefined }
|
|
21
19
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const t0 = performance.now();
|
|
21
|
+
|
|
22
|
+
const grid = opts.grid.features;
|
|
23
|
+
const polys = opts.polygons.features;
|
|
24
|
+
const gridbyindex = new Map(grid.map((d, i) => [i, d]));
|
|
25
|
+
|
|
26
|
+
// ---- 1. Créer l’index spatial RBush ----
|
|
27
|
+
const tree = new RBush();
|
|
28
|
+
const items = grid.map((g, i) => {
|
|
29
|
+
const [minX, minY, maxX, maxY] = bbox(g);
|
|
30
|
+
return { minX, minY, maxX, maxY, i };
|
|
31
|
+
});
|
|
32
|
+
tree.load(items);
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
const arr = [];
|
|
35
|
+
const path = d3.geoPath();
|
|
36
|
+
|
|
37
|
+
// ---- 2. Boucle sur les polygones ----
|
|
27
38
|
polys.forEach((p, i) => {
|
|
28
|
-
const area =
|
|
29
|
-
|
|
39
|
+
const area = path.area(p);
|
|
40
|
+
const [minX, minY, maxX, maxY] = bbox(p);
|
|
41
|
+
|
|
42
|
+
// 3. Chercher uniquement les cellules qui peuvent intersecter
|
|
43
|
+
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
44
|
+
|
|
45
|
+
// 4. Tester seulement les candidats
|
|
46
|
+
for (const cand of candidates) {
|
|
47
|
+
const g = gridbyindex.get(cand.i);
|
|
30
48
|
const f = intersect(featureCollection([p, g]));
|
|
31
|
-
if (f
|
|
32
|
-
|
|
33
|
-
arr.push([i,
|
|
49
|
+
if (f) {
|
|
50
|
+
const areapiece = path.area(f);
|
|
51
|
+
arr.push([i, cand.i, areapiece / area]);
|
|
34
52
|
}
|
|
35
|
-
}
|
|
53
|
+
}
|
|
36
54
|
});
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
56
|
+
// ---- 5. Calcul des valeurs ----
|
|
57
|
+
const accessor = new Map(
|
|
58
|
+
polys.map((d, i) => [
|
|
59
|
+
i,
|
|
60
|
+
opts.var ? parseFloat(d.properties[opts.var]) || 0 : 1,
|
|
61
|
+
])
|
|
62
|
+
);
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
const datagrid = d3.groups(arr, (d) => d[1]);
|
|
48
65
|
|
|
49
66
|
function getsum(cell) {
|
|
50
67
|
let sum = 0;
|
|
51
68
|
cell[1].forEach((d) => {
|
|
52
69
|
sum += accessor.get(d[0]) * d[2];
|
|
53
70
|
});
|
|
54
|
-
return sum
|
|
71
|
+
return sum === 0 ? undefined : sum;
|
|
55
72
|
}
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
// ---- 6. Assemblage du résultat ----
|
|
75
|
+
const result = {
|
|
58
76
|
type: "FeatureCollection",
|
|
59
|
-
features: datagrid.map((
|
|
60
|
-
|
|
77
|
+
features: datagrid.map(([key, vals]) => {
|
|
78
|
+
const tmp = gridbyindex.get(key);
|
|
61
79
|
return {
|
|
62
80
|
type: tmp.type,
|
|
63
|
-
properties: { ...tmp.properties, sum: getsum(
|
|
81
|
+
properties: { ...tmp.properties, sum: getsum([key, vals]) },
|
|
64
82
|
geometry: tmp.geometry,
|
|
65
83
|
};
|
|
66
84
|
}),
|
|
67
85
|
};
|
|
86
|
+
|
|
87
|
+
const t1 = performance.now();
|
|
88
|
+
console.log(`Temps d'exécution: ${(t1 - t0).toFixed(2)} ms`);
|
|
89
|
+
return result;
|
|
68
90
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { geoProject } from "d3-geo-projection";
|
|
2
|
+
const d3 = Object.assign({}, { geoProject });
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @function tool/project
|
|
6
|
+
* @description The function `tool.project` use geoproject from d3-geo-projection to project a geoJSON. It returns a GeoJSON FeatureCollection with coordinates in the page map.
|
|
7
|
+
* @property {object} data - a GeoJSON FeatureCollection
|
|
8
|
+
* @property {function} options.projection - projection definition. See [d3-geo](https://github.com/d3/d3-geo) & [d3-geo-projection](https://github.com/d3/d3-geo-projection)
|
|
9
|
+
* @example
|
|
10
|
+
* let newGeoJSON = geoviz.tool.project(world, { projection: d3.geoOrthographic()})
|
|
11
|
+
*/
|
|
12
|
+
export function project(data, { projection = null } = {}) {
|
|
13
|
+
return projection == null ? data : d3.geoProject(data, projection);
|
|
14
|
+
}
|
package/src/helpers/rewind.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
}
|
package/src/helpers/rewind2.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
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;
|