geogrid 1.1.1 → 1.2.2
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 -1
- package/package.json +8 -2
- package/src/grid/square_sph.js +95 -0
- package/src/helpers/sphere.js +41 -0
- package/src/helpers/stitchmerge.js +144 -0
- package/src/helpers/unstitch.js +31 -0
- package/src/index.js +10 -0
- package/src/operator/linestogrid.js +104 -44
- package/src/operator/pointstogrid.js +15 -2
- package/src/operator/polygonstogrid.js +39 -26
- package/src/operator/linestogrid_save.js +0 -124
- package/src/operator/polygonstogrid_save.js +0 -144
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
import { bbox } from "@turf/bbox";
|
|
2
2
|
import { intersect } from "@turf/intersect";
|
|
3
|
+
import { area } from "@turf/area";
|
|
3
4
|
import { geoPath } from "d3-geo";
|
|
5
|
+
import { stitchmerge } from "../helpers/stitchmerge.js";
|
|
6
|
+
import { unstitch } from "../helpers/unstitch.js";
|
|
4
7
|
import RBush from "rbush";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* @function polygonstogrid
|
|
8
11
|
* @description Assign polygons to a grid and compute weighted sums per cell.
|
|
9
|
-
*
|
|
10
|
-
* Optimized and removes cells with count = 0
|
|
11
|
-
* Supports multiple variables in varField (string or array of strings)
|
|
12
|
-
* Treats undefined or NaN as zero when summing
|
|
13
|
-
* If `values` is true, stores an array of intersected polygon properties
|
|
12
|
+
* Supports planar (path.area) or spherical (Turf area) surface calculation.
|
|
13
|
+
* Optimized and removes cells with count = 0.
|
|
14
|
+
* Supports multiple variables in varField (string or array of strings).
|
|
15
|
+
* Treats undefined or NaN as zero when summing.
|
|
16
|
+
* If `values` is true, stores an array of intersected polygon properties.
|
|
14
17
|
* @param {object} opts
|
|
15
|
-
* @property {object} [polygons] - GeoJSON polygons or
|
|
18
|
+
* @property {object} [polygons] - GeoJSON polygons or multipolygons to assign
|
|
16
19
|
* @property {object} [grid] - GeoJSON grid
|
|
17
|
-
* @property {string|Array} [var] - Field(s)
|
|
18
|
-
* @property {boolean} [values=false] - Include array of raw
|
|
20
|
+
* @property {string|Array} [var] - Field(s) to compute weighted sums (optional)
|
|
21
|
+
* @property {boolean} [values=false] - Include array of raw polygon properties
|
|
22
|
+
* @property {boolean} [spherical=false] - Compute areas on the sphere (Turf) instead of planar (D3)
|
|
19
23
|
*/
|
|
20
24
|
export function polygonstogrid(opts = {}) {
|
|
21
|
-
|
|
25
|
+
let {
|
|
22
26
|
grid,
|
|
23
27
|
polygons,
|
|
24
|
-
grid_id = "index",
|
|
25
28
|
var: varField,
|
|
26
29
|
values: includeValues = false,
|
|
30
|
+
spherical = false,
|
|
27
31
|
} = opts;
|
|
28
32
|
|
|
29
33
|
const t0 = performance.now();
|
|
30
34
|
const path = geoPath();
|
|
31
35
|
|
|
36
|
+
// Unstitch grids if needed
|
|
37
|
+
if (spherical) {
|
|
38
|
+
grid = unstitch(grid);
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
// --- Normalize varField to array ---
|
|
33
42
|
const varFields = varField
|
|
34
43
|
? Array.isArray(varField)
|
|
@@ -36,18 +45,18 @@ export function polygonstogrid(opts = {}) {
|
|
|
36
45
|
: [varField]
|
|
37
46
|
: [];
|
|
38
47
|
|
|
39
|
-
// --- 1. Compute
|
|
48
|
+
// --- 1. Compute areas for polygons and grid cells ---
|
|
40
49
|
for (const poly of polygons.features) {
|
|
41
|
-
poly.properties.
|
|
50
|
+
if (spherical) poly.properties.area_spherical = area(poly);
|
|
51
|
+
else poly.properties.area_plan = path.area(poly);
|
|
42
52
|
}
|
|
53
|
+
|
|
43
54
|
for (const cell of grid.features) {
|
|
44
|
-
cell.properties.
|
|
55
|
+
if (spherical) cell.properties.area_spherical = area(cell);
|
|
56
|
+
else cell.properties.area_plan = path.area(cell);
|
|
45
57
|
|
|
46
|
-
// initialize statistics
|
|
47
58
|
cell.properties.count = 0;
|
|
48
|
-
for (const v of varFields)
|
|
49
|
-
cell.properties[v] = 0;
|
|
50
|
-
}
|
|
59
|
+
for (const v of varFields) cell.properties[v] = 0;
|
|
51
60
|
if (includeValues) cell.properties.values = [];
|
|
52
61
|
}
|
|
53
62
|
|
|
@@ -67,27 +76,31 @@ export function polygonstogrid(opts = {}) {
|
|
|
67
76
|
for (const cand of candidates) {
|
|
68
77
|
const cell = cand.cell;
|
|
69
78
|
|
|
79
|
+
// vérifier que les deux features existent
|
|
80
|
+
if (!poly || !cell) continue;
|
|
81
|
+
|
|
82
|
+
// Turf v7: intersect via FeatureCollection contenant exactement deux géométries
|
|
70
83
|
const inter = intersect({
|
|
71
84
|
type: "FeatureCollection",
|
|
72
85
|
features: [poly, cell],
|
|
73
86
|
});
|
|
74
87
|
if (!inter) continue;
|
|
75
88
|
|
|
76
|
-
const
|
|
77
|
-
const
|
|
89
|
+
const areaVal = spherical ? area(inter) : path.area(inter);
|
|
90
|
+
const pctArea =
|
|
91
|
+
areaVal /
|
|
92
|
+
(spherical
|
|
93
|
+
? poly.properties.area_spherical
|
|
94
|
+
: poly.properties.area_plan);
|
|
78
95
|
|
|
79
96
|
// update cell statistics
|
|
80
97
|
cell.properties.count += 1;
|
|
81
|
-
|
|
82
98
|
for (const v of varFields) {
|
|
83
99
|
const value = parseFloat(poly.properties[v]);
|
|
84
|
-
cell.properties[v] += !isNaN(value) ? value *
|
|
100
|
+
cell.properties[v] += !isNaN(value) ? value * pctArea : 0;
|
|
85
101
|
}
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
if (includeValues) {
|
|
89
|
-
cell.properties.values.push({ ...poly.properties });
|
|
90
|
-
}
|
|
103
|
+
if (includeValues) cell.properties.values.push({ ...poly.properties });
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
|
|
@@ -104,5 +117,5 @@ export function polygonstogrid(opts = {}) {
|
|
|
104
117
|
).toFixed(2)} ms`
|
|
105
118
|
);
|
|
106
119
|
|
|
107
|
-
return filteredGrid;
|
|
120
|
+
return spherical ? stitchmerge(filteredGrid) : filteredGrid;
|
|
108
121
|
}
|
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
*/
|
|
18
|
-
export function linestogrid(opts = {}) {
|
|
19
|
-
const {
|
|
20
|
-
grid,
|
|
21
|
-
lines,
|
|
22
|
-
var: varField,
|
|
23
|
-
values: includeValues = false,
|
|
24
|
-
sum: calcSum = true,
|
|
25
|
-
median: calcMedian = false,
|
|
26
|
-
min: calcMin = false,
|
|
27
|
-
max: calcMax = false,
|
|
28
|
-
mean: calcMean = false,
|
|
29
|
-
} = opts;
|
|
30
|
-
|
|
31
|
-
const t0 = performance.now();
|
|
32
|
-
const gridFeatures = grid.features;
|
|
33
|
-
const lineFeatures = lines.features;
|
|
34
|
-
const hasVar = varField !== undefined && varField !== null;
|
|
35
|
-
|
|
36
|
-
const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
|
|
37
|
-
|
|
38
|
-
// ---- 1. Spatial index RBush ----
|
|
39
|
-
const tree = new RBush();
|
|
40
|
-
const items = gridFeatures.map((g, i) => {
|
|
41
|
-
const [minX, minY, maxX, maxY] = bbox(g);
|
|
42
|
-
return { minX, minY, maxX, maxY, i };
|
|
43
|
-
});
|
|
44
|
-
tree.load(items);
|
|
45
|
-
|
|
46
|
-
// ---- 2. Prepare stats storage per cell ----
|
|
47
|
-
const gridStats = new Map();
|
|
48
|
-
gridFeatures.forEach((g, i) => {
|
|
49
|
-
gridStats.set(i, { countSet: new Set(), valuesList: [], numericList: [] });
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// ---- 3. Loop over lines ----
|
|
53
|
-
lineFeatures.forEach((line, i) => {
|
|
54
|
-
const totalLen = turfLength(line);
|
|
55
|
-
if (totalLen === 0) return;
|
|
56
|
-
|
|
57
|
-
const val = hasVar ? parseFloat(line.properties?.[varField]) || 0 : 1;
|
|
58
|
-
const [minX, minY, maxX, maxY] = bbox(line);
|
|
59
|
-
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
60
|
-
|
|
61
|
-
for (const cand of candidates) {
|
|
62
|
-
const g = gridbyindex.get(cand.i);
|
|
63
|
-
if (!booleanIntersects(g, line)) continue;
|
|
64
|
-
|
|
65
|
-
const split = lineSplit(line, g);
|
|
66
|
-
if (!split || split.features.length === 0) continue;
|
|
67
|
-
|
|
68
|
-
let lenInside = 0;
|
|
69
|
-
split.features.forEach((seg) => {
|
|
70
|
-
const segLen = turfLength(seg);
|
|
71
|
-
if (segLen > 0 && booleanIntersects(g, seg)) {
|
|
72
|
-
lenInside += segLen;
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (lenInside > 0) {
|
|
77
|
-
const stats = gridStats.get(cand.i);
|
|
78
|
-
stats.countSet.add(i); // unique line IDs
|
|
79
|
-
|
|
80
|
-
if (includeValues)
|
|
81
|
-
stats.valuesList.push(hasVar ? line.properties[varField] : i);
|
|
82
|
-
|
|
83
|
-
if (hasVar) stats.numericList.push(val * (lenInside / totalLen));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// ---- 4. Build final GeoJSON ----
|
|
89
|
-
const result = {
|
|
90
|
-
type: "FeatureCollection",
|
|
91
|
-
features: gridFeatures
|
|
92
|
-
.map((g, i) => {
|
|
93
|
-
const stats = gridStats.get(i);
|
|
94
|
-
const numericValues = stats.numericList;
|
|
95
|
-
const values = stats.valuesList;
|
|
96
|
-
const count = stats.countSet.size;
|
|
97
|
-
|
|
98
|
-
if (count === 0) return null;
|
|
99
|
-
|
|
100
|
-
const cellProps = { count };
|
|
101
|
-
|
|
102
|
-
if (hasVar && numericValues.length > 0) {
|
|
103
|
-
if (calcSum) cellProps.sum = sum(numericValues);
|
|
104
|
-
if (calcMean) cellProps.mean = mean(numericValues);
|
|
105
|
-
if (calcMedian) cellProps.median = median(numericValues);
|
|
106
|
-
if (calcMin) cellProps.min = min(numericValues);
|
|
107
|
-
if (calcMax) cellProps.max = max(numericValues);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (includeValues) cellProps.values = values;
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
type: g.type,
|
|
114
|
-
properties: { ...g.properties, ...cellProps },
|
|
115
|
-
geometry: g.geometry,
|
|
116
|
-
};
|
|
117
|
-
})
|
|
118
|
-
.filter((f) => f !== null),
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const t1 = performance.now();
|
|
122
|
-
console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { featureCollection } from "@turf/helpers";
|
|
2
|
-
import { bbox } from "@turf/bbox";
|
|
3
|
-
import { intersect } from "@turf/intersect";
|
|
4
|
-
import area from "@turf/area";
|
|
5
|
-
import RBush from "rbush";
|
|
6
|
-
import { min, max, median, mean, sum } from "d3-array";
|
|
7
|
-
|
|
8
|
-
/**
|
|
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
|
|
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;
|
|
35
|
-
|
|
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 ----
|
|
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. Stockage pour chaque carré ----
|
|
53
|
-
const gridStats = new Map();
|
|
54
|
-
gridFeatures.forEach((_, i) => {
|
|
55
|
-
gridStats.set(i, { numericList: [], valuesList: [], countSet: new Set() });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ---- 3. Boucle sur les polygones ----
|
|
59
|
-
for (let i = 0; i < polys.length; i++) {
|
|
60
|
-
const p = polys[i];
|
|
61
|
-
if (!p.geometry || !["Polygon", "MultiPolygon"].includes(p.geometry.type))
|
|
62
|
-
continue;
|
|
63
|
-
|
|
64
|
-
const val = hasVar ? parseFloat(p.properties?.[varField]) || 0 : 1;
|
|
65
|
-
|
|
66
|
-
// 3a. Récupérer tous les carrés intersectant ce polygone
|
|
67
|
-
const [minX, minY, maxX, maxY] = bbox(p);
|
|
68
|
-
const candidates = tree.search({ minX, minY, maxX, maxY });
|
|
69
|
-
|
|
70
|
-
// 3b. Calculer toutes les intersections et leur aire
|
|
71
|
-
const pieces = [];
|
|
72
|
-
for (const cand of candidates) {
|
|
73
|
-
const g = gridbyindex.get(cand.i);
|
|
74
|
-
if (!g.geometry || !["Polygon", "MultiPolygon"].includes(g.geometry.type))
|
|
75
|
-
continue;
|
|
76
|
-
|
|
77
|
-
let f;
|
|
78
|
-
try {
|
|
79
|
-
f = intersect(featureCollection([p, g]));
|
|
80
|
-
} catch (e) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (!f) continue;
|
|
84
|
-
|
|
85
|
-
const areapiece = area(f);
|
|
86
|
-
if (areapiece <= 0) continue;
|
|
87
|
-
|
|
88
|
-
pieces.push({ index: cand.i, area: areapiece });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 3c. Somme totale des aires des morceaux pour ce polygone
|
|
92
|
-
const totalPieceArea = sum(pieces.map((d) => d.area));
|
|
93
|
-
if (totalPieceArea === 0) continue;
|
|
94
|
-
|
|
95
|
-
// 3d. Calculer la valeur proportionnelle de chaque morceau
|
|
96
|
-
for (const piece of pieces) {
|
|
97
|
-
const fraction = piece.area / totalPieceArea;
|
|
98
|
-
const pieceValue = val * fraction;
|
|
99
|
-
|
|
100
|
-
const stats = gridStats.get(piece.index);
|
|
101
|
-
stats.numericList.push(pieceValue);
|
|
102
|
-
if (includeValues) stats.valuesList.push(pieceValue);
|
|
103
|
-
stats.countSet.add(i);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---- 4. Construire le GeoJSON final ----
|
|
108
|
-
const result = {
|
|
109
|
-
type: "FeatureCollection",
|
|
110
|
-
features: gridFeatures
|
|
111
|
-
.map((g, i) => {
|
|
112
|
-
const stats = gridStats.get(i);
|
|
113
|
-
const numericValues = stats.numericList;
|
|
114
|
-
const values = stats.valuesList;
|
|
115
|
-
const count = stats.countSet.size;
|
|
116
|
-
|
|
117
|
-
if (count === 0) return null;
|
|
118
|
-
|
|
119
|
-
const cellProps = { count };
|
|
120
|
-
|
|
121
|
-
if (hasVar && numericValues.length > 0) {
|
|
122
|
-
if (calcSum) cellProps.sum = sum(numericValues);
|
|
123
|
-
if (calcMean) cellProps.mean = mean(numericValues);
|
|
124
|
-
if (calcMedian) cellProps.median = median(numericValues);
|
|
125
|
-
if (calcMin) cellProps.min = min(numericValues);
|
|
126
|
-
if (calcMax) cellProps.max = max(numericValues);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (includeValues) cellProps.values = values;
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
type: g.type,
|
|
133
|
-
properties: { ...g.properties, ...cellProps },
|
|
134
|
-
geometry: g.geometry,
|
|
135
|
-
};
|
|
136
|
-
})
|
|
137
|
-
.filter((f) => f !== null),
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const t1 = performance.now();
|
|
141
|
-
console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
|
|
142
|
-
|
|
143
|
-
return result;
|
|
144
|
-
}
|