ixmaps-gl 0.1.0 → 0.2.1
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 +13 -3
- package/ixmaps-gl.js +87 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,7 +181,9 @@ script. For brokers that query by view, like flat's bbox data providers, ixmaps-
|
|
|
181
181
|
`ixmaps.getBoundingBox()`, `ixmaps.refreshTheme(id)` (loads the theme's data again, in place),
|
|
182
182
|
`ixmaps.setTitle(html)`/`setTitleBox(text, color)`, `ixmaps.getThemeObj(id).fVisible` (false for a
|
|
183
183
|
FEATURE theme out of scale) and calls a page's `ixmaps.htmlgui_onZoomAndPan(zoom)` after each zoom
|
|
184
|
-
or pan
|
|
184
|
+
or pan — and also during a zoom, at the pace of the layer refresh (about every 150 ms, zoom changes only),
|
|
185
|
+
so zoom-dependent page state (e.g. a basemap opacity ramp) changes in step with the scale gates
|
|
186
|
+
(`featurelower`, `chartupper`, …) instead of after the gesture has ended. Scripts that reach further into flat's internals (`ixmaps.parentApi`, other `htmlgui_*`
|
|
185
187
|
hooks) aren't supported. `test/project-report.mjs` shows which themes
|
|
186
188
|
of your own project files gl can render.
|
|
187
189
|
|
|
@@ -190,13 +192,21 @@ of your own project files gl can render.
|
|
|
190
192
|
Load the engine from jsDelivr, pinned to a release tag (or `@main` for the latest commit):
|
|
191
193
|
|
|
192
194
|
```html
|
|
193
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.1
|
|
195
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.1/ixmaps-gl.js"></script>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
or from unpkg, the npm package [`ixmaps-gl`](https://www.npmjs.com/package/ixmaps-gl)
|
|
199
|
+
(`https://unpkg.com/ixmaps-gl` for the latest version; `npm install ixmaps-gl` installs the same
|
|
200
|
+
browser script, which defines the global `ixmaps`):
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<script src="https://unpkg.com/ixmaps-gl@0.2.1/ixmaps-gl.js"></script>
|
|
194
204
|
```
|
|
195
205
|
|
|
196
206
|
or use a local copy (`<script src="ixmaps-gl.js"></script>`). A minimal page:
|
|
197
207
|
|
|
198
208
|
```html
|
|
199
|
-
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.1
|
|
209
|
+
<script src="https://cdn.jsdelivr.net/gh/gjrichter/ixmaps-gl@v0.2.1/ixmaps-gl.js"></script>
|
|
200
210
|
<div id="map_div" style="position:absolute;inset:0;"></div>
|
|
201
211
|
|
|
202
212
|
<script>
|
package/ixmaps-gl.js
CHANGED
|
@@ -78,14 +78,27 @@
|
|
|
78
78
|
// Same CDN builds every example page already pinned; centralized here
|
|
79
79
|
// so bumping a version is a one-file edit, not a 13-file one.
|
|
80
80
|
// ---------------------------------------------------------------
|
|
81
|
+
// this engine's release (= package.json "version", checked by
|
|
82
|
+
// test/unit/version.test.mjs); ixmaps.glVersion, and logged once at start
|
|
83
|
+
const IXMAPS_GL_VERSION = '0.2.1';
|
|
84
|
+
|
|
81
85
|
const LIB_URLS = {
|
|
82
86
|
// Bumped 3.6.2 -> 5.x (2026-09-21, globe-projection compat fix): native
|
|
83
87
|
// globe projection (map.setProjection({type:'globe'})) needs >=5.0.1
|
|
84
88
|
// (5.0.0 shipped a style-spec regression, reverted in 5.0.1). Requires
|
|
85
89
|
// MapLibre GL JS v4.5.1, v5, or v6 on the deck.gl side (see `deck`
|
|
86
90
|
// below) — v5 satisfies that.
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
// 5.x -> 6.x (2026-09-29): MapLibre 6 is published only as an ES
|
|
92
|
+
// module (dist/maplibre-gl.mjs, named exports, no UMD bundle and no
|
|
93
|
+
// window.maplibregl) — loadMaplibre imports it and provides the global.
|
|
94
|
+
// Its worker (maplibre-gl-worker.mjs, resolved next to the module) is
|
|
95
|
+
// cross-origin from the CDN; MapLibre wraps it in a same-origin blob URL
|
|
96
|
+
// itself. Pinned to an exact version: unpkg answers a version range
|
|
97
|
+
// (@6) with a redirect, and a browser refuses a worker script that
|
|
98
|
+
// redirects ("Refused to cross-origin redirects of the top-level worker
|
|
99
|
+
// script" — the map never loaded on a file:// page).
|
|
100
|
+
maplibreCss: 'https://unpkg.com/maplibre-gl@6.11.2/dist/maplibre-gl.css',
|
|
101
|
+
maplibreJs: 'https://unpkg.com/maplibre-gl@6.11.2/dist/maplibre-gl.mjs',
|
|
89
102
|
// Bumped 8.9.35 -> 9.4.0 (2026-09-21, globe-reprojection fix): v8's
|
|
90
103
|
// interleaving (MapboxOverlay) always computed flat Web-Mercator
|
|
91
104
|
// screen positions regardless of the map's actual projection —
|
|
@@ -117,6 +130,49 @@
|
|
|
117
130
|
document.head.appendChild(el);
|
|
118
131
|
});
|
|
119
132
|
}
|
|
133
|
+
// MapLibre 6's ES module → the window.maplibregl the engine and pages use
|
|
134
|
+
// (a copy of its named exports: a module namespace is read-only)
|
|
135
|
+
function loadMaplibre(src) {
|
|
136
|
+
installFileWorkerShim();
|
|
137
|
+
return import(src).then(mod => {
|
|
138
|
+
global.maplibregl = Object.assign({}, mod.default || {}, mod);
|
|
139
|
+
}, err => { throw new Error(`[ixmaps-gl] failed to load ${src}: ${err && err.message}`); });
|
|
140
|
+
}
|
|
141
|
+
// MapLibre 6 starts its worker as a module worker; for a worker on the
|
|
142
|
+
// CDN it wraps it in a blob URL (`import "…maplibre-gl-worker.mjs"`).
|
|
143
|
+
// On a file:// page Chrome refuses every module worker from a blob URL
|
|
144
|
+
// ("Refused to cross-origin redirects of the top-level worker script" —
|
|
145
|
+
// the map never loaded), while a module worker from a data: URL works.
|
|
146
|
+
// Only there, and only for MapLibre's own wrapper, the Worker is started
|
|
147
|
+
// from the same import as a data: URL. Other workers are untouched.
|
|
148
|
+
function installFileWorkerShim() {
|
|
149
|
+
if (!global.location || global.location.protocol !== 'file:' || !global.Worker || global.Worker.__ixmapsGlFileShim) return;
|
|
150
|
+
const NativeBlob = global.Blob, NativeWorker = global.Worker;
|
|
151
|
+
const maplibreWorkerImport = new WeakMap(); // Blob → its import statement
|
|
152
|
+
const blobUrls = new Map(); // blob: URL → import statement
|
|
153
|
+
const isMaplibreImport = t => typeof t === 'string' && /^import "https?:[^"]*\/maplibre-gl-worker(-dev)?\.mjs"$/.test(t);
|
|
154
|
+
function Blob(parts, opts) {
|
|
155
|
+
const b = new NativeBlob(parts, opts);
|
|
156
|
+
if (Array.isArray(parts) && parts.length === 1 && isMaplibreImport(parts[0])) maplibreWorkerImport.set(b, parts[0]);
|
|
157
|
+
return b;
|
|
158
|
+
}
|
|
159
|
+
Blob.prototype = NativeBlob.prototype;
|
|
160
|
+
global.Blob = Blob;
|
|
161
|
+
const nativeCreate = global.URL.createObjectURL;
|
|
162
|
+
global.URL.createObjectURL = function (obj) {
|
|
163
|
+
const u = nativeCreate.call(this, obj);
|
|
164
|
+
if (maplibreWorkerImport.has(obj)) blobUrls.set(u, maplibreWorkerImport.get(obj));
|
|
165
|
+
return u;
|
|
166
|
+
};
|
|
167
|
+
function Worker(url, opts) {
|
|
168
|
+
const imp = blobUrls.get(String(url));
|
|
169
|
+
if (imp && opts && opts.type === 'module') return new NativeWorker('data:text/javascript,' + encodeURIComponent(imp), opts);
|
|
170
|
+
return new NativeWorker(url, opts);
|
|
171
|
+
}
|
|
172
|
+
Worker.prototype = NativeWorker.prototype;
|
|
173
|
+
Worker.__ixmapsGlFileShim = true;
|
|
174
|
+
global.Worker = Worker;
|
|
175
|
+
}
|
|
120
176
|
function loadStylesheet(href) {
|
|
121
177
|
return new Promise((resolve, reject) => {
|
|
122
178
|
const el = document.createElement('link');
|
|
@@ -188,7 +244,7 @@
|
|
|
188
244
|
}
|
|
189
245
|
if (i === 2) return true;
|
|
190
246
|
}
|
|
191
|
-
console.warn(`[ixmaps-gl] this page loads maplibre-gl ${v || '(unknown version)'}; deck.gl needs >= ${MAPLIBRE_MIN_VERSION.join('.')} — loading ${LIB_URLS.maplibreJs.match(/maplibre-gl@([\d.]+)/)[1]}
|
|
247
|
+
console.warn(`[ixmaps-gl] this page loads maplibre-gl ${v || '(unknown version)'}; deck.gl needs >= ${MAPLIBRE_MIN_VERSION.join('.')} — loading ${LIB_URLS.maplibreJs.match(/maplibre-gl@([\d.]+)/)[1]} instead (it replaces window.maplibregl); remove the page's own maplibre-gl <script> tag`);
|
|
192
248
|
return false;
|
|
193
249
|
}
|
|
194
250
|
// data.js, unless the page has it already: .options({datajs: url}) loads
|
|
@@ -213,12 +269,16 @@
|
|
|
213
269
|
function ensureLibrariesLoaded() {
|
|
214
270
|
if (!_librariesPromise) {
|
|
215
271
|
_librariesPromise = Promise.all([
|
|
216
|
-
pageMaplibreUsable() ? Promise.resolve() :
|
|
272
|
+
pageMaplibreUsable() ? Promise.resolve() : loadMaplibre(LIB_URLS.maplibreJs),
|
|
217
273
|
pageDeckUsable() ? Promise.resolve() : loadScript(LIB_URLS.deck),
|
|
218
274
|
global.Mustache ? Promise.resolve() : loadScript(LIB_URLS.mustache),
|
|
219
275
|
[...document.styleSheets].some(s => s.href === LIB_URLS.maplibreCss) ? Promise.resolve() : loadStylesheet(LIB_URLS.maplibreCss)
|
|
220
276
|
]).then(() => {
|
|
221
277
|
({ IconLayer, ScatterplotLayer, GeoJsonLayer, MapLibreOverlay, TextLayer } = global.deck);
|
|
278
|
+
// which engine and libraries this page really runs (a page may
|
|
279
|
+
// bring its own MapLibre/deck.gl — the loaded ones are named)
|
|
280
|
+
const ml = global.maplibregl && typeof global.maplibregl.getVersion === 'function' ? global.maplibregl.getVersion() : '?';
|
|
281
|
+
console.info(`[ixmaps-gl] ${IXMAPS_GL_VERSION} · MapLibre ${ml} · deck.gl ${global.deck.VERSION || '?'}`);
|
|
222
282
|
// Matches the real engine's own ui/js/tools/tooltip_mustache.js,
|
|
223
283
|
// which overrides Mustache.escape to identity: tooltip HTML (the
|
|
224
284
|
// template itself, and this engine's own chart/data-table
|
|
@@ -2668,6 +2728,16 @@
|
|
|
2668
2728
|
// broker re-queries its data from it. Read at call time: pages wrap
|
|
2669
2729
|
// it after loading. flat passes its SVG zoom scale; this passes the
|
|
2670
2730
|
// flat zoom level, the one value gl has.
|
|
2731
|
+
//
|
|
2732
|
+
// gl also calls it DURING a zoom (notifyZoomAndPanMidGesture, wired
|
|
2733
|
+
// into the throttled refresh below), zoom changes only: the scale
|
|
2734
|
+
// gates (featurelower/chartupper ...) switch on the live zoom while
|
|
2735
|
+
// the gesture runs, so a page that ties something zoom-dependent to
|
|
2736
|
+
// this hook (e.g. a basemap opacity ramp) must be told in the same
|
|
2737
|
+
// tick — from 'moveend' alone it arrived after the gate had already
|
|
2738
|
+
// flipped (FEATURE gone, basemap still at the old, faint opacity: a
|
|
2739
|
+
// blank background until the zoom ended).
|
|
2740
|
+
let notifyZoomAndPanMidGesture = () => {};
|
|
2671
2741
|
{
|
|
2672
2742
|
let last = null;
|
|
2673
2743
|
map.on('moveend', () => {
|
|
@@ -2679,6 +2749,14 @@
|
|
|
2679
2749
|
if (z === prev.z && Math.abs(p.x - q.x) <= 10 && Math.abs(p.y - q.y) <= 10) return;
|
|
2680
2750
|
try { hook.call(global.ixmaps, mapLibreToFlatZoom(z)); } catch (e) { console.error('[ixmaps-gl] htmlgui_onZoomAndPan:', e); }
|
|
2681
2751
|
});
|
|
2752
|
+
notifyZoomAndPanMidGesture = () => {
|
|
2753
|
+
const hook = pageIxmaps() && pageIxmaps().htmlgui_onZoomAndPan;
|
|
2754
|
+
if (typeof hook !== 'function' || !last) return;
|
|
2755
|
+
const z = map.getZoom();
|
|
2756
|
+
if (z === last.z) return; // a pan alone waits for 'moveend' (and its 10 px rule)
|
|
2757
|
+
last = { z, c: map.getCenter() };
|
|
2758
|
+
try { hook.call(global.ixmaps, mapLibreToFlatZoom(z)); } catch (e) { console.error('[ixmaps-gl] htmlgui_onZoomAndPan:', e); }
|
|
2759
|
+
};
|
|
2682
2760
|
// flat calls it on the first draw too (its old zoom is unset then)
|
|
2683
2761
|
map.once('load', () => {
|
|
2684
2762
|
last = { z: map.getZoom(), c: map.getCenter() };
|
|
@@ -3818,10 +3896,11 @@
|
|
|
3818
3896
|
const elapsed = now - lastRefreshAt;
|
|
3819
3897
|
if (elapsed >= REFRESH_INTERVAL_MS) {
|
|
3820
3898
|
lastRefreshAt = now;
|
|
3899
|
+
notifyZoomAndPanMidGesture();
|
|
3821
3900
|
refreshLayers();
|
|
3822
3901
|
} else {
|
|
3823
3902
|
clearTimeout(refreshTimer);
|
|
3824
|
-
refreshTimer = setTimeout(() => { lastRefreshAt = Date.now(); refreshLayers(); }, REFRESH_INTERVAL_MS - elapsed);
|
|
3903
|
+
refreshTimer = setTimeout(() => { lastRefreshAt = Date.now(); notifyZoomAndPanMidGesture(); refreshLayers(); }, REFRESH_INTERVAL_MS - elapsed);
|
|
3825
3904
|
}
|
|
3826
3905
|
scheduleNotifyRedraw();
|
|
3827
3906
|
}
|
|
@@ -9887,6 +9966,8 @@
|
|
|
9887
9966
|
szResourceBase: ixmapsSzResourceBase, getProjectString, setProjectJSON, loadProject,
|
|
9888
9967
|
markThemeClass, unmarkThemeClass, getBoundingBox, setTitle, setTitleBox, refreshTheme, formatValue: flatFormatValue,
|
|
9889
9968
|
map: mapHandle, getZoom,
|
|
9969
|
+
// this engine's version (flat's own ixmaps.version numbers flat)
|
|
9970
|
+
glVersion: IXMAPS_GL_VERSION,
|
|
9890
9971
|
// flat always has this page hook (its layer legend tool defines it,
|
|
9891
9972
|
// ui/js/tools/layer_legend.js 702-712): pages wrap it and call the one
|
|
9892
9973
|
// they replaced — the chain ends here
|
|
@@ -9905,6 +9986,7 @@
|
|
|
9905
9986
|
// test-only: lets test/unit/*.test.mjs call pure internals directly (the
|
|
9906
9987
|
// engine runs in a Node vm there); deliberately NOT on the ixmaps object
|
|
9907
9988
|
global.__ixmapsGlInternals = {
|
|
9989
|
+
IXMAPS_GL_VERSION,
|
|
9908
9990
|
normalizeTheme, projectThemeToDefinition, LayerBuilder, LayerRuntime, typeStyleNumbers, styleNum,
|
|
9909
9991
|
resolveScriptUrl, isTrustedScriptUrl, loadProcessingScript, loadBrokerData, applyBrokerThemePatch, makeBrokerTheme,
|
|
9910
9992
|
equalIntervalBreaks, quantileBreaks, naturalBreaks, evenStrideSample, jenksBreakValues, partsFromBreakValues, resolvePartsClass,
|