passerelle-geo-components 0.2.0-alpha.3 → 0.2.0-alpha.5
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
CHANGED
|
@@ -231,6 +231,9 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
231
231
|
const catalog = [];
|
|
232
232
|
const overrides = [];
|
|
233
233
|
for (const l of params.layers) {
|
|
234
|
+
// V0.3.2 alpha.5 : propager source complete pour permettre fetch async
|
|
235
|
+
// universel (geojson_path, geojson_url, wfs, boundary_admin, grist_table, x_*).
|
|
236
|
+
// Legacy V1.13 catalog_layers.geojson reste pour compat (source.type='geojson' inline).
|
|
234
237
|
const geojson = l.source && l.source.type === "geojson" ? l.source.data : null;
|
|
235
238
|
const style = l.style || {};
|
|
236
239
|
const interactions = l.interactions || {};
|
|
@@ -240,17 +243,29 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
240
243
|
name: l.name,
|
|
241
244
|
geometry_type: l.geometry_type,
|
|
242
245
|
geojson,
|
|
246
|
+
source_v031: l.source,
|
|
243
247
|
n_features: l.n_features,
|
|
244
248
|
bbox: l.bbox,
|
|
245
249
|
classification: classifShim,
|
|
246
250
|
outline: style.outline,
|
|
247
251
|
hollow_point: style.hollow_point
|
|
248
252
|
});
|
|
253
|
+
// V0.3.1 opacity.reactive (Sprint V0.2 alpha.4, 2026-07-10) : preserver
|
|
254
|
+
// la config fade progressif pour applyBinding("time") au lieu du filter
|
|
255
|
+
// binaire hide/show historique.
|
|
256
|
+
const opacityStatic = typeof style.opacity === "object"
|
|
257
|
+
? (style.opacity.kind === "static" ? style.opacity.value : undefined)
|
|
258
|
+
: style.opacity;
|
|
259
|
+
const opacityReactive = (typeof style.opacity === "object"
|
|
260
|
+
&& style.opacity.kind === "reactive")
|
|
261
|
+
? style.opacity
|
|
262
|
+
: null;
|
|
249
263
|
overrides.push({
|
|
250
264
|
layer_id_ref: l.id,
|
|
251
265
|
visible: style.visible !== false,
|
|
252
266
|
z_index: style.z_index,
|
|
253
|
-
opacity:
|
|
267
|
+
opacity: opacityStatic,
|
|
268
|
+
opacity_reactive: opacityReactive,
|
|
254
269
|
classification: classifShim,
|
|
255
270
|
outline: style.outline,
|
|
256
271
|
hollow_point: style.hollow_point,
|
|
@@ -267,6 +282,108 @@ function _v031ToLegacyShim(params, hostContext) {
|
|
|
267
282
|
return { params: shimParams, hostContext: shimHost };
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
// V0.3.2 alpha.5 (Sprint V0.2 session finale, 2026-07-11) : resolveur
|
|
286
|
+
// universel des 7 types de source du contract V0.3.2. Retourne une Promise
|
|
287
|
+
// qui resout vers un GeoJSON FeatureCollection utilisable par MapLibre.
|
|
288
|
+
// Pattern async : le layer est ajoute immediatement avec source vide, puis
|
|
289
|
+
// setData appele quand la promise resout. Consequence : la carte s'affiche
|
|
290
|
+
// instantanement (fond + controls), features apparaissent async.
|
|
291
|
+
//
|
|
292
|
+
// Types supportes :
|
|
293
|
+
// - geojson → source.data (immediat)
|
|
294
|
+
// - geojson_path → fetch(path, {credentials:'include'}) same-origin
|
|
295
|
+
// - geojson_url → fetch(url) public sans credentials
|
|
296
|
+
// - wfs → construct WFS GetFeature URL + fetch
|
|
297
|
+
// - boundary_admin → hostContext.boundary_resolver({catalog_id, filter})
|
|
298
|
+
// - grist_table → hostContext.grist_resolver({table, id_field, sync})
|
|
299
|
+
// - x_<prefix> → hostContext.custom_resolvers[prefix]
|
|
300
|
+
// - vector/pmtiles → PAS gere ici (utiliser addSource type=vector direct)
|
|
301
|
+
async function _fetchSourceData(source, hostContext) {
|
|
302
|
+
if (!source || !source.type) return { type: "FeatureCollection", features: [] };
|
|
303
|
+
const t = source.type;
|
|
304
|
+
|
|
305
|
+
if (t === "geojson") {
|
|
306
|
+
if (typeof source.data === "string") {
|
|
307
|
+
// legacy : data peut etre une URL string
|
|
308
|
+
const resp = await fetch(source.data);
|
|
309
|
+
if (!resp.ok) throw new Error(`geojson data fetch failed: ${resp.status}`);
|
|
310
|
+
return resp.json();
|
|
311
|
+
}
|
|
312
|
+
return source.data || { type: "FeatureCollection", features: [] };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (t === "geojson_path") {
|
|
316
|
+
// Path serveur (PVC hub). Fetch same-origin avec cookies OIDC.
|
|
317
|
+
const url = source.fetch_url || source.path;
|
|
318
|
+
const resp = await fetch(url, { credentials: "include" });
|
|
319
|
+
if (!resp.ok) throw new Error(`geojson_path fetch failed: ${resp.status}`);
|
|
320
|
+
return resp.json();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (t === "geojson_url") {
|
|
324
|
+
// URL publique. Fetch sans credentials.
|
|
325
|
+
const resp = await fetch(source.url);
|
|
326
|
+
if (!resp.ok) throw new Error(`geojson_url fetch failed: ${resp.status}`);
|
|
327
|
+
return resp.json();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (t === "wfs") {
|
|
331
|
+
// Construct GetFeature URL avec parametres par defaut V0.3.2.
|
|
332
|
+
const p = new URLSearchParams({
|
|
333
|
+
service: "WFS",
|
|
334
|
+
version: "2.0.0",
|
|
335
|
+
request: "GetFeature",
|
|
336
|
+
typeName: source.type_name,
|
|
337
|
+
outputFormat: "application/json",
|
|
338
|
+
srsName: "EPSG:4326",
|
|
339
|
+
});
|
|
340
|
+
if (source.max_features) p.set("count", String(source.max_features));
|
|
341
|
+
if (source.filter_cql) p.set("cql_filter", source.filter_cql);
|
|
342
|
+
const sep = source.url.includes("?") ? "&" : "?";
|
|
343
|
+
const resp = await fetch(source.url + sep + p.toString());
|
|
344
|
+
if (!resp.ok) throw new Error(`wfs fetch failed: ${resp.status}`);
|
|
345
|
+
return resp.json();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (t === "boundary_admin") {
|
|
349
|
+
// Delegue au hostContext du consommateur.
|
|
350
|
+
const resolver = hostContext && hostContext.boundary_resolver;
|
|
351
|
+
if (typeof resolver !== "function") {
|
|
352
|
+
throw new Error(
|
|
353
|
+
"boundary_admin requires hostContext.boundary_resolver(source)"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return await resolver(source);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (t === "grist_table") {
|
|
360
|
+
// Delegue au widget Grist parent.
|
|
361
|
+
const resolver = hostContext && hostContext.grist_resolver;
|
|
362
|
+
if (typeof resolver !== "function") {
|
|
363
|
+
throw new Error(
|
|
364
|
+
"grist_table requires hostContext.grist_resolver(source)"
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return await resolver(source);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (typeof t === "string" && t.startsWith("x_")) {
|
|
371
|
+
// Extension par-projet.
|
|
372
|
+
const resolvers = (hostContext && hostContext.custom_resolvers) || {};
|
|
373
|
+
const resolver = resolvers[t];
|
|
374
|
+
if (typeof resolver !== "function") {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`custom source type '${t}' requires hostContext.custom_resolvers['${t}']`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return await resolver(source);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Types vector/pmtiles/... : geres directement par MapLibre addSource,
|
|
383
|
+
// pas via ce resolveur.
|
|
384
|
+
throw new Error(`_fetchSourceData: type '${t}' non supporte comme geojson`);
|
|
385
|
+
}
|
|
386
|
+
|
|
270
387
|
// V0.3.1 classification : {color: {mode, field, method, breaks, palette,
|
|
271
388
|
// _compiled_expression}, size, label} -> {paint_expression, method, field, ...}
|
|
272
389
|
// attendus par _paintForClassification (V1.13).
|
|
@@ -292,6 +409,15 @@ function _v031ClassificationShim(classif) {
|
|
|
292
409
|
return out;
|
|
293
410
|
}
|
|
294
411
|
|
|
412
|
+
// V0.3.1 alpha.4 : normalise une position (top-left/bottom-right/...) vers
|
|
413
|
+
// les 4 positions acceptees par MapLibre. Fallback sur defaut si absente ou
|
|
414
|
+
// invalide (avoid warning MapLibre "Position undefined").
|
|
415
|
+
function _mlPosition(pos, fallback) {
|
|
416
|
+
const valid = ["top-left", "top-right", "bottom-left", "bottom-right"];
|
|
417
|
+
if (typeof pos === "string" && valid.includes(pos)) return pos;
|
|
418
|
+
return fallback;
|
|
419
|
+
}
|
|
420
|
+
|
|
295
421
|
/**
|
|
296
422
|
* Résout la zone d'étude en {center, zoom, bbox} exploitables par MapLibre.
|
|
297
423
|
* Supporte les 3 modes du contrat V1.13 : commune | manual | study.
|
|
@@ -1090,13 +1216,37 @@ export class GeoMap extends HTMLElement {
|
|
|
1090
1216
|
center: zone.center,
|
|
1091
1217
|
zoom: zone.zoom,
|
|
1092
1218
|
});
|
|
1093
|
-
|
|
1219
|
+
|
|
1220
|
+
// V0.3.1 alpha.4 : declaratifs params.scalebar / params.north_arrow.
|
|
1221
|
+
// Sans north_arrow declare, on garde le comportement historique
|
|
1222
|
+
// NavigationControl sans compass. Avec, on affiche compass a la position
|
|
1223
|
+
// demandee. scalebar => ScaleControl natif MapLibre a la position/unit.
|
|
1224
|
+
const northCfg = params.north_arrow;
|
|
1225
|
+
const scaleCfg = params.scalebar;
|
|
1226
|
+
const showCompass = !!northCfg;
|
|
1227
|
+
const northPos = northCfg && northCfg.position ? northCfg.position : "top-left";
|
|
1228
|
+
this.map.addControl(
|
|
1229
|
+
new ml.NavigationControl({ showCompass, visualizePitch: showCompass }),
|
|
1230
|
+
_mlPosition(northPos, "top-left"),
|
|
1231
|
+
);
|
|
1232
|
+
if (scaleCfg) {
|
|
1233
|
+
const unit = scaleCfg.unit === "imperial" ? "imperial" : "metric";
|
|
1234
|
+
const scalePos = _mlPosition(scaleCfg.position, "bottom-right");
|
|
1235
|
+
this.map.addControl(
|
|
1236
|
+
new ml.ScaleControl({ maxWidth: 120, unit }),
|
|
1237
|
+
scalePos,
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1094
1240
|
|
|
1095
1241
|
const layersOverride = params.layers_override || [];
|
|
1096
1242
|
const catalogLayers =
|
|
1097
1243
|
this._hostContext.catalog_layers || params._catalog_layers || [];
|
|
1098
1244
|
|
|
1099
1245
|
this._layerIds = [];
|
|
1246
|
+
// V0.3.1 (Sprint V0.2 alpha.4) : cache config opacity.reactive par layerId
|
|
1247
|
+
// pour permettre au binding 'time' de faire un fade progressif au lieu
|
|
1248
|
+
// d'un setFilter binaire hide/show. Cle = layer id MapLibre applique.
|
|
1249
|
+
this._reactiveOpacity = {};
|
|
1100
1250
|
|
|
1101
1251
|
// P2 contract-drift V1.13 (review 2026-07-08) : respecter LayerOverride.z_index.
|
|
1102
1252
|
// Le contrat V1.13 declare z_index (int) pour reordonner les layers a
|
|
@@ -1130,6 +1280,40 @@ export class GeoMap extends HTMLElement {
|
|
|
1130
1280
|
data: sceneLayer.geojson || { type: "FeatureCollection", features: [] },
|
|
1131
1281
|
});
|
|
1132
1282
|
|
|
1283
|
+
// V0.3.2 alpha.5 : fetch async des sources non-inline
|
|
1284
|
+
// (geojson_path, geojson_url, wfs, boundary_admin, grist_table, x_*).
|
|
1285
|
+
// La carte s'affiche instantanement, features arrivent async.
|
|
1286
|
+
if (sceneLayer.source_v031
|
|
1287
|
+
&& sceneLayer.source_v031.type !== "geojson"
|
|
1288
|
+
&& !sceneLayer.geojson) {
|
|
1289
|
+
const hostCtx = this._hostContext || {};
|
|
1290
|
+
_fetchSourceData(sceneLayer.source_v031, hostCtx)
|
|
1291
|
+
.then((geojson) => {
|
|
1292
|
+
const src = this.map.getSource(sourceId);
|
|
1293
|
+
if (src && typeof src.setData === "function") {
|
|
1294
|
+
src.setData(geojson);
|
|
1295
|
+
}
|
|
1296
|
+
this.dispatchEvent(new CustomEvent("geo:source-loaded", {
|
|
1297
|
+
detail: {
|
|
1298
|
+
layerId: sceneLayer.id,
|
|
1299
|
+
features: (geojson.features || []).length,
|
|
1300
|
+
},
|
|
1301
|
+
}));
|
|
1302
|
+
})
|
|
1303
|
+
.catch((e) => {
|
|
1304
|
+
console.warn(
|
|
1305
|
+
"[geo-components] fetch source failed for",
|
|
1306
|
+
sceneLayer.id, "->", sceneLayer.source_v031, e
|
|
1307
|
+
);
|
|
1308
|
+
this.dispatchEvent(new CustomEvent("geo:source-error", {
|
|
1309
|
+
detail: {
|
|
1310
|
+
layerId: sceneLayer.id,
|
|
1311
|
+
error: String(e).slice(0, 200),
|
|
1312
|
+
},
|
|
1313
|
+
}));
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1133
1317
|
// V0.2.0 Chantier 4 : détection type + auto-split polygon+point
|
|
1134
1318
|
// Analyse la geojson pour détecter FC hétérogène et éviter le bug
|
|
1135
1319
|
// POINT invisibles (remontée cerema-livrables).
|
|
@@ -1182,6 +1366,12 @@ export class GeoMap extends HTMLElement {
|
|
|
1182
1366
|
this.map.addLayer(layerDef);
|
|
1183
1367
|
this._layerIds.push(layerDef.id);
|
|
1184
1368
|
|
|
1369
|
+
// V0.3.1 alpha.4 : memoriser config opacity.reactive pour fade
|
|
1370
|
+
// progressif via applyBinding('time').
|
|
1371
|
+
if (styleOverride.opacity_reactive) {
|
|
1372
|
+
this._reactiveOpacity[layerDef.id] = styleOverride.opacity_reactive;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1185
1375
|
// V0.3.1 style.outline : ajout automatique layer 'line' sur même source
|
|
1186
1376
|
if (
|
|
1187
1377
|
styleOverride.outline &&
|
|
@@ -1209,6 +1399,19 @@ export class GeoMap extends HTMLElement {
|
|
|
1209
1399
|
}
|
|
1210
1400
|
});
|
|
1211
1401
|
|
|
1402
|
+
// V0.3.1 alpha.4 : legend.mode='auto' + from_layer -> derive chips
|
|
1403
|
+
// depuis classification._compiled_expression du layer designe et
|
|
1404
|
+
// injecte une <geo-legend> auto-generee dans le shadow DOM du <geo-map>.
|
|
1405
|
+
const legendCfg = params.legend;
|
|
1406
|
+
if (legendCfg && (legendCfg.mode === "auto" || legendCfg.items)) {
|
|
1407
|
+
const items = Array.isArray(legendCfg.items)
|
|
1408
|
+
? legendCfg.items
|
|
1409
|
+
: this._autoLegendItems(legendCfg, catalogLayers, orderedLayers);
|
|
1410
|
+
if (items && items.length) {
|
|
1411
|
+
this._injectLegend(legendCfg, items);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1212
1415
|
// fitBounds auto sur zone.bbox ou premier layer avec features
|
|
1213
1416
|
if (zone.bbox) {
|
|
1214
1417
|
this.map.fitBounds(zone.bbox, { padding: 40, maxZoom: 16 });
|
|
@@ -1378,6 +1581,89 @@ export class GeoMap extends HTMLElement {
|
|
|
1378
1581
|
/**
|
|
1379
1582
|
* Point d'entrée de l'orchestrateur bindings. Applique un binding reçu.
|
|
1380
1583
|
*/
|
|
1584
|
+
// V0.3.1 alpha.4 : derive items de legende auto depuis
|
|
1585
|
+
// classification._compiled_expression du layer from_layer.
|
|
1586
|
+
_autoLegendItems(legendCfg, catalogLayers, orderedLayers) {
|
|
1587
|
+
const fromId = legendCfg.from_layer;
|
|
1588
|
+
if (!fromId) return null;
|
|
1589
|
+
const target = orderedLayers.find(o => o.scene && o.scene.id === fromId);
|
|
1590
|
+
if (!target) return null;
|
|
1591
|
+
const classif = (target.override && target.override.classification)
|
|
1592
|
+
|| target.scene.classification;
|
|
1593
|
+
if (!classif) return null;
|
|
1594
|
+
// Support 2 formats : V1.13 paint_expression direct, ou V0.3.1 shimme
|
|
1595
|
+
const expr = classif.paint_expression;
|
|
1596
|
+
if (!Array.isArray(expr) || expr.length < 3) return null;
|
|
1597
|
+
const items = [];
|
|
1598
|
+
if (expr[0] === "step") {
|
|
1599
|
+
// ["step", ["get", field], color0, threshold1, color1, threshold2, color2, ...]
|
|
1600
|
+
const firstColor = expr[2];
|
|
1601
|
+
items.push({ label: "avant " + expr[3], color: firstColor });
|
|
1602
|
+
for (let i = 3; i + 1 < expr.length; i += 2) {
|
|
1603
|
+
const thr = expr[i];
|
|
1604
|
+
const col = expr[i + 1];
|
|
1605
|
+
const next = expr[i + 2];
|
|
1606
|
+
const label = (i + 2 < expr.length)
|
|
1607
|
+
? thr + "-" + next
|
|
1608
|
+
: "≥ " + thr;
|
|
1609
|
+
items.push({ label, color: col });
|
|
1610
|
+
}
|
|
1611
|
+
} else if (expr[0] === "match") {
|
|
1612
|
+
// ["match", ["get", field], v1, c1, v2, c2, ..., fallback]
|
|
1613
|
+
for (let i = 2; i + 1 < expr.length; i += 2) {
|
|
1614
|
+
items.push({ label: String(expr[i]), color: expr[i + 1] });
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return items;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// V0.3.1 alpha.4 : injecte une legende auto-generee dans le shadow DOM.
|
|
1621
|
+
// Utilise le meme rendering que <geo-legend> mais integre inline.
|
|
1622
|
+
_injectLegend(legendCfg, items) {
|
|
1623
|
+
const shadow = this.shadowRoot;
|
|
1624
|
+
if (!shadow) return;
|
|
1625
|
+
const pos = legendCfg.position || "bottom-left";
|
|
1626
|
+
const title = legendCfg.title || "Légende";
|
|
1627
|
+
const wrap = document.createElement("div");
|
|
1628
|
+
wrap.className = "gc-legend";
|
|
1629
|
+
const posStyles = {
|
|
1630
|
+
"top-left": "top:8px;left:8px;",
|
|
1631
|
+
"top-right": "top:8px;right:8px;",
|
|
1632
|
+
"bottom-left": "bottom:8px;left:8px;",
|
|
1633
|
+
"bottom-right": "bottom:44px;right:8px;",
|
|
1634
|
+
};
|
|
1635
|
+
const styleAttr = posStyles[pos] || posStyles["bottom-left"];
|
|
1636
|
+
wrap.setAttribute("style",
|
|
1637
|
+
"position:absolute;" + styleAttr +
|
|
1638
|
+
"background:rgba(255,255,255,0.94);padding:8px 12px;border-radius:4px;" +
|
|
1639
|
+
"box-shadow:0 1px 4px rgba(0,0,0,0.15);font-family:Marianne,system-ui,sans-serif;" +
|
|
1640
|
+
"font-size:11px;color:#333;z-index:5;max-width:260px;"
|
|
1641
|
+
);
|
|
1642
|
+
const t = document.createElement("div");
|
|
1643
|
+
t.setAttribute("style",
|
|
1644
|
+
"color:#000091;text-transform:uppercase;font-size:10px;letter-spacing:0.5px;" +
|
|
1645
|
+
"font-weight:700;margin-bottom:6px;");
|
|
1646
|
+
t.textContent = title;
|
|
1647
|
+
wrap.appendChild(t);
|
|
1648
|
+
for (const it of items) {
|
|
1649
|
+
const chip = document.createElement("div");
|
|
1650
|
+
chip.setAttribute("style",
|
|
1651
|
+
"display:flex;align-items:center;gap:6px;margin:2px 0;");
|
|
1652
|
+
const sw = document.createElement("span");
|
|
1653
|
+
sw.setAttribute("style",
|
|
1654
|
+
"width:14px;height:10px;background:" + it.color + ";" +
|
|
1655
|
+
"border:1px solid rgba(0,0,0,0.15);border-radius:2px;flex-shrink:0;");
|
|
1656
|
+
chip.appendChild(sw);
|
|
1657
|
+
const lbl = document.createElement("span");
|
|
1658
|
+
lbl.textContent = it.label;
|
|
1659
|
+
chip.appendChild(lbl);
|
|
1660
|
+
wrap.appendChild(chip);
|
|
1661
|
+
}
|
|
1662
|
+
// Injecter dans .gc-map pour que la legende suive la carte
|
|
1663
|
+
const mapDiv = shadow.querySelector(".gc-map");
|
|
1664
|
+
if (mapDiv) mapDiv.appendChild(wrap);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1381
1667
|
applyBinding(detail) {
|
|
1382
1668
|
const { prop, value } = detail || {};
|
|
1383
1669
|
if (!this.map || !prop) return;
|
|
@@ -1385,11 +1671,47 @@ export class GeoMap extends HTMLElement {
|
|
|
1385
1671
|
|
|
1386
1672
|
switch (prop) {
|
|
1387
1673
|
case "time": {
|
|
1388
|
-
//
|
|
1674
|
+
// V0.3.1 alpha.4 : si le layer a opacity.reactive prop=time, on
|
|
1675
|
+
// applique un fade progressif (matched/before/after/null_value)
|
|
1676
|
+
// au lieu du filter binaire hide/show V1.13.
|
|
1389
1677
|
const year = _asYear(value);
|
|
1390
|
-
if (year
|
|
1391
|
-
|
|
1392
|
-
|
|
1678
|
+
if (year == null || !primaryLayer) break;
|
|
1679
|
+
const timeField = detail.field || "annee";
|
|
1680
|
+
|
|
1681
|
+
for (const lid of this._layerIds) {
|
|
1682
|
+
const cfg = this._reactiveOpacity[lid];
|
|
1683
|
+
if (cfg && cfg.prop === "time") {
|
|
1684
|
+
// Fade progressif : expression case selon (field vs value)
|
|
1685
|
+
const field = cfg.field || timeField;
|
|
1686
|
+
const matched = cfg.matched ?? 0.85;
|
|
1687
|
+
const before = cfg.before ?? 0.85;
|
|
1688
|
+
const after = cfg.after ?? 0.12;
|
|
1689
|
+
const nullValue = cfg.null_value ?? 0.35;
|
|
1690
|
+
const expr = [
|
|
1691
|
+
"case",
|
|
1692
|
+
["==", ["typeof", ["get", field]], "null"], nullValue,
|
|
1693
|
+
["<", ["to-number", ["get", field]], year], before,
|
|
1694
|
+
[">", ["to-number", ["get", field]], year], after,
|
|
1695
|
+
matched,
|
|
1696
|
+
];
|
|
1697
|
+
const layer = this.map.getLayer(lid);
|
|
1698
|
+
if (!layer) continue;
|
|
1699
|
+
const paintKey =
|
|
1700
|
+
layer.type === "fill" ? "fill-opacity" :
|
|
1701
|
+
layer.type === "line" ? "line-opacity" :
|
|
1702
|
+
layer.type === "circle" ? "circle-opacity" :
|
|
1703
|
+
layer.type === "fill-extrusion" ? "fill-extrusion-opacity" :
|
|
1704
|
+
null;
|
|
1705
|
+
if (paintKey) {
|
|
1706
|
+
try { this.map.setPaintProperty(lid, paintKey, expr); }
|
|
1707
|
+
catch (e) { /* pas critique */ }
|
|
1708
|
+
}
|
|
1709
|
+
} else if (lid === primaryLayer) {
|
|
1710
|
+
// Fallback V1.13 : filter binaire sur le layer primaire
|
|
1711
|
+
try {
|
|
1712
|
+
this.map.setFilter(lid, ["<=", ["get", timeField], year]);
|
|
1713
|
+
} catch (e) { /* ignore */ }
|
|
1714
|
+
}
|
|
1393
1715
|
}
|
|
1394
1716
|
break;
|
|
1395
1717
|
}
|
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.5",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# generated by datamodel-codegen:
|
|
2
2
|
# filename: scene_manifest.schema.json
|
|
3
|
-
# timestamp: 2026-07-
|
|
3
|
+
# timestamp: 2026-07-11T12:39:43+00:00
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -337,6 +337,18 @@ class LayerSource2(BaseModel):
|
|
|
337
337
|
|
|
338
338
|
|
|
339
339
|
class LayerSource3(BaseModel):
|
|
340
|
+
model_config = ConfigDict(
|
|
341
|
+
extra="allow",
|
|
342
|
+
)
|
|
343
|
+
type: Literal["geojson_url"]
|
|
344
|
+
url: AnyUrl = Field(
|
|
345
|
+
...,
|
|
346
|
+
description="URL publique du GeoJSON (S3 public, CDN, endpoint sans auth). Fetche cote client sans credentials.",
|
|
347
|
+
)
|
|
348
|
+
crs: Literal["EPSG:4326"] = "EPSG:4326"
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class LayerSource4(BaseModel):
|
|
340
352
|
model_config = ConfigDict(
|
|
341
353
|
extra="allow",
|
|
342
354
|
)
|
|
@@ -350,7 +362,7 @@ class LayerSource3(BaseModel):
|
|
|
350
362
|
promote_id: str | None = None
|
|
351
363
|
|
|
352
364
|
|
|
353
|
-
class
|
|
365
|
+
class LayerSource5(BaseModel):
|
|
354
366
|
model_config = ConfigDict(
|
|
355
367
|
extra="allow",
|
|
356
368
|
)
|
|
@@ -369,7 +381,7 @@ class Sync(Enum):
|
|
|
369
381
|
save = "save"
|
|
370
382
|
|
|
371
383
|
|
|
372
|
-
class
|
|
384
|
+
class LayerSource6(BaseModel):
|
|
373
385
|
model_config = ConfigDict(
|
|
374
386
|
extra="allow",
|
|
375
387
|
)
|
|
@@ -382,7 +394,7 @@ class LayerSource5(BaseModel):
|
|
|
382
394
|
)
|
|
383
395
|
|
|
384
396
|
|
|
385
|
-
class
|
|
397
|
+
class LayerSource7(BaseModel):
|
|
386
398
|
model_config = ConfigDict(
|
|
387
399
|
extra="allow",
|
|
388
400
|
)
|
|
@@ -391,7 +403,7 @@ class LayerSource6(BaseModel):
|
|
|
391
403
|
type_name: str = Field(..., description="WFS TypeName (ex: BATIMENT.BATIMENT).")
|
|
392
404
|
|
|
393
405
|
|
|
394
|
-
class
|
|
406
|
+
class LayerSource8(BaseModel):
|
|
395
407
|
model_config = ConfigDict(
|
|
396
408
|
extra="allow",
|
|
397
409
|
)
|
|
@@ -405,7 +417,7 @@ class LayerSource7(BaseModel):
|
|
|
405
417
|
)
|
|
406
418
|
|
|
407
419
|
|
|
408
|
-
class
|
|
420
|
+
class LayerSource9(BaseModel):
|
|
409
421
|
model_config = ConfigDict(
|
|
410
422
|
extra="allow",
|
|
411
423
|
)
|
|
@@ -424,9 +436,10 @@ class LayerSource(
|
|
|
424
436
|
| LayerSource6
|
|
425
437
|
| LayerSource7
|
|
426
438
|
| LayerSource8
|
|
439
|
+
| LayerSource9
|
|
427
440
|
]
|
|
428
441
|
):
|
|
429
|
-
root: LayerSource1 | LayerSource2 | LayerSource3 | LayerSource4 | LayerSource5 | LayerSource6 | LayerSource7 | LayerSource8 = Field(
|
|
442
|
+
root: LayerSource1 | LayerSource2 | LayerSource3 | LayerSource4 | LayerSource5 | LayerSource6 | LayerSource7 | LayerSource8 | LayerSource9 = Field(
|
|
430
443
|
...,
|
|
431
444
|
description="Source de données. Discriminated union sur 'type'. Alignée MapLibre style spec (v8+).",
|
|
432
445
|
)
|
|
@@ -912,7 +925,7 @@ class Layer(BaseModel):
|
|
|
912
925
|
extensions: dict[constr(pattern=r"^[a-z][a-z_0-9]*$"), dict[str, Any]] | None = None
|
|
913
926
|
|
|
914
927
|
|
|
915
|
-
class
|
|
928
|
+
class ScenemanifestV032(BaseModel):
|
|
916
929
|
model_config = ConfigDict(
|
|
917
930
|
extra="forbid",
|
|
918
931
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://nic01asfr.github.io/Passerelle/schemas/scene_manifest/0.3.
|
|
4
|
-
"title": "SceneManifest V0.3.
|
|
3
|
+
"$id": "https://nic01asfr.github.io/Passerelle/schemas/scene_manifest/0.3.2.json",
|
|
4
|
+
"title": "SceneManifest V0.3.2",
|
|
5
5
|
"description": "Contract unifie passerelle-geo-components pivot cross-projet (workspace SIG + editeur livrables + widget carto). Voir CONTRACT-V0.3.1.md pour la spec humaine complete.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
@@ -442,6 +442,16 @@
|
|
|
442
442
|
},
|
|
443
443
|
"additionalProperties": true
|
|
444
444
|
},
|
|
445
|
+
{
|
|
446
|
+
"type": "object",
|
|
447
|
+
"required": ["type", "url"],
|
|
448
|
+
"properties": {
|
|
449
|
+
"type": { "const": "geojson_url" },
|
|
450
|
+
"url": { "type": "string", "format": "uri", "description": "URL publique du GeoJSON (S3 public, CDN, endpoint sans auth). Fetche cote client sans credentials." },
|
|
451
|
+
"crs": { "type": "string", "const": "EPSG:4326", "default": "EPSG:4326" }
|
|
452
|
+
},
|
|
453
|
+
"additionalProperties": true
|
|
454
|
+
},
|
|
445
455
|
{
|
|
446
456
|
"type": "object",
|
|
447
457
|
"required": ["type", "url", "source_layer"],
|