passerelle-geo-components 0.2.0-alpha.2 → 0.2.0-alpha.4
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/geo-components.js +220 -12
- package/package.json +1 -1
package/geo-components.js
CHANGED
|
@@ -232,6 +232,9 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
232
232
|
const overrides = [];
|
|
233
233
|
for (const l of params.layers) {
|
|
234
234
|
const geojson = l.source && l.source.type === "geojson" ? l.source.data : null;
|
|
235
|
+
const style = l.style || {};
|
|
236
|
+
const interactions = l.interactions || {};
|
|
237
|
+
const classifShim = _v031ClassificationShim(style.classification);
|
|
235
238
|
catalog.push({
|
|
236
239
|
id: l.id,
|
|
237
240
|
name: l.name,
|
|
@@ -239,18 +242,27 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
239
242
|
geojson,
|
|
240
243
|
n_features: l.n_features,
|
|
241
244
|
bbox: l.bbox,
|
|
242
|
-
classification:
|
|
243
|
-
outline:
|
|
244
|
-
hollow_point:
|
|
245
|
+
classification: classifShim,
|
|
246
|
+
outline: style.outline,
|
|
247
|
+
hollow_point: style.hollow_point
|
|
245
248
|
});
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
// V0.3.1 opacity.reactive (Sprint V0.2 alpha.4, 2026-07-10) : preserver
|
|
250
|
+
// la config fade progressif pour applyBinding("time") au lieu du filter
|
|
251
|
+
// binaire hide/show historique.
|
|
252
|
+
const opacityStatic = typeof style.opacity === "object"
|
|
253
|
+
? (style.opacity.kind === "static" ? style.opacity.value : undefined)
|
|
254
|
+
: style.opacity;
|
|
255
|
+
const opacityReactive = (typeof style.opacity === "object"
|
|
256
|
+
&& style.opacity.kind === "reactive")
|
|
257
|
+
? style.opacity
|
|
258
|
+
: null;
|
|
248
259
|
overrides.push({
|
|
249
260
|
layer_id_ref: l.id,
|
|
250
261
|
visible: style.visible !== false,
|
|
251
262
|
z_index: style.z_index,
|
|
252
|
-
opacity:
|
|
253
|
-
|
|
263
|
+
opacity: opacityStatic,
|
|
264
|
+
opacity_reactive: opacityReactive,
|
|
265
|
+
classification: classifShim,
|
|
254
266
|
outline: style.outline,
|
|
255
267
|
hollow_point: style.hollow_point,
|
|
256
268
|
popup_template: interactions.popup_template,
|
|
@@ -266,6 +278,40 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
266
278
|
return { params: shimParams, hostContext: shimHost };
|
|
267
279
|
}
|
|
268
280
|
|
|
281
|
+
// V0.3.1 classification : {color: {mode, field, method, breaks, palette,
|
|
282
|
+
// _compiled_expression}, size, label} -> {paint_expression, method, field, ...}
|
|
283
|
+
// attendus par _paintForClassification (V1.13).
|
|
284
|
+
function _v031ClassificationShim(classif) {
|
|
285
|
+
if (!classif) return null;
|
|
286
|
+
// Deja au format V1.13 (paint_expression direct) -> passthrough
|
|
287
|
+
if (classif.paint_expression) return classif;
|
|
288
|
+
// V0.3.1 nested {color: {...}}
|
|
289
|
+
const color = classif.color || classif;
|
|
290
|
+
if (!color || !color.mode) return classif;
|
|
291
|
+
const out = {
|
|
292
|
+
method: color.method || color.mode,
|
|
293
|
+
field: color.field
|
|
294
|
+
};
|
|
295
|
+
// Priorite : expression MapLibre pre-compilee dans _compiled_expression
|
|
296
|
+
if (Array.isArray(color._compiled_expression)) {
|
|
297
|
+
out.paint_expression = color._compiled_expression;
|
|
298
|
+
} else if (color.mode === "single" && color.value) {
|
|
299
|
+
out.paint_expression = color.value;
|
|
300
|
+
} else if (color.mode === "expression" && Array.isArray(color.expression)) {
|
|
301
|
+
out.paint_expression = color.expression;
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// V0.3.1 alpha.4 : normalise une position (top-left/bottom-right/...) vers
|
|
307
|
+
// les 4 positions acceptees par MapLibre. Fallback sur defaut si absente ou
|
|
308
|
+
// invalide (avoid warning MapLibre "Position undefined").
|
|
309
|
+
function _mlPosition(pos, fallback) {
|
|
310
|
+
const valid = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
311
|
+
if (typeof pos === "string" && valid.includes(pos)) return pos;
|
|
312
|
+
return fallback;
|
|
313
|
+
}
|
|
314
|
+
|
|
269
315
|
/**
|
|
270
316
|
* Résout la zone d'étude en {center, zoom, bbox} exploitables par MapLibre.
|
|
271
317
|
* Supporte les 3 modes du contrat V1.13 : commune | manual | study.
|
|
@@ -1064,13 +1110,37 @@ export class GeoMap extends HTMLElement {
|
|
|
1064
1110
|
center: zone.center,
|
|
1065
1111
|
zoom: zone.zoom,
|
|
1066
1112
|
});
|
|
1067
|
-
|
|
1113
|
+
|
|
1114
|
+
// V0.3.1 alpha.4 : declaratifs params.scalebar / params.north_arrow.
|
|
1115
|
+
// Sans north_arrow declare, on garde le comportement historique
|
|
1116
|
+
// NavigationControl sans compass. Avec, on affiche compass a la position
|
|
1117
|
+
// demandee. scalebar => ScaleControl natif MapLibre a la position/unit.
|
|
1118
|
+
const northCfg = params.north_arrow;
|
|
1119
|
+
const scaleCfg = params.scalebar;
|
|
1120
|
+
const showCompass = !!northCfg;
|
|
1121
|
+
const northPos = northCfg && northCfg.position ? northCfg.position : "top-left";
|
|
1122
|
+
this.map.addControl(
|
|
1123
|
+
new ml.NavigationControl({ showCompass, visualizePitch: showCompass }),
|
|
1124
|
+
_mlPosition(northPos, "top-left"),
|
|
1125
|
+
);
|
|
1126
|
+
if (scaleCfg) {
|
|
1127
|
+
const unit = scaleCfg.unit === "imperial" ? "imperial" : "metric";
|
|
1128
|
+
const scalePos = _mlPosition(scaleCfg.position, "bottom-right");
|
|
1129
|
+
this.map.addControl(
|
|
1130
|
+
new ml.ScaleControl({ maxWidth: 120, unit }),
|
|
1131
|
+
scalePos,
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1068
1134
|
|
|
1069
1135
|
const layersOverride = params.layers_override || [];
|
|
1070
1136
|
const catalogLayers =
|
|
1071
1137
|
this._hostContext.catalog_layers || params._catalog_layers || [];
|
|
1072
1138
|
|
|
1073
1139
|
this._layerIds = [];
|
|
1140
|
+
// V0.3.1 (Sprint V0.2 alpha.4) : cache config opacity.reactive par layerId
|
|
1141
|
+
// pour permettre au binding 'time' de faire un fade progressif au lieu
|
|
1142
|
+
// d'un setFilter binaire hide/show. Cle = layer id MapLibre applique.
|
|
1143
|
+
this._reactiveOpacity = {};
|
|
1074
1144
|
|
|
1075
1145
|
// P2 contract-drift V1.13 (review 2026-07-08) : respecter LayerOverride.z_index.
|
|
1076
1146
|
// Le contrat V1.13 declare z_index (int) pour reordonner les layers a
|
|
@@ -1156,6 +1226,12 @@ export class GeoMap extends HTMLElement {
|
|
|
1156
1226
|
this.map.addLayer(layerDef);
|
|
1157
1227
|
this._layerIds.push(layerDef.id);
|
|
1158
1228
|
|
|
1229
|
+
// V0.3.1 alpha.4 : memoriser config opacity.reactive pour fade
|
|
1230
|
+
// progressif via applyBinding('time').
|
|
1231
|
+
if (styleOverride.opacity_reactive) {
|
|
1232
|
+
this._reactiveOpacity[layerDef.id] = styleOverride.opacity_reactive;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1159
1235
|
// V0.3.1 style.outline : ajout automatique layer 'line' sur même source
|
|
1160
1236
|
if (
|
|
1161
1237
|
styleOverride.outline &&
|
|
@@ -1183,6 +1259,19 @@ export class GeoMap extends HTMLElement {
|
|
|
1183
1259
|
}
|
|
1184
1260
|
});
|
|
1185
1261
|
|
|
1262
|
+
// V0.3.1 alpha.4 : legend.mode='auto' + from_layer -> derive chips
|
|
1263
|
+
// depuis classification._compiled_expression du layer designe et
|
|
1264
|
+
// injecte une <geo-legend> auto-generee dans le shadow DOM du <geo-map>.
|
|
1265
|
+
const legendCfg = params.legend;
|
|
1266
|
+
if (legendCfg && (legendCfg.mode === "auto" || legendCfg.items)) {
|
|
1267
|
+
const items = Array.isArray(legendCfg.items)
|
|
1268
|
+
? legendCfg.items
|
|
1269
|
+
: this._autoLegendItems(legendCfg, catalogLayers, orderedLayers);
|
|
1270
|
+
if (items && items.length) {
|
|
1271
|
+
this._injectLegend(legendCfg, items);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1186
1275
|
// fitBounds auto sur zone.bbox ou premier layer avec features
|
|
1187
1276
|
if (zone.bbox) {
|
|
1188
1277
|
this.map.fitBounds(zone.bbox, { padding: 40, maxZoom: 16 });
|
|
@@ -1352,6 +1441,89 @@ export class GeoMap extends HTMLElement {
|
|
|
1352
1441
|
/**
|
|
1353
1442
|
* Point d'entrée de l'orchestrateur bindings. Applique un binding reçu.
|
|
1354
1443
|
*/
|
|
1444
|
+
// V0.3.1 alpha.4 : derive items de legende auto depuis
|
|
1445
|
+
// classification._compiled_expression du layer from_layer.
|
|
1446
|
+
_autoLegendItems(legendCfg, catalogLayers, orderedLayers) {
|
|
1447
|
+
const fromId = legendCfg.from_layer;
|
|
1448
|
+
if (!fromId) return null;
|
|
1449
|
+
const target = orderedLayers.find(o => o.scene && o.scene.id === fromId);
|
|
1450
|
+
if (!target) return null;
|
|
1451
|
+
const classif = (target.override && target.override.classification)
|
|
1452
|
+
|| target.scene.classification;
|
|
1453
|
+
if (!classif) return null;
|
|
1454
|
+
// Support 2 formats : V1.13 paint_expression direct, ou V0.3.1 shimme
|
|
1455
|
+
const expr = classif.paint_expression;
|
|
1456
|
+
if (!Array.isArray(expr) || expr.length < 3) return null;
|
|
1457
|
+
const items = [];
|
|
1458
|
+
if (expr[0] === "step") {
|
|
1459
|
+
// ["step", ["get", field], color0, threshold1, color1, threshold2, color2, ...]
|
|
1460
|
+
const firstColor = expr[2];
|
|
1461
|
+
items.push({ label: "avant " + expr[3], color: firstColor });
|
|
1462
|
+
for (let i = 3; i + 1 < expr.length; i += 2) {
|
|
1463
|
+
const thr = expr[i];
|
|
1464
|
+
const col = expr[i + 1];
|
|
1465
|
+
const next = expr[i + 2];
|
|
1466
|
+
const label = (i + 2 < expr.length)
|
|
1467
|
+
? thr + "-" + next
|
|
1468
|
+
: "≥ " + thr;
|
|
1469
|
+
items.push({ label, color: col });
|
|
1470
|
+
}
|
|
1471
|
+
} else if (expr[0] === "match") {
|
|
1472
|
+
// ["match", ["get", field], v1, c1, v2, c2, ..., fallback]
|
|
1473
|
+
for (let i = 2; i + 1 < expr.length; i += 2) {
|
|
1474
|
+
items.push({ label: String(expr[i]), color: expr[i + 1] });
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return items;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// V0.3.1 alpha.4 : injecte une legende auto-generee dans le shadow DOM.
|
|
1481
|
+
// Utilise le meme rendering que <geo-legend> mais integre inline.
|
|
1482
|
+
_injectLegend(legendCfg, items) {
|
|
1483
|
+
const shadow = this.shadowRoot;
|
|
1484
|
+
if (!shadow) return;
|
|
1485
|
+
const pos = legendCfg.position || "bottom-left";
|
|
1486
|
+
const title = legendCfg.title || "Légende";
|
|
1487
|
+
const wrap = document.createElement("div");
|
|
1488
|
+
wrap.className = "gc-legend";
|
|
1489
|
+
const posStyles = {
|
|
1490
|
+
"top-left": "top:8px;left:8px;",
|
|
1491
|
+
"top-right": "top:8px;right:8px;",
|
|
1492
|
+
"bottom-left": "bottom:8px;left:8px;",
|
|
1493
|
+
"bottom-right": "bottom:44px;right:8px;",
|
|
1494
|
+
};
|
|
1495
|
+
const styleAttr = posStyles[pos] || posStyles["bottom-left"];
|
|
1496
|
+
wrap.setAttribute("style",
|
|
1497
|
+
"position:absolute;" + styleAttr +
|
|
1498
|
+
"background:rgba(255,255,255,0.94);padding:8px 12px;border-radius:4px;" +
|
|
1499
|
+
"box-shadow:0 1px 4px rgba(0,0,0,0.15);font-family:Marianne,system-ui,sans-serif;" +
|
|
1500
|
+
"font-size:11px;color:#333;z-index:5;max-width:260px;"
|
|
1501
|
+
);
|
|
1502
|
+
const t = document.createElement("div");
|
|
1503
|
+
t.setAttribute("style",
|
|
1504
|
+
"color:#000091;text-transform:uppercase;font-size:10px;letter-spacing:0.5px;" +
|
|
1505
|
+
"font-weight:700;margin-bottom:6px;");
|
|
1506
|
+
t.textContent = title;
|
|
1507
|
+
wrap.appendChild(t);
|
|
1508
|
+
for (const it of items) {
|
|
1509
|
+
const chip = document.createElement("div");
|
|
1510
|
+
chip.setAttribute("style",
|
|
1511
|
+
"display:flex;align-items:center;gap:6px;margin:2px 0;");
|
|
1512
|
+
const sw = document.createElement("span");
|
|
1513
|
+
sw.setAttribute("style",
|
|
1514
|
+
"width:14px;height:10px;background:" + it.color + ";" +
|
|
1515
|
+
"border:1px solid rgba(0,0,0,0.15);border-radius:2px;flex-shrink:0;");
|
|
1516
|
+
chip.appendChild(sw);
|
|
1517
|
+
const lbl = document.createElement("span");
|
|
1518
|
+
lbl.textContent = it.label;
|
|
1519
|
+
chip.appendChild(lbl);
|
|
1520
|
+
wrap.appendChild(chip);
|
|
1521
|
+
}
|
|
1522
|
+
// Injecter dans .gc-map pour que la legende suive la carte
|
|
1523
|
+
const mapDiv = shadow.querySelector(".gc-map");
|
|
1524
|
+
if (mapDiv) mapDiv.appendChild(wrap);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1355
1527
|
applyBinding(detail) {
|
|
1356
1528
|
const { prop, value } = detail || {};
|
|
1357
1529
|
if (!this.map || !prop) return;
|
|
@@ -1359,11 +1531,47 @@ export class GeoMap extends HTMLElement {
|
|
|
1359
1531
|
|
|
1360
1532
|
switch (prop) {
|
|
1361
1533
|
case "time": {
|
|
1362
|
-
//
|
|
1534
|
+
// V0.3.1 alpha.4 : si le layer a opacity.reactive prop=time, on
|
|
1535
|
+
// applique un fade progressif (matched/before/after/null_value)
|
|
1536
|
+
// au lieu du filter binaire hide/show V1.13.
|
|
1363
1537
|
const year = _asYear(value);
|
|
1364
|
-
if (year
|
|
1365
|
-
|
|
1366
|
-
|
|
1538
|
+
if (year == null || !primaryLayer) break;
|
|
1539
|
+
const timeField = detail.field || "annee";
|
|
1540
|
+
|
|
1541
|
+
for (const lid of this._layerIds) {
|
|
1542
|
+
const cfg = this._reactiveOpacity[lid];
|
|
1543
|
+
if (cfg && cfg.prop === "time") {
|
|
1544
|
+
// Fade progressif : expression case selon (field vs value)
|
|
1545
|
+
const field = cfg.field || timeField;
|
|
1546
|
+
const matched = cfg.matched ?? 0.85;
|
|
1547
|
+
const before = cfg.before ?? 0.85;
|
|
1548
|
+
const after = cfg.after ?? 0.12;
|
|
1549
|
+
const nullValue = cfg.null_value ?? 0.35;
|
|
1550
|
+
const expr = [
|
|
1551
|
+
"case",
|
|
1552
|
+
["==", ["typeof", ["get", field]], "null"], nullValue,
|
|
1553
|
+
["<", ["to-number", ["get", field]], year], before,
|
|
1554
|
+
[">", ["to-number", ["get", field]], year], after,
|
|
1555
|
+
matched,
|
|
1556
|
+
];
|
|
1557
|
+
const layer = this.map.getLayer(lid);
|
|
1558
|
+
if (!layer) continue;
|
|
1559
|
+
const paintKey =
|
|
1560
|
+
layer.type === "fill" ? "fill-opacity" :
|
|
1561
|
+
layer.type === "line" ? "line-opacity" :
|
|
1562
|
+
layer.type === "circle" ? "circle-opacity" :
|
|
1563
|
+
layer.type === "fill-extrusion" ? "fill-extrusion-opacity" :
|
|
1564
|
+
null;
|
|
1565
|
+
if (paintKey) {
|
|
1566
|
+
try { this.map.setPaintProperty(lid, paintKey, expr); }
|
|
1567
|
+
catch (e) { /* pas critique */ }
|
|
1568
|
+
}
|
|
1569
|
+
} else if (lid === primaryLayer) {
|
|
1570
|
+
// Fallback V1.13 : filter binaire sur le layer primaire
|
|
1571
|
+
try {
|
|
1572
|
+
this.map.setFilter(lid, ["<=", ["get", timeField], year]);
|
|
1573
|
+
} catch (e) { /* ignore */ }
|
|
1574
|
+
}
|
|
1367
1575
|
}
|
|
1368
1576
|
break;
|
|
1369
1577
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "passerelle-geo-components",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.4",
|
|
4
4
|
"description": "Web Components pour cartes MapLibre + composants controllers pilotes - brique commune multi-projet avec contract SceneManifest V0.3.1. Zero dependance runtime, ES module, encapsulation Shadow DOM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "geo-components.js",
|