geogrid 0.0.3 → 0.0.4
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/dist/index.min.js +1 -24
- package/package.json +6 -2
- package/src/{operator → helpers}/project.js +3 -3
- package/src/index.js +9 -7
- package/src/operator/linestogrid.js +129 -0
- package/src/operator/pointstogrid.js +90 -49
- package/src/operator/polygonstogrid.js +91 -55
- package/src/operator/save.js +100 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geogrid",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Regular and irregular geoJSON grids ",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -36,10 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/neocarto/geogrid#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@turf/area": "^7.2.0",
|
|
39
40
|
"@turf/bbox": "^7.2.0",
|
|
41
|
+
"@turf/boolean-intersects": "^7.2.0",
|
|
40
42
|
"@turf/boolean-point-in-polygon": "^7.1.0",
|
|
43
|
+
"@turf/boolean-within": "^7.2.0",
|
|
41
44
|
"@turf/helpers": "^7.1.0",
|
|
42
45
|
"@turf/intersect": "^7.1.0",
|
|
46
|
+
"@turf/length": "^7.2.0",
|
|
47
|
+
"@turf/line-split": "^7.2.0",
|
|
43
48
|
"d3-array": "^3.2.4",
|
|
44
49
|
"d3-delaunay": "^6.0.4",
|
|
45
50
|
"d3-geo": "^3.1.1",
|
|
@@ -47,7 +52,6 @@
|
|
|
47
52
|
"docs": "^0.3.2-canary.0",
|
|
48
53
|
"documentation": "^14.0.3",
|
|
49
54
|
"geojson2h3": "^1.2.0",
|
|
50
|
-
"geotoolbox": "^3.0.3",
|
|
51
55
|
"h3-js": "^4.1.0",
|
|
52
56
|
"rbush": "^4.0.1",
|
|
53
57
|
"rollup": "^4.10.0"
|
|
@@ -2,12 +2,12 @@ import { geoProject } from "d3-geo-projection";
|
|
|
2
2
|
const d3 = Object.assign({}, { geoProject });
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @function
|
|
6
|
-
* @description The function `
|
|
5
|
+
* @function project
|
|
6
|
+
* @description The function `project` use geoproject from d3-geo-projection to project a geoJSON. It returns a GeoJSON FeatureCollection with coordinates in the page map.
|
|
7
7
|
* @property {object} data - a GeoJSON FeatureCollection
|
|
8
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
9
|
* @example
|
|
10
|
-
* let newGeoJSON =
|
|
10
|
+
* let newGeoJSON = project(world, { projection: d3.geoOrthographic()})
|
|
11
11
|
*/
|
|
12
12
|
export function project(data, { projection = null } = {}) {
|
|
13
13
|
return projection == null ? data : d3.geoProject(data, projection);
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { square } from "./grid/square.js";
|
|
2
|
-
export { triangle } from "./grid/triangle.js";
|
|
3
|
-
export { dot } from "./grid/dot.js";
|
|
4
|
-
export { random } from "./grid/random.js";
|
|
5
|
-
export { diamond } from "./grid/diamond.js";
|
|
1
|
+
// export { square } from "./grid/square.js";
|
|
2
|
+
// export { triangle } from "./grid/triangle.js";
|
|
3
|
+
// export { dot } from "./grid/dot.js";
|
|
4
|
+
//export { random } from "./grid/random.js";
|
|
5
|
+
// export { diamond } from "./grid/diamond.js";
|
|
6
6
|
export { hexbin } from "./grid/hexbin.js";
|
|
7
|
-
export { h3 } from "./grid/h3.js";
|
|
7
|
+
// export { h3 } from "./grid/h3.js";
|
|
8
8
|
export { pointstogrid } from "./operator/pointstogrid.js";
|
|
9
|
-
export { polygonstogrid } from "./operator/polygonstogrid.js";
|
|
9
|
+
// export { polygonstogrid } from "./operator/polygonstogrid.js";
|
|
10
|
+
// export { linestogrid } from "./operator/linestogrid.js";
|
|
11
|
+
// export { project } from "./helpers/project.js";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { bbox } from "@turf/bbox";
|
|
2
|
+
import { lineSplit } from "@turf/line-split";
|
|
3
|
+
import { length as turfLength } from "@turf/length";
|
|
4
|
+
import { booleanIntersects } from "@turf/boolean-intersects";
|
|
5
|
+
import RBush from "rbush";
|
|
6
|
+
import { min, max, median, mean, sum } from "d3-array";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @function linestogrid
|
|
10
|
+
* @description Assigns lines to a grid and computes statistics per cell.
|
|
11
|
+
* Supports weighted lengths or simple counts.
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
* @property {object} [grid] - GeoJSON grid
|
|
14
|
+
* @property {object} [lines] - GeoJSON lines (LineString or MultiLineString)
|
|
15
|
+
* @property {string} [var] - Field for weighting length (optional)
|
|
16
|
+
* @property {boolean} [values=false] - Include array of raw values/IDs
|
|
17
|
+
* @property {boolean} [sum=true] - Compute sum of weighted lengths
|
|
18
|
+
* @property {boolean} [median=false] - Compute median
|
|
19
|
+
* @property {boolean} [min=false] - Compute minimum
|
|
20
|
+
* @property {boolean} [max=false] - Compute maximum
|
|
21
|
+
* @property {boolean} [mean=false] - Compute mean
|
|
22
|
+
*/
|
|
23
|
+
export function linestogrid(opts = {}) {
|
|
24
|
+
const {
|
|
25
|
+
grid,
|
|
26
|
+
lines,
|
|
27
|
+
var: varField,
|
|
28
|
+
values: includeValues = false,
|
|
29
|
+
sum: calcSum = true,
|
|
30
|
+
median: calcMedian = false,
|
|
31
|
+
min: calcMin = false,
|
|
32
|
+
max: calcMax = false,
|
|
33
|
+
mean: calcMean = false,
|
|
34
|
+
} = opts;
|
|
35
|
+
|
|
36
|
+
const t0 = performance.now();
|
|
37
|
+
const gridFeatures = grid.features;
|
|
38
|
+
const lineFeatures = lines.features;
|
|
39
|
+
const hasVar = varField !== undefined && varField !== null;
|
|
40
|
+
|
|
41
|
+
const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
|
|
42
|
+
|
|
43
|
+
// ---- 1. Spatial index RBush ----
|
|
44
|
+
const tree = new RBush();
|
|
45
|
+
const items = gridFeatures.map((g, i) => {
|
|
46
|
+
const [minX, minY, maxX, maxY] = bbox(g);
|
|
47
|
+
return { minX, minY, maxX, maxY, i };
|
|
48
|
+
});
|
|
49
|
+
tree.load(items);
|
|
50
|
+
|
|
51
|
+
// ---- 2. Prepare stats storage per cell ----
|
|
52
|
+
const gridStats = new Map();
|
|
53
|
+
gridFeatures.forEach((g, i) => {
|
|
54
|
+
gridStats.set(i, { countSet: new Set(), valuesList: [], numericList: [] });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---- 3. Loop over lines ----
|
|
58
|
+
lineFeatures.forEach((line, i) => {
|
|
59
|
+
const totalLen = turfLength(line);
|
|
60
|
+
if (totalLen === 0) return;
|
|
61
|
+
|
|
62
|
+
const val = hasVar ? parseFloat(line.properties?.[varField]) || 0 : 1;
|
|
63
|
+
const [minX, minY, maxX, maxY] = bbox(line);
|
|
64
|
+
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
65
|
+
|
|
66
|
+
for (const cand of candidates) {
|
|
67
|
+
const g = gridbyindex.get(cand.i);
|
|
68
|
+
if (!booleanIntersects(g, line)) continue;
|
|
69
|
+
|
|
70
|
+
const split = lineSplit(line, g);
|
|
71
|
+
if (!split || split.features.length === 0) continue;
|
|
72
|
+
|
|
73
|
+
let lenInside = 0;
|
|
74
|
+
split.features.forEach((seg) => {
|
|
75
|
+
const segLen = turfLength(seg);
|
|
76
|
+
if (segLen > 0 && booleanIntersects(g, seg)) {
|
|
77
|
+
lenInside += segLen;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (lenInside > 0) {
|
|
82
|
+
const stats = gridStats.get(cand.i);
|
|
83
|
+
stats.countSet.add(i); // unique line IDs
|
|
84
|
+
|
|
85
|
+
if (includeValues)
|
|
86
|
+
stats.valuesList.push(hasVar ? line.properties[varField] : i);
|
|
87
|
+
|
|
88
|
+
if (hasVar) stats.numericList.push(val * (lenInside / totalLen));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ---- 4. Build final GeoJSON ----
|
|
94
|
+
const result = {
|
|
95
|
+
type: "FeatureCollection",
|
|
96
|
+
features: gridFeatures
|
|
97
|
+
.map((g, i) => {
|
|
98
|
+
const stats = gridStats.get(i);
|
|
99
|
+
const numericValues = stats.numericList;
|
|
100
|
+
const values = stats.valuesList;
|
|
101
|
+
const count = stats.countSet.size;
|
|
102
|
+
|
|
103
|
+
if (count === 0) return null;
|
|
104
|
+
|
|
105
|
+
const cellProps = { count };
|
|
106
|
+
|
|
107
|
+
if (hasVar && numericValues.length > 0) {
|
|
108
|
+
if (calcSum) cellProps.sum = sum(numericValues);
|
|
109
|
+
if (calcMean) cellProps.mean = mean(numericValues);
|
|
110
|
+
if (calcMedian) cellProps.median = median(numericValues);
|
|
111
|
+
if (calcMin) cellProps.min = min(numericValues);
|
|
112
|
+
if (calcMax) cellProps.max = max(numericValues);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (includeValues) cellProps.values = values;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
type: g.type,
|
|
119
|
+
properties: { ...g.properties, ...cellProps },
|
|
120
|
+
geometry: g.geometry,
|
|
121
|
+
};
|
|
122
|
+
})
|
|
123
|
+
.filter((f) => f !== null),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const t1 = performance.now();
|
|
127
|
+
console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
@@ -1,72 +1,113 @@
|
|
|
1
1
|
import RBush from "rbush";
|
|
2
2
|
import { bbox } from "@turf/bbox";
|
|
3
3
|
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
|
|
4
|
-
import {
|
|
5
|
-
import { groups } from "d3-array";
|
|
6
|
-
const d3 = Object.assign({}, { geoPath, groups });
|
|
4
|
+
import { min, max, median, mean, sum } from "d3-array";
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
|
-
* @function
|
|
10
|
-
* @description
|
|
11
|
-
* @
|
|
12
|
-
* @property {object} [
|
|
13
|
-
* @property {
|
|
7
|
+
* @function pointstogrid
|
|
8
|
+
* @description Assigns points to grid cells and computes statistics per cell.
|
|
9
|
+
* @param {object} opts
|
|
10
|
+
* @property {object} [points] - GeoJSON points
|
|
11
|
+
* @property {object} [grid] - GeoJSON grid (polygons)
|
|
12
|
+
* @property {string} [var] - Field for weighting points (optional)
|
|
13
|
+
* @property {boolean} [values=false] - Include array of raw values/IDs
|
|
14
|
+
* @property {boolean} [sum=true] - Compute sum
|
|
15
|
+
* @property {boolean} [median=false] - Compute median
|
|
16
|
+
* @property {boolean} [min=false] - Compute minimum
|
|
17
|
+
* @property {boolean} [max=false] - Compute maximum
|
|
18
|
+
* @property {boolean} [mean=false] - Compute mean
|
|
14
19
|
*/
|
|
15
|
-
export function pointstogrid(
|
|
16
|
-
|
|
17
|
-
points
|
|
18
|
-
grid
|
|
19
|
-
var:
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
export function pointstogrid(opts = {}) {
|
|
21
|
+
const {
|
|
22
|
+
points,
|
|
23
|
+
grid,
|
|
24
|
+
var: varField,
|
|
25
|
+
values: includeValues = false,
|
|
26
|
+
sum: calcSum = true,
|
|
27
|
+
median: calcMedian = false,
|
|
28
|
+
min: calcMin = false,
|
|
29
|
+
max: calcMax = false,
|
|
30
|
+
mean: calcMean = false,
|
|
31
|
+
} = opts;
|
|
32
|
+
|
|
22
33
|
const t0 = performance.now();
|
|
34
|
+
const gridFeatures = grid.features;
|
|
35
|
+
const pointFeatures = points.features;
|
|
36
|
+
const hasVar = varField !== undefined && varField !== null;
|
|
23
37
|
|
|
24
|
-
const
|
|
25
|
-
const points = opts.points.features;
|
|
26
|
-
const count = new Array(polys.length).fill(0);
|
|
38
|
+
const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
|
|
27
39
|
|
|
28
|
-
// ---- 1.
|
|
40
|
+
// ---- 1. Spatial index for polygons ----
|
|
29
41
|
const tree = new RBush();
|
|
30
|
-
const items =
|
|
31
|
-
const [minX, minY, maxX, maxY] = bbox(
|
|
42
|
+
const items = gridFeatures.map((g, i) => {
|
|
43
|
+
const [minX, minY, maxX, maxY] = bbox(g);
|
|
32
44
|
return { minX, minY, maxX, maxY, i };
|
|
33
45
|
});
|
|
34
46
|
tree.load(items);
|
|
35
47
|
|
|
36
|
-
// ---- 2.
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
// ---- 2. Prepare stats storage per cell ----
|
|
49
|
+
const gridStats = new Map();
|
|
50
|
+
gridFeatures.forEach((g, i) => {
|
|
51
|
+
gridStats.set(i, { countSet: new Set(), valuesList: [], numericList: [] });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ---- 3. Loop over points ----
|
|
55
|
+
pointFeatures.forEach((pt, i) => {
|
|
56
|
+
const x = pt.geometry.coordinates[0];
|
|
57
|
+
const y = pt.geometry.coordinates[1];
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
const candidates = tree.search({
|
|
42
|
-
minX: x,
|
|
43
|
-
minY: y,
|
|
44
|
-
maxX: x,
|
|
45
|
-
maxY: y,
|
|
46
|
-
});
|
|
59
|
+
const candidates = tree.search({ minX: x, minY: y, maxX: x, maxY: y });
|
|
47
60
|
|
|
48
|
-
// Tester seulement ces candidats
|
|
49
61
|
for (const cand of candidates) {
|
|
50
|
-
const poly =
|
|
51
|
-
if (booleanPointInPolygon(pt, poly))
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
const poly = gridbyindex.get(cand.i);
|
|
63
|
+
if (!booleanPointInPolygon(pt, poly)) continue;
|
|
64
|
+
|
|
65
|
+
const stats = gridStats.get(cand.i);
|
|
66
|
+
stats.countSet.add(i);
|
|
67
|
+
|
|
68
|
+
const val = hasVar ? pt.properties[varField] : 1;
|
|
69
|
+
|
|
70
|
+
if (includeValues)
|
|
71
|
+
stats.valuesList.push(hasVar ? pt.properties[varField] : i);
|
|
72
|
+
if (hasVar) stats.numericList.push(parseFloat(val) || 0);
|
|
73
|
+
break; // point counted once per cell
|
|
57
74
|
}
|
|
58
75
|
});
|
|
59
76
|
|
|
60
|
-
// ----
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
// ---- 4. Build final GeoJSON ----
|
|
78
|
+
const result = {
|
|
79
|
+
type: "FeatureCollection",
|
|
80
|
+
features: gridFeatures
|
|
81
|
+
.map((g, i) => {
|
|
82
|
+
const stats = gridStats.get(i);
|
|
83
|
+
const numericValues = stats.numericList;
|
|
84
|
+
const values = stats.valuesList;
|
|
85
|
+
const count = stats.countSet.size;
|
|
86
|
+
|
|
87
|
+
if (count === 0) return null;
|
|
88
|
+
|
|
89
|
+
const cellProps = { count };
|
|
90
|
+
|
|
91
|
+
if (hasVar && numericValues.length > 0) {
|
|
92
|
+
if (calcSum) cellProps.sum = sum(numericValues);
|
|
93
|
+
if (calcMean) cellProps.mean = mean(numericValues);
|
|
94
|
+
if (calcMedian) cellProps.median = median(numericValues);
|
|
95
|
+
if (calcMin) cellProps.min = min(numericValues);
|
|
96
|
+
if (calcMax) cellProps.max = max(numericValues);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (includeValues) cellProps.values = values;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
type: g.type,
|
|
103
|
+
properties: { ...g.properties, ...cellProps },
|
|
104
|
+
geometry: g.geometry,
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.filter((f) => f !== null),
|
|
108
|
+
};
|
|
68
109
|
|
|
69
110
|
const t1 = performance.now();
|
|
70
|
-
console.log(`
|
|
71
|
-
return
|
|
111
|
+
console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
112
|
+
return result;
|
|
72
113
|
}
|
|
@@ -1,90 +1,126 @@
|
|
|
1
1
|
import { featureCollection } from "@turf/helpers";
|
|
2
2
|
import { bbox } from "@turf/bbox";
|
|
3
3
|
import { intersect } from "@turf/intersect";
|
|
4
|
+
import area from "@turf/area";
|
|
4
5
|
import RBush from "rbush";
|
|
5
|
-
import {
|
|
6
|
-
import { groups } from "d3-array";
|
|
7
|
-
|
|
8
|
-
const d3 = Object.assign({}, { geoPath, groups });
|
|
6
|
+
import { min, max, median, mean, sum } from "d3-array";
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
* @function
|
|
12
|
-
* @description
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
15
|
-
* @property {
|
|
9
|
+
* @function polygonstogrid
|
|
10
|
+
* @description Assign polygons to a grid and compute statistics per cell.
|
|
11
|
+
* Optimized and removes cells with count = 0
|
|
12
|
+
* @param {object} opts
|
|
13
|
+
* @property {object} [grid] - GeoJSON grid
|
|
14
|
+
* @property {object} [polygons] - GeoJSON polygons to assign
|
|
15
|
+
* @property {string} [var] - Field for weighting (optional)
|
|
16
|
+
* @property {boolean} [values=false] - Include array of raw values
|
|
17
|
+
* @property {boolean} [sum=true] - Compute sum
|
|
18
|
+
* @property {boolean} [median=false] - Compute median
|
|
19
|
+
* @property {boolean} [min=false] - Compute minimum
|
|
20
|
+
* @property {boolean} [max=false] - Compute maximum
|
|
21
|
+
* @property {boolean} [mean=false] - Compute mean
|
|
16
22
|
*/
|
|
17
|
-
export function polygonstogrid(
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
export function polygonstogrid(opts = {}) {
|
|
24
|
+
const {
|
|
25
|
+
grid,
|
|
26
|
+
polygons,
|
|
27
|
+
var: varField,
|
|
28
|
+
values: includeValues = false,
|
|
29
|
+
sum: calcSum = true,
|
|
30
|
+
median: calcMedian = false,
|
|
31
|
+
min: calcMin = false,
|
|
32
|
+
max: calcMax = false,
|
|
33
|
+
mean: calcMean = false,
|
|
34
|
+
} = opts;
|
|
35
|
+
|
|
20
36
|
const t0 = performance.now();
|
|
21
37
|
|
|
22
|
-
const
|
|
23
|
-
const polys =
|
|
24
|
-
const
|
|
38
|
+
const gridFeatures = grid.features;
|
|
39
|
+
const polys = polygons.features;
|
|
40
|
+
const hasVar = varField !== undefined && varField !== null;
|
|
41
|
+
|
|
42
|
+
const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
|
|
25
43
|
|
|
26
|
-
// ---- 1.
|
|
44
|
+
// ---- 1. Spatial index RBush ----
|
|
27
45
|
const tree = new RBush();
|
|
28
|
-
const items =
|
|
46
|
+
const items = gridFeatures.map((g, i) => {
|
|
29
47
|
const [minX, minY, maxX, maxY] = bbox(g);
|
|
30
48
|
return { minX, minY, maxX, maxY, i };
|
|
31
49
|
});
|
|
32
50
|
tree.load(items);
|
|
33
51
|
|
|
34
|
-
|
|
35
|
-
const
|
|
52
|
+
// ---- 2. Prepare stats storage per cell ----
|
|
53
|
+
const gridStats = new Map();
|
|
54
|
+
gridFeatures.forEach((g, i) => {
|
|
55
|
+
gridStats.set(i, { countSet: new Set(), valuesList: [], numericList: [] });
|
|
56
|
+
});
|
|
36
57
|
|
|
37
|
-
// ----
|
|
58
|
+
// ---- 3. Loop over polygons ----
|
|
38
59
|
polys.forEach((p, i) => {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
60
|
+
const polygonArea = area(p);
|
|
61
|
+
const val = hasVar ? parseFloat(p.properties?.[varField]) || 0 : 1;
|
|
41
62
|
|
|
42
|
-
|
|
63
|
+
const [minX, minY, maxX, maxY] = bbox(p);
|
|
43
64
|
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
44
65
|
|
|
45
|
-
// 4. Tester seulement les candidats
|
|
46
66
|
for (const cand of candidates) {
|
|
47
67
|
const g = gridbyindex.get(cand.i);
|
|
48
68
|
const f = intersect(featureCollection([p, g]));
|
|
49
|
-
if (f)
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
if (!f) continue;
|
|
70
|
+
|
|
71
|
+
const areapiece = area(f);
|
|
72
|
+
const fraction = areapiece / polygonArea;
|
|
73
|
+
|
|
74
|
+
const stats = gridStats.get(cand.i);
|
|
75
|
+
stats.countSet.add(i);
|
|
76
|
+
|
|
77
|
+
if (includeValues) {
|
|
78
|
+
stats.valuesList.push(hasVar ? p.properties[varField] : 1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (hasVar) {
|
|
82
|
+
stats.numericList.push(val * fraction);
|
|
52
83
|
}
|
|
53
84
|
}
|
|
54
85
|
});
|
|
55
86
|
|
|
56
|
-
// ----
|
|
57
|
-
const accessor = new Map(
|
|
58
|
-
polys.map((d, i) => [
|
|
59
|
-
i,
|
|
60
|
-
opts.var ? parseFloat(d.properties[opts.var]) || 0 : 1,
|
|
61
|
-
])
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const datagrid = d3.groups(arr, (d) => d[1]);
|
|
65
|
-
|
|
66
|
-
function getsum(cell) {
|
|
67
|
-
let sum = 0;
|
|
68
|
-
cell[1].forEach((d) => {
|
|
69
|
-
sum += accessor.get(d[0]) * d[2];
|
|
70
|
-
});
|
|
71
|
-
return sum === 0 ? undefined : sum;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---- 6. Assemblage du résultat ----
|
|
87
|
+
// ---- 4. Build final GeoJSON ----
|
|
75
88
|
const result = {
|
|
76
89
|
type: "FeatureCollection",
|
|
77
|
-
features:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
features: gridFeatures
|
|
91
|
+
.map((g, i) => {
|
|
92
|
+
const stats = gridStats.get(i);
|
|
93
|
+
const numericValues = stats.numericList;
|
|
94
|
+
const values = stats.valuesList;
|
|
95
|
+
const count = stats.countSet.size;
|
|
96
|
+
|
|
97
|
+
if (count === 0) return null;
|
|
98
|
+
|
|
99
|
+
const cellProps = { count };
|
|
100
|
+
|
|
101
|
+
if (hasVar && numericValues.length > 0) {
|
|
102
|
+
if (calcSum) cellProps.sum = sum(numericValues);
|
|
103
|
+
if (calcMean) cellProps.mean = mean(numericValues);
|
|
104
|
+
if (calcMedian) cellProps.median = median(numericValues);
|
|
105
|
+
if (calcMin) cellProps.min = min(numericValues);
|
|
106
|
+
if (calcMax) cellProps.max = max(numericValues);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (includeValues) {
|
|
110
|
+
cellProps.values = values;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
type: g.type,
|
|
115
|
+
properties: { ...g.properties, ...cellProps },
|
|
116
|
+
geometry: g.geometry,
|
|
117
|
+
};
|
|
118
|
+
})
|
|
119
|
+
.filter((f) => f !== null),
|
|
85
120
|
};
|
|
86
121
|
|
|
87
122
|
const t1 = performance.now();
|
|
88
|
-
console.log(`
|
|
123
|
+
console.log(`Optimized execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
124
|
+
|
|
89
125
|
return result;
|
|
90
126
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { featureCollection } from "@turf/helpers";
|
|
2
|
+
import { bbox } from "@turf/bbox";
|
|
3
|
+
import { intersect } from "@turf/intersect";
|
|
4
|
+
import RBush from "rbush";
|
|
5
|
+
import { geoPath } from "d3-geo";
|
|
6
|
+
import { groups } from "d3-array";
|
|
7
|
+
|
|
8
|
+
const d3 = Object.assign({}, { geoPath, groups });
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @function polygonstogrid
|
|
12
|
+
* @description Répartit des polygones (optionnellement pondérés) sur une grille polygonale.
|
|
13
|
+
* @property {object} [polygons] - GeoJSON de polygones
|
|
14
|
+
* @property {object} [grid] - GeoJSON de grille
|
|
15
|
+
* @property {string} [var] - Champ de pondération (facultatif)
|
|
16
|
+
*/
|
|
17
|
+
export function polygonstogrid(
|
|
18
|
+
opts = { grid: undefined, polygons: undefined, var: undefined }
|
|
19
|
+
) {
|
|
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);
|
|
33
|
+
|
|
34
|
+
const arr = [];
|
|
35
|
+
const path = d3.geoPath();
|
|
36
|
+
|
|
37
|
+
// ---- 2. Boucle sur les polygones ----
|
|
38
|
+
polys.forEach((p, i) => {
|
|
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);
|
|
48
|
+
const f = intersect(featureCollection([p, g]));
|
|
49
|
+
if (f) {
|
|
50
|
+
const areapiece = path.area(f);
|
|
51
|
+
arr.push([i, cand.i, areapiece / area]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ---- 5. Calcul des valeurs ----
|
|
57
|
+
const hasVar = opts.var !== undefined && opts.var !== null;
|
|
58
|
+
|
|
59
|
+
const accessor = new Map(
|
|
60
|
+
polys.map((d, i) => [
|
|
61
|
+
i,
|
|
62
|
+
hasVar ? parseFloat(d.properties[opts.var]) || 0 : 1,
|
|
63
|
+
])
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const datagrid = d3.groups(arr, (d) => d[1]);
|
|
67
|
+
|
|
68
|
+
function getsum(cell) {
|
|
69
|
+
const vals = cell[1];
|
|
70
|
+
if (hasVar) {
|
|
71
|
+
// Cas pondéré
|
|
72
|
+
let sum = 0;
|
|
73
|
+
vals.forEach((d) => {
|
|
74
|
+
sum += accessor.get(d[0]) * d[2];
|
|
75
|
+
});
|
|
76
|
+
return sum === 0 ? undefined : sum;
|
|
77
|
+
} else {
|
|
78
|
+
// Cas non pondéré : juste compter le nombre de polygones intersectés
|
|
79
|
+
const uniquePolygons = new Set(vals.map((d) => d[0]));
|
|
80
|
+
return uniquePolygons.size;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- 6. Assemblage du résultat ----
|
|
85
|
+
const result = {
|
|
86
|
+
type: "FeatureCollection",
|
|
87
|
+
features: datagrid.map(([key, vals]) => {
|
|
88
|
+
const tmp = gridbyindex.get(key);
|
|
89
|
+
return {
|
|
90
|
+
type: tmp.type,
|
|
91
|
+
properties: { ...tmp.properties, sum: getsum([key, vals]) },
|
|
92
|
+
geometry: tmp.geometry,
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const t1 = performance.now();
|
|
98
|
+
console.log(`Temps d'exécution: ${(t1 - t0).toFixed(2)} ms`);
|
|
99
|
+
return result;
|
|
100
|
+
}
|