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.
@@ -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
- let polys = opts.grid.features;
18
- let points = opts.dots.features;
19
- let count = new Array(polys.length).fill(0);
20
- let nb = points.length;
21
- let test = new Array(nb).fill(true);
22
- polys.forEach((p, i) => {
23
- points.forEach((d, j) => {
24
- if (test[j]) {
25
- if (booleanPointInPolygon(d, p)) {
26
- if (opts.var == undefined) {
27
- count[i] = count[i] + 1;
28
- } else {
29
- count[i] = count[i] + parseFloat(d.properties[opts.var]);
30
- }
31
- test[j] = false;
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
- // Rebuild grid
38
- let output = polys
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
- //const endTime = performance.now();
47
- //const elapsedTime = endTime - startTime;
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.polygonstogrid
9
- * @description The `op.polygonstogrid()` function allows to affect polygons values tu grid cells.
10
- * @property {object} [polygons] - polygons geoJSON
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 (absolute quantitative data only)
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
- let grid = opts.grid.features;
23
- let polys = opts.polygons.features;
24
- let gridbyindex = new Map(grid.map((d, i) => [i, d]));
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
- let arr = [];
34
+ const arr = [];
35
+ const path = d3.geoPath();
36
+
37
+ // ---- 2. Boucle sur les polygones ----
27
38
  polys.forEach((p, i) => {
28
- const area = d3.geoPath().area(p);
29
- grid.forEach((g, j) => {
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 !== null) {
32
- let areapiece = d3.geoPath().area(f);
33
- arr.push([i, j, areapiece / area]);
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
- let accessor;
39
- if (opts.var) {
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
- }
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
- let datagrid = d3.groups(arr, (d) => d[1]);
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 == 0 ? undefined : sum;
71
+ return sum === 0 ? undefined : sum;
55
72
  }
56
73
 
57
- return {
74
+ // ---- 6. Assemblage du résultat ----
75
+ const result = {
58
76
  type: "FeatureCollection",
59
- features: datagrid.map((d) => {
60
- let tmp = gridbyindex.get(d[0]);
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(d) },
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
+ }
@@ -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
- }
@@ -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;