geogrid 0.0.1 → 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/README.md +6 -0
- package/dist/index.min.js +1 -24
- package/package.json +14 -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/helpers/project.js +14 -0
- package/src/index.js +11 -21
- package/src/operator/linestogrid.js +129 -0
- package/src/operator/pointstogrid.js +106 -42
- package/src/operator/polygonstogrid.js +112 -54
- package/src/operator/save.js +100 -0
- package/src/helpers/rewind.js +0 -45
- package/src/helpers/rewind2.ts +0 -159
|
@@ -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,49 +1,113 @@
|
|
|
1
|
+
import RBush from "rbush";
|
|
2
|
+
import { bbox } from "@turf/bbox";
|
|
1
3
|
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
|
|
4
|
+
import { min, max, median, mean, sum } from "d3-array";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
|
-
* @function
|
|
5
|
-
* @description
|
|
6
|
-
* @
|
|
7
|
-
* @property {object} [
|
|
8
|
-
* @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
|
|
9
19
|
*/
|
|
10
|
-
export function pointstogrid(
|
|
11
|
-
|
|
12
|
-
points
|
|
13
|
-
grid
|
|
14
|
-
var:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
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
|
+
|
|
33
|
+
const t0 = performance.now();
|
|
34
|
+
const gridFeatures = grid.features;
|
|
35
|
+
const pointFeatures = points.features;
|
|
36
|
+
const hasVar = varField !== undefined && varField !== null;
|
|
37
|
+
|
|
38
|
+
const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
|
|
39
|
+
|
|
40
|
+
// ---- 1. Spatial index for polygons ----
|
|
41
|
+
const tree = new RBush();
|
|
42
|
+
const items = gridFeatures.map((g, i) => {
|
|
43
|
+
const [minX, minY, maxX, maxY] = bbox(g);
|
|
44
|
+
return { minX, minY, maxX, maxY, i };
|
|
45
|
+
});
|
|
46
|
+
tree.load(items);
|
|
47
|
+
|
|
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];
|
|
58
|
+
|
|
59
|
+
const candidates = tree.search({ minX: x, minY: y, maxX: x, maxY: y });
|
|
60
|
+
|
|
61
|
+
for (const cand of candidates) {
|
|
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
|
|
74
|
+
}
|
|
35
75
|
});
|
|
36
76
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
};
|
|
109
|
+
|
|
110
|
+
const t1 = performance.now();
|
|
111
|
+
console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
112
|
+
return result;
|
|
49
113
|
}
|
|
@@ -1,68 +1,126 @@
|
|
|
1
1
|
import { featureCollection } from "@turf/helpers";
|
|
2
|
+
import { bbox } from "@turf/bbox";
|
|
2
3
|
import { intersect } from "@turf/intersect";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import area from "@turf/area";
|
|
5
|
+
import RBush from "rbush";
|
|
6
|
+
import { min, max, median, mean, sum } from "d3-array";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* @function
|
|
9
|
-
* @description
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
12
|
-
* @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
|
|
13
22
|
*/
|
|
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;
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
const t0 = performance.now();
|
|
37
|
+
|
|
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]));
|
|
43
|
+
|
|
44
|
+
// ---- 1. Spatial index RBush ----
|
|
45
|
+
const tree = new RBush();
|
|
46
|
+
const items = gridFeatures.map((g, i) => {
|
|
47
|
+
const [minX, minY, maxX, maxY] = bbox(g);
|
|
48
|
+
return { minX, minY, maxX, maxY, i };
|
|
49
|
+
});
|
|
50
|
+
tree.load(items);
|
|
51
|
+
|
|
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
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---- 3. Loop over polygons ----
|
|
27
59
|
polys.forEach((p, i) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
60
|
+
const polygonArea = area(p);
|
|
61
|
+
const val = hasVar ? parseFloat(p.properties?.[varField]) || 0 : 1;
|
|
62
|
+
|
|
63
|
+
const [minX, minY, maxX, maxY] = bbox(p);
|
|
64
|
+
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
65
|
+
|
|
66
|
+
for (const cand of candidates) {
|
|
67
|
+
const g = gridbyindex.get(cand.i);
|
|
30
68
|
const f = intersect(featureCollection([p, g]));
|
|
31
|
-
if (f
|
|
32
|
-
|
|
33
|
-
|
|
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);
|
|
34
83
|
}
|
|
35
|
-
}
|
|
84
|
+
}
|
|
36
85
|
});
|
|
37
86
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
accessor = new Map(
|
|
41
|
-
polys.map((d, i) => [i, parseFloat(d.properties[opts.var]) || 0])
|
|
42
|
-
);
|
|
43
|
-
} else {
|
|
44
|
-
accessor = new Map(polys.map((d, i) => [i, 1]));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let datagrid = d3.groups(arr, (d) => d[1]);
|
|
48
|
-
|
|
49
|
-
function getsum(cell) {
|
|
50
|
-
let sum = 0;
|
|
51
|
-
cell[1].forEach((d) => {
|
|
52
|
-
sum += accessor.get(d[0]) * d[2];
|
|
53
|
-
});
|
|
54
|
-
return sum == 0 ? undefined : sum;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
87
|
+
// ---- 4. Build final GeoJSON ----
|
|
88
|
+
const result = {
|
|
58
89
|
type: "FeatureCollection",
|
|
59
|
-
features:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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),
|
|
67
120
|
};
|
|
121
|
+
|
|
122
|
+
const t1 = performance.now();
|
|
123
|
+
console.log(`Optimized execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
124
|
+
|
|
125
|
+
return result;
|
|
68
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
|
+
}
|
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
|
-
}
|