passerelle-geo-components 0.2.0-alpha.3 → 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 +188 -6
- package/package.json +1 -1
package/geo-components.js
CHANGED
|
@@ -246,11 +246,22 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
246
246
|
outline: style.outline,
|
|
247
247
|
hollow_point: style.hollow_point
|
|
248
248
|
});
|
|
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;
|
|
249
259
|
overrides.push({
|
|
250
260
|
layer_id_ref: l.id,
|
|
251
261
|
visible: style.visible !== false,
|
|
252
262
|
z_index: style.z_index,
|
|
253
|
-
opacity:
|
|
263
|
+
opacity: opacityStatic,
|
|
264
|
+
opacity_reactive: opacityReactive,
|
|
254
265
|
classification: classifShim,
|
|
255
266
|
outline: style.outline,
|
|
256
267
|
hollow_point: style.hollow_point,
|
|
@@ -292,6 +303,15 @@ function _v031ClassificationShim(classif) {
|
|
|
292
303
|
return out;
|
|
293
304
|
}
|
|
294
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
|
+
|
|
295
315
|
/**
|
|
296
316
|
* Résout la zone d'étude en {center, zoom, bbox} exploitables par MapLibre.
|
|
297
317
|
* Supporte les 3 modes du contrat V1.13 : commune | manual | study.
|
|
@@ -1090,13 +1110,37 @@ export class GeoMap extends HTMLElement {
|
|
|
1090
1110
|
center: zone.center,
|
|
1091
1111
|
zoom: zone.zoom,
|
|
1092
1112
|
});
|
|
1093
|
-
|
|
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
|
+
}
|
|
1094
1134
|
|
|
1095
1135
|
const layersOverride = params.layers_override || [];
|
|
1096
1136
|
const catalogLayers =
|
|
1097
1137
|
this._hostContext.catalog_layers || params._catalog_layers || [];
|
|
1098
1138
|
|
|
1099
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 = {};
|
|
1100
1144
|
|
|
1101
1145
|
// P2 contract-drift V1.13 (review 2026-07-08) : respecter LayerOverride.z_index.
|
|
1102
1146
|
// Le contrat V1.13 declare z_index (int) pour reordonner les layers a
|
|
@@ -1182,6 +1226,12 @@ export class GeoMap extends HTMLElement {
|
|
|
1182
1226
|
this.map.addLayer(layerDef);
|
|
1183
1227
|
this._layerIds.push(layerDef.id);
|
|
1184
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
|
+
|
|
1185
1235
|
// V0.3.1 style.outline : ajout automatique layer 'line' sur même source
|
|
1186
1236
|
if (
|
|
1187
1237
|
styleOverride.outline &&
|
|
@@ -1209,6 +1259,19 @@ export class GeoMap extends HTMLElement {
|
|
|
1209
1259
|
}
|
|
1210
1260
|
});
|
|
1211
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
|
+
|
|
1212
1275
|
// fitBounds auto sur zone.bbox ou premier layer avec features
|
|
1213
1276
|
if (zone.bbox) {
|
|
1214
1277
|
this.map.fitBounds(zone.bbox, { padding: 40, maxZoom: 16 });
|
|
@@ -1378,6 +1441,89 @@ export class GeoMap extends HTMLElement {
|
|
|
1378
1441
|
/**
|
|
1379
1442
|
* Point d'entrée de l'orchestrateur bindings. Applique un binding reçu.
|
|
1380
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
|
+
|
|
1381
1527
|
applyBinding(detail) {
|
|
1382
1528
|
const { prop, value } = detail || {};
|
|
1383
1529
|
if (!this.map || !prop) return;
|
|
@@ -1385,11 +1531,47 @@ export class GeoMap extends HTMLElement {
|
|
|
1385
1531
|
|
|
1386
1532
|
switch (prop) {
|
|
1387
1533
|
case "time": {
|
|
1388
|
-
//
|
|
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.
|
|
1389
1537
|
const year = _asYear(value);
|
|
1390
|
-
if (year
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
+
}
|
|
1393
1575
|
}
|
|
1394
1576
|
break;
|
|
1395
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",
|