https://github.com/abelvm/maplibre-contourmap
Contour maps plugin for MapLibreGL JS
https://github.com/abelvm/maplibre-contourmap
contour contourmap isolines isopleth maplibre maplibre-gl-js
Last synced: 13 days ago
JSON representation
Contour maps plugin for MapLibreGL JS
- Host: GitHub
- URL: https://github.com/abelvm/maplibre-contourmap
- Owner: AbelVM
- License: mit
- Created: 2024-09-21T13:43:33.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2024-10-28T09:17:15.000Z (7 months ago)
- Last Synced: 2025-05-08T01:44:39.008Z (13 days ago)
- Topics: contour, contourmap, isolines, isopleth, maplibre, maplibre-gl-js
- Language: JavaScript
- Homepage: https://abelvm.github.io/maplibre-contourmap/example/
- Size: 17.6 MB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# maplibre-contourmap
https://github.com/user-attachments/assets/b468f5f2-26c2-4353-b32d-1d8fb483e847
This [MaplibreGL JS](https://maplibre.org/) plugin allows to generate a real-time, client-side, [contour map](https://en.wikipedia.org/wiki/Contour_line) (isolines, isopleths, whatever you wanna call it 😅) based on the data provided by a scattered points vector (tiled or geoJSON) layer.
One of the main advantages of the approeach used here is that source points don't need to be regularly placed in a grid, as we are using the [meandering triangles](https://en.wikipedia.org/wiki/Marching_squares#Contouring_triangle_meshes) variation of the [marching squares](https://en.wikipedia.org/wiki/Marching_squares) algorithm, that allows us to use scattered points and work on a [TIN](https://en.wikipedia.org/wiki/Triangulated_irregular_network) defined by those points
You can play with the example in the video above at
https://abelvm.github.io/maplibre-contourmap/example/
## How it works
As the points are arbitrarily scattered, we can't foresee the placement of the points in the tiles. That tiny detail forces us to process the whole available data at once every time the user moves around the map, and refrains us from using [custom protocols](https://maplibre.org/maplibre-gl-js/docs/API/functions/addProtocol/) to process data on the fly, tile by tile, before rendering. So we need to trigger the generation of the contour map once new data is loaded for the linked points layer.
To improve the performance and avoid UI jerkyness, we've used two common techniques here:
* [Webworkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), so everything is computed out of the main thread
* [Memoization](https://en.wikipedia.org/wiki/Memoization). Due to the nature of the algorithm itself and the need to regenerate the whole contour map as new data is gathered, there is a lot of cells and segments that would be processed multiple times, wasting CPU time. So we memoize both processes (cells and segments) within the worker itself, so it doesn't leak. One of the main potential caveats of memoization is the chance of a huge impact on consumed memory, but the approach used here keeps it under reasonable limits## How to use
First of all
```bash
npm install
npm run build
```Now you have the everything you need in the `./dist` folder.
In order to add this plugin to your Maplibre app, you need to import the module and initialize it, passing the mapping library object (usually, `maplibregl`)
```javascript
import init from 'maplibre-contourmap.js';
init(maplibregl);
```Now we have two new `maplibregl.Map` methods, that needs the name of the **points** layer to be targeted:
* `addContourSource`(input_layer_name, options_object): once added, a new geoJSON **multilinestring** source is added to the map, called `contour-source-input_layer_name`
* `removeContourSource`(input_layer_name) : to remove the contour map sourceThe `options_object` contain:
* `measure`: the name of the numerical property of input_layer_name to be used to generate the contour map
* `breaks`: array of numeric values that will define the classes of the isolines
* `filter` [optional, defaults to empty]: layer [filter](https://maplibre.org/maplibre-style-spec/layers/#filter) according to specs, to limit the points of the source layer that will be used for the contour map
* `debug` [optional, defaults to false]: if sets to `true` some extra validations are run and debug messages will be sent to console logOptions yet to be implemented
* `type` [optional, defaults to "MultiLineString"]: Geometry type of the output. As of today, only `MultiLineString` is supported
* `max_workers`[optional, defaults to `((!!navigator.hardwareConcurrency) ? navigator.hardwareConcurrency - 1 : 3) - maplibregl.getWorkerCount()`]: size of the workers pool. As of today, there is no pool management, just one lonely workerOnce the map is loaded, and the target points layer is added, we can attach to the points layer to generate its contour map
```javascript
map.on('load', () => {// Raw points source
map.addSource('points_source', {
type: 'vector',
...
});// Raw points layer
map.addLayer({
'id': 'points_layer',
'type': 'circle',
'source': 'points_source',
'source-layer': 'points_source_layer',
..
});// Contour map source,
// linked to the former points layer and its COTAS property
map.addContourSource('points_layer', {
'measure': 'my_measure',
'breaks': breaks
});// Line layer the for contour map
map.addLayer({
'id': 'isolines',
'type': 'line',
'source': 'contour-source-points_layer',
...
});});
```**The original points layer must be visible, otherwise its source data won't be loaded. If you want to hide the points layer, use paint property `opacity` instead of layout property `visible`**
## Dependencies
The only external dependency is [@turf/tin](https://turfjs.org/docs/api/tin), that is side-loaded and embedded within the worker in development time.
`The whole plugin is self-contained in a single 8.6kB file`
## To-Do's
* If the source is GeoJSON, add the option to process the whole data just once
* Verify geometry are points and get centroids if not
* dynamic worker pool
* separete workers for cells and segments
* corner cases in tiles' boundaries when panning