├── .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 | ![npm](https://img.shields.io/npm/v/geotoolbox) ![jsdeliver](https://img.shields.io/jsdelivr/npm/hw/geotoolbox) ![license](https://img.shields.io/badge/license-MIT-success) ![code size](https://img.shields.io/github/languages/code-size/riatelab/geotoolbox) 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 | ![logo](img/geotoolbox.svg) 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 | 127 | 128 |
129 | 130 | 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 | 49 | 50 |
51 | 52 | 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 | 70 | 71 |
72 | 73 | 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 | 92 | 93 |
94 | 95 | 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 | 79 | 80 |
81 | 82 | 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/>![Simplify](img/simplify.svg)
 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 | 96 | 97 |
98 | 99 | 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 | 72 | 73 |
74 | 75 | 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 | 78 | 79 |
80 | 81 | 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 | 64 | 65 |
66 | 67 | 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 | 62 | 63 |
64 | 65 | 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 | 59 | 60 |
61 | 62 | 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 | 70 | 71 |
72 | 73 | 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 | 65 | 66 |
67 | 68 | 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 | 49 | 50 |
51 | 52 | 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 | 64 | 65 |
66 | 67 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------