geogrid 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geogrid",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Regular and irregular geoJSON grids ",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -1,129 +1,108 @@
1
1
  import { bbox } from "@turf/bbox";
2
2
  import { lineSplit } from "@turf/line-split";
3
- import { length as turfLength } from "@turf/length";
4
- import { booleanIntersects } from "@turf/boolean-intersects";
3
+ import { length } from "@turf/length";
5
4
  import RBush from "rbush";
6
- import { min, max, median, mean, sum } from "d3-array";
7
5
 
8
6
  /**
9
7
  * @function linestogrid
10
- * @description Assigns lines to a grid and computes statistics per cell.
11
- * Supports weighted lengths or simple counts.
8
+ * @description Assign lines to a grid and compute weighted sums per cell.
9
+ * Uses a spatial index to speed up calculations.
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 line properties
12
14
  * @param {object} opts
15
+ * @property {object} [lines] - GeoJSON lines to assign
13
16
  * @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
17
+ * @property {string|Array} [var] - Field(s) to compute weighted sums (optional)
18
+ * @property {boolean} [values=false] - Include array of raw lines properties
22
19
  */
23
20
  export function linestogrid(opts = {}) {
24
21
  const {
25
22
  grid,
26
23
  lines,
24
+ grid_id = "index",
27
25
  var: varField,
28
26
  values: includeValues = false,
29
- sum: calcSum = true,
30
- median: calcMedian = false,
31
- min: calcMin = false,
32
- max: calcMax = false,
33
- mean: calcMean = false,
34
27
  } = opts;
35
28
 
36
29
  const t0 = performance.now();
37
- const gridFeatures = grid.features;
38
- const lineFeatures = lines.features;
39
- const hasVar = varField !== undefined && varField !== null;
40
30
 
41
- const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
31
+ // --- Normalize varField to array ---
32
+ const varFields = varField
33
+ ? Array.isArray(varField)
34
+ ? varField
35
+ : [varField]
36
+ : [];
37
+
38
+ // --- 1. Compute total lengths for lines and initialize grid cells ---
39
+ for (const line of lines.features) {
40
+ line.properties.length_total = length(line, { units: "meters" });
41
+ }
42
+
43
+ for (const cell of grid.features) {
44
+ cell.properties.count = 0;
45
+ for (const v of varFields) {
46
+ cell.properties[v] = 0;
47
+ }
48
+ if (includeValues) cell.properties.values = [];
49
+ }
42
50
 
43
- // ---- 1. Spatial index RBush ----
51
+ // --- 2. Build spatial index (RBush) on the grid ---
44
52
  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 };
53
+ const items = grid.features.map((cell) => {
54
+ const [minX, minY, maxX, maxY] = bbox(cell);
55
+ return { minX, minY, maxX, maxY, cell };
48
56
  });
49
57
  tree.load(items);
50
58
 
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;
59
+ // --- 3. Loop over lines ---
60
+ for (const line of lines.features) {
63
61
  const [minX, minY, maxX, maxY] = bbox(line);
64
62
  const candidates = tree.search({ minX, minY, maxX, maxY });
65
63
 
66
64
  for (const cand of candidates) {
67
- const g = gridbyindex.get(cand.i);
68
- if (!booleanIntersects(g, line)) continue;
65
+ const cell = cand.cell;
69
66
 
70
- const split = lineSplit(line, g);
71
- if (!split || split.features.length === 0) continue;
67
+ // split line by cell polygon
68
+ const splitLines = lineSplit(line, cell);
69
+ if (!splitLines.features.length) continue;
72
70
 
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
- });
71
+ // compute total length of segments inside the cell
72
+ let totalSegLength = 0;
73
+ for (const seg of splitLines.features) {
74
+ totalSegLength += length(seg, { units: "meters" });
75
+ }
76
+ if (totalSegLength === 0) continue;
80
77
 
81
- if (lenInside > 0) {
82
- const stats = gridStats.get(cand.i);
83
- stats.countSet.add(i); // unique line IDs
78
+ // --- update cell statistics ---
79
+ cell.properties.count += 1; // one line per cell
84
80
 
85
- if (includeValues)
86
- stats.valuesList.push(hasVar ? line.properties[varField] : i);
81
+ for (const v of varFields) {
82
+ const value = parseFloat(line.properties[v]);
83
+ cell.properties[v] += !isNaN(value)
84
+ ? value * (totalSegLength / line.properties.length_total)
85
+ : 0;
86
+ }
87
87
 
88
- if (hasVar) stats.numericList.push(val * (lenInside / totalLen));
88
+ if (includeValues) {
89
+ cell.properties.values.push({ ...line.properties });
89
90
  }
90
91
  }
91
- });
92
+ }
92
93
 
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),
94
+ // --- 4. Filter out cells with count == 0 ---
95
+ const filteredGrid = {
96
+ ...grid,
97
+ features: grid.features.filter((cell) => cell.properties.count > 0),
124
98
  };
125
99
 
126
100
  const t1 = performance.now();
127
- console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
128
- return result;
101
+ console.log(
102
+ `Line intersection completed for ${filteredGrid.features.length} cells — ${(
103
+ t1 - t0
104
+ ).toFixed(2)} ms`
105
+ );
106
+
107
+ return filteredGrid;
129
108
  }
@@ -0,0 +1,124 @@
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,43 +1,35 @@
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 { min, max, median, mean, sum } from "d3-array";
5
4
 
6
5
  /**
7
6
  * @function pointstogrid
8
- * @description Assigns points to grid cells and computes statistics per cell.
7
+ * @description Assigns points to grid cells and computes sums per cell.
8
+ * Supports multiple variables and stores point properties if values=true
9
9
  * @param {object} opts
10
10
  * @property {object} [points] - GeoJSON points
11
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
12
+ * @property {string|Array} [var] - Field(s) for summing values
13
+ * @property {boolean} [values=false] - Include array of raw point properties
19
14
  */
20
15
  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;
16
+ const { points, grid, var: varField, values: includeValues = false } = opts;
32
17
 
33
18
  const t0 = performance.now();
19
+
34
20
  const gridFeatures = grid.features;
35
21
  const pointFeatures = points.features;
36
- const hasVar = varField !== undefined && varField !== null;
22
+
23
+ // --- Normalize varField to array ---
24
+ const varFields = varField
25
+ ? Array.isArray(varField)
26
+ ? varField
27
+ : [varField]
28
+ : [];
37
29
 
38
30
  const gridbyindex = new Map(gridFeatures.map((d, i) => [i, d]));
39
31
 
40
- // ---- 1. Spatial index for polygons ----
32
+ // --- 1. Spatial index for polygons ---
41
33
  const tree = new RBush();
42
34
  const items = gridFeatures.map((g, i) => {
43
35
  const [minX, minY, maxX, maxY] = bbox(g);
@@ -45,13 +37,14 @@ export function pointstogrid(opts = {}) {
45
37
  });
46
38
  tree.load(items);
47
39
 
48
- // ---- 2. Prepare stats storage per cell ----
40
+ // --- 2. Prepare stats storage per cell ---
49
41
  const gridStats = new Map();
50
42
  gridFeatures.forEach((g, i) => {
51
- gridStats.set(i, { countSet: new Set(), valuesList: [], numericList: [] });
43
+ gridStats.set(i, { countSet: new Set(), valuesList: [], numericLists: {} });
44
+ varFields.forEach((v) => (gridStats.get(i).numericLists[v] = []));
52
45
  });
53
46
 
54
- // ---- 3. Loop over points ----
47
+ // --- 3. Loop over points ---
55
48
  pointFeatures.forEach((pt, i) => {
56
49
  const x = pt.geometry.coordinates[0];
57
50
  const y = pt.geometry.coordinates[1];
@@ -65,38 +58,40 @@ export function pointstogrid(opts = {}) {
65
58
  const stats = gridStats.get(cand.i);
66
59
  stats.countSet.add(i);
67
60
 
68
- const val = hasVar ? pt.properties[varField] : 1;
61
+ // --- handle numeric variables ---
62
+ varFields.forEach((v) => {
63
+ const val = parseFloat(pt.properties[v]);
64
+ if (!isNaN(val)) stats.numericLists[v].push(val);
65
+ });
66
+
67
+ // --- handle values option ---
68
+ if (includeValues) stats.valuesList.push({ ...pt.properties });
69
69
 
70
- if (includeValues)
71
- stats.valuesList.push(hasVar ? pt.properties[varField] : i);
72
- if (hasVar) stats.numericList.push(parseFloat(val) || 0);
73
70
  break; // point counted once per cell
74
71
  }
75
72
  });
76
73
 
77
- // ---- 4. Build final GeoJSON ----
74
+ // --- 4. Build final GeoJSON ---
78
75
  const result = {
79
76
  type: "FeatureCollection",
80
77
  features: gridFeatures
81
78
  .map((g, i) => {
82
79
  const stats = gridStats.get(i);
83
- const numericValues = stats.numericList;
84
- const values = stats.valuesList;
85
80
  const count = stats.countSet.size;
86
-
87
81
  if (count === 0) return null;
88
82
 
89
83
  const cellProps = { count };
90
84
 
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
- }
85
+ // sum each variable
86
+ varFields.forEach((v) => {
87
+ const numericValues = stats.numericLists[v];
88
+ cellProps[v] =
89
+ numericValues.length > 0
90
+ ? numericValues.reduce((a, b) => a + b, 0)
91
+ : 0;
92
+ });
98
93
 
99
- if (includeValues) cellProps.values = values;
94
+ if (includeValues) cellProps.values = stats.valuesList;
100
95
 
101
96
  return {
102
97
  type: g.type,
@@ -109,5 +104,6 @@ export function pointstogrid(opts = {}) {
109
104
 
110
105
  const t1 = performance.now();
111
106
  console.log(`Execution time: ${(t1 - t0).toFixed(2)} ms`);
107
+
112
108
  return result;
113
109
  }
@@ -1,126 +1,108 @@
1
- import { featureCollection } from "@turf/helpers";
2
1
  import { bbox } from "@turf/bbox";
3
2
  import { intersect } from "@turf/intersect";
4
- import area from "@turf/area";
3
+ import { geoPath } from "d3-geo";
5
4
  import RBush from "rbush";
6
- import { min, max, median, mean, sum } from "d3-array";
7
5
 
8
6
  /**
9
7
  * @function polygonstogrid
10
- * @description Assign polygons to a grid and compute statistics per cell.
8
+ * @description Assign polygons to a grid and compute weighted sums per cell.
9
+ * Uses a spatial index to speed up calculations
11
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
14
  * @param {object} opts
15
+ * @property {object} [polygons] - GeoJSON polygons or multi polygons to assign
13
16
  * @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
17
+ * @property {string|Array} [var] - Field(s) ton compute weighted sums (optional)
18
+ * @property {boolean} [values=false] - Include array of raw polygons properties
22
19
  */
23
20
  export function polygonstogrid(opts = {}) {
24
21
  const {
25
22
  grid,
26
23
  polygons,
24
+ grid_id = "index",
27
25
  var: varField,
28
26
  values: includeValues = false,
29
- sum: calcSum = true,
30
- median: calcMedian = false,
31
- min: calcMin = false,
32
- max: calcMax = false,
33
- mean: calcMean = false,
34
27
  } = opts;
35
28
 
36
29
  const t0 = performance.now();
30
+ const path = geoPath();
31
+
32
+ // --- Normalize varField to array ---
33
+ const varFields = varField
34
+ ? Array.isArray(varField)
35
+ ? varField
36
+ : [varField]
37
+ : [];
38
+
39
+ // --- 1. Compute planar areas for polygons and grid cells ---
40
+ for (const poly of polygons.features) {
41
+ poly.properties.area_plan = path.area(poly);
42
+ }
43
+ for (const cell of grid.features) {
44
+ cell.properties.area_plan = path.area(cell);
45
+
46
+ // initialize statistics
47
+ cell.properties.count = 0;
48
+ for (const v of varFields) {
49
+ cell.properties[v] = 0;
50
+ }
51
+ if (includeValues) cell.properties.values = [];
52
+ }
37
53
 
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 ----
54
+ // --- 2. Build spatial index (RBush) on the grid ---
45
55
  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 };
56
+ const items = grid.features.map((cell) => {
57
+ const [minX, minY, maxX, maxY] = bbox(cell);
58
+ return { minX, minY, maxX, maxY, cell };
49
59
  });
50
60
  tree.load(items);
51
61
 
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 ----
59
- polys.forEach((p, i) => {
60
- const polygonArea = area(p);
61
- const val = hasVar ? parseFloat(p.properties?.[varField]) || 0 : 1;
62
-
63
- const [minX, minY, maxX, maxY] = bbox(p);
62
+ // --- 3. Loop over polygons ---
63
+ for (const poly of polygons.features) {
64
+ const [minX, minY, maxX, maxY] = bbox(poly);
64
65
  const candidates = tree.search({ minX, minY, maxX, maxY });
65
66
 
66
67
  for (const cand of candidates) {
67
- const g = gridbyindex.get(cand.i);
68
- const f = intersect(featureCollection([p, g]));
69
- if (!f) continue;
68
+ const cell = cand.cell;
70
69
 
71
- const areapiece = area(f);
72
- const fraction = areapiece / polygonArea;
70
+ const inter = intersect({
71
+ type: "FeatureCollection",
72
+ features: [poly, cell],
73
+ });
74
+ if (!inter) continue;
73
75
 
74
- const stats = gridStats.get(cand.i);
75
- stats.countSet.add(i);
76
+ const areaPlan = path.area(inter);
77
+ const pctAreaPlan = areaPlan / poly.properties.area_plan;
76
78
 
77
- if (includeValues) {
78
- stats.valuesList.push(hasVar ? p.properties[varField] : 1);
79
+ // update cell statistics
80
+ cell.properties.count += 1;
81
+
82
+ for (const v of varFields) {
83
+ const value = parseFloat(poly.properties[v]);
84
+ cell.properties[v] += !isNaN(value) ? value * pctAreaPlan : 0;
79
85
  }
80
86
 
81
- if (hasVar) {
82
- stats.numericList.push(val * fraction);
87
+ // add properties of intersected polygons
88
+ if (includeValues) {
89
+ cell.properties.values.push({ ...poly.properties });
83
90
  }
84
91
  }
85
- });
92
+ }
86
93
 
87
- // ---- 4. Build final GeoJSON ----
88
- const result = {
89
- type: "FeatureCollection",
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),
94
+ // --- 4. Filter out cells with count == 0 ---
95
+ const filteredGrid = {
96
+ ...grid,
97
+ features: grid.features.filter((cell) => cell.properties.count > 0),
120
98
  };
121
99
 
122
100
  const t1 = performance.now();
123
- console.log(`Optimized execution time: ${(t1 - t0).toFixed(2)} ms`);
101
+ console.log(
102
+ `Intersection completed for ${filteredGrid.features.length} cells — ${(
103
+ t1 - t0
104
+ ).toFixed(2)} ms`
105
+ );
124
106
 
125
- return result;
107
+ return filteredGrid;
126
108
  }