├── .DS_Store
├── .gitignore
├── LICENSE
├── README.md
├── doc_index.md
├── docs
├── GIS.html
├── addproperty.js.html
├── aggregate.js.html
├── autotype.js.html
├── bbox.js.html
├── border.js.html
├── buffer.js.html
├── centroid.js.html
├── clip.js.html
├── clipbyrect.js.html
├── columns.js.html
├── combine.js.html
├── concavehull.js.html
├── convexhull.js.html
├── copy.js.html
├── data
│ └── search.json
├── dedupe.js.html
├── densify.js.html
├── deprecated_add.js.html
├── deprecated_coords2geo.js.html
├── deprecated_head.js.html
├── deprecated_keep.js.html
├── deprecated_remove.js.html
├── deprecated_select.js.html
├── deprecated_subset.js.html
├── deprecated_table.js.html
├── deprecated_tail.js.html
├── derive.js.html
├── disolve_save.js.html
├── dissolve.js.html
├── envelope.js.html
├── featurecollection.js.html
├── filter.js.html
├── fonts
│ ├── Inconsolata-Regular.ttf
│ ├── Montserrat
│ │ ├── Montserrat-Bold.eot
│ │ ├── Montserrat-Bold.ttf
│ │ ├── Montserrat-Bold.woff
│ │ ├── Montserrat-Bold.woff2
│ │ ├── Montserrat-Regular.eot
│ │ ├── Montserrat-Regular.ttf
│ │ ├── Montserrat-Regular.woff
│ │ └── Montserrat-Regular.woff2
│ ├── OpenSans-Bold-webfont.eot
│ ├── OpenSans-Bold-webfont.svg
│ ├── OpenSans-Bold-webfont.woff
│ ├── OpenSans-BoldItalic-webfont.eot
│ ├── OpenSans-BoldItalic-webfont.svg
│ ├── OpenSans-BoldItalic-webfont.woff
│ ├── OpenSans-Italic-webfont.eot
│ ├── OpenSans-Italic-webfont.svg
│ ├── OpenSans-Italic-webfont.woff
│ ├── OpenSans-Light-webfont.eot
│ ├── OpenSans-Light-webfont.svg
│ ├── OpenSans-Light-webfont.woff
│ ├── OpenSans-LightItalic-webfont.eot
│ ├── OpenSans-LightItalic-webfont.svg
│ ├── OpenSans-LightItalic-webfont.woff
│ ├── OpenSans-Regular-webfont.eot
│ ├── OpenSans-Regular-webfont.svg
│ ├── OpenSans-Regular-webfont.woff
│ ├── OpenSans-Regular.ttf
│ ├── Source-Sans-Pro
│ │ ├── sourcesanspro-light-webfont.eot
│ │ ├── sourcesanspro-light-webfont.svg
│ │ ├── sourcesanspro-light-webfont.ttf
│ │ ├── sourcesanspro-light-webfont.woff
│ │ ├── sourcesanspro-light-webfont.woff2
│ │ ├── sourcesanspro-regular-webfont.eot
│ │ ├── sourcesanspro-regular-webfont.svg
│ │ ├── sourcesanspro-regular-webfont.ttf
│ │ ├── sourcesanspro-regular-webfont.woff
│ │ └── sourcesanspro-regular-webfont.woff2
│ └── WorkSans-Bold.ttf
├── geolines.js.html
├── gis_aggregate.js.html
├── gis_bbox.js.html
├── gis_border.js.html
├── gis_buffer.js.html
├── gis_buffer2.js.html
├── gis_centroid.js.html
├── gis_coords2geo.js.html
├── gis_densify.js.html
├── gis_dissolve.js.html
├── gis_geolines.js.html
├── gis_simplify.js.html
├── gis_tissot.js.html
├── gis_union.js.html
├── global.html
├── groupby.js.html
├── head.js.html
├── helpers_type.js.html
├── htmltable.js.html
├── img
│ ├── aggregate.svg
│ ├── bbox.svg
│ ├── border.svg
│ ├── buffer.svg
│ ├── centroid.svg
│ ├── clip.svg
│ ├── clip_reverse.svg
│ ├── coords2geo.svg
│ ├── densify.svg
│ ├── dissolve.svg
│ ├── featurecollection.svg
│ ├── filter.svg
│ ├── geolines.svg
│ ├── geotoolbox.svg
│ ├── map.svg
│ ├── nodes.svg
│ ├── rewind.svg
│ ├── simplify.svg
│ ├── tissot.svg
│ └── union.svg
├── index.html
├── info.js.html
├── isvalid.js.html
├── iterate.js.html
├── iterator_filter.js.html
├── iterator_map.js.html
├── join.js.html
├── largestemptycircle.js.html
├── makevalid.js.html
├── module-GIS.html
├── module-Properties.html
├── module-Utils.html
├── nodes.js.html
├── operators.js.html
├── properties.js.html
├── properties_add.js.html
├── properties_head.js.html
├── properties_keep.js.html
├── properties_remove.js.html
├── properties_select.js.html
├── properties_subset.js.html
├── properties_table.js.html
├── properties_tail.js.html
├── removeemptygeom.js.html
├── removerepetedpoints.js.html
├── replace.js.html
├── resolveemptygeom.js.html
├── reverse.js.html
├── rewind.js.html
├── rewind2.js.html
├── roundcoordinates.js.html
├── scripts
│ ├── collapse.js
│ ├── commonNav.js
│ ├── core.js
│ ├── core.min.js
│ ├── linenumber.js
│ ├── nav.js
│ ├── polyfill.js
│ ├── prettify
│ │ ├── Apache-License-2.0.txt
│ │ ├── lang-css.js
│ │ └── prettify.js
│ ├── resize.js
│ ├── search.js
│ ├── search.min.js
│ └── third-party
│ │ ├── Apache-License-2.0.txt
│ │ ├── fuse.js
│ │ ├── hljs-line-num-original.js
│ │ ├── hljs-line-num.js
│ │ ├── hljs-original.js
│ │ ├── hljs.js
│ │ ├── popper.js
│ │ ├── tippy.js
│ │ ├── tocbot.js
│ │ └── tocbot.min.js
├── simplify.js.html
├── sort.js.html
├── stitch.js.html
├── styles
│ ├── clean-jsdoc-theme-base.css
│ ├── clean-jsdoc-theme-dark.css
│ ├── clean-jsdoc-theme-light.css
│ ├── clean-jsdoc-theme-scrollbar.css
│ ├── clean-jsdoc-theme-without-scrollbar.min.css
│ ├── clean-jsdoc-theme.min.css
│ ├── jsdoc-default.css
│ ├── jsdoc.css
│ ├── prettify-jsdoc.css
│ ├── prettify-tomorrow.css
│ └── prettify.css
├── table.js.html
├── tail.js.html
├── tissot.js.html
├── togeojson.js.html
├── union.js.html
├── unionbyid.js.html
├── utils_featurecollection.js.html
├── utils_makevalid.js.html
├── utils_rewind.js.html
└── utils_type.js.html
├── examples
├── simplify.html
├── test.html
└── world.json
├── img
├── aggregate.svg
├── bbox.svg
├── border.svg
├── buffer.svg
├── centroid.svg
├── clip.svg
├── clip_reverse.svg
├── densify.svg
├── dissolve.svg
├── filter.svg
├── geolines.svg
├── geotoolbox.svg
├── iterate.svg
├── nodes.svg
├── rewind.svg
├── simplify.svg
├── tissot.svg
├── togeojson.svg
└── union.svg
├── jsdoc.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── aggregate.js
├── autotype.js
├── bbox.js
├── border.js
├── buffer.js
├── centroid.js
├── clip.js
├── clipbyrect.js
├── columns.js
├── combine.js
├── concavehull.js
├── convexhull.js
├── copy.js
├── dedupe.js
├── densify.js
├── deprecated
│ ├── add.js
│ ├── coords2geo.js
│ ├── head.js
│ ├── keep.js
│ ├── remove.js
│ ├── select.js
│ ├── subset.js
│ ├── table.js
│ └── tail.js
├── derive.js
├── dissolve.js
├── envelope.js
├── filter.js
├── geolines.js
├── geomfunctions.js
├── groupby.js
├── head.js
├── helpers
│ ├── check.js
│ ├── expressiontovaluesinageojson.js
│ ├── geos.js
│ ├── helpers.js
│ ├── implantation.js
│ ├── table2geo.js
│ └── type.js
├── htmltable.js
├── index.js
├── info.js
├── iterate.js
├── join.js
├── largestemptycircle.js
├── makevalid.js
├── nodes.js
├── operators.js
├── removeemptygeom.js
├── replace.js
├── resolveemptygeom.js
├── reverse.js
├── rewind.js
├── rewind2.js
├── roundcoordinates.js
├── simplify.js
├── sort.js
├── stitch.js
├── table.js
├── tail.js
├── tissot.js
├── togeojson.js
└── union.js
└── test
├── features
├── buff1000km.json
├── buff1000km_union_geos.json
└── buff1000km_union_jsts.json
└── test.mjs
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | .nyc_output
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Nicolas LAMBERT
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |    
2 |
3 | # `geotoolbox@3`
4 |
5 | **`geotoolbox`** is a javascript tool for geographers. It allows one to manage GeoJSON properties (attribute data) and provides several useful **GIS operations** for thematic cartography. The aim of geotoolbox is to offer functions designed to handle geoJSON directly, not just single features or geometries. As a result, the library is particularly **user-friendly** for users with little experience of javascript development. From a technical point of view, geotoolbox is largely based on **geos-wasm** GIS operators (a big thanks to Christoph Pahmeyer 🙏), but also on d3.geo and topojson. Geotoolbox also works well with other cartographic libraries such as `geoviz` and `bertin.js`. Note that there are other GIS libraries like `turf.js`, which is really great.
6 |
7 | 
8 |
9 |
10 |
11 | ### ➡️ Installation
12 |
13 | - CDN
14 |
15 | ``` html
16 |
17 | ```
18 |
19 | - npm
20 |
21 | ```
22 | npm install geotoolbox@3
23 | ```
24 |
25 | - Observable notebooks
26 |
27 | ``` js
28 | geo = require("geotoolbox@3");
29 | ```
30 |
31 | ### ➡️ Usage
32 |
33 | Most functions take the same type of argument as input - a dataset and options - like `geotoolbox.myfunction(data, {options})`. Please note that functions based on geos-wasm are asynchronous.
34 |
35 | - A buffer example
36 |
37 | ``` js
38 | const mybyffer = await geotoolbox.buffer(data, {dist: 1000});
39 | ```
40 |
41 | - Data handling example (add a field in a geoJSON)
42 |
43 | ``` js
44 | geotoolbox.derive(data, {
45 | field: "gdppc", // the name of the new field
46 | value: "gdp/pop*1000", // a function to calculate the value
47 | mutate: true // to update the dataset
48 | });
49 | ```
50 | - Tests if two geometries intersect
51 |
52 | ``` js
53 | geotoolbox.intersects(data1, data2);
54 | ```
55 |
56 | ### ➡️ How it works?
57 |
58 | See documentation api: https://riatelab.github.io/geotoolbox
59 |
60 |
--------------------------------------------------------------------------------
/docs/GIS.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Namespace: GIS
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Namespace: GIS
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | GIS
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Source:
72 |
75 |
76 |
77 |
78 |
79 |
80 | See:
81 |
82 |
83 | the union
function
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | Namespaces Global
126 |
127 |
128 |
129 |
130 |
131 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 08:22:14 GMT+0100 (Central European Standard Time)
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/docs/deprecated_table.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: deprecated/table.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: deprecated/table.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function <s>properties/remove</s>
31 | * @deprecated
32 | * @summary From now on, use directly `geojson.features.map(d => d.properties)`
33 | */
34 | export function table(geojson) {
35 | return JSON.parse(JSON.stringify(geojson.features.map((d) => d.properties)));
36 | }
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Global
48 |
49 |
50 |
51 |
52 |
53 | Documentation generated by JSDoc 4.0.4 on Fri Apr 04 2025 16:17:25 GMT+0200 (Central European Summer Time)
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/fonts/Inconsolata-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Inconsolata-Regular.ttf
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Bold.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Bold.eot
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Bold.woff
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Bold.woff2
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Regular.eot
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Regular.woff
--------------------------------------------------------------------------------
/docs/fonts/Montserrat/Montserrat-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Montserrat/Montserrat-Regular.woff2
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Bold-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Bold-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-BoldItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-BoldItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-BoldItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Italic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Italic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Light-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Light-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-LightItalic-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-LightItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-LightItalic-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Regular-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Regular-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/OpenSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/OpenSans-Regular.ttf
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2
--------------------------------------------------------------------------------
/docs/fonts/WorkSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/riatelab/geotoolbox/c242599242aaeeb50bb563135ef3a0e65d8e4f0b/docs/fonts/WorkSans-Bold.ttf
--------------------------------------------------------------------------------
/docs/gis_densify.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: gis/densify.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: gis/densify.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import initGeosJs from "geos-wasm";
30 | import { geojsonToGeosGeom } from "../helpers/geojsonToGeosGeom";
31 | import { geosGeomToGeojson } from "../helpers/geosGeomToGeojson";
32 | import { featurecollection } from "../featurecollection.js";
33 |
34 | /**
35 | * Densify a geoJSON with GEOS-WASM
36 | *
37 | * Example: {@link https://observablehq.com/@neocartocnrs/densify?collection=@neocartocnrs/geotoolbox Observable notebook}
38 | *
39 | * @param {object|array} x - The targeted FeatureCollection / Features / Geometries
40 | * @param {object} options - Optional parameters
41 | * @param {number} options.dist - The minimal distance between nodes
42 | *
43 | */
44 | export async function densify(x, options = { dist: 1 }) {
45 | // TODO: This will create a new GEOS instance with every call
46 | // to geosunion. Ideally, we should create a single instance
47 | // when the library is loaded and then just pass it around
48 | const geos = await initGeosJs();
49 | x = featurecollection(x);
50 |
51 | const geosGeom = geojsonToGeosGeom(x, geos);
52 | const newGeom = geos.GEOSDensify(geosGeom, options.dist);
53 | const densiygeom = geosGeomToGeojson(newGeom, geos).geometries;
54 |
55 | x.features.forEach((d, i) => (d.geometry = densiygeom[i]));
56 | return x;
57 | }
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Modules Global
69 |
70 |
71 |
72 |
73 |
74 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 11:29:56 GMT+0100 (Central European Standard Time)
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/gis_dissolve.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: gis/dissolve.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: gis/dissolve.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import { geoArea } from "d3-geo";
30 | import { featurecollection } from "../featurecollection.js";
31 |
32 | /**
33 | * Dissolve multipart geometries to single part geometries.
34 | *
35 | * Example: {@link https://observablehq.com/@neocartocnrs/dissolve?collection=@neocartocnrs/geotoolbox Observable notebook}
36 | *
37 | * @param geojson - The target GeoJSON FeatureCollection / array of Features / array of Geometries
38 | *
39 | */
40 | export function dissolve(geojson) {
41 | geojson = featurecollection(geojson);
42 | let result = [];
43 | geojson.features.forEach((d) => {
44 | result.push(sp(d));
45 | });
46 |
47 | const keys = Object.keys(geojson).filter((e) => e != "features");
48 | const obj = {};
49 | keys.forEach((d) => {
50 | obj[d] = geojson[d];
51 | });
52 | obj.features = result.flat();
53 |
54 | return obj;
55 | }
56 |
57 | function sp(feature) {
58 | let result = [];
59 |
60 | if (feature.geometry.type.includes("Multi")) {
61 | feature.geometry.coordinates.forEach((d) => {
62 | result.push({
63 | type: "Feature",
64 | properties: feature.properties,
65 | geometry: {
66 | type: feature.geometry.type.replace("Multi", ""),
67 | coordinates: d,
68 | },
69 | });
70 | });
71 | } else {
72 | result.push({ ...feature });
73 | }
74 |
75 | const totalArea = geoArea(feature);
76 | result.forEach((d) => (d.__share = geoArea(d) / totalArea));
77 |
78 | return JSON.parse(JSON.stringify(result));
79 | }
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Modules Global
91 |
92 |
93 |
94 |
95 |
96 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 11:29:56 GMT+0100 (Central European Standard Time)
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/docs/gis_geolines.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: gis/geolines.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: gis/geolines.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Returns a GeoJSON FeatureCollection of natural geographic lines such as
31 | * the equator, tropics & polar circles.
32 | *
33 | * Example: {@link https://observablehq.com/@neocartocnrs/geolines?collection=@neocartocnrs/geotoolbox Observable notebook}
34 | *
35 | *
36 | */
37 | export function geolines() {
38 | let features = [];
39 | let arr = [
40 | ["Equator", 0],
41 | ["Tropic of Cancer", 23.43656],
42 | ["Tropic of Capricorn", -23.43636],
43 | ["Arctic Circle", 66.56345],
44 | ["Antarctic Circle", -66.56364],
45 | ];
46 |
47 | arr.forEach((d) => {
48 | features.push({
49 | type: "Feature",
50 | properties: { name: d[0], latitude: d[1] },
51 | geometry: line(d[1]),
52 | });
53 | });
54 |
55 | return { type: "FeatureCollection", features: features };
56 | }
57 |
58 | function line(lat) {
59 | let arr = [];
60 | let i = -180;
61 | while (i <= 180) {
62 | arr.push([i, lat]);
63 | i += 2.5;
64 | }
65 | return { type: "MultiLineString", coordinates: [arr] };
66 | }
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Modules Global
78 |
79 |
80 |
81 |
82 |
83 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 17:24:37 GMT+0100 (Central European Standard Time)
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/docs/gis_simplify.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: gis/simplify.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: gis/simplify.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import { topology } from "topojson-server";
30 | import { feature, quantize as quantiz } from "topojson-client";
31 | import { presimplify, quantile, simplify as simple } from "topojson-simplify";
32 |
33 | const topojson = Object.assign(
34 | {},
35 | { topology, presimplify, quantiz, quantile, simple, feature }
36 | );
37 |
38 | /**
39 | * @function simplify
40 | * @export
41 | * @description Simplify geometries. The `simplify()` function allows to simplify a geometry using <code>topojson-simplify</code> library. The parameter k difine the The quantile of the simplification. By default, the generalization level is calculated automatically to ensure smooth map display.
42 | * <br/><br/>
43 | * @param {object} data - a GeoJSON FeatureCollection
44 | * @param {object} options - Optional parameters
45 | * @param {number} [options.k = undefined] - quantile of the simplification (from 0 to 1). If not defened, the generalization level is calculated automatically to ensure smooth map display.
46 | * @param {number} [options.quantize = undefined] - A smaller quantizeAmount produces a smaller file. Typical values are between 1e4 and 1e6, although it depends on the resolution you want in the output file.
47 | * @param {number} [options.arcs = 15000] - Instead of the k parameter, you can determine the level of generalization by targeting a specific number of arcs. The result will be an approximation.
48 | * @param {boolean} [options.deepcopy = true] - Use true to ensure that the input object is not modified and to create a new object.
49 | * @example
50 | * geotoolbox.simplify(*a geojson*, {k: 0.1})
51 | */
52 |
53 | export function simplify(
54 | data,
55 | { k = undefined, quantize = undefined, arcs = 15000, deepcopy = true } = {}
56 | ) {
57 | // deep copy ?
58 | let geojson;
59 | if (deepcopy) {
60 | geojson = JSON.parse(JSON.stringify(data));
61 | } else {
62 | geojson = data;
63 | }
64 |
65 | let topo = topojson.topology({ foo: geojson });
66 | let simpl = topojson.presimplify(topo);
67 |
68 | if (k == undefined) {
69 | k = arcs / simpl.arcs.flat().length;
70 | k = k > 1 ? 1 : k;
71 | }
72 |
73 | simpl = topojson.simple(simpl, topojson.quantile(simpl, k));
74 | if (quantize) {
75 | simpl = topojson.quantiz(simpl, quantize);
76 | }
77 |
78 | geojson.features = topojson.feature(
79 | simpl,
80 | Object.keys(simpl.objects)[0]
81 | ).features;
82 | return geojson;
83 | }
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | Modules Global
95 |
96 |
97 |
98 |
99 |
100 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 10:02:58 GMT+0100 (Central European Standard Time)
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/docs/gis_tissot.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: gis/tissot.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: gis/tissot.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import { geoCircle } from "d3-geo";
30 |
31 | /**
32 | * Generate Tissot's indicatrix.
33 | *
34 | * Example: {@link https://observablehq.com/@neocartocnrs/tissot?collection=@neocartocnrs/geotoolbox Observable notebook}
35 | *
36 | * @param {number} step - The distance between each circle
37 | *
38 | */
39 | export function tissot(step) {
40 | const circle = geoCircle()
41 | .center((d) => d)
42 | .radius(step / 4)
43 | .precision(10);
44 | const features = [];
45 | for (let y = -80; y <= 80; y += step) {
46 | for (let x = -180; x < 180; x += step) {
47 | features.push({
48 | type: "Feature",
49 | properties: {},
50 | geometry: {
51 | type: "MultiPolygon",
52 | coordinates: [circle([x, y]).coordinates],
53 | },
54 | });
55 | }
56 | }
57 |
58 | return { type: "FeatureCollection", features: features };
59 | }
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Modules Global
71 |
72 |
73 |
74 |
75 |
76 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 17:08:58 GMT+0100 (Central European Standard Time)
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/docs/properties_add.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/add.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/add.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | // Add
30 | import { str2fun } from "../helpers/str2fun.js";
31 |
32 | /**
33 | * @function properties/add
34 | * @deprecated
35 | * @summary From now on, use {@link addproperty}
36 | */
37 | export function add({ x, field, expression }) {
38 | let data = [...x.features.map((d) => ({ ...d.properties }))];
39 |
40 | // Get keys
41 | let keys = [];
42 | x.features
43 | .map((d) => d.properties)
44 | .forEach((d) => {
45 | keys.push(Object.keys(d));
46 | });
47 | keys = Array.from(new Set(keys.flat()));
48 |
49 | keys.forEach((d) => {
50 | expression = expression.replace(d, `d.${d}`);
51 | });
52 |
53 | expression = "d=> " + expression;
54 |
55 | let newfield = data.map(str2fun(expression));
56 | // let newfield = data.map((d) => d.pop / d.gdp);
57 |
58 | data.forEach((d, i) => {
59 | d = Object.assign(d, { [field]: newfield[i] });
60 | });
61 |
62 | let output = JSON.parse(JSON.stringify(x));
63 | output.features.map((d, i) => (d.properties = { ...data[i] }));
64 | return output;
65 | }
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Global
77 |
78 |
79 |
80 |
81 |
82 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/docs/properties_head.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/head.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/head.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function properties/head
31 | * @deprecated
32 | * @summary From now on, use {@link head}
33 | */
34 | export function head({ x, field, nb = 10 }) {
35 | let features = [...x.features];
36 | features = features
37 | .filter((d) => d.properties[field] != "")
38 | .filter((d) => d.properties[field] != null)
39 | .filter((d) => d.properties[field] != undefined)
40 | .filter((d) => d.properties[field] != +Infinity)
41 | .filter((d) => d.properties[field] != -Infinity)
42 | .filter((d) => d.properties[field] != NaN);
43 |
44 | let head = features.sort(
45 | (a, b) => +b.properties[field] - +a.properties[field]
46 | );
47 | features = features.slice(0, nb);
48 | let output = JSON.parse(JSON.stringify(x));
49 | output.features = features;
50 | return output;
51 | }
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Global
63 |
64 |
65 |
66 |
67 |
68 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/docs/properties_keep.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/keep.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/keep.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import { remove } from "./remove.js";
30 |
31 | /**
32 | * @function properties/keep
33 | * @deprecated
34 | * @summary From now on, use {@link columns}
35 | */
36 | export function keep({ x, fields }) {
37 | // Get all keys
38 | let keys = [];
39 | x.features
40 | .map((d) => d.properties)
41 | .forEach((d) => {
42 | keys.push(Object.keys(d));
43 | });
44 | keys = Array.from(new Set(keys.flat()));
45 |
46 | // Fields to be removed
47 | let diff = keys.filter((k) => !fields.includes(k));
48 | return remove({ x, field: diff });
49 | }
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Global
61 |
62 |
63 |
64 |
65 |
66 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/docs/properties_remove.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/remove.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/remove.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function properties/remove
31 | * @deprecated
32 | * @summary From now on, use {@link columns}
33 | */
34 | export function remove({ x, field }) {
35 | let data = [...x.features.map((d) => ({ ...d.properties }))];
36 | data.forEach((d) => {
37 | if (Array.isArray(field)) {
38 | field.forEach((e) => delete d[e]);
39 | } else {
40 | delete d[field];
41 | }
42 | });
43 | let output = JSON.parse(JSON.stringify(x));
44 | output.features.map((d, i) => (d.properties = { ...data[i] }));
45 | return output;
46 | }
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Global
58 |
59 |
60 |
61 |
62 |
63 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/docs/properties_select.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/select.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/select.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import { str2fun } from "../helpers/str2fun.js";
30 |
31 | /**
32 | * @function properties/select
33 | * @deprecated
34 | * @summary From now on, use {@link filter}
35 | */
36 | export function select({ x, expression }) {
37 | let features = [...x.features];
38 |
39 | // Get keys
40 | let keys = [];
41 | x.features
42 | .map((d) => d.properties)
43 | .forEach((d) => {
44 | keys.push(Object.keys(d));
45 | });
46 | keys = Array.from(new Set(keys.flat()));
47 |
48 | keys.forEach((d) => {
49 | expression = expression.replace(d, `d.properties.${d}`);
50 | });
51 |
52 | expression = "d => " + expression;
53 |
54 | let output = JSON.parse(JSON.stringify(x));
55 | output.features = features.filter(str2fun(expression));
56 | return output;
57 | }
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Global
69 |
70 |
71 |
72 |
73 |
74 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/properties_subset.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/subset.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/subset.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function properties/subset
31 | * @deprecated
32 | * @summary From now on, use {@link filter}
33 | */
34 | export function subset({ x, field, selection, inverse = false }) {
35 | let features = [...x.features];
36 | selection = !Array.isArray(selection) ? [selection] : selection;
37 |
38 | if (inverse) {
39 | selection = Array.from(
40 | new Set(features.map((d) => d.properties[field]))
41 | ).filter((d) => !selection.includes(d));
42 | }
43 | let result = [];
44 |
45 | selection.forEach((e) => {
46 | result.push(features.filter((d) => d.properties[field] == e));
47 | });
48 |
49 | let output = JSON.parse(JSON.stringify(x));
50 | output.features = result.flat();
51 | return output;
52 | }
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Global
64 |
65 |
66 |
67 |
68 |
69 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/docs/properties_table.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/table.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/table.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function properties/remove
31 | * @deprecated
32 | * @summary From now on, use directly `geojson.features.map(d => d.properties)`
33 | */
34 | export function table(geojson) {
35 | return JSON.parse(JSON.stringify(geojson.features.map((d) => d.properties)));
36 | }
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Global
48 |
49 |
50 |
51 |
52 |
53 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/properties_tail.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: properties/tail.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: properties/tail.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * @function properties/tail
31 | * @deprecated
32 | * @summary From now on, use {@link tail}
33 | */
34 | export function tail({ x, field, nb = 10 }) {
35 | let features = [...x.features];
36 | features = features
37 | .filter((d) => d.properties[field] != "")
38 | .filter((d) => d.properties[field] != null)
39 | .filter((d) => d.properties[field] != undefined)
40 | .filter((d) => d.properties[field] != +Infinity)
41 | .filter((d) => d.properties[field] != -Infinity)
42 | .filter((d) => d.properties[field] != NaN);
43 |
44 | let head = features.sort(
45 | (a, b) => +a.properties[field] - +b.properties[field]
46 | );
47 | features = features.slice(0, nb);
48 | let output = JSON.parse(JSON.stringify(x));
49 | output.features = features;
50 | return output;
51 | }
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Global
63 |
64 |
65 |
66 |
67 |
68 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 08:52:10 GMT+0100 (Central European Standard Time)
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/docs/scripts/collapse.js:
--------------------------------------------------------------------------------
1 | function hideAllButCurrent(){
2 | //by default all submenut items are hidden
3 | //but we need to rehide them for search
4 | document.querySelectorAll("nav > ul").forEach(function(parent) {
5 | if (parent.className.indexOf("collapse_top") !== -1) {
6 | parent.style.display = "none";
7 | }
8 | });
9 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) {
10 | parent.style.display = "none";
11 | });
12 | document.querySelectorAll("nav > h3").forEach(function(section) {
13 | if (section.className.indexOf("collapsed_header") !== -1) {
14 | section.addEventListener("click", function(){
15 | if (section.nextSibling.style.display === "none") {
16 | section.nextSibling.style.display = "block";
17 | } else {
18 | section.nextSibling.style.display = "none";
19 | }
20 | });
21 | }
22 | });
23 |
24 | //only current page (if it exists) should be opened
25 | var file = window.location.pathname.split("/").pop().replace(/\.html/, '');
26 | document.querySelectorAll("nav > ul > li > a").forEach(function(parent) {
27 | var href = parent.attributes.href.value.replace(/\.html/, '');
28 | if (file === href) {
29 | if (parent.parentNode.parentNode.className.indexOf("collapse_top") !== -1) {
30 | parent.parentNode.parentNode.style.display = "block";
31 | }
32 | parent.parentNode.querySelectorAll("ul li").forEach(function(elem) {
33 | elem.style.display = "block";
34 | });
35 | }
36 | });
37 | }
38 |
39 | hideAllButCurrent();
--------------------------------------------------------------------------------
/docs/scripts/commonNav.js:
--------------------------------------------------------------------------------
1 | if (typeof fetch === 'function') {
2 | const init = () => {
3 | if (typeof scrollToNavItem !== 'function') return false
4 | scrollToNavItem()
5 | // hideAllButCurrent not always loaded
6 | if (typeof hideAllButCurrent === 'function') hideAllButCurrent()
7 | return true
8 | }
9 | fetch('./nav.inc.html')
10 | .then(response => response.ok ? response.text() : `${response.url} => ${response.status} ${response.statusText}`)
11 | .then(body => {
12 | document.querySelector('nav').innerHTML += body
13 | // nav.js should be quicker to load than nav.inc.html, a fallback just in case
14 | return init()
15 | })
16 | .then(done => {
17 | if (done) return
18 | let i = 0
19 | ;(function waitUntilNavJs () {
20 | if (init()) return
21 | if (i++ < 100) return setTimeout(waitUntilNavJs, 300)
22 | console.error(Error('nav.js not loaded after 30s waiting for it'))
23 | })()
24 | })
25 | .catch(error => console.error(error))
26 | } else {
27 | console.error(Error('Browser too old to display commonNav (remove commonNav docdash option)'))
28 | }
29 |
--------------------------------------------------------------------------------
/docs/scripts/linenumber.js:
--------------------------------------------------------------------------------
1 | /*global document */
2 | (() => {
3 | const source = document.getElementsByClassName('prettyprint source linenums');
4 | let i = 0;
5 | let lineNumber = 0;
6 | let lineId;
7 | let lines;
8 | let totalLines;
9 | let anchorHash;
10 |
11 | if (source && source[0]) {
12 | anchorHash = document.location.hash.substring(1);
13 | lines = source[0].getElementsByTagName('li');
14 | totalLines = lines.length;
15 |
16 | for (; i < totalLines; i++) {
17 | lineNumber++;
18 | lineId = `line${lineNumber}`;
19 | lines[i].id = lineId;
20 | if (lineId === anchorHash) {
21 | lines[i].className += ' selected';
22 | }
23 | }
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/docs/scripts/nav.js:
--------------------------------------------------------------------------------
1 | function scrollToNavItem() {
2 | var path = window.location.href.split('/').pop().replace(/\.html/, '');
3 | document.querySelectorAll('nav a').forEach(function(link) {
4 | var href = link.attributes.href.value.replace(/\.html/, '');
5 | if (path === href) {
6 | link.scrollIntoView({block: 'center'});
7 | return;
8 | }
9 | })
10 | }
11 |
12 | scrollToNavItem();
13 |
--------------------------------------------------------------------------------
/docs/scripts/polyfill.js:
--------------------------------------------------------------------------------
1 | //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/
2 | if (typeof(NodeList.prototype.forEach)!==typeof(alert)){
3 | NodeList.prototype.forEach=Array.prototype.forEach;
4 | }
--------------------------------------------------------------------------------
/docs/scripts/prettify/lang-css.js:
--------------------------------------------------------------------------------
1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
3 |
--------------------------------------------------------------------------------
/docs/scripts/resize.js:
--------------------------------------------------------------------------------
1 | /* global document */
2 | // This file is @deprecated
3 |
4 | var NAVBAR_OPTIONS = {};
5 |
6 | (function() {
7 | var NAVBAR_RESIZE_LOCAL_STORAGE_KEY = 'NAVBAR_RESIZE_LOCAL_STORAGE_KEY';
8 |
9 | var navbar = document.querySelector('#navbar');
10 | var footer = document.querySelector('#footer');
11 | var mainSection = document.querySelector('#main');
12 | var localStorageResizeObject = JSON.parse(
13 | // eslint-disable-next-line no-undef
14 | localStorage.getItem(NAVBAR_RESIZE_LOCAL_STORAGE_KEY)
15 | );
16 |
17 | /**
18 | * Check whether we have any resize value in local storage or not.
19 | * If we have resize value then resize the navbar.
20 | **/
21 | if (localStorageResizeObject) {
22 | navbar.style.width = localStorageResizeObject.width;
23 | mainSection.style.marginLeft = localStorageResizeObject.width;
24 | footer.style.marginLeft = localStorageResizeObject.width;
25 | }
26 |
27 | var navbarSlider = document.querySelector('#navbar-resize');
28 |
29 | function resizeNavbar(event) {
30 | var pageX = event.pageX,
31 | pageXPlusPx = event.pageX + 'px',
32 | min = Number.parseInt(NAVBAR_OPTIONS.min, 10) || 300,
33 | max = Number.parseInt(NAVBAR_OPTIONS.max, 10) || 600;
34 |
35 | /**
36 | * Just to add some checks. If min is smaller than 10 then
37 | * user may accidentally end up reducing the size of navbar
38 | * less than 10. In that case user will not able to resize navbar
39 | * because navbar slider will be hidden.
40 | */
41 | if (min < 10) {
42 | min = 10;
43 | }
44 |
45 | /**
46 | * Only resize if pageX in range between min and max
47 | * allowed value.
48 | */
49 | if (min < pageX && pageX < max) {
50 | navbar.style.width = pageXPlusPx;
51 | mainSection.style.marginLeft = pageXPlusPx;
52 | footer.style.marginLeft = pageXPlusPx;
53 | }
54 | }
55 |
56 | function setupEventListeners() {
57 | // eslint-disable-next-line no-undef
58 | window.addEventListener('mousemove', resizeNavbar);
59 | // eslint-disable-next-line no-undef
60 | window.addEventListener('touchmove', resizeNavbar);
61 | }
62 |
63 | function afterRemovingEventListeners() {
64 | // eslint-disable-next-line no-undef
65 | localStorage.setItem(
66 | NAVBAR_RESIZE_LOCAL_STORAGE_KEY,
67 | JSON.stringify({
68 | width: navbar.style.width
69 | })
70 | );
71 | }
72 |
73 | function removeEventListeners() {
74 | // eslint-disable-next-line no-undef
75 | window.removeEventListener('mousemove', resizeNavbar);
76 | // eslint-disable-next-line no-undef
77 | window.removeEventListener('touchend', resizeNavbar);
78 | afterRemovingEventListeners();
79 | }
80 |
81 | navbarSlider.addEventListener('mousedown', setupEventListeners);
82 | navbarSlider.addEventListener('touchstart', setupEventListeners);
83 | // eslint-disable-next-line no-undef
84 | window.addEventListener('mouseup', removeEventListeners);
85 | })();
86 |
87 | // eslint-disable-next-line no-unused-vars
88 | function setupResizeOptions(options) {
89 | NAVBAR_OPTIONS = options;
90 | }
91 |
--------------------------------------------------------------------------------
/docs/scripts/search.js:
--------------------------------------------------------------------------------
1 |
2 | var searchAttr = 'data-search-mode';
3 | function contains(a,m){
4 | return (a.textContent || a.innerText || "").toUpperCase().indexOf(m) !== -1;
5 | };
6 |
7 | //on search
8 | document.getElementById("nav-search").addEventListener("keyup", function(event) {
9 | var search = this.value.toUpperCase();
10 |
11 | if (!search) {
12 | //no search, show all results
13 | document.documentElement.removeAttribute(searchAttr);
14 |
15 | document.querySelectorAll("nav > ul > li:not(.level-hide)").forEach(function(elem) {
16 | elem.style.display = "block";
17 | });
18 |
19 | if (typeof hideAllButCurrent === "function"){
20 | //let's do what ever collapse wants to do
21 | hideAllButCurrent();
22 | } else {
23 | //menu by default should be opened
24 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
25 | elem.style.display = "block";
26 | });
27 | }
28 | } else {
29 | //we are searching
30 | document.documentElement.setAttribute(searchAttr, '');
31 |
32 | //show all parents
33 | document.querySelectorAll("nav > ul > li").forEach(function(elem) {
34 | elem.style.display = "block";
35 | });
36 | document.querySelectorAll("nav > ul").forEach(function(elem) {
37 | elem.style.display = "block";
38 | });
39 | //hide all results
40 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
41 | elem.style.display = "none";
42 | });
43 | //show results matching filter
44 | document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) {
45 | if (!contains(elem.parentNode, search)) {
46 | return;
47 | }
48 | elem.parentNode.style.display = "block";
49 | });
50 | //hide parents without children
51 | document.querySelectorAll("nav > ul > li").forEach(function(parent) {
52 | var countSearchA = 0;
53 | parent.querySelectorAll("a").forEach(function(elem) {
54 | if (contains(elem, search)) {
55 | countSearchA++;
56 | }
57 | });
58 |
59 | var countUl = 0;
60 | var countUlVisible = 0;
61 | parent.querySelectorAll("ul").forEach(function(ulP) {
62 | // count all elements that match the search
63 | if (contains(ulP, search)) {
64 | countUl++;
65 | }
66 |
67 | // count all visible elements
68 | var children = ulP.children
69 | for (i=0; i ul.collapse_top").forEach(function(parent) {
86 | var countVisible = 0;
87 | parent.querySelectorAll("li").forEach(function(elem) {
88 | if (elem.style.display !== "none") {
89 | countVisible++;
90 | }
91 | });
92 |
93 | if (countVisible == 0) {
94 | //has no child at all and does not contain text
95 | parent.style.display = "none";
96 | }
97 | });
98 | }
99 | });
--------------------------------------------------------------------------------
/docs/scripts/search.min.js:
--------------------------------------------------------------------------------
1 | const searchId="LiBfqbJVcV",searchHash="#"+searchId,searchContainer=document.querySelector("#PkfLWpAbet"),searchWrapper=document.querySelector("#iCxFxjkHbP"),searchCloseButton=document.querySelector("#VjLlGakifb"),searchInput=document.querySelector("#vpcKVYIppa"),resultBox=document.querySelector("#fWwVHRuDuN");function showResultText(e){resultBox.innerHTML=`${e} `}function hideSearch(){window.location.hash===searchHash&&history.go(-1),window.onhashchange=null,searchContainer&&(searchContainer.style.display="none")}function listenCloseKey(e){"Escape"===e.key&&(hideSearch(),window.removeEventListener("keyup",listenCloseKey))}function showSearch(){try{hideMobileMenu()}catch(e){console.error(e)}window.onhashchange=hideSearch,window.location.hash!==searchHash&&history.pushState(null,null,searchHash),searchContainer&&(searchContainer.style.display="flex",window.addEventListener("keyup",listenCloseKey)),searchInput&&searchInput.focus()}async function fetchAllData(){var{hostname:e,protocol:t,port:n}=location,t=t+"//"+e+(""!==n?":"+n:"")+baseURL,e=new URL("data/search.json",t);const a=await fetch(e);n=(await a.json()).list;return n}function onClickSearchItem(t){const n=t.currentTarget;if(n){const a=n.getAttribute("href")||"";t=a.split("#")[1]||"";let e=document.getElementById(t);e||(t=decodeURI(t),e=document.getElementById(t)),e&&setTimeout(function(){bringElementIntoView(e)},100)}}function buildSearchResult(e){let t="";var n=/(<([^>]+)>)/gi;for(const s of e){const{title:c="",description:i=""}=s.item;var a=s.item.link.replace('.*/,""),o=c.replace(n,""),r=i.replace(n,"");t+=`
2 |
3 | ${o}
4 | ${r||"No description available."}
5 |
6 | `}return t}function getSearchResult(e,t,n){var t={...{shouldSort:!0,threshold:.4,location:0,distance:100,maxPatternLength:32,minMatchCharLength:1,keys:t}},a=Fuse.createIndex(t.keys,e);const o=new Fuse(e,t,a),r=o.search(n);return 20{o=null,a||t.apply(this,e)},n),a&&!o&&t.apply(this,e)}}let searchData;async function search(e){e=e.target.value;if(resultBox)if(e){if(!searchData){showResultText("Loading...");try{searchData=await fetchAllData()}catch(e){return console.log(e),void showResultText("Failed to load result.")}}e=getSearchResult(searchData,["title","description"],e);e.length?resultBox.innerHTML=buildSearchResult(e):showResultText("No result found! Try some different combination.")}else showResultText("Type anything to view search result");else console.error("Search result container not found")}function onDomContentLoaded(){const e=document.querySelectorAll(".search-button");var t=debounce(search,300);searchCloseButton&&searchCloseButton.addEventListener("click",hideSearch),e&&e.forEach(function(e){e.addEventListener("click",showSearch)}),searchContainer&&searchContainer.addEventListener("click",hideSearch),searchWrapper&&searchWrapper.addEventListener("click",function(e){e.stopPropagation()}),searchInput&&searchInput.addEventListener("keyup",t),window.location.hash===searchHash&&showSearch()}window.addEventListener("DOMContentLoaded",onDomContentLoaded),window.addEventListener("hashchange",function(){window.location.hash===searchHash&&showSearch()});
--------------------------------------------------------------------------------
/docs/scripts/third-party/hljs-line-num.js:
--------------------------------------------------------------------------------
1 | !function(r,o){"use strict";var e,l="hljs-ln",s="hljs-ln-line",f="hljs-ln-code",c="hljs-ln-numbers",u="hljs-ln-n",h="data-line-number",n=/\r\n|\r|\n/g;function t(e){for(var n=e.toString(),t=e.anchorNode;"TD"!==t.nodeName;)t=t.parentNode;for(var r=e.focusNode;"TD"!==r.nodeName;)r=r.parentNode;var e=parseInt(t.dataset.lineNumber),o=parseInt(r.dataset.lineNumber);if(e==o)return n;var a,i=t.textContent,l=r.textContent;for(o{6} ',[s,c,u,h,f,a+t.startFrom,0{1}',[l,o])}return e}function m(e){var n=e.className;if(/hljs-/.test(n)){for(var t=g(e.innerHTML),r=0,o="";r{1}\n',[n,0
2 |
3 |
4 |
5 | JSDoc: Source: utils/makevalid.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utils/makevalid.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | import initGeosJs from "geos-wasm";
30 | import { geojsonToGeosGeom } from "../helpers/geojsonToGeosGeom";
31 | import { geosGeomToGeojson } from "../helpers/geosGeomToGeojson";
32 | import { featurecollection } from "./featurecollection.js";
33 |
34 | /**
35 | * Returns a geoJSON which is valid according to the GEOS validity rules, and preserves as much as possible of the input geometry's extent, dimension, and structure.
36 | *
37 | * @param {object|array} x - The targeted FeatureCollection / Features / Geometries
38 | *
39 | */
40 | export async function makevalid(x) {
41 | // TODO: This will create a new GEOS instance with every call
42 | // to geosunion. Ideally, we should create a single instance
43 | // when the library is loaded and then just pass it around
44 |
45 | const geos = await initGeosJs();
46 | x = featurecollection(x);
47 | const geosGeom = geojsonToGeosGeom(x, geos);
48 | const newGeom = geos.GEOSMakeValid(geosGeom);
49 | const validgeom = geosGeomToGeojson(newGeom, geos).geometries;
50 | x.features.forEach((d, i) => (d.geometry = validgeom[i]));
51 | return x;
52 | }
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Global
64 |
65 |
66 |
67 |
68 |
69 | Documentation generated by JSDoc 4.0.4 on Thu Mar 27 2025 16:28:16 GMT+0100 (Central European Standard Time)
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/docs/utils_rewind.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Source: utils/rewind.js
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Source: utils/rewind.js
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | /**
30 | * Rewind a FeatureCollection counterclockwise and inner rings.
31 | * Adapted from MapBox geojson-rewind code (https://github.com/mapbox/grojson-rewind) under ISC license
32 | *
33 | * Example: {@link https://observablehq.com/@neocartocnrs/rewind?collection=@neocartocnrs/geotoolbox Observable notebook}
34 | *
35 | * @param {object} x - a FeatureCollection
36 | * @param {object} options - Optional parameters
37 | * @param {boolean} [options.outer=true] - Rewind Rings Outer
38 | * @param {boolean} [options.mutate=true] - Mutate the Input geoJSON
39 | * @returns {{features: {geometry: {}, type: string, properties: {}}[], type: string}} - The resulting GeoJSON FeatureCollection
40 | *
41 | */
42 |
43 | export function rewind(x, options = {}) {
44 | let outer = options.outer === false ? false : true;
45 | let mutate = options.mutate === false ? false : true;
46 | let geo = mutate === true ? x : JSON.parse(JSON.stringify(x));
47 | for (let i = 0; i < geo.features.length; i++) {
48 | if (geo.features[i].geometry.type === "Polygon") {
49 | rewindRings(geo.features[i].geometry.coordinates, outer);
50 | } else if (geo.features[i].geometry.type === "MultiPolygon") {
51 | for (let j = 0; j < geo.features[i].geometry.coordinates.length; j++) {
52 | rewindRings(geo.features[i].geometry.coordinates[j], outer);
53 | }
54 | }
55 | }
56 | return geo;
57 | }
58 |
59 | function rewindRings(rings, outer) {
60 | if (rings.length === 0) return;
61 | rewindRing(rings[0], outer);
62 | for (let i = 1; i < rings.length; i++) {
63 | rewindRing(rings[i], !outer);
64 | }
65 | }
66 |
67 | function rewindRing(ring, dir) {
68 | let tArea = 0;
69 | let err = 0;
70 | for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
71 | const k = (ring[i][0] - ring[j][0]) * (ring[j][1] + ring[i][1]);
72 | const m = tArea + k;
73 | err += Math.abs(tArea) >= Math.abs(k) ? tArea - m + k : k - m + tArea;
74 | tArea = m;
75 | }
76 | if (tArea + err >= 0 !== !!dir) ring.reverse();
77 | }
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Global
89 |
90 |
91 |
92 |
93 |
94 | Documentation generated by JSDoc 4.0.4 on Fri Mar 21 2025 08:15:43 GMT+0100 (Central European Standard Time)
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/examples/simplify.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
geotoolbox.simplify(geojson, {k})
10 |
11 | k (0.10)
12 |
13 |
14 |
15 |
16 |
17 |
53 |
--------------------------------------------------------------------------------
/examples/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "include": ["src", "README.md"],
4 | "includePattern": ".js$",
5 | "excludePattern": "(node_modules/|docs)"
6 | },
7 | "plugins": ["plugins/markdown", "plugins/summarize"],
8 |
9 | "opts": {
10 | "encoding": "utf8",
11 | "readme": "./doc_index.md",
12 | "destination": "docs/",
13 | "recurse": true,
14 | "verbose": false,
15 | // "template": "node_modules/docdash",
16 | "theme_opts": {
17 | "homepageTitle": "Geotoolbox",
18 | "meta": [
19 | {
20 | "name": "author",
21 | "content": "Nicolas Lambert"
22 | }
23 | ],
24 | "displayModuleHeader": true,
25 | "static_dir": ["./img", "./examples"],
26 | "includeFilesListInHomepage": true,
27 | "homepageTitle": "Geotoolbox"
28 | },
29 | "markdown": {
30 | "hardwrap": false,
31 | "idInHeadings": false
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "geotoolbox",
3 | "version": "3.0.4",
4 | "description": "geotoolbox is GIS javascript library. It is based on d3geo, topojson and geos-wasm.",
5 | "main": "src/index.js",
6 | "module": "src/index.js",
7 | "jsdelivr": "dist/index.min.js",
8 | "unpkg": "dist/index.min.js",
9 | "exports": {
10 | "umd": "./dist/index.min.js",
11 | "default": "./src/index.js"
12 | },
13 | "type": "module",
14 | "files": [
15 | "src",
16 | "dist"
17 | ],
18 | "scripts": {
19 | "build": "rollup --config",
20 | "prepare": "npm run build",
21 | "docs": "jsdoc --configure jsdoc.json --verbose",
22 | "test": "node test/test.mjs"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/neocarto/geotoolbox.git"
27 | },
28 | "keywords": [
29 | "GIS",
30 | "geoJSON",
31 | "geospatial",
32 | "geocomputing",
33 | "geography"
34 | ],
35 | "author": "Nicolas Lambert",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/neocarto/geotoolbox/issues"
39 | },
40 | "homepage": "https://github.com/neocarto/geotoolbox#readme",
41 | "devDependencies": {
42 | "@rollup/plugin-babel": "^5.3.0",
43 | "@rollup/plugin-commonjs": "^21.0.1",
44 | "@rollup/plugin-node-resolve": "^13.1.3",
45 | "json-diff": "^1.0.6",
46 | "lodash": "^4.17.21",
47 | "minami": "^1.2.3",
48 | "rollup-plugin-terser": "^7.0.2"
49 | },
50 | "dependencies": {
51 | "d3-array": "^3.2.4",
52 | "d3-dsv": "^3.0.1",
53 | "d3-geo": "^3.0.1",
54 | "d3-geo-projection": "^4.0.0",
55 | "docdash": "^2.0.2",
56 | "gdal3.js": "^2.8.1",
57 | "geojson-precision": "^1.0.0",
58 | "geos-wasm": "^1.1.6",
59 | "jsdoc": "^4.0.4",
60 | "topojson-client": "^3.1.0",
61 | "topojson-server": "^3.0.1",
62 | "topojson-simplify": "^3.0.3"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | // import de nos plugins
2 | import commonjs from "@rollup/plugin-commonjs";
3 | import noderesolve from "@rollup/plugin-node-resolve";
4 | import babel from "@rollup/plugin-babel";
5 | import { terser } from "rollup-plugin-terser";
6 |
7 | export default {
8 | input: "src/index.js",
9 | output: {
10 | format: "umd",
11 | file: "dist/index.min.js",
12 | inlineDynamicImports: true,
13 | name: "geotoolbox",
14 | },
15 |
16 | plugins: [
17 | commonjs(), // prise en charge de require
18 | noderesolve(), // prise en charge des modules depuis node_modules
19 | babel({ babelHelpers: "bundled" }), // transpilation
20 | terser(), // minification
21 | ],
22 | };
23 |
--------------------------------------------------------------------------------
/src/aggregate.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 | import { type } from "./helpers/type.js";
3 | import { dissolve } from "./dissolve.js";
4 | import { topology } from "topojson-server";
5 | import { merge } from "topojson-client";
6 | const topojson = Object.assign({}, { topology, merge });
7 |
8 | /**
9 | * @function aggregate
10 | * @summary Aggregate geometries (based on topojson). The `aggregate()` function allows to merge all geometries of a geoJSON based on their topology. The `id` parameter allows to aggregate based on a specific field.
11 | * @description Based on `topojson.merge`.
12 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
13 | * @param {object} options - Optional parameters
14 | * @param {string} [options.id = null] - The id of the features to aggregate
15 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
16 | * @example
17 | * geotoolbox.aggregate(*a geojson*)
18 | */
19 |
20 | export function aggregate(data, { id = null } = {}) {
21 | const handle = check(data);
22 | let x = handle.import(data);
23 | let result;
24 |
25 | let dim = type(x).dimension;
26 | if (id != null && id != undefined) {
27 | let arr = Array.from(new Set(x.features.map((d) => d.properties[id])));
28 | let features = [];
29 | arr.forEach((myid) => {
30 | let geo = {
31 | type: "FeatureCollection",
32 | features: x.features.filter((d) => d.properties[id] == myid),
33 | };
34 | //return geo;
35 |
36 | let geom;
37 | if (dim == 3) {
38 | let topo = topojson.topology({ foo: geo });
39 | geom = topojson.merge(topo, topo.objects.foo.geometries);
40 | }
41 |
42 | if (dim == 2) {
43 | geom = {
44 | type: "MultiLineString",
45 | coordinates: dissolve(geo).features.map(
46 | (d) => d.geometry.coordinates
47 | ),
48 | };
49 | }
50 |
51 | if (dim == 1) {
52 | geom = {
53 | type: "MultiPoint",
54 | coordinates: dissolve(geo).features.map(
55 | (d) => d.geometry.coordinates
56 | ),
57 | };
58 | }
59 |
60 | features.push({
61 | type: "Feature",
62 | properties: { id: myid },
63 | geometry: geom,
64 | });
65 | });
66 |
67 | result = {
68 | type: "FeatureCollection",
69 | features: features,
70 | };
71 | } else {
72 | let geom;
73 | if (dim == 3) {
74 | let topo = topojson.topology({ foo: x });
75 | geom = topojson.merge(topo, topo.objects.foo.geometries);
76 | }
77 |
78 | if (dim == 2) {
79 | geom = {
80 | type: "MultiLineString",
81 | coordinates: dissolve(x).features.map((d) => d.geometry.coordinates),
82 | };
83 | }
84 |
85 | if (dim == 1) {
86 | geom = {
87 | type: "MultiPoint",
88 | coordinates: dissolve(x).features.map((d) => d.geometry.coordinates),
89 | };
90 | }
91 |
92 | result = {
93 | type: "FeatureCollection",
94 | features: [
95 | {
96 | type: "Feature",
97 | properties: {},
98 | geometry: geom,
99 | },
100 | ],
101 | };
102 | }
103 | result.name = "aggregate";
104 | return handle.export(result);
105 | }
106 |
--------------------------------------------------------------------------------
/src/autotype.js:
--------------------------------------------------------------------------------
1 | import { isgeojson, isarrayofobjects } from "./helpers/helpers";
2 | import { autoType } from "d3-dsv";
3 | const d3 = Object.assign({}, { autoType });
4 |
5 | /**
6 | * @function autotype
7 | * @summary The function detects common data types such as numbers, dates and booleans, and convert properties values to the corresponding JavaScript type. Besed on d3.autoType().
8 | * @description Based on `d3.autoType`.
9 | * @param {object|Array} data - A GeoJSON FeatureCollection or an array of objects
10 | * @param {object} options - Optional parameters
11 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
12 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
13 | * @example
14 | * geotoolbox.autotype(*a geojson or an array of objects*)
15 | */
16 | export function autotype(data, { mutate = false } = {}) {
17 | let x = data;
18 | if (isgeojson(x)) {
19 | if (!mutate) {
20 | x = JSON.parse(JSON.stringify(data));
21 | }
22 | x.features = x.features.map((d) => ({
23 | ...d,
24 | properties: d3.autoType(
25 | Object.fromEntries(
26 | Object.entries(d.properties).map(([key, value]) => [
27 | key,
28 | String(value),
29 | ])
30 | )
31 | ),
32 | }));
33 | } else if (isarrayofobjects(x)) {
34 | x = x.map((d) =>
35 | d3.autoType(
36 | Object.fromEntries(
37 | Object.entries(d).map(([key, value]) => [key, String(value)])
38 | )
39 | )
40 | );
41 | if (mutate) {
42 | data.splice(0, data.length, ...x);
43 | }
44 | }
45 | return x;
46 | }
47 |
--------------------------------------------------------------------------------
/src/border.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 | import { topology } from "topojson-server";
3 | import { neighbors, mesh } from "topojson-client";
4 | const topojson = Object.assign({}, { topology, neighbors, mesh });
5 | import * as d3array from "d3-array";
6 | const d3 = Object.assign({}, d3array);
7 |
8 | /**
9 | * @function border
10 | * @summary Extract boundaries from a GeoJSON FeatureCollection
11 | * @description Based on `topojson.mesh()` and `topojson.neighbors()`
12 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
13 | * @param {object} options - Optional parameters.
14 | * @param {boolean} [options.id = false] - If you don't provide an id field (default), then the function returns a geoJSON with a single geometry. If you choose an id field, the function returns a geoJSON with multiple geometries and associated codes (two ids for each geometry).
15 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
16 | * @example
17 | * geotoolbox.border(*a geojson*, {id: "ISO3"})
18 | */
19 | export function border(data, { id } = {}) {
20 | const handle = check(data);
21 | let x = handle.import(data);
22 | const topo = topojson.topology({ d: x });
23 |
24 | // With ids
25 | if (typeof id === "string" && id !== "") {
26 | const ids = [...new Set(x.features.map((d) => d?.properties[id]))].filter(
27 | (d) => ![null, undefined, ""].includes(d)
28 | );
29 | const neighbors = topojson.neighbors(topo.objects["d"].geometries);
30 | let result = [];
31 | ids.forEach((e) => {
32 | let r = neighbors[ids.indexOf(e)].map((i) => ({
33 | type: "Feature",
34 | properties: {
35 | id: e.toString() + "|" + ids[i].toString(),
36 | i: e,
37 | j: ids[i],
38 | },
39 | geometry: topojson.mesh(
40 | topo,
41 | topo.objects["d"],
42 | (a, b) => (a.properties[id] == e) & (b.properties[id] == ids[i])
43 | ),
44 | }));
45 | result.push(r);
46 | });
47 |
48 | x.features = result.flat();
49 | } else {
50 | x.features = [
51 | {
52 | type: "Feature",
53 | properties: {},
54 | geometry: topojson.mesh(
55 | topo,
56 | Object.entries(topo.objects)[0][1],
57 | (a, b) => a !== b
58 | ),
59 | },
60 | ];
61 | }
62 | x.name = "border";
63 | return handle.export(x);
64 | }
65 |
--------------------------------------------------------------------------------
/src/centroid.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 | import { geoArea, geoCentroid, geoIdentity, geoPath } from "d3-geo";
3 | const d3 = Object.assign({}, { geoArea, geoCentroid, geoIdentity, geoPath });
4 |
5 | /**
6 | * @function centroid
7 | * @summary Calculates the centroids of geometries.
8 | * @description Based on `d3.geoArea()` and `d3.geoCentroid()`
9 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
10 | * @param {object} options - Optional parameters.
11 | * @param {boolean} [options.larget = true] - If true, set the point at the centre of the largest polygon.
12 | * @param {boolean} [options.geo = true] - Use true to consider the centroid from world coordinates on the globe. If you use false, then you are considering the coordinates within the svg document.
13 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
14 | * @example
15 | * geotoolbox.centroid(*a geojson*, {largest: true})
16 | */
17 |
18 | export function centroid(data, { largest = true, geo = true } = {}) {
19 | const handle = check(data);
20 | let x = handle.import(data);
21 |
22 | let path = d3.geoPath(d3.geoIdentity());
23 | function largestPolygon(d) {
24 | var best = {};
25 | var bestArea = 0;
26 | d.geometry.coordinates.forEach(function (coords) {
27 | var poly = { type: "Polygon", coordinates: coords };
28 | var area = geo ? d3.geoArea(poly) : path.area(poly);
29 | if (area > bestArea) {
30 | bestArea = area;
31 | best = poly;
32 | }
33 | });
34 | return best;
35 | }
36 |
37 | let centers = x.features.map((d) => {
38 | if (geo) {
39 | console.log(d);
40 | d.geometry.coordinates = d3.geoCentroid(
41 | largest == true
42 | ? d.geometry.type == "Polygon"
43 | ? d
44 | : largestPolygon(d, true)
45 | : d
46 | );
47 | } else {
48 | d.geometry.coordinates = path.centroid(
49 | largest == true
50 | ? d.geometry.type == "Polygon"
51 | ? d
52 | : largestPolygon(d, false)
53 | : d
54 | );
55 | }
56 |
57 | d.geometry.type = "Point";
58 | return d;
59 | });
60 |
61 | x.features = centers;
62 | x.name = "centroid";
63 | return handle.export(x);
64 | }
65 |
--------------------------------------------------------------------------------
/src/clip.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { isemptygeom } from "./helpers/helpers";
4 | import { check } from "./helpers/check.js";
5 |
6 | /**
7 | * @function clip
8 | * @summary Clip a geometry with another
9 | * @description Based on `geos.GEOSIntersection() and geos.GEOSDifference()`.
10 | * @async
11 | * @param {object|array} data - data to be clipped. A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
12 | * @param {object} options - Optional parameters
13 | * @param {object|array} [options.clip] - Clip. A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
14 | * @param {boolean} [options.reverse = fase] - Use true to use `geos.GEOSDifference()` operator instead of `geos.GEOSIntersection()`.
15 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
16 | * @example
17 | * await geotoolbox.clip(*a geojson*, { clip: *another geojson* })
18 | */
19 |
20 | export async function clip(data, { clip, reverse = false } = {}) {
21 | if (clip) {
22 | const geos = await geosloader();
23 | const handle = check(data);
24 | let x = handle.import(data);
25 | const geosClip = geos.GEOSUnaryUnion(
26 | geojsonToGeosGeom(check(clip).import(clip), geos)
27 | );
28 |
29 | let result = [];
30 | x.features.forEach((d) => {
31 | const geosGeom = geojsonToGeosGeom(d, geos);
32 | const newGeom =
33 | reverse == true
34 | ? geos.GEOSDifference(geosGeom, geosClip)
35 | : geos.GEOSIntersection(geosGeom, geosClip);
36 |
37 | const geom = geosGeomToGeojson(newGeom, geos);
38 |
39 | if (!isemptygeom(geom)) {
40 | result.push({
41 | type: "Feature",
42 | properties: d.properties,
43 | geometry: geom,
44 | });
45 | }
46 | geos.GEOSFree(newGeom);
47 | });
48 | geos.GEOSFree(geosClip);
49 |
50 | const final = {
51 | type: "FeatureCollection",
52 | name: "clip",
53 | features: result,
54 | };
55 | final.name = "clip";
56 | return handle.export(final);
57 | } else {
58 | return data;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/clipbyrect.js:
--------------------------------------------------------------------------------
1 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
2 | import { check } from "./helpers/check.js";
3 | import { geosloader } from "./helpers/geos.js";
4 |
5 | /**
6 | * @function clipbyrect
7 | * @summary Intersection optimized for a rectangular clipping polygon. By default, the function cuts off anything that exceeds the Earth's bbox.
8 | * @description Based on `geos.GEOSClipByRect()`.
9 | * @async
10 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
11 | * @param {object} options - Optional parameters
12 | * @param {array} [options.bbox = [90, 180, -90, -180]] - Coordinates of the bbox [top, right, bottom, left].
13 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
14 | * @example
15 | * await geotoolbox.clipbyrect(*a geojson*, {bbox:[50, 50, -50, -50]})
16 | */
17 | export async function clipbyrect(data, { bbox = [90, 180, -90, -180] } = {}) {
18 | const geos = await geosloader();
19 | const handle = check(data);
20 | let x = handle.import(data);
21 | const geosgeom = geojsonToGeosGeom(x, geos);
22 | const newgeom = geos.GEOSClipByRect(
23 | geosgeom,
24 | bbox[3],
25 | bbox[2],
26 | bbox[1],
27 | bbox[0]
28 | );
29 | let result = geosGeomToGeojson(newgeom, geos);
30 | geos.GEOSFree(geosgeom);
31 | geos.GEOSFree(newgeom);
32 | x.features = [{ type: "Feature", properties: {}, geometry: result }];
33 | x.name = "clipbyrect";
34 | return handle.export(x);
35 | }
36 |
--------------------------------------------------------------------------------
/src/columns.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function columns
5 | * @summary Select, rename and reorder properties
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {array} [options.keys ] - Properties to keep
9 | * @param {array} [options.rename] - Properties to rename
10 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
11 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
12 | * @example
13 | * geotoolbox.columns(*a geojson or an array of objects*, {keys: ["ISO3","Population"]", rename:["id","pop"]})
14 | */
15 |
16 | export function columns(data, { keys, rename, mutate = false } = {}) {
17 | let x = data;
18 | if (isgeojson(x)) {
19 | if (!mutate) {
20 | x = JSON.parse(JSON.stringify(data));
21 | }
22 | if (
23 | rename != undefined &&
24 | Array.isArray(rename) &&
25 | rename.length == keys.length
26 | ) {
27 | // Select and rename properties
28 | const fields = keys.map((d, i) => [d, rename[i]]);
29 | x.features.forEach((d) => {
30 | d.properties = Object.fromEntries(
31 | fields.map((k) => [k[1], d?.properties[k[0]]])
32 | );
33 | });
34 | } else if (keys !== undefined && Array.isArray(keys)) {
35 | // Select properties
36 | x.features.forEach((d) => {
37 | d.properties = Object.fromEntries(
38 | keys.map((k) => [k, d?.properties[k]])
39 | );
40 | });
41 | }
42 | } else if (isarrayofobjects(x)) {
43 | if (
44 | rename != undefined &&
45 | Array.isArray(rename) &&
46 | rename.length == keys.length
47 | ) {
48 | const fields = keys.map((d, i) => [d, rename[i]]);
49 | x = x.map((d) => Object.fromEntries(fields.map((k) => [k[1], d[k[0]]])));
50 | } else if (keys !== undefined && Array.isArray(keys)) {
51 | x = x.map((d) => Object.fromEntries(keys.map((k) => [k, d[k]])));
52 | }
53 |
54 | if (mutate) {
55 | data.splice(0, data.length, ...x);
56 | }
57 | }
58 |
59 | return x;
60 | }
61 |
--------------------------------------------------------------------------------
/src/combine.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function combine
5 | * @summary Get the first n Features. The function sort data and returns the nb first elements. If a field is selected, then the function returns the top values. If the entries are strings, then the alphabetic order is applied.
6 | * @param {object|array} data - An array containig several of GeoJSONs or arrays of objects
7 | * @param {object} options - Optional parameters
8 | * @param {boolean} [options.fillkeys = true] - Use true to ensure that all features have all properties.
9 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
10 | * @example
11 | * geotoolbox.head(*a geojson or an array of objects*)
12 | */
13 | export function combine(data, { fillkeys = true } = {}) {
14 | if (data.every((x) => isgeojson(x))) {
15 | let features = JSON.parse(
16 | JSON.stringify(data.map((d) => d.features).flat())
17 | );
18 | if (fillkeys) {
19 | let prop = [
20 | ...new Set(features.map((d) => Object.keys(d.properties)).flat()),
21 | ];
22 | features.forEach((feature) => {
23 | prop.forEach((key) => {
24 | if (!(key in feature.properties)) {
25 | feature.properties[key] = undefined;
26 | }
27 | });
28 | });
29 | }
30 |
31 | return { type: "FeatureCollection", features };
32 | } else if (data.every((x) => isarrayofobjects(x))) {
33 | let output = JSON.parse(JSON.stringify(data.flat()));
34 | if (fillkeys) {
35 | let prop = [...new Set(output.map((d) => Object.keys(d)).flat())];
36 | output = output.map((obj) =>
37 | Object.fromEntries(prop.map((key) => [key, obj[key]]))
38 | );
39 | }
40 | return output;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/concavehull.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { check } from "./helpers/check.js";
4 |
5 | /**
6 | * @function concavehull
7 | * @summary Returns a "concave hull" of a geometry. A concave hull is a polygon which contains all the points of the input, but is a better approximation than the convex hull to the area occupied by the input. Frequently used to convert a multi-point into a polygonal area. that contains all the points in the input Geometry.
8 | * @description Based on `geos.GEOSConcaveHull()`.
9 | * @async
10 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
11 | * @param {object} options - Optional parameters.
12 | * @param {boolean} [options.ratio = 0] - The edge length ratio value, between 0 and 1.
13 | * @param {boolean} [options.holes = true] - When non-zero, the polygonal output may contain holes.
14 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
15 | * @example
16 | * await geotoolbox.concavehull(*a geojson*, {ratio: 0.5})
17 | */
18 |
19 | export async function concavehull(data, { ratio = 0, holes = true } = {}) {
20 | const geos = await geosloader();
21 | const handle = check(data);
22 | let x = handle.import(data);
23 | const geosgeom = geojsonToGeosGeom(x, geos);
24 | const output = geos.GEOSConcaveHull(geosgeom, ratio, holes ? 1 : 0);
25 | let result = geosGeomToGeojson(output, geos);
26 | geos.GEOSFree(geosgeom);
27 | geos.GEOSFree(output);
28 |
29 | return handle.export({
30 | type: "FeatureCollection",
31 | name: "concavehull",
32 | features: [{ type: "Feature", properties: {}, geometry: result }],
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/src/convexhull.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { check } from "./helpers/check.js";
4 |
5 | /**
6 | * @function convexhull
7 | * @summary Returns a "convex hull" of a geometry. The smallest convex Geometry that contains all the points in the input Geometry
8 | * @description Based on `geos.GEOSConvexHull()`.
9 | * @async
10 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
11 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
12 | * @example
13 | * await geotoolbox.convexehull(*a geojson*)
14 | */
15 |
16 | export async function convexhull(data) {
17 | const geos = await geosloader();
18 | const handle = check(data);
19 | let x = handle.import(data);
20 | const geosgeom = geojsonToGeosGeom(x, geos);
21 | const output = geos.GEOSConvexHull(geosgeom);
22 | let result = geosGeomToGeojson(output, geos);
23 | geos.GEOSFree(geosgeom);
24 | geos.GEOSFree(output);
25 | return handle.export({
26 | type: "FeatureCollection",
27 | name: "convexhull",
28 | features: [{ type: "Feature", properties: {}, geometry: result }],
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/src/copy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function copy
3 | * @summary Deep copy
4 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
5 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
6 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
7 | * @example
8 | * geotoolbox.copy(*a geojson or an array of objects*)
9 | */
10 | export function copy(data, { mutatebydefault = false } = {}) {
11 | return structuredClone(data);
12 | }
13 |
--------------------------------------------------------------------------------
/src/dedupe.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function dedupe
5 | * @summary Deletes duplicates. Keeps the first element.
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {number} [options.key] - id to consider
9 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
10 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
11 | * @example
12 | * geotoolbox.dedupe(*a geojson or an array of objects*, {key: "ISO3"})
13 | */
14 |
15 | export function dedupe(data, { key, mutate = false } = {}) {
16 | let x = data;
17 |
18 | if (isgeojson(x)) {
19 | if (!mutate) {
20 | x = JSON.parse(JSON.stringify(data));
21 | }
22 |
23 | const values = [...new Set(x.features.map((d) => d?.properties[key]))];
24 | let arr = [];
25 | values.forEach((e) => {
26 | arr.push(x.features.find((d) => d?.properties[key] == e));
27 | });
28 | x.features = arr;
29 | } else if (isarrayofobjects(x)) {
30 | const values = [...new Set(x.map((d) => d[key]))];
31 |
32 | let arr = [];
33 | values.forEach((e) => {
34 | arr.push(x.find((d) => d[key] == e));
35 | });
36 |
37 | x = arr;
38 | if (mutate) {
39 | data.splice(0, data.length, ...x);
40 | }
41 | }
42 | return x;
43 | }
44 |
--------------------------------------------------------------------------------
/src/densify.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { isemptygeom } from "./helpers/helpers";
4 | import { check } from "./helpers/check.js";
5 |
6 | /**
7 | * @function densify
8 | * @summary Densifies a geometry using a given distance tolerance
9 | * @description Based on `geos.GEOSDensify()`
10 | * @async
11 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
12 | * @param {object} options - Optional parameters
13 | * @param {number} [options.dist = 1] - The minimal distance between nodes
14 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
15 | * @example
16 | * await geotoolbox.densify(*a geojson*, { dist:0.5 })
17 | */
18 |
19 | export async function densify(data, { dist = 1, mutate = false } = {}) {
20 | const geos = await geosloader();
21 | const handle = check(data, mutate);
22 | let x = handle.import(data);
23 |
24 | x.features.forEach((d) => {
25 | if (isemptygeom(d?.geometry)) {
26 | d.geometry = undefined;
27 | } else {
28 | const geosGeom = geojsonToGeosGeom(d, geos);
29 | const newGeom = geos.GEOSDensify(geosGeom, dist);
30 | const densiygeom = geosGeomToGeojson(newGeom, geos);
31 | d.geometry = densiygeom;
32 | geos.GEOSFree(geosGeom);
33 | geos.GEOSFree(newGeom);
34 | geos.GEOSFree(densiygeom);
35 | }
36 | });
37 | x.name = "densify";
38 | return handle.export(x);
39 | }
40 |
--------------------------------------------------------------------------------
/src/deprecated/add.js:
--------------------------------------------------------------------------------
1 | // Add
2 | import { str2fun } from "../helpers/helpers.js";
3 |
4 | /**
5 | * @function properties/add
6 | * @deprecated
7 | * @summary From now on, use {@link derive}
8 | */
9 | export function add({ x, field, expression }) {
10 | let data = [...x.features.map((d) => ({ ...d.properties }))];
11 |
12 | // Get keys
13 | let keys = [];
14 | x.features
15 | .map((d) => d.properties)
16 | .forEach((d) => {
17 | keys.push(Object.keys(d));
18 | });
19 | keys = Array.from(new Set(keys.flat()));
20 |
21 | keys.forEach((d) => {
22 | expression = expression.replace(d, `d.${d}`);
23 | });
24 |
25 | expression = "d=> " + expression;
26 |
27 | let newfield = data.map(str2fun(expression));
28 | // let newfield = data.map((d) => d.pop / d.gdp);
29 |
30 | data.forEach((d, i) => {
31 | d = Object.assign(d, { [field]: newfield[i] });
32 | });
33 |
34 | let output = JSON.parse(JSON.stringify(x));
35 | output.features.map((d, i) => (d.properties = { ...data[i] }));
36 | return output;
37 | }
38 |
--------------------------------------------------------------------------------
/src/deprecated/coords2geo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function coords2geo
3 | * @deprecated
4 | * @summary From now on, use {@link featurecollection}
5 | */
6 | export function coords2geo(data, options = {}) {
7 | let arr = JSON.parse(JSON.stringify(data));
8 |
9 | let lat = options.lat || options.latitude;
10 | let lon = options.lon || options.lng || options.longitude;
11 | let coords = options.coords || options.coordinates;
12 | let sep =
13 | options.sep || options.separator ? options.sep || options.separator : ",";
14 | let reverse = options.reverse ? true : false;
15 |
16 | // check fields
17 | if (lat == undefined && lon == undefined && coords == undefined) {
18 | let checkcoords = [
19 | "coords",
20 | "Coords",
21 | "coord",
22 | "Coords",
23 | "Coordinates",
24 | "coordinates",
25 | "Coordinate",
26 | "coordinate",
27 | ];
28 | let checklat = ["lat", "Lat", "LAT", "Latitude", "latitude"];
29 | let checklon = [
30 | "lon",
31 | "Lon",
32 | "LON",
33 | "lng",
34 | "Lng",
35 | "LNG",
36 | "Longitude",
37 | "longitude",
38 | ];
39 |
40 | let keys = [];
41 | arr.forEach((d) => keys.push(Object.keys(d)));
42 | keys = Array.from(new Set(keys.flat()));
43 |
44 | lat = checklat.filter((d) => keys.includes(d))[0];
45 | lon = checklon.filter((d) => keys.includes(d))[0];
46 | coords = checkcoords.filter((d) => keys.includes(d))[0];
47 | }
48 |
49 | // case1: lat & lng coords in separate columns
50 | if (lat && lon) {
51 | let x = reverse ? lon : lat;
52 | let y = reverse ? lat : lon;
53 |
54 | return {
55 | type: "FeatureCollection",
56 | features: data.map((d) => ({
57 | type: "Feature",
58 | properties: d,
59 | geometry: {
60 | type: "Point",
61 | coordinates: [+d[y], +d[x]],
62 | },
63 | })),
64 | };
65 | }
66 |
67 | // case2: lat & lng coords in a single column
68 |
69 | if (coords) {
70 | return {
71 | type: "FeatureCollection",
72 | features: data.map((d) => ({
73 | type: "Feature",
74 | properties: d,
75 | geometry: {
76 | type: "Point",
77 | coordinates: reverse
78 | ? getcoords(d[coords])
79 | : getcoords(d[coords]).reverse(),
80 | },
81 | })),
82 | };
83 | }
84 |
85 | return coords;
86 | }
87 |
88 | function txt2coords(str, sep = ",") {
89 | str = str.replace(/[ ]+/g, "");
90 | let coords = str
91 | .split(sep)
92 | .map((d) => d.replace(",", "."))
93 | .map((d) => d.replace(/[^\d.-]/g, ""))
94 | .map((d) => +d);
95 |
96 | if (coords.length != 2) {
97 | coords = [undefined, undefined];
98 | }
99 | return coords;
100 | }
101 |
102 | function wkt2coords(str) {
103 | let result = str.match(/\(([^)]+)\)/g);
104 | return result === null
105 | ? [undefined, undefined]
106 | : result[0]
107 | .replace(/\s\s+/g, " ")
108 | .replace("(", "")
109 | .replace(")", "")
110 | .trimStart()
111 | .trimEnd()
112 | .split(" ")
113 | .map((d) => d.replace(",", "."))
114 | .map((d) => +d);
115 | }
116 |
117 | function getcoords(str) {
118 | return str
119 | ? str.toLowerCase().includes("point")
120 | ? wkt2coords(str)
121 | : txt2coords(str)
122 | : null;
123 | }
124 |
--------------------------------------------------------------------------------
/src/deprecated/head.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function properties/head
3 | * @deprecated
4 | * @summary From now on, use {@link head}
5 | */
6 | export function head({ x, field, nb = 10 }) {
7 | let features = [...x.features];
8 | features = features
9 | .filter((d) => d.properties[field] != "")
10 | .filter((d) => d.properties[field] != null)
11 | .filter((d) => d.properties[field] != undefined)
12 | .filter((d) => d.properties[field] != +Infinity)
13 | .filter((d) => d.properties[field] != -Infinity)
14 | .filter((d) => d.properties[field] != NaN);
15 |
16 | let head = features.sort(
17 | (a, b) => +b.properties[field] - +a.properties[field]
18 | );
19 | features = features.slice(0, nb);
20 | let output = JSON.parse(JSON.stringify(x));
21 | output.features = features;
22 | return output;
23 | }
24 |
--------------------------------------------------------------------------------
/src/deprecated/keep.js:
--------------------------------------------------------------------------------
1 | import { remove } from "./remove.js";
2 |
3 | /**
4 | * @function properties/keep
5 | * @deprecated
6 | * @summary From now on, use {@link columns}
7 | */
8 | export function keep({ x, fields }) {
9 | // Get all keys
10 | let keys = [];
11 | x.features
12 | .map((d) => d.properties)
13 | .forEach((d) => {
14 | keys.push(Object.keys(d));
15 | });
16 | keys = Array.from(new Set(keys.flat()));
17 |
18 | // Fields to be removed
19 | let diff = keys.filter((k) => !fields.includes(k));
20 | return remove({ x, field: diff });
21 | }
22 |
--------------------------------------------------------------------------------
/src/deprecated/remove.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function properties/remove
3 | * @deprecated
4 | * @summary From now on, use {@link columns}
5 | */
6 | export function remove({ x, field }) {
7 | let data = [...x.features.map((d) => ({ ...d.properties }))];
8 | data.forEach((d) => {
9 | if (Array.isArray(field)) {
10 | field.forEach((e) => delete d[e]);
11 | } else {
12 | delete d[field];
13 | }
14 | });
15 | let output = JSON.parse(JSON.stringify(x));
16 | output.features.map((d, i) => (d.properties = { ...data[i] }));
17 | return output;
18 | }
19 |
--------------------------------------------------------------------------------
/src/deprecated/select.js:
--------------------------------------------------------------------------------
1 | import { str2fun } from "../helpers/helpers.js";
2 |
3 | /**
4 | * @function properties/select
5 | * @deprecated
6 | * @summary From now on, use {@link filter}
7 | */
8 | export function select({ x, expression }) {
9 | let features = [...x.features];
10 |
11 | // Get keys
12 | let keys = [];
13 | x.features
14 | .map((d) => d.properties)
15 | .forEach((d) => {
16 | keys.push(Object.keys(d));
17 | });
18 | keys = Array.from(new Set(keys.flat()));
19 |
20 | keys.forEach((d) => {
21 | expression = expression.replace(d, `d.properties.${d}`);
22 | });
23 |
24 | expression = "d => " + expression;
25 |
26 | let output = JSON.parse(JSON.stringify(x));
27 | output.features = features.filter(str2fun(expression));
28 | return output;
29 | }
30 |
--------------------------------------------------------------------------------
/src/deprecated/subset.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function properties/subset
3 | * @deprecated
4 | * @summary From now on, use {@link filter}
5 | */
6 | export function subset({ x, field, selection, inverse = false }) {
7 | let features = [...x.features];
8 | selection = !Array.isArray(selection) ? [selection] : selection;
9 |
10 | if (inverse) {
11 | selection = Array.from(
12 | new Set(features.map((d) => d.properties[field]))
13 | ).filter((d) => !selection.includes(d));
14 | }
15 | let result = [];
16 |
17 | selection.forEach((e) => {
18 | result.push(features.filter((d) => d.properties[field] == e));
19 | });
20 |
21 | let output = JSON.parse(JSON.stringify(x));
22 | output.features = result.flat();
23 | return output;
24 | }
25 |
--------------------------------------------------------------------------------
/src/deprecated/table.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function properties/remove
3 | * @deprecated
4 | * @summary From now on, use directly `geojson.features.map(d => d.properties)`
5 | */
6 | export function table(geojson) {
7 | return JSON.parse(JSON.stringify(geojson.features.map((d) => d.properties)));
8 | }
9 |
--------------------------------------------------------------------------------
/src/deprecated/tail.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function properties/tail
3 | * @deprecated
4 | * @summary From now on, use {@link tail}
5 | */
6 | export function tail({ x, field, nb = 10 }) {
7 | let features = [...x.features];
8 | features = features
9 | .filter((d) => d.properties[field] != "")
10 | .filter((d) => d.properties[field] != null)
11 | .filter((d) => d.properties[field] != undefined)
12 | .filter((d) => d.properties[field] != +Infinity)
13 | .filter((d) => d.properties[field] != -Infinity)
14 | .filter((d) => d.properties[field] != NaN);
15 |
16 | let head = features.sort(
17 | (a, b) => +a.properties[field] - +b.properties[field]
18 | );
19 | features = features.slice(0, nb);
20 | let output = JSON.parse(JSON.stringify(x));
21 | output.features = features;
22 | return output;
23 | }
24 |
--------------------------------------------------------------------------------
/src/derive.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function derive
5 | * @summary Add a field to a dataset. The function allows to add a new property
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {string} [options.key = "_newkey"] - Name of the property
9 | * @param {number|string|function} [options.value] - You can set a simple number or string. You can also specify a function like `d=> d.properties.gdp/d.properties.pop * 1000`. With the *, +, - and / operators, you can also directly write `“gdp/pop*100”`.
10 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
11 | * @async
12 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
13 | * @example
14 | * // A simple example
15 | * geotoolbox.derive(*a geojson or an array of objects*, {key: "gdppc", value:"gdp/pop"})
16 | * // Another exemple with a, async function on geometries
17 | * geotoolbox.derive(world, {key: "isvalid", value: async (d) => (await geo.isvalidreason(d))})
18 | */
19 |
20 | export async function derive(
21 | data,
22 | { key = "_newkey", value, mutate = false } = {}
23 | ) {
24 | let x = data;
25 | const operators = /[+\-/*]/;
26 |
27 | // GeoJSON FeatureCollection
28 | if (isgeojson(data)) {
29 | if (!mutate) {
30 | x = JSON.parse(JSON.stringify(data));
31 | }
32 |
33 | if (
34 | typeof value == "number" ||
35 | (typeof value == "string" && !operators.test(value))
36 | ) {
37 | x.features.forEach((d) => {
38 | d.properties[key] = value;
39 | });
40 | } else if (typeof value == "function") {
41 | const values = await Promise.all(x.features.map(value)); // Gère les promesses
42 | x.features.forEach((d, i) => {
43 | d.properties[key] = values[i];
44 | });
45 | } else if (typeof value == "string" && operators.test(value)) {
46 | const prop = [
47 | ...new Set(
48 | x.features
49 | .map((d) => d.properties)
50 | .map((d) => Object.keys(d))
51 | .flat()
52 | ),
53 | ];
54 | const newprop = prop.map((d) => `d.properties['${d}']`);
55 | const functrsing =
56 | "d => " +
57 | prop.reduce(
58 | (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
59 | value
60 | );
61 | const values = await Promise.all(x.features.map(eval(functrsing))); // Gère les promesses
62 | x.features.forEach((d, i) => {
63 | d.properties[key] = values[i];
64 | });
65 | }
66 | }
67 |
68 | // Array of objects
69 | else if (isarrayofobjects(data)) {
70 | if (
71 | typeof value == "number" ||
72 | (typeof value == "string" && !operators.test(value))
73 | ) {
74 | x.forEach((d) => {
75 | d[key] = value;
76 | });
77 | } else if (typeof value == "function") {
78 | const values = await Promise.all(x.map(value)); // Gère les promesses
79 | x.forEach((d, i) => {
80 | d[key] = values[i];
81 | });
82 | } else if (typeof value == "string" && operators.test(value)) {
83 | const prop = [...new Set(x.map((d) => Object.keys(d)).flat())];
84 | const newprop = prop.map((d) => `d['${d}']`);
85 | const functrsing =
86 | "d => " +
87 | prop.reduce(
88 | (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
89 | value
90 | );
91 | const values = await Promise.all(x.map(eval(functrsing))); // Gère les promesses
92 | x.forEach((d, i) => {
93 | d[key] = values[i];
94 | });
95 | }
96 |
97 | if (mutate) {
98 | data.splice(0, data.length, ...x);
99 | }
100 | }
101 |
102 | return x;
103 | }
104 |
--------------------------------------------------------------------------------
/src/dissolve.js:
--------------------------------------------------------------------------------
1 | import { geoArea } from "d3-geo";
2 | import { check } from "./helpers/check.js";
3 | /**
4 | * @function dissolve
5 | * @summary Multi part to single part geometries. The `disolve()` function allows to convert "MultiPoint", "MultiLineString" or "MultiPolygon" to single "Point", "LineString" or "Polygon". In addition, a `__share` field is calculated, representing the surface share of each part.
6 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geome
7 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
8 | * @example
9 | * geotoolbox.dissolve(*a geojson*)
10 | */
11 |
12 | export function dissolve(data) {
13 | const handle = check(data);
14 | let x = handle.import(data);
15 |
16 | let result = [];
17 | x.features.forEach((d) => {
18 | result.push(sp(d));
19 | });
20 |
21 | const keys = Object.keys(x).filter((e) => e != "features");
22 | const obj = {};
23 | keys.forEach((d) => {
24 | obj[d] = x[d];
25 | });
26 | obj.features = result.flat();
27 |
28 | return handle.export(obj);
29 | }
30 |
31 | function sp(feature) {
32 | let result = [];
33 |
34 | if (feature?.geometry?.type?.includes("Multi")) {
35 | feature?.geometry?.coordinates.forEach((d) => {
36 | result.push({
37 | type: "Feature",
38 | properties: feature?.properties,
39 | geometry: {
40 | type: feature?.geometry?.type.replace("Multi", ""),
41 | coordinates: d,
42 | },
43 | });
44 | });
45 | } else {
46 | result.push({ ...feature });
47 | }
48 |
49 | const totalArea = geoArea(feature);
50 | result.forEach((d) => (d.__share = geoArea(d) / totalArea));
51 |
52 | return result;
53 | }
54 |
--------------------------------------------------------------------------------
/src/envelope.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { check } from "./helpers/check.js";
4 |
5 | /**
6 | * @function envelope
7 | * @summary Returns a "concave hull" of a geometry. A concave hull is a polygon which contains all the points of the input, but is a better approximation than the convex hull to the area occupied by the input. Frequently used to convert a multi-point into a polygonal area. that contains all the points in the input Geometry.
8 | * @description Based on `geos.GEOSConcaveHull()`.
9 | * @async
10 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
11 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
12 | * @example
13 | * await geotoolbox.envelope(*a geojson*)
14 | */
15 | export async function envelope(data) {
16 | const geos = await geosloader();
17 | const handle = check(data);
18 | let x = handle.import(data);
19 | const geosgeom = geojsonToGeosGeom(x, geos);
20 | const output = geos.GEOSEnvelope(geosgeom);
21 | let result = geosGeomToGeojson(output, geos);
22 | geos.GEOSFree(geosgeom);
23 | geos.GEOSFree(output);
24 | return handle.export({
25 | type: "FeatureCollection",
26 | features: [
27 | { type: "Feature", name: "envelope", properties: {}, geometry: result },
28 | ],
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/src/filter.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function filter
5 | * @summary Filter a dataset. The functions allows to subset a geoJSON or an array of objects
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {string|function} [options.func] - A function to filter the datset. But you can aslo use un string like "ISO3 = FRA" ou "pop > 1000"
9 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
10 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
11 | * @example
12 | * geotoolbox.filter(*a geojson or an array of objects*, {filter: "gdp >= 1000000" })
13 | */
14 | export function filter(data, { func, mutate = false } = {}) {
15 | let x = data;
16 | // geoJSON
17 | if (isgeojson(data)) {
18 | if (!mutate) {
19 | x = JSON.parse(JSON.stringify(data));
20 | }
21 |
22 | if (typeof func == "function") {
23 | x.features = x.features.filter(func);
24 | } else if (typeof func == "string") {
25 | const prop = [
26 | ...new Set(
27 | x.features
28 | .map((d) => d.properties)
29 | .map((d) => Object.keys(d))
30 | .flat()
31 | ),
32 | ];
33 | const newprop = prop.map((d) => "d.properties['" + d + "']");
34 |
35 | func =
36 | "d => " +
37 | replaceEquals(
38 | addQuotesIfString(
39 | prop.reduce(
40 | (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
41 | func
42 | )
43 | )
44 | );
45 |
46 | x.features = x.features.filter(eval(func));
47 | }
48 | } else if (isarrayofobjects(data)) {
49 | if (typeof func == "function") {
50 | x = x.filter(func);
51 | } else if (typeof func == "string") {
52 | const prop = [...new Set(x.map((d) => Object.keys(d)).flat())];
53 | const newprop = prop.map((d) => "d['" + d + "']");
54 |
55 | const func =
56 | "d => " +
57 | replaceEquals(
58 | addQuotesIfString(
59 | prop.reduce(
60 | (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
61 | func
62 | )
63 | )
64 | );
65 | x = x.filter(eval(func));
66 | }
67 |
68 | if (mutate) {
69 | data.splice(0, data.length, ...x);
70 | }
71 | }
72 |
73 | return x;
74 | }
75 |
76 | function replaceEquals(str) {
77 | return str.replace(/(?<=!]=?|==)\s*([A-Za-z_]+)/g,
83 | (match, operator, value) => {
84 | return `${operator} '${value}'`;
85 | }
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/src/geolines.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @function geolines
3 | * @summary Returns a GeoJSON FeatureCollection with the equator, tropics & polar circles.
4 | * @example
5 | * geotoolbox.geolines(*a geojson*)
6 | */
7 |
8 | export function geolines() {
9 | let features = [];
10 | let arr = [
11 | ["Equator", 0],
12 | ["Tropic of Cancer", 23.43656],
13 | ["Tropic of Capricorn", -23.43636],
14 | ["Arctic Circle", 66.56345],
15 | ["Antarctic Circle", -66.56364],
16 | ];
17 |
18 | arr.forEach((d) => {
19 | features.push({
20 | type: "Feature",
21 | properties: { name: d[0], latitude: d[1] },
22 | geometry: line(d[1]),
23 | });
24 | });
25 |
26 | return { type: "FeatureCollection", features: features };
27 | }
28 |
29 | function line(lat) {
30 | let arr = [];
31 | let i = -180;
32 | while (i <= 180) {
33 | arr.push([i, lat]);
34 | i += 2.5;
35 | }
36 | return { type: "MultiLineString", coordinates: [arr] };
37 | }
38 |
--------------------------------------------------------------------------------
/src/geomfunctions.js:
--------------------------------------------------------------------------------
1 | import { togeojson } from "./togeojson.js";
2 | import initGeosJs from "geos-wasm";
3 | import { geojsonToGeosGeom } from "geos-wasm/helpers";
4 |
5 | export async function area(g) {
6 | const geos = await initGeosJs();
7 | const geosgeom = geojsonToGeosGeom(togeojson(g), geos);
8 |
9 | const areaPtr = geos.Module._malloc(8);
10 | geos.GEOSArea(geosgeom, areaPtr);
11 | const area = geos.Module.getValue(areaPtr, "double");
12 | geos.GEOSFree(areaPtr);
13 | geos.GEOSFree(geosgeom);
14 |
15 | return area;
16 | }
17 |
--------------------------------------------------------------------------------
/src/head.js:
--------------------------------------------------------------------------------
1 | import {
2 | isFieldNumber,
3 | isFieldNumber2,
4 | isgeojson,
5 | isarrayofobjects,
6 | } from "./helpers/helpers";
7 | import { descending, ascending } from "d3-array";
8 | import { autoType } from "d3-dsv";
9 | const d3 = Object.assign({}, { descending, ascending, autoType });
10 |
11 | /**
12 | * @function head
13 | * @summary Get the first n Features. The function sort data and returns the nb first elements. If a field is selected, then the function returns the top values. If the entries are strings, then the alphabetic order is applied.
14 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
15 | * @param {object} options - Optional parameters
16 | * @param {number} [options.nb = 6] - Number of features to return
17 | * @param {boolean} [options.key = true] - Field to sort
18 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
19 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
20 | * @example
21 | * geotoolbox.head(*a geojson or an array of objects*)
22 | */
23 | export function head(data, { key, nb = 6, mutate = false } = {}) {
24 | let x = data;
25 |
26 | if (isgeojson(x)) {
27 | if (!mutate) {
28 | x = JSON.parse(JSON.stringify(data));
29 | }
30 | let features = [...x.features];
31 | if (key != undefined) {
32 | features = features
33 | .filter((d) => d.properties[key] != "")
34 | .filter((d) => d.properties[key] != null)
35 | .filter((d) => d.properties[key] != undefined)
36 | .filter((d) => d.properties[key] != +Infinity)
37 | .filter((d) => d.properties[key] != -Infinity)
38 | .filter((d) => d.properties[key] != NaN);
39 |
40 | const mysort = isFieldNumber(x, key) ? d3.descending : d3.ascending;
41 | features = features.sort((a, b) =>
42 | mysort(
43 | d3.autoType([String(a.properties[key])])[0],
44 | d3.autoType([String(b.properties[key])])[0]
45 | )
46 | );
47 | }
48 |
49 | x.features = features.slice(0, nb);
50 | } else if (isarrayofobjects(x)) {
51 | if (key != undefined) {
52 | x = x
53 | .filter((d) => d[key] != "")
54 | .filter((d) => d[key] != null)
55 | .filter((d) => d[key] != undefined)
56 | .filter((d) => d[key] != +Infinity)
57 | .filter((d) => d[key] != -Infinity)
58 | .filter((d) => d[key] != NaN);
59 |
60 | const mysort = isFieldNumber2(x, key) ? d3.descending : d3.ascending;
61 | x = x.sort((a, b) =>
62 | mysort(
63 | d3.autoType([String(a[key])])[0],
64 | d3.autoType([String(b[key])])[0]
65 | )
66 | );
67 | }
68 |
69 | x = x.slice(0, nb);
70 |
71 | if (mutate) {
72 | data.splice(0, data.length, ...x);
73 | }
74 | }
75 |
76 | return x;
77 | }
78 |
--------------------------------------------------------------------------------
/src/helpers/check.js:
--------------------------------------------------------------------------------
1 | import {
2 | isFeatureCollection,
3 | isFeatures,
4 | isFeature,
5 | isGeometries,
6 | isGeometry,
7 | isarrayofobjects,
8 | } from "./helpers.js";
9 |
10 | export function check(data) {
11 | let type = "";
12 | let funcimport;
13 | let funcexport;
14 |
15 | // Case where the input is already a valid GeoJSON (FeatureCollection)
16 | if (isFeatureCollection(data)) {
17 | type = "FeatureCollection";
18 | funcimport = (d) => copy(d); // Return a copy of the object
19 | funcexport = (d) => d;
20 | }
21 | // Case where the input is an array of features
22 | else if (isFeatures(data)) {
23 | type = "Features";
24 | funcimport = (d) => copy({ type: "FeatureCollection", features: d }); // Return a copy of the features array
25 | funcexport = (d) => d.features;
26 | }
27 | // Case where the input is a single feature
28 | else if (isFeature(data)) {
29 | type = "Feature";
30 | funcimport = (d) => copy({ type: "FeatureCollection", features: [d] }); // Return a copy of the feature
31 | funcexport = (d) => (d.features.length == 1 ? d.features[0] : d.features);
32 | }
33 | // Case where the input is an array of geometries
34 | else if (isGeometries(data)) {
35 | type = "Geometries";
36 | funcimport = (d) =>
37 | copy({
38 | type: "FeatureCollection",
39 | features: d.map((geometry) => ({
40 | type: "Feature",
41 | properties: {},
42 | geometry: geometry,
43 | })),
44 | });
45 | funcexport = (d) => d.features.map((f) => f.geometry);
46 | }
47 | // Case where the input is a single geometry
48 | else if (isGeometry(data)) {
49 | type = "Geometry";
50 | funcimport = (d) =>
51 | copy({
52 | type: "FeatureCollection",
53 | features: [{ type: "Feature", properties: {}, geometry: d }],
54 | });
55 | funcexport = (d) =>
56 | d.features.length == 1
57 | ? d.features[0].geometry
58 | : d.features.map((d) => d.geometry);
59 | }
60 | // else if (isarrayofobjects(data)) {
61 | // type = "Array of objects";
62 | // funcimport = (d) =>
63 | // copy({
64 | // type: "FeatureCollection",
65 | // features: d.map((e) => ({
66 | // type: null,
67 | // properties: e,
68 | // geometry: null,
69 | // })),
70 | // });
71 |
72 | // funcexport = (d) => d.features.map((f) => f.properties);
73 | // }
74 | else {
75 | type = undefined;
76 | funcimport = (d) => copy(d);
77 | funcexport = (d) => copy(d);
78 | }
79 |
80 | // Return the import/export functions
81 | return { type, import: funcimport, export: funcexport };
82 | }
83 |
84 | function copy(x) {
85 | return JSON.parse(JSON.stringify(x));
86 | }
87 |
--------------------------------------------------------------------------------
/src/helpers/expressiontovaluesinageojson.js:
--------------------------------------------------------------------------------
1 | import { isNumber } from "./helpers.js";
2 |
3 | export function expressiontovaluesinageojson(geojson, input) {
4 | const operators = /[+\-/*]/;
5 |
6 | if (isNumber(input)) {
7 | return geojson.features.map((d) => input);
8 | } else if (typeof input == "function") {
9 | return geojson.features.map(input);
10 | } else if (typeof input == "string") {
11 | const prop = [
12 | ...new Set(geojson.features.map((d) => Object.keys(d.properties)).flat()),
13 | ];
14 |
15 | const newprop = prop.map((d) => "d.properties['" + d + "']");
16 | const functrsing =
17 | "d => " +
18 | prop.reduce(
19 | (acc, mot, i) => acc.replace(new RegExp(mot, "g"), newprop[i]),
20 | input
21 | );
22 | const values = geojson.features.map(eval(functrsing));
23 | return values;
24 | }
25 |
26 | // if()
27 |
28 | // if (operators.test(str)) {
29 | // }
30 | }
31 |
--------------------------------------------------------------------------------
/src/helpers/geos.js:
--------------------------------------------------------------------------------
1 | import initGeos from "geos-wasm";
2 |
3 | let geosModule = null;
4 |
5 | export async function geosloader() {
6 | if (!geosModule) {
7 | geosModule = await initGeos();
8 | console.log("GEOS loaded!");
9 | }
10 | return geosModule;
11 | }
12 |
--------------------------------------------------------------------------------
/src/helpers/implantation.js:
--------------------------------------------------------------------------------
1 | export function implantation(x) {
2 | let types = Array.from(
3 | new Set(
4 | x.features
5 | .filter((d) => d != undefined)
6 | .filter((d) => d?.geometry !== null)
7 | .map((d) => d?.geometry?.type)
8 | )
9 | ).filter((d) => d != undefined);
10 |
11 | let tmp = [];
12 | if (types.indexOf("Polygon") !== -1 || types.indexOf("MultiPolygon") !== -1) {
13 | tmp.push(3);
14 | }
15 | if (
16 | types.indexOf("LineString") !== -1 ||
17 | types.indexOf("MultiLineString") !== -1
18 | ) {
19 | tmp.push(2);
20 | }
21 | if (types.indexOf("Point") !== -1 || types.indexOf("MultiPoint") !== -1) {
22 | tmp.push(1);
23 | }
24 | let result = tmp.length == 1 ? tmp[0] : -1;
25 | return result;
26 | }
27 |
--------------------------------------------------------------------------------
/src/helpers/table2geo.js:
--------------------------------------------------------------------------------
1 | export function table2geo(arr, lat, lon, coords, reverse) {
2 | // check fields
3 | let keys = [];
4 | arr.forEach((d) => keys.push(Object.keys(d)));
5 | keys = Array.from(new Set(keys.flat()));
6 | if (lat == undefined) {
7 | lat = ["lat", "Lat", "LAT", "Latitude", "latitude"].filter((d) =>
8 | keys.includes(d)
9 | )[0];
10 | }
11 |
12 | if (lon == undefined) {
13 | lon = [
14 | "lon",
15 | "Lon",
16 | "LON",
17 | "lng",
18 | "Lng",
19 | "LNG",
20 | "Longitude",
21 | "longitude",
22 | ].filter((d) => keys.includes(d))[0];
23 | }
24 | if (coords == undefined) {
25 | coords = [
26 | "coords",
27 | "Coords",
28 | "coord",
29 | "Coords",
30 | "Coordinates",
31 | "coordinates",
32 | "Coordinate",
33 | "coordinate",
34 | ].filter((d) => keys.includes(d))[0];
35 | }
36 |
37 | // case1: lat & lng coords in separate columns
38 | if (lat && lon) {
39 | let x = lat;
40 | let y = lon;
41 |
42 | return {
43 | type: "FeatureCollection",
44 | features: arr.map((d) => ({
45 | type: "Feature",
46 | properties: d,
47 | geometry: {
48 | type: "Point",
49 | coordinates: reverse
50 | ? [parseFloat(d[x]), parseFloat(d[y])]
51 | : [parseFloat(d[y]), parseFloat(d[x])],
52 | },
53 | })),
54 | };
55 | }
56 |
57 | // case2: lat & lng coords in a single column
58 |
59 | if (coords) {
60 | return {
61 | type: "FeatureCollection",
62 | features: arr.map((d) => ({
63 | type: "Feature",
64 | properties: d,
65 | geometry: {
66 | type: "Point",
67 | coordinates: reverse
68 | ? getcoords(d[coords])
69 | : getcoords(d[coords]).reverse(),
70 | },
71 | })),
72 | };
73 | }
74 |
75 | return coords;
76 | }
77 |
78 | function txt2coords(str, sep = ",") {
79 | str = str.replace(/[ ]+/g, "");
80 | let coords = str
81 | .split(sep)
82 | .map((d) => d.replace(",", "."))
83 | .map((d) => d.replace(/[^\d.-]/g, ""))
84 | .map((d) => +d);
85 |
86 | if (coords.length != 2) {
87 | coords = [undefined, undefined];
88 | }
89 | return coords;
90 | }
91 |
92 | function wkt2coords(str) {
93 | let result = str.match(/\(([^)]+)\)/g);
94 | return result === null
95 | ? [undefined, undefined]
96 | : result[0]
97 | .replace(/\s\s+/g, " ")
98 | .replace("(", "")
99 | .replace(")", "")
100 | .trimStart()
101 | .trimEnd()
102 | .split(" ")
103 | .map((d) => d.replace(",", "."))
104 | .map((d) => +d);
105 | }
106 |
107 | function getcoords(str) {
108 | return str
109 | ? str.toLowerCase().includes("point")
110 | ? wkt2coords(str)
111 | : txt2coords(str)
112 | : null;
113 | }
114 |
--------------------------------------------------------------------------------
/src/helpers/type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Return the geometry type contained in a GeoJSON FeatureCollection
3 | *
4 | * @param {object} x - The GeoJSON FeatureCollection
5 | * @returns {object} - The number of dimensions of the geometries (1 for punctual, 2 for lineal, 3 for zonal and -1 for composite) and the types of the geometries ("Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon")
6 | *
7 | */
8 |
9 | export function type(x) {
10 | let types = Array.from(new Set(x.features.map((d) => d.geometry.type)));
11 | let tmp = [];
12 |
13 | if (types.indexOf("Polygon") !== -1 || types.indexOf("MultiPolygon") !== -1) {
14 | tmp.push(3);
15 | }
16 |
17 | if (
18 | types.indexOf("LineString") !== -1 ||
19 | types.indexOf("MultiLineString") !== -1
20 | ) {
21 | tmp.push(2);
22 | }
23 |
24 | if (types.indexOf("Point") !== -1 || types.indexOf("MultiPoint") !== -1) {
25 | tmp.push(1);
26 | }
27 |
28 | let result = tmp.length == 1 ? tmp[0] : -1;
29 |
30 | return { dimension: result, types: types };
31 | }
32 |
--------------------------------------------------------------------------------
/src/htmltable.js:
--------------------------------------------------------------------------------
1 | import { isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function htmltable
5 | * @summary View a data table.
6 | * @param {object|array} data - a GeoJSON FeatureCollection or an array of features
7 | * @param {object} options - Optional parameters
8 | * @param {number} [options.maxrows] - Number max of lines
9 | * @returns {HTMLElement} - A html sortable table.
10 | * @example
11 | * geottolbox.htmltable(*a geojson*)
12 | */
13 |
14 | export function htmltable(data, { maxrows = null } = {}) {
15 | let x;
16 | let test = isgeojson(data);
17 | if (test) {
18 | x = data.features.map((d) => {
19 | return Object.assign(
20 | {
21 | ["GEOMETRY"]: d?.geometry?.type,
22 | // +
23 | // " (" +
24 | // d.geometry.coordinates.flat(10).length / 2 +
25 | // " pts)"
26 | },
27 | d.properties
28 | );
29 | });
30 | } else {
31 | x = data;
32 | }
33 |
34 | let table = document.createElement("table");
35 | let thead = document.createElement("thead");
36 | let headerRow = document.createElement("tr");
37 | const headers = Object.keys(x[0]);
38 |
39 | headers.forEach((header, index) => {
40 | const values = x.map((d) => d[header]);
41 | const count = values.length;
42 | console.log(values);
43 | const nb = values.filter(
44 | (d) =>
45 | !["", " ", " ", undefined, NaN, null, Infinity, -Infinity].includes(d)
46 | ).length;
47 |
48 | let th = document.createElement("th");
49 | th.innerHTML =
50 | header +
51 | ` ${nb}/${count} `;
52 | th.style.cursor = "pointer";
53 | th.style.backgroundColor = "white";
54 | th.style.color = "#444";
55 | th.style.padding = "10px";
56 | th.style.textAlign = "left";
57 | th.style.position = "sticky";
58 | th.style.top = "0";
59 | th.style.zIndex = "1";
60 | th.style.borderBottom = "2px solid #444";
61 | th.style.fontSize = "1em";
62 |
63 | let asc = true;
64 | th.addEventListener("click", () => {
65 | asc = !asc;
66 | sortTable(index, asc);
67 | });
68 |
69 | headerRow.appendChild(th);
70 | });
71 |
72 | thead.appendChild(headerRow);
73 | table.appendChild(thead);
74 |
75 | let tbody = document.createElement("tbody");
76 | const displayedData = maxrows ? x.slice(0, maxrows) : x;
77 |
78 | function renderTable(data) {
79 | tbody.innerHTML = "";
80 | data.forEach((row, index) => {
81 | let tr = document.createElement("tr");
82 | tr.style.backgroundColor = index % 2 === 0 ? "#f9f9f9" : "#fff";
83 | tr.style.fontSize = "0.9em";
84 | headers.forEach((header, i) => {
85 | let td = document.createElement("td");
86 | td.textContent = row[header];
87 | td.style.padding = "5px";
88 | td.style.borderBottom = "1px solid #ddd";
89 | td.style.textAlign = "left";
90 | if (test) {
91 | td.style.backgroundColor = i === 0 ? "#444" : undefined;
92 | td.style.color = i === 0 ? "white" : undefined;
93 | }
94 | tr.appendChild(td);
95 | });
96 | tbody.appendChild(tr);
97 | });
98 | }
99 |
100 | function sortTable(columnIndex, ascending) {
101 | displayedData.sort((a, b) => {
102 | let valA = a[headers[columnIndex]];
103 | let valB = b[headers[columnIndex]];
104 | if (!isNaN(parseFloat(valA)) && !isNaN(parseFloat(valB))) {
105 | return ascending ? valA - valB : valB - valA;
106 | }
107 | return ascending ? valA.localeCompare(valB) : valB.localeCompare(valA);
108 | });
109 | renderTable(displayedData);
110 | }
111 |
112 | renderTable(displayedData);
113 | table.appendChild(tbody);
114 |
115 | let container = document.createElement("div");
116 | container.style.overflowY = "auto";
117 | container.style.maxHeight = "400px";
118 | container.style.margin = "0";
119 | container.style.padding = "0";
120 | container.style.boxSizing = "border-box";
121 | container.style.overflowX = "hidden";
122 |
123 | container.appendChild(table);
124 | setTimeout(() => {
125 | const tableWidth = table.offsetWidth + 20;
126 | const containerWidth = container.offsetWidth;
127 | if (tableWidth > containerWidth) {
128 | container.style.overflowX = "scroll";
129 | } else {
130 | container.style.width = `${tableWidth}px`;
131 | container.style.overflowX = "hidden";
132 | }
133 | }, 0);
134 |
135 | return container;
136 | }
137 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { iterate } from "./iterate.js";
2 | export { htmltable } from "./htmltable.js";
3 | export { info } from "./info.js";
4 | export { copy } from "./copy.js";
5 | export { groupby } from "./groupby.js";
6 | export { dedupe } from "./dedupe.js";
7 | export { table } from "./table.js";
8 | export { join } from "./join.js";
9 | export { autotype } from "./autotype.js";
10 | export { head } from "./head.js";
11 | export { tail } from "./tail.js";
12 | export { derive } from "./derive.js";
13 | export { filter } from "./filter.js";
14 | export { replace } from "./replace.js";
15 | export { columns } from "./columns.js";
16 | export { sort } from "./sort.js";
17 | export { combine } from "./combine.js";
18 | export { simplify } from "./simplify.js";
19 | export { togeojson } from "./togeojson.js";
20 | export { stitch } from "./stitch.js";
21 | export { removeemptygeom } from "./removeemptygeom.js";
22 | export { resolveemptygeom } from "./resolveemptygeom.js";
23 | export { nodes } from "./nodes.js";
24 | export { tissot } from "./tissot.js";
25 | export { geolines } from "./geolines.js";
26 | export { bbox } from "./bbox.js";
27 | export { border } from "./border.js";
28 | export { roundcoordinates } from "./roundcoordinates.js";
29 | export { centroid } from "./centroid.js";
30 | export { aggregate } from "./aggregate.js";
31 | export { rewind } from "./rewind.js";
32 | export { rewind2 } from "./rewind2.js";
33 | export { reverse } from "./reverse.js";
34 | export { dissolve } from "./dissolve.js";
35 | export { clip } from "./clip.js";
36 | export { buffer } from "./buffer.js";
37 | export { clipbyrect } from "./clipbyrect.js";
38 | export { makevalid } from "./makevalid.js";
39 | export { densify } from "./densify.js";
40 | export { union } from "./union.js";
41 | export { concavehull } from "./concavehull.js";
42 | export { convexhull } from "./convexhull.js";
43 | export { envelope } from "./envelope.js";
44 | export {
45 | contains,
46 | covers,
47 | crosses,
48 | disjoint,
49 | coveredby,
50 | equals,
51 | intersects,
52 | overlaps,
53 | touches,
54 | within,
55 | isvalid,
56 | isvalidreason,
57 | } from "./operators.js";
58 |
59 | // -----------------------------------
60 | // deprecated
61 | export { coords2geo } from "./deprecated/coords2geo.js";
62 | import { add } from "./deprecated/add.js";
63 | import { select } from "./deprecated/select.js";
64 | import { keep } from "./deprecated/keep.js";
65 | import { remove } from "./deprecated/remove.js";
66 | import { table } from "./deprecated/table.js";
67 | import { subset } from "./deprecated/subset.js";
68 | import { head } from "./deprecated/head.js";
69 | import { tail } from "./deprecated/tail.js";
70 | export let properties = {
71 | add,
72 | select,
73 | keep,
74 | remove,
75 | table,
76 | subset,
77 | head,
78 | tail,
79 | };
80 |
--------------------------------------------------------------------------------
/src/info.js:
--------------------------------------------------------------------------------
1 | import { implantation } from "./helpers/implantation.js";
2 | import { geomtypes } from "./helpers/helpers";
3 | import { geosloader } from "./helpers/geos.js";
4 | import { geojsonToGeosGeom } from "geos-wasm/helpers";
5 | import { isemptygeom } from "./helpers/helpers";
6 |
7 | /**
8 | * @function info
9 | * @summary GeoJSON information. The function gives some informations about a geoJSON (size, number of nodes, type of features, etc)
10 | * @async
11 | * @param {object} data - A GeoJSON FeatureCollection
12 | * @example
13 | * geottolbox.info(*a geojson*)
14 | */
15 |
16 | export async function info(data) {
17 | const weight = (
18 | new Blob([JSON.stringify(data, null, 2)], {
19 | type: "application/json",
20 | }).size / 1024
21 | ).toFixed(0);
22 |
23 | const nodes =
24 | data.features
25 | .map((d) => d.geometry.coordinates)
26 | .flat(4)
27 | .filter((d) => d !== undefined).length / 2;
28 | 0;
29 |
30 | const accessor = new Map([
31 | [1, "point"],
32 | [2, "line"],
33 | [3, "poly"],
34 | ]);
35 |
36 | const type = implantation(data);
37 |
38 | const geos = await geosloader();
39 | let diagnostic = [];
40 | data.features.forEach((d, i) => {
41 | if (isemptygeom(d?.geometry)) {
42 | diagnostic.push({ index: i, isValid: 0, reason: "empty" });
43 | } else {
44 | const geosGeom = geojsonToGeosGeom(d, geos);
45 | if (geos.GEOSisEmpty(geosGeom)) {
46 | diagnostic.push({ index: i, isValid: 0, reason: "empty" });
47 | } else {
48 | diagnostic.push({
49 | index: i,
50 | isValid: geos.GEOSisValid(geosGeom),
51 | reason: geos.GEOSisValidReason(geosGeom),
52 | });
53 | }
54 | geos.GEOSFree(geosGeom);
55 | }
56 | });
57 |
58 | diagnostic = diagnostic.filter((d) => d.isValid == 0);
59 | diagnostic = diagnostic.length == 0 ? "Valid Geometries" : diagnostic;
60 |
61 | return {
62 | type: accessor.get(type),
63 | geometries: geomtypes(data),
64 | diagnostic,
65 | properties: [
66 | ...new Set(data?.features.map((d) => Object.keys(d?.properties)).flat()),
67 | ],
68 | weight: "~" + weight + " KO",
69 | nodes,
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/src/iterate.js:
--------------------------------------------------------------------------------
1 | import { isgeojson, isarrayofobjects } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function iterate
5 | * @summary Iterate and apply a function
6 | * @async
7 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
8 | * @param {object} options - Optional parameters
9 | * @param {function} [options.func = d => d] - A function to apply to each feature.
10 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
11 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
12 | * @example
13 | * // A simple example. Rename and select fields.
14 | * await geo.iterate(*a geojson*, {
15 | func: (d) => ({
16 | ...d
17 | properties: { id: d.properties.ISO3, pop: d.properties.population },
18 | })
19 | })
20 | * // Another example with a geojson and an async function
21 | * await geo.iterate(*a geojson*, {
22 | func: async (d) => ({
23 | ...d,
24 | geometry: await geo.buffer(d.geometry, { dist: d.properties.size })
25 | })
26 | */
27 |
28 | export async function iterate(data, { func = (d) => d, mutate = false } = {}) {
29 | let x = data;
30 | if (isgeojson(x)) {
31 | if (!mutate) {
32 | x = JSON.parse(JSON.stringify(data));
33 | }
34 | x.features = await Promise.all(
35 | x.features.map((d) => Promise.resolve(func(d)))
36 | );
37 | } else if (isarrayofobjects(x)) {
38 | x = await Promise.all(x.map((d) => Promise.resolve(func(d))));
39 | if (mutate) {
40 | data.splice(0, data.length, ...x);
41 | }
42 | }
43 | return x;
44 | }
45 |
--------------------------------------------------------------------------------
/src/largestemptycircle.js:
--------------------------------------------------------------------------------
1 | import initGeosJs from "geos-wasm";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 |
4 | /**
5 | * @function largestemptycircle
6 | * @summary Constructs the "largest empty circle" (LEC) for a set of obstacle geometries and within a polygonal boundary, with accuracy to to a specified distance tolerance. The obstacles may be any collection of points, lines and polygons.
7 | * @description Based on `geos.GEOSLargestEmptyCircle()`.
8 | * @param {object} data - A GeoJSON FeatureCollection.
9 | * @param {object} options - Optional parameters.
10 | * @param {GeoJSON} [options.boundary = null] - The area to contain the LEC center (may be null or empty)
11 | * @param {number} [options.tolerance = undefined] - Stop the algorithm when the search area is smaller than this toleranc
12 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
13 | * @example
14 | * geotoolbox.largestemptycircle()
15 | */
16 |
17 | // DO NOT WORK !
18 |
19 | export async function largestemptycircle(
20 | obstacles,
21 | { boundary, tolerance, mutate = false } = {}
22 | ) {
23 | let x;
24 | if (!mutate) {
25 | console.log("!mutate");
26 | x = JSON.parse(JSON.stringify(obstacles));
27 | } else {
28 | x = obstacles;
29 | console.log("mutate");
30 | }
31 |
32 | const geos = await initGeosJs();
33 | const geosobstacles = geojsonToGeosGeom(obstacles, geos);
34 | const geoboundary = geojsonToGeosGeom(boundary, geos);
35 | const output = geos.GEOSLargestEmptyCircle(
36 | geosobstacles,
37 | geoboundary,
38 | tolerance
39 | );
40 | let result = geosGeomToGeojson(output, geos);
41 | geos.GEOSFree(geosobstacles);
42 | geos.GEOSFree(geoboundary);
43 | geos.GEOSFree(output);
44 |
45 | x.features = [{ type: "Feature", properties: {}, geometry: result }];
46 | return x;
47 | }
48 |
--------------------------------------------------------------------------------
/src/makevalid.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { isemptygeom } from "./helpers/helpers";
4 | import { check } from "./helpers/check.js";
5 |
6 | /**
7 | * @function makevalid
8 | * @summary The `makevalid()` function repair an invalid geometry. It returns a repaired geometry.
9 | * @description Based on `geos.GEOSisValid()`.
10 | * @async
11 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
12 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
13 | * @example
14 | * await geotoolbox.makevalid(*a geojson*)
15 | */
16 | export async function makevalid(data) {
17 | const geos = await geosloader();
18 | const handle = check(data);
19 | let x = handle.import(data);
20 |
21 | x.features.forEach((d) => {
22 | if (isemptygeom(d?.geometry)) {
23 | d.geometry = undefined;
24 | } else {
25 | const geosGeom = geojsonToGeosGeom(d, geos);
26 | const validity = geos.GEOSisValid(geosGeom);
27 |
28 | if (validity != 1) {
29 | const newGeom = geos.GEOSMakeValid(geosGeom);
30 | d.geometry = geosGeomToGeojson(newGeom, geos);
31 | geos.GEOSFree(geosGeom);
32 | geos.GEOSFree(newGeom);
33 | }
34 | }
35 | });
36 |
37 | return handle.export(x);
38 | }
39 |
--------------------------------------------------------------------------------
/src/nodes.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 | /**
3 | * @function nodes
4 | * @summary Retrieve geometry nodes
5 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
6 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
7 | * @example
8 | * geotoolbox.nodes(*a geojson*)
9 | */
10 |
11 | export function nodes(data) {
12 | const handle = check(data);
13 | let x = handle.import(data);
14 |
15 | let features = [];
16 |
17 | x.features.forEach((d) => {
18 | let n = d.geometry.coordinates.flat(Infinity);
19 | let f = [];
20 | for (let i = 0; i < n.length; i = i + 2) {
21 | f.push({
22 | type: "Feature",
23 | properties: d.properties,
24 | geometry: { type: "Point", coordinates: [n[i], n[i + 1]] },
25 | });
26 | }
27 | features.push(f);
28 | });
29 |
30 | let result = {
31 | type: "FeatureCollection",
32 | name: "nodes",
33 | features: features.flat(),
34 | };
35 |
36 | return handle.export(result);
37 | }
38 |
--------------------------------------------------------------------------------
/src/removeemptygeom.js:
--------------------------------------------------------------------------------
1 | import { isemptygeom } from "./helpers/helpers";
2 | import { check } from "./helpers/check.js";
3 |
4 | /**
5 | * @function removeemptygeom
6 | * @summary The function remove all features with undefined geometries.
7 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
8 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
9 | * @example
10 | * geotoolbox.removeemptygeom(*a geojson*)
11 | */
12 | export function removeemptygeom(data) {
13 | const handle = check(data);
14 | let x = handle.import(data);
15 |
16 | let features = [];
17 | x.features.forEach((d) => {
18 | if (!isemptygeom(d.geometry)) {
19 | features.push(d);
20 | }
21 | });
22 |
23 | x.features = features;
24 |
25 | return handle.export(x);
26 | }
27 |
--------------------------------------------------------------------------------
/src/replace.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function replace
5 | * @summary Replace substrings. the function allows a string to be replaced by another string in the entire dataset
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {string} [options.search] - string to search for
9 | * @param {string} [options.replacement] -substitute character string
10 | * @param {array} [options.keys] - an array of keys to limit replacement to certain fields
11 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
12 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
13 | * @example
14 | * geotoolbox.replace(*a geojson or an array of objects*, {search: ",", replacement: " " })
15 | */
16 |
17 | export function replace(
18 | data,
19 | { search = "", replacement = "", keys, mutate = false } = {}
20 | ) {
21 | let x;
22 | if (!mutate) {
23 | x = JSON.parse(JSON.stringify(data));
24 | } else {
25 | x = data;
26 | }
27 |
28 | if (isgeojson(x)) {
29 | x.features.forEach((d) => {
30 | replaceinobj(d.properties, search, replacement, keys);
31 | });
32 | } else if (isarrayofobjects(x)) {
33 | x.forEach((d) => {
34 | replaceinobj(d, search, replacement, keys);
35 | });
36 | if (mutate) {
37 | data.splice(0, data.length, ...x);
38 | }
39 | }
40 | return x;
41 | }
42 |
43 | function replaceinobj(obj, search, replacement, keys) {
44 | let entries = Object.entries(obj);
45 | if (keys !== undefined && Array.isArray(keys)) {
46 | entries = entries.filter((d) => keys.includes(d[0]));
47 | }
48 | entries.forEach(([key, value]) => {
49 | if (typeof value === "string") {
50 | obj[key] = value.replaceAll(search, replacement);
51 | }
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/src/resolveemptygeom.js:
--------------------------------------------------------------------------------
1 | import { implantation } from "./helpers/implantation.js";
2 | import { isemptygeom } from "./helpers/helpers";
3 | import { check } from "./helpers/check.js";
4 |
5 | /**
6 | * @function resolveemptygeom
7 | * @summary The function replace all features with undefined geometries by a valid geometry, but without coordinates
8 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
9 | * @param {object} options - Optional parameters
10 | * @param {string} [options.defaultype = "Point"] - type of geometry to use for undefined features if this cannot be determined
11 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
12 | * @example
13 | * geotoolbox.resolveemptygeom(*a geojson*)
14 | */
15 | export function resolveemptygeom(data, { defaultype = "Point" } = {}) {
16 | const handle = check(data);
17 | let x = handle.import(data);
18 |
19 | let type;
20 | switch (implantation(x)) {
21 | case 1:
22 | type = "Point";
23 | break;
24 | case 2:
25 | type = "LineString";
26 | break;
27 | case 3:
28 | type = "Polygon";
29 | break;
30 | default:
31 | type = defaultype;
32 | }
33 |
34 | x.features.map(
35 | (d) =>
36 | (d.geometry = isemptygeom(d.geometry)
37 | ? { type: type, coordinates: [] }
38 | : d.geometry)
39 | );
40 |
41 | return handle.export(x);
42 | }
43 |
--------------------------------------------------------------------------------
/src/reverse.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { isemptygeom } from "./helpers/helpers.js";
4 | import { check } from "./helpers/check.js";
5 |
6 | /**
7 | * @function reverse
8 | * @summary For geometries with coordinate sequences, reverses the order of the sequences. Converts CCW rings to CW. Reverses direction of LineStrings.
9 | * @description Based on `geos.GEOSReverse()`
10 | * @async
11 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
12 | * @param {object} options - Optional parameters
13 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
14 | * @example
15 | * await geotoolbox.geosreverse(*a geojson*)
16 | */
17 |
18 | export async function reverse(data, { dist = 1, mutate = false } = {}) {
19 | const geos = await geosloader();
20 | const handle = check(data, mutate);
21 | let x = handle.import(data);
22 |
23 | x.features.forEach((d) => {
24 | if (isemptygeom(d?.geometry)) {
25 | d.geometry = undefined;
26 | } else {
27 | const geosGeom = geojsonToGeosGeom(d, geos);
28 | const newGeom = geos.GEOSReverse(geosGeom, dist);
29 | const result = geosGeomToGeojson(newGeom, geos);
30 | d.geometry = result;
31 | geos.GEOSFree(geosGeom);
32 | geos.GEOSFree(newGeom);
33 | geos.GEOSFree(result);
34 | }
35 | });
36 | return handle.export(x);
37 | }
38 |
--------------------------------------------------------------------------------
/src/rewind2.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 |
3 | /**
4 | * @function rewind2
5 | * @summary Rewind a geoJSON (Mapbox). The function allows to rewind the winding order of a GeoJSON object. The winding order of a polygon is the order in which the vertices are visited by the path that defines the polygon. The winding order of a polygon is significant because it determines the interior of the polygon. The winding order of a polygon is typically either clockwise or counterclockwise.
6 | * @description Adapted from MapBox geojson-rewind code (https://github.com/mapbox/grojson-rewind) under ISC license.
7 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
8 | * @property {boolean} [options.outer = false] - rewind Rings Outer
9 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
10 | * @example
11 | * geotoolbox.rewind2(*a geojson*)
12 | */
13 |
14 | export function rewind2(data, { outer = false } = {}) {
15 | const handle = check(data);
16 | let geo = handle.import(data);
17 |
18 | for (let i = 0; i < geo.features.length; i++) {
19 | if (geo.features[i].geometry.type === "Polygon") {
20 | rewindRings(geo.features[i].geometry.coordinates, outer);
21 | } else if (geo.features[i].geometry.type === "MultiPolygon") {
22 | for (let j = 0; j < geo.features[i].geometry.coordinates.length; j++) {
23 | rewindRings(geo.features[i].geometry.coordinates[j], outer);
24 | }
25 | }
26 | }
27 | return handle.export(geo);
28 | }
29 |
30 | function rewindRings(rings, outer) {
31 | if (rings.length === 0) return;
32 | rewindRing(rings[0], outer);
33 | for (let i = 1; i < rings.length; i++) {
34 | rewindRing(rings[i], !outer);
35 | }
36 | }
37 |
38 | function rewindRing(ring, dir) {
39 | let tArea = 0;
40 | let err = 0;
41 | for (let i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
42 | const k = (ring[i][0] - ring[j][0]) * (ring[j][1] + ring[i][1]);
43 | const m = tArea + k;
44 | err += Math.abs(tArea) >= Math.abs(k) ? tArea - m + k : k - m + tArea;
45 | tArea = m;
46 | }
47 | if (tArea + err >= 0 !== !!dir) ring.reverse();
48 | }
49 |
--------------------------------------------------------------------------------
/src/roundcoordinates.js:
--------------------------------------------------------------------------------
1 | import { parse } from "geojson-precision";
2 | import { check } from "./helpers/check.js";
3 |
4 | /**
5 | * @function roundcoordinates
6 | * @summary Round coordinates. The `round()` function allows to round coordinates. This reduces file size and speeds up display.
7 | * @description Based on `geojson-precision`.
8 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
9 | * @param {object} options - Optional parameters
10 | * @param {number} [options.precision = 2] - The minimal distance between nodes
11 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
12 | * @example
13 | * geotoolbox.roundcoordinates(*a geojson*, {precision: 2})
14 | */
15 | export function roundcoordinates(data, { precision = 2 } = {}) {
16 | const handle = check(data);
17 | let x = handle.import(data);
18 | let result = parse(x, precision);
19 | return handle.export(result);
20 | }
21 |
--------------------------------------------------------------------------------
/src/simplify.js:
--------------------------------------------------------------------------------
1 | import { check } from "./helpers/check.js";
2 | import { topology } from "topojson-server";
3 | import { feature, quantize as quantiz } from "topojson-client";
4 | import { presimplify, quantile, simplify as simple } from "topojson-simplify";
5 |
6 | const topojson = Object.assign(
7 | {},
8 | { topology, presimplify, quantiz, quantile, simple, feature }
9 | );
10 |
11 | /**
12 | * @function simplify
13 | * @export
14 | * @summary Simplify geometries. The `simplify()` function allows to simplify a geometry using topojson-simplify
library. The parameter k difine the The quantile of the simplification. By default, the generalization level is calculated automatically to ensure smooth map display.
15 | * @description Based on `topojson.simplify`.
16 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
17 | * @param {object} options - Optional parameters
18 | * @param {number} [options.k = undefined] - quantile of the simplification (from 0 to 1). If not defened, the generalization level is calculated automatically to ensure smooth map display.
19 | * @param {number} [options.quantize = undefined] - A smaller quantizeAmount produces a smaller file. Typical values are between 1e4 and 1e6, although it depends on the resolution you want in the output file.
20 | * @param {number} [options.arcs = 15000] - Instead of the k parameter, you can determine the level of generalization by targeting a specific number of arcs. The result will be an approximation.
21 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
22 | * @example
23 | * geotoolbox.simplify(*a geojson*, {k: 0.1})
24 | */
25 |
26 | export function simplify(
27 | data,
28 | { k = undefined, quantize = undefined, arcs = 15000 } = {}
29 | ) {
30 | const handle = check(data);
31 | let x = handle.import(data);
32 | let topo = topojson.topology({ foo: x });
33 | let simpl = topojson.presimplify(topo);
34 |
35 | if (k == undefined) {
36 | k = arcs / simpl.arcs.flat().length;
37 | k = k > 1 ? 1 : k;
38 | }
39 |
40 | simpl = topojson.simple(simpl, topojson.quantile(simpl, k));
41 | if (quantize) {
42 | simpl = topojson.quantiz(simpl, quantize);
43 | }
44 |
45 | x.features = topojson.feature(simpl, Object.keys(simpl.objects)[0]).features;
46 | x.name = "simplify";
47 | return handle.export(x);
48 | }
49 |
--------------------------------------------------------------------------------
/src/sort.js:
--------------------------------------------------------------------------------
1 | import { isgeojson, isarrayofobjects } from "./helpers/helpers";
2 | import { descending, ascending } from "d3-array";
3 | import { autoType } from "d3-dsv";
4 | const d3 = Object.assign({}, { descending, ascending, autoType });
5 |
6 | /**
7 | * @function sort
8 | * @summary Sorting data according to a field
9 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
10 | * @param {object} options - Optional parameters
11 | * @param {array} [options.key ] - Field on which sorting is performed
12 | * @param {array} [options.ascending = false] - To change the sort order
13 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
14 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
15 | * @example
16 | * geotoolbox.sort(*a geojson or an array of objects*, {key: "ISO3", ascending: true})
17 | */
18 |
19 | export function sort(data, { key, ascending = true, mutate = false } = {}) {
20 | let x = data;
21 | const test = ["", null, undefined, +Infinity, -Infinity, NaN];
22 |
23 | if (isgeojson(x)) {
24 | if (!mutate) {
25 | x = JSON.parse(JSON.stringify(data));
26 | }
27 |
28 | if (key != undefined) {
29 | let featurestosort = x.features.filter(
30 | (d) => !test.includes(d?.properties[key])
31 | );
32 | let rest = x.features.filter((d) => test.includes(d?.properties[key]));
33 |
34 | const mysort = ascending ? d3.ascending : d3.descending;
35 | featurestosort = featurestosort.sort((a, b) =>
36 | mysort(
37 | d3.autoType([String(a.properties[key])])[0],
38 | d3.autoType([String(b.properties[key])])[0]
39 | )
40 | );
41 | x.features = [...featurestosort, ...rest];
42 | }
43 | } else if (isarrayofobjects(x)) {
44 | if (key != undefined) {
45 | let xtosort = x.filter((d) => !test.includes(d[key]));
46 | let rest = x.filter((d) => test.includes(d[key]));
47 | const mysort = ascending ? d3.ascending : d3.descending;
48 | xtosort = xtosort.sort((a, b) =>
49 | mysort(
50 | d3.autoType([String(a[key])])[0],
51 | d3.autoType([String(b[key])])[0]
52 | )
53 | );
54 | x = [...xtosort, ...rest];
55 | }
56 |
57 | if (mutate) {
58 | data.splice(0, data.length, ...x);
59 | }
60 | }
61 |
62 | return x;
63 | }
64 |
--------------------------------------------------------------------------------
/src/stitch.js:
--------------------------------------------------------------------------------
1 | import { geoStitch } from "d3-geo-projection";
2 | import { check } from "./helpers/check.js";
3 |
4 | /**
5 | * @function stitch
6 | * @summary The `stitch()` function returns a shallow copy of the specified GeoJSON object, removing antimeridian and polar cuts, and replacing straight Cartesian line segments with geodesic segments. The input object must have coordinates in longitude and latitude in decimal degrees per RFC 7946. Antimeridian cutting, if needed, can then be re-applied after rotating to the desired projection aspect.
7 | * @description Based on `d3.geoStitch()`.
8 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
9 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`)
10 | * @example
11 | * geotoolbox.stitch(*a geojson*)
12 | */
13 |
14 | export function stitch(data) {
15 | const handle = check(data);
16 | let x = handle.import(data);
17 | x = geoStitch(x);
18 | x.name = "stitch";
19 | return handle.export(x);
20 | }
21 |
--------------------------------------------------------------------------------
/src/table.js:
--------------------------------------------------------------------------------
1 | import { isarrayofobjects, isgeojson } from "./helpers/helpers.js";
2 |
3 | /**
4 | * @function table
5 | * @summary Retrieves the dataset's attribute table (properties). By default, a deep copy is returned.
6 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
7 | * @param {object} options - Optional parameters
8 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
9 | * @returns {array} - An array of objects.
10 | * @example
11 | * geotoolbox.table(*a geojson or an array of objects*)
12 | */
13 |
14 | export function table(data, { mutate = false } = {}) {
15 | let x;
16 | if (!mutate) {
17 | x = structuredClone(data);
18 | } else {
19 | x = data;
20 | }
21 | if (isgeojson(x)) {
22 | return x?.features.map((d) => d?.properties);
23 | }
24 | if (isarrayofobjects(x)) {
25 | return x;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/tail.js:
--------------------------------------------------------------------------------
1 | import {
2 | isFieldNumber,
3 | isFieldNumber2,
4 | isgeojson,
5 | isarrayofobjects,
6 | } from "./helpers/helpers";
7 | import { descending, ascending } from "d3-array";
8 | import { autoType } from "d3-dsv";
9 | const d3 = Object.assign({}, { descending, ascending, autoType });
10 |
11 | /**
12 | * @function tail
13 | * @summary Get the last n Features. The function sort data and returns the nb first elements. If a field is selected, then the function returns the top values. If the entries are strings, then the alphabetic order is applied.
14 | * @param {object|array} data - A GeoJSON FeatureCollection or an array of objects
15 | * @param {object} options - Optional parameters
16 | * @param {number} [options.nb = 6] - Number of features to return
17 | * @param {boolean} [options.key = true] - Field to sort
18 | * @param {boolean} [options.mutate = false] - Use `true` to update the input data. With false, you create a new object, but the input object remains the same.
19 | * @returns {object|array} - A GeoJSON FeatureCollection or an array of objects. (it depends on what you've set as `data`).
20 | * @example
21 | * geotoolbox.tail(*a geojson or an array of objects*)
22 | */
23 | export function tail(data, { key, nb = 6, mutate = false } = {}) {
24 | let x = data;
25 |
26 | if (isgeojson(x)) {
27 | if (!mutate) {
28 | x = JSON.parse(JSON.stringify(data));
29 | }
30 | let features = [...x.features];
31 |
32 | if (key != undefined) {
33 | features = features
34 | .filter((d) => d.properties[key] != "")
35 | .filter((d) => d.properties[key] != null)
36 | .filter((d) => d.properties[key] != undefined)
37 | .filter((d) => d.properties[key] != +Infinity)
38 | .filter((d) => d.properties[key] != -Infinity)
39 | .filter((d) => d.properties[key] != NaN);
40 |
41 | const mysort = isFieldNumber(x, key) ? d3.ascending : d3.descending;
42 | features = features.sort((a, b) =>
43 | mysort(
44 | d3.autoType([String(a.properties[key])])[0],
45 | d3.autoType([String(b.properties[key])])[0]
46 | )
47 | );
48 | }
49 |
50 | x.features = features.slice(0, nb);
51 | } else if (isarrayofobjects(x)) {
52 | if (key != undefined) {
53 | x = x
54 | .filter((d) => d[key] != "")
55 | .filter((d) => d[key] != null)
56 | .filter((d) => d[key] != undefined)
57 | .filter((d) => d[key] != +Infinity)
58 | .filter((d) => d[key] != -Infinity)
59 | .filter((d) => d[key] != NaN);
60 |
61 | const mysort = isFieldNumber2(x, field) ? ascending : descending;
62 | x = x.sort((a, b) =>
63 | mysort(
64 | d3.autoType([String(a[key])])[0],
65 | d3.autoType([String(b[key])])[0]
66 | )
67 | );
68 | }
69 | x = x.slice(0, nb);
70 | if (mutate) {
71 | data.splice(0, data.length, ...x);
72 | }
73 | }
74 | return x;
75 | }
76 |
--------------------------------------------------------------------------------
/src/tissot.js:
--------------------------------------------------------------------------------
1 | import { geoCircle } from "d3-geo";
2 |
3 | /**
4 | * @function tissot
5 | * @summary Generate Tissot's indicatrix.
6 | * @param {number} [step = 20] - The distance between each circle
7 | * @example
8 | * geotoolbox.tissot(*a geojson*)
9 | */
10 |
11 | export function tissot(step = 20) {
12 | const circle = geoCircle()
13 | .center((d) => d)
14 | .radius(step / 4)
15 | .precision(10);
16 | const features = [];
17 | for (let y = -80; y <= 80; y += step) {
18 | for (let x = -180; x < 180; x += step) {
19 | features.push({
20 | type: "Feature",
21 | properties: {},
22 | geometry: {
23 | type: "MultiPolygon",
24 | coordinates: [circle([x, y]).coordinates],
25 | },
26 | });
27 | }
28 | }
29 |
30 | return { type: "FeatureCollection", features: features };
31 | }
32 |
--------------------------------------------------------------------------------
/src/union.js:
--------------------------------------------------------------------------------
1 | import { geosloader } from "./helpers/geos.js";
2 | import { geojsonToGeosGeom, geosGeomToGeojson } from "geos-wasm/helpers";
3 | import { check } from "./helpers/check.js";
4 |
5 | /**
6 | * @function union
7 | * @summary Merge geometries
8 | * @description Based on `geos.GEOSUnaryUnion()`.
9 | * @async
10 | * @param {object|array} data - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry.
11 | * @param {object} options - Optional parameters
12 | * @param {number} [options.id] - An id to merge by id
13 | * @returns {object|array} - A GeoJSON FeatureCollection, an array of features, an array of geometries, a single feature or a single geometry (it depends on what you've set as `data`).
14 | * @example
15 | * await geotoolbox.union(*a geojson*)
16 | */
17 |
18 | export async function union(data, { id } = {}) {
19 | const geos = await geosloader();
20 | const handle = check(data);
21 | let x = handle.import(data);
22 |
23 | if (id) {
24 | const ids = [...new Set(x.features.map((d) => d.properties[id]))].filter(
25 | (d) => !["", undefined, null, NaN, Infinity, -Infinity].includes(d)
26 | );
27 | const acessor = new Map(
28 | ids.map((d) => [
29 | d,
30 | {
31 | type: "FeatureCollection",
32 | features: x.features.filter((e) => e.properties[id] == d),
33 | },
34 | ])
35 | );
36 |
37 | x.features = await Promise.all(
38 | ids.map(async (d) => {
39 | const geosGeom = geojsonToGeosGeom(acessor.get(d), geos);
40 | const newGeom = geos.GEOSUnaryUnion(geosGeom);
41 | let feature = {
42 | type: "Feature",
43 | properties: { id: d },
44 | geometry: geosGeomToGeojson(newGeom, geos),
45 | };
46 | geos.GEOSFree(geosGeom);
47 | geos.GEOSFree(newGeom);
48 | return feature;
49 | })
50 | );
51 | } else {
52 | const geosGeom = geojsonToGeosGeom(x, geos);
53 | const newGeom = geos.GEOSUnaryUnion(geosGeom);
54 | x.features = [
55 | {
56 | type: "Feature",
57 | properties: {},
58 | geometry: geosGeomToGeojson(newGeom, geos),
59 | },
60 | ];
61 | geos.GEOSFree(geosGeom);
62 | geos.GEOSFree(newGeom);
63 | }
64 |
65 | x.name = "union";
66 | return handle.export(x);
67 | }
68 |
--------------------------------------------------------------------------------
/test/test.mjs:
--------------------------------------------------------------------------------
1 | import _ from "lodash";
2 | import jsonDiff from "json-diff";
3 | import test from "node:test";
4 | import geo from "../dist/index.min.js";
5 | import { readFileSync, writeFileSync } from "fs";
6 | const buff1000km = JSON.parse(readFileSync("test/features/buff1000km.json"));
7 |
8 | test("geos-union", async (t) => {
9 | const result = await geo.geosunion(buff1000km);
10 | const results_jsts = await geo.union(buff1000km);
11 | if (_.isEqual(result, results_jsts)) {
12 | t.pass();
13 | } else {
14 | //console.log(jsonDiff.diff(result, results_jsts));
15 | }
16 | });
17 |
--------------------------------------------------------------------------------