geo-morpher 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -86
- package/data/winchester/winchester_lsoa_cartogram.geojson +3245 -0
- package/data/winchester/winchester_lsoa_geo.geojson +24263 -0
- package/data/winchester/winchester_msoa_cartogram.geojson +635 -0
- package/data/winchester/winchester_msoa_geo.geojson +10743 -0
- package/data/winchester/winchester_ward_cartogram.geojson +725 -0
- package/data/winchester/winchester_ward_geo.geojson +10087 -0
- package/examples/README.md +55 -0
- package/examples/leaflet/canvas-glyphs.html +44 -0
- package/examples/leaflet/canvas-main.js +208 -0
- package/examples/{browser → leaflet}/index.html +16 -12
- package/examples/{browser → leaflet}/main.js +91 -37
- package/examples/leaflet/zoom-scaling-glyphs.html +122 -0
- package/examples/{browser → leaflet}/zoom-scaling-glyphs.js +27 -77
- package/examples/{browser/maplibre → maplibre}/index.html +2 -0
- package/examples/maplibre/indonesia/index.html +284 -0
- package/examples/maplibre/indonesia/main.js +668 -0
- package/examples/{browser/maplibre → maplibre}/main.js +11 -23
- package/examples/maplibre/projections/index.html +77 -0
- package/examples/maplibre/projections/main.js +151 -0
- package/examples/winchester/index.html +294 -0
- package/examples/winchester/main.js +333 -0
- package/examples/winchester/winchester_example.md +145 -0
- package/package.json +22 -47
- package/src/adapters/leaflet/glyphLayer.js +23 -9
- package/src/adapters/leaflet/index.js +3 -1
- package/src/adapters/leaflet/utils/collections.js +3 -37
- package/src/adapters/leaflet/utils/coordinates.js +1 -5
- package/src/adapters/leaflet/utils/glyphNormalizer.js +19 -74
- package/src/adapters/maplibre/glyphLayer.js +21 -10
- package/src/adapters/maplibre/index.js +2 -0
- package/src/adapters/maplibre/morphLayers.js +6 -5
- package/src/adapters/maplibre/utils/coordinates.js +2 -26
- package/src/adapters/maplibre/utils/customGlyphLayer.js +485 -0
- package/src/adapters/maplibre/utils/glyphNormalizer.js +14 -117
- package/src/adapters/shared/collections.js +27 -0
- package/src/adapters/shared/dom.js +6 -0
- package/src/adapters/shared/geometry.js +26 -0
- package/src/adapters/shared/glyphNormalizer.js +103 -0
- package/src/adapters/shared/markerAdapter.js +64 -0
- package/src/core/geomorpher.js +95 -8
- package/src/index.js +10 -2
- package/src/utils/projections.js +34 -16
- package/dist/index.cjs +0 -3304
- package/dist/index.cjs.map +0 -1
- package/dist/index.js +0 -3271
- package/dist/index.js.map +0 -1
- package/examples/browser/README.md +0 -189
- package/examples/browser/indonesia/index.html +0 -262
- package/examples/browser/indonesia/main.js +0 -400
- package/examples/browser/zoom-scaling-glyphs.html +0 -257
- package/examples/custom-projection.js +0 -236
- package/examples/native.js +0 -52
- package/morphs.js +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# geo-morpher
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/geo-morpher)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
GeoJSON morphing utilities for animating between regular geography and cartograms, packaged as a native JavaScript library with a MapLibre-first adapter and Leaflet compatibility helpers.
|
|
4
7
|
|
|
5
8
|

|
|
6
9
|
|
|
10
|
+
To quickly create a grid cartogram, checkout my other library: .
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **MapLibre & Leaflet Adapters**: High-performance MapLibre-first implementation with Leaflet compatibility helpers.
|
|
16
|
+
- **Generic Morphing Engine**: Smoothly interpolates between any two aligned GeoJSON geometries using `flubber`.
|
|
17
|
+
- **Multivariate Glyphs**: Highly customizable DOM/SVG/Canvas overlays (charts, icons, sparklines) that stay synced with morphing geometry.
|
|
18
|
+
- **Basemap Effects**: Synchronized fading, blurring, or grayscale effects for basemap layers during transitions.
|
|
19
|
+
- **Projection Agnostic**: Auto-detects WGS84 (lat/lng) data; defaults to OSGB (British National Grid) for UK data but supports any CRS via `proj4`.
|
|
20
|
+
|
|
7
21
|
|
|
8
22
|
## Installation
|
|
9
23
|
|
|
@@ -11,44 +25,16 @@ Imperative GeoJSON morphing utilities for animating between regular geography an
|
|
|
11
25
|
npm install geo-morpher
|
|
12
26
|
```
|
|
13
27
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### Choosing an adapter
|
|
17
|
-
|
|
18
|
-
- MapLibre adapter (`createMapLibreMorphLayers`, `createMapLibreGlyphLayer`) is the default. Use it for performance, style expression control, and WebGL-based effects.
|
|
19
|
-
- Leaflet adapter (`createLeafletMorphLayers`, `createLeafletGlyphLayer`) remains supported for teams who want to manipulate markers, tooltips, or UI directly in the DOM.
|
|
20
|
-
- Both adapters share the same `GeoMorpher` core, so you can swap adapters without recalculating morph data.
|
|
21
|
-
- Examples under `examples/browser/` demonstrate both integrations; switch adapters by loading the corresponding entry point.
|
|
28
|
+
Leaflet is provided as a peer dependency—bring your own Leaflet instance when using the compatibility helpers. MapLibre remains the default adapter and is bundled as a dependency for out-of-the-box usage; if your build already supplies `maplibre-gl`, mark it as external to avoid duplicating the library.
|
|
22
29
|
|
|
23
30
|
## Usage
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
```text
|
|
28
|
-
src/
|
|
29
|
-
core/ # GeoMorpher core engine
|
|
30
|
-
adapters/ # Integration helpers (Leaflet, etc.)
|
|
31
|
-
lib/ # Shared runtime utilities (OSGB projection)
|
|
32
|
-
utils/ # Data enrichment and projection helpers
|
|
33
|
-
data/ # Sample Oxford LSOA datasets
|
|
34
|
-
examples/ # Runnable native JS scripts
|
|
35
|
-
test/ # node:test coverage for core behaviours
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### MapLibre adapter
|
|
32
|
+
### MapLibre adapter (default)
|
|
39
33
|
|
|
40
34
|
- `createMapLibreMorphLayers` provisions GeoJSON sources and fill layers for regular, cartogram, and interpolated geometries, exposing an `updateMorphFactor` helper to drive tweening from UI controls.
|
|
41
35
|
- `createMapLibreGlyphLayer` renders glyphs with `maplibregl.Marker` instances; enable `scaleWithZoom` to regenerate glyph markup as users zoom.
|
|
42
36
|
- Pass your MapLibre namespace explicitly (`maplibreNamespace: maplibregl`) when calling glyph helpers in module-bundled builds where `maplibregl` is not attached to `globalThis`.
|
|
43
37
|
- For heavy glyph scenes, consider upgrading to a [CustomLayerInterface](https://www.maplibre.org/maplibre-gl-js/docs/API/interfaces/CustomLayerInterface/) implementation that batches drawing on the GPU. The marker pipeline keeps the API simple while offering a documented migration path.
|
|
44
|
-
- Track ongoing enhancements and open items in `docs/maplibre-migration-plan.md` before adopting advanced features like custom shader glyphs.
|
|
45
|
-
|
|
46
|
-
### Leaflet adapter
|
|
47
|
-
|
|
48
|
-
- `createLeafletMorphLayers` keeps the existing DOM-centric workflow for basemap blur/fade effects and marker overlays.
|
|
49
|
-
- `createLeafletGlyphLayer` accepts any HTML, SVG, Canvas, or Leaflet icon—handy when you need tooltips, popups, and other DOM components under full control.
|
|
50
|
-
- The basemap effect still uses DOM filters (`blur`, `opacity`, `grayscale`) and can target any Leaflet layer container.
|
|
51
|
-
- Stick with Leaflet when you require deep integration with existing Leaflet plugins or server-rendered markup; migrate to MapLibre later without touching your data preparation code.
|
|
52
38
|
|
|
53
39
|
#### MapLibre basemap effects
|
|
54
40
|
|
|
@@ -109,44 +95,32 @@ const cartogram = morpher.getCartogramFeatureCollection();
|
|
|
109
95
|
const tween = morpher.getInterpolatedFeatureCollection(0.5);
|
|
110
96
|
```
|
|
111
97
|
|
|
112
|
-
####
|
|
98
|
+
#### Projections & Coordinate Systems
|
|
113
99
|
|
|
114
|
-
|
|
100
|
+
While `geo-morpher` was born out of UK-centric cartography, it is fully generic. It internally projects all data to WGS84 (latitude/longitude) for mapping compatibility.
|
|
115
101
|
|
|
116
|
-
|
|
117
|
-
|
|
102
|
+
- **Auto-detection**: If no projection is provided, `GeoMorpher` inspects your coordinates. If they look like WGS84, it uses them as-is.
|
|
103
|
+
- **OSGB Default**: If coordinates fall outside the geographic range, it assumes OSGB (EPSG:27700) and transforms them to WGS84.
|
|
104
|
+
- **Manual Override**: Pass a helper like `WebMercatorProjection` or a custom `proj4` wrapper for other systems.
|
|
118
105
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
```js
|
|
107
|
+
import { GeoMorpher, WGS84Projection, createProj4Projection } from "geo-morpher";
|
|
108
|
+
import proj4 from "proj4";
|
|
122
109
|
|
|
123
|
-
//
|
|
124
|
-
const morpher = new GeoMorpher({
|
|
125
|
-
regularGeoJSON,
|
|
126
|
-
cartogramGeoJSON,
|
|
127
|
-
projection: WGS84Projection, // No transformation needed
|
|
128
|
-
});
|
|
110
|
+
// 1. Auto-detected (usually works for WGS84 or OSGB)
|
|
111
|
+
const morpher = new GeoMorpher({ regularGeoJSON, cartogramGeoJSON });
|
|
129
112
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
const morpher = new GeoMorpher({
|
|
133
|
-
regularGeoJSON,
|
|
134
|
-
cartogramGeoJSON,
|
|
135
|
-
projection: WebMercatorProjection,
|
|
136
|
-
});
|
|
113
|
+
// 2. Explicit WGS84 (Identity)
|
|
114
|
+
const morpher = new GeoMorpher({ ..., projection: WGS84Projection });
|
|
137
115
|
|
|
138
|
-
// Custom
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
// Transform [x, y] to [lng, lat]
|
|
142
|
-
return [lng, lat];
|
|
143
|
-
}
|
|
144
|
-
};
|
|
116
|
+
// 3. Custom Projection (e.g., UTM Zone 33N)
|
|
117
|
+
const projection = createProj4Projection("+proj=utm +zone=33 +datum=WGS84", proj4);
|
|
118
|
+
const morpher = new GeoMorpher({ ..., projection });
|
|
145
119
|
```
|
|
146
120
|
|
|
147
|
-
See `examples/
|
|
121
|
+
See `examples/maplibre/projections/index.html` for a browser-based custom projection demo.
|
|
148
122
|
|
|
149
|
-
### 2. Drop the morph straight into Leaflet
|
|
123
|
+
### 2. Drop the morph straight into Leaflet (compat)
|
|
150
124
|
|
|
151
125
|
```js
|
|
152
126
|
import L from "leaflet";
|
|
@@ -195,6 +169,8 @@ Provide either `basemapLayer` (any Leaflet layer with a container) or `basemapEf
|
|
|
195
169
|
|
|
196
170
|
The glyph system is **completely customizable** with no hardcoded chart types. You provide a rendering function that can return any visualization you can create with HTML, SVG, Canvas, or third-party libraries like D3.js or Chart.js. The helper automatically keeps markers positioned and synchronized with the morphing geometry.
|
|
197
171
|
|
|
172
|
+
See the full glyphs guide: `docs/glyphs.md`
|
|
173
|
+
|
|
198
174
|
**Example with pie charts:**
|
|
199
175
|
|
|
200
176
|
```js
|
|
@@ -245,6 +221,24 @@ slider.addEventListener("input", (event) => {
|
|
|
245
221
|
updateMorphFactor(value);
|
|
246
222
|
glyphLayer.updateGlyphs({ morphFactor: value });
|
|
247
223
|
});
|
|
224
|
+
|
|
225
|
+
// You can also decouple glyphs from the GeoMorpher by providing a
|
|
226
|
+
// `featureCollection` or a live `featureProvider({ geometry, morphFactor })`:
|
|
227
|
+
//
|
|
228
|
+
// const glyph = await createLeafletGlyphLayer({
|
|
229
|
+
// drawGlyph,
|
|
230
|
+
// L,
|
|
231
|
+
// featureCollection: morpher.getRegularFeatureCollection(), // static set
|
|
232
|
+
// });
|
|
233
|
+
//
|
|
234
|
+
// const glyphProvider = await createLeafletGlyphLayer({
|
|
235
|
+
// drawGlyph,
|
|
236
|
+
// L,
|
|
237
|
+
// featureProvider: ({ geometry, morphFactor }) => morpher.getInterpolatedFeatureCollection(morphFactor),
|
|
238
|
+
// });
|
|
239
|
+
//
|
|
240
|
+
// The adapters also export small helper functions to convert normalized glyph values
|
|
241
|
+
// into platform-specific objects: `createLeafletIcon` and `createMapLibreMarkerData`.
|
|
248
242
|
```
|
|
249
243
|
|
|
250
244
|
`drawGlyph` receives `{ feature, featureId, data, morpher, geometry, morphFactor }` and can return:
|
|
@@ -276,7 +270,6 @@ By default `createLeafletGlyphLayer` will surface whatever the core `GeoMorpher`
|
|
|
276
270
|
| field | type | description |
|
|
277
271
|
|--------------|----------|-------------|
|
|
278
272
|
| `feature` | GeoJSON Feature | The rendered feature taken from the requested geography (`regular`, `cartogram`, or tweened). Includes `feature.properties` and a `centroid` array. |
|
|
279
|
-
enum{} | Resolved via `getFeatureId(feature)` (defaults to `feature.properties.code ?? feature.properties.id`). |
|
|
280
273
|
| `featureId` | string | Resolved via `getFeatureId(feature)` (defaults to `feature.properties.code ?? feature.properties.id`). |
|
|
281
274
|
| `data` | object \| null | When using the built-in lookup this is the morpher key entry: `{ code, population, data }`. The `data` property holds the *enriched* GeoJSON feature returned from `GeoMorpher.prepare()`—handy when you stored additional indicators during enrichment. |
|
|
282
275
|
| `morpher` | `GeoMorpher` | The instance you passed in, allowing on-demand queries (`getInterpolatedLookup`, etc.). |
|
|
@@ -439,7 +432,7 @@ When `scaleWithZoom` is enabled:
|
|
|
439
432
|
- Glyphs automatically update when users zoom in/out
|
|
440
433
|
- Call `glyphLayer.destroy()` to clean up zoom listeners when removing the layer
|
|
441
434
|
|
|
442
|
-
A complete example is available at `examples/
|
|
435
|
+
A complete example is available at `examples/leaflet/zoom-scaling-glyphs.html`.
|
|
443
436
|
|
|
444
437
|
### Legacy wrapper
|
|
445
438
|
|
|
@@ -459,17 +452,11 @@ const result = await geoMorpher({
|
|
|
459
452
|
console.log(result.tweenLookup);
|
|
460
453
|
```
|
|
461
454
|
|
|
462
|
-
###
|
|
455
|
+
### Node script (removed)
|
|
463
456
|
|
|
464
|
-
|
|
457
|
+
The previous Node-only example has been removed in favor of browser-based demos under `examples/maplibre` and `examples/leaflet`.
|
|
465
458
|
|
|
466
|
-
|
|
467
|
-
node examples/native.js
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
It loads `data/oxford_lsoas_regular.json` and `data/oxford_lsoas_cartogram.json`, mirrors their population/household properties into a basic dataset, and prints counts plus a sample tweened feature—all without any bundlers or UI frameworks.
|
|
471
|
-
|
|
472
|
-
### Native browser examples (Leaflet & MapLibre)
|
|
459
|
+
### Native browser examples (MapLibre & Leaflet)
|
|
473
460
|
|
|
474
461
|
Serve the browser demos to see geo-morpher running on top of either Leaflet or MapLibre without a build step. Dependencies are resolved via import maps to CDN-hosted ES modules.
|
|
475
462
|
|
|
@@ -478,14 +465,17 @@ npm run examples:browser
|
|
|
478
465
|
```
|
|
479
466
|
|
|
480
467
|
Then open:
|
|
481
|
-
-
|
|
482
|
-
- MapLibre
|
|
468
|
+
- MapLibre demo: <http://localhost:4173/examples/maplibre/index.html>
|
|
469
|
+
- Indonesia (MapLibre): <http://localhost:4173/examples/maplibre/indonesia/index.html>
|
|
470
|
+
- MapLibre (Projections): <http://localhost:4173/examples/maplibre/projections/index.html>
|
|
471
|
+
- Leaflet demo: <http://localhost:4173/examples/leaflet/index.html>
|
|
472
|
+
- Leaflet zoom-scaling: <http://localhost:4173/examples/leaflet/zoom-scaling-glyphs.html>
|
|
483
473
|
|
|
484
474
|
Each demo provides a morph slider and glyph overlays; the MapLibre version showcases GPU-driven rendering, paint-property basemap fading, and DOM marker glyphs running through the new adapter. (An internet connection is required to fetch CDN-hosted modules and map tiles.)
|
|
485
475
|
|
|
486
476
|
**Additional examples:**
|
|
487
|
-
- `examples/
|
|
488
|
-
- `examples/
|
|
477
|
+
- `examples/maplibre/index.html` - MapLibre adaptation with basemap paint-property effects and layer toggles
|
|
478
|
+
- `examples/leaflet/zoom-scaling-glyphs.html` - Demonstrates zoom-responsive waffle charts that resize to fill cartogram polygons as you zoom in/out
|
|
489
479
|
|
|
490
480
|
## Testing
|
|
491
481
|
|
|
@@ -495,19 +485,15 @@ Run the bundled smoke tests with:
|
|
|
495
485
|
npm test
|
|
496
486
|
```
|
|
497
487
|
|
|
498
|
-
##
|
|
488
|
+
## Author
|
|
499
489
|
|
|
500
|
-
|
|
501
|
-
- `npm run clean` removes the generated `dist/` folder prior to fresh builds.
|
|
502
|
-
- Publishing runs `npm run build` automatically through the `prepare` hook; verify outputs before executing `npm publish`.
|
|
503
|
-
|
|
504
|
-
## Roadmap & pending updates
|
|
505
|
-
|
|
506
|
-
- GPU-batched glyph rendering via MapLibre `CustomLayerInterface` remains on the roadmap for high-volume scenes—follow progress in `docs/maplibre-migration-plan.md`.
|
|
507
|
-
- Adapter-level automated tests (Jest + MapLibre mocks) are planned to complement the current core-only test suite.
|
|
508
|
-
- Post-processing basemap effects (true blur, grayscale for vector tiles) are being explored as custom layers; today, stick to opacity/brightness tweaks via `basemapEffect`.
|
|
509
|
-
- Globe projection support will land after the MapLibre adapter stabilizes on the current release line; expect a follow-up example once the API settles.
|
|
490
|
+
Dany Laksono
|
|
510
491
|
|
|
511
492
|
## License
|
|
512
493
|
|
|
513
494
|
MIT
|
|
495
|
+
|
|
496
|
+
## Documentation
|
|
497
|
+
|
|
498
|
+
- API Reference: `docs/api.md`
|
|
499
|
+
- Glyphs Guide: `docs/glyphs.md`
|