passerelle-geo-components 0.2.0-alpha.4 → 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,6 +243,7 @@ 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,
@@ -278,6 +282,108 @@ function _v031ToLegacyShim(params, hostContext) {
278
282
  return { params: shimParams, hostContext: shimHost };
279
283
  }
280
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
+
281
387
  // V0.3.1 classification : {color: {mode, field, method, breaks, palette,
282
388
  // _compiled_expression}, size, label} -> {paint_expression, method, field, ...}
283
389
  // attendus par _paintForClassification (V1.13).
@@ -1174,6 +1280,40 @@ export class GeoMap extends HTMLElement {
1174
1280
  data: sceneLayer.geojson || { type: "FeatureCollection", features: [] },
1175
1281
  });
1176
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
+
1177
1317
  // V0.2.0 Chantier 4 : détection type + auto-split polygon+point
1178
1318
  // Analyse la geojson pour détecter FC hétérogène et éviter le bug
1179
1319
  // POINT invisibles (remontée cerema-livrables).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passerelle-geo-components",
3
- "version": "0.2.0-alpha.4",
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-10T13:35:15+00:00
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 LayerSource4(BaseModel):
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 LayerSource5(BaseModel):
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 LayerSource6(BaseModel):
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 LayerSource7(BaseModel):
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 LayerSource8(BaseModel):
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 ScenemanifestV031(BaseModel):
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.1.json",
4
- "title": "SceneManifest V0.3.1",
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"],