@orbat-mapper/tactical-draw-adapter-maplibre 0.2.0-alpha.12 → 0.2.0-alpha.13

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/dist/index.d.mts CHANGED
@@ -36,6 +36,23 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
36
36
  private updateSourceData;
37
37
  private flushDeferredLayerRemoval;
38
38
  deferLayerRemovalUntilLayerUpdate(layerId: LayerId, updatedLayerId: LayerId): void;
39
+ /**
40
+ * Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
41
+ * and picks can key on the caller's feature id) and, for a hatch-patterned
42
+ * Polygon, `_fillPatternImageId` (registering the tile image on demand). At
43
+ * most one object spread — a feature needing neither passes through
44
+ * untouched.
45
+ */
46
+ private promoteFeature;
47
+ /**
48
+ * Resolve a hatch-patterned Polygon's `style.fillPattern`/`fillColor` to its
49
+ * hatch-tile image id, registering the image on demand. Returns `undefined`
50
+ * for features without a hatch pattern, so `fillPatternImageId` stays the
51
+ * single source of truth for the id baked into `_fillPatternImageId`.
52
+ */
53
+ private resolveFillPatternImage;
54
+ /** Register a hatch-tile image once per (color, pattern) pair, keyed by `fillPatternImageId`. */
55
+ private ensureFillPatternImage;
39
56
  protected applyIconResources(resources: readonly IconResource[]): void;
40
57
  protected removeIconResource(id: string): void;
41
58
  protected attachNativeEvent(type: "click" | "pointermove" | "dblclick", onEvent: (lonLat: Position, pixel: PixelCoordinate, originalEvent: unknown) => void): NativeEventEntry<MapLibreEventToken>;
package/dist/index.mjs CHANGED
@@ -1,6 +1,11 @@
1
1
  import { project, unproject } from "@orbat-mapper/control-measures";
2
- import { BaseMapAdapter, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, PointerCallbackHub, TOUCH_HIT_TOLERANCE_PX, bindAbortable, nearestWorldCopyLng, pickHitTolerance, tryControlMeasureIdFromFeature } from "@orbat-mapper/tactical-draw";
2
+ import { BaseMapAdapter, DEFAULT_TEXT_SIZE_MAX_PIXELS, DEFAULT_TEXT_SIZE_MIN_PIXELS, HATCH_PATTERNS, PointerCallbackHub, TOUCH_HIT_TOLERANCE_PX, bindAbortable, fillPatternImageId, makeFillPatternImage, nearestWorldCopyLng, pickHitTolerance, tryControlMeasureIdFromFeature } from "@orbat-mapper/tactical-draw";
3
3
  //#region src/ml-adapter.ts
4
+ /** Feature property key for the resolved hatch-pattern image id (see `promoteFeature`). */
5
+ const FILL_PATTERN_IMAGE_ID_PROPERTY = "_fillPatternImageId";
6
+ function isHatchPattern(value) {
7
+ return HATCH_PATTERNS.includes(value);
8
+ }
4
9
  function isPromiseLike(value) {
5
10
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
6
11
  }
@@ -309,6 +314,60 @@ function buildFillPaint(style) {
309
314
  "fill-opacity": 1
310
315
  };
311
316
  }
317
+ /**
318
+ * `properties.style.fillPattern` read as a GL expression, defaulting to
319
+ * `"solid"`. `fillPattern` is a per-feature-only field (ADR-0007) — unlike
320
+ * `fillColor`/`strokeColor` it has no layer-level `StyleOptions` default, so
321
+ * this expression takes no `style` argument.
322
+ */
323
+ function fillPatternField() {
324
+ return [
325
+ "coalesce",
326
+ [
327
+ "get",
328
+ "fillPattern",
329
+ [
330
+ "coalesce",
331
+ ["get", "style"],
332
+ ["literal", {}]
333
+ ]
334
+ ],
335
+ "solid"
336
+ ];
337
+ }
338
+ /**
339
+ * Filter for one of the two mutually-exclusive Polygon fill layers, keyed on
340
+ * whether the feature's resolved `fillPattern` is a hatch pattern. The solid
341
+ * `-fill` layer takes `hatched: false` (every Polygon except hatched ones);
342
+ * the `-fill-pattern` layer takes `hatched: true`. Split because MapLibre's
343
+ * `fill-color` and `fill-pattern` paints cannot coexist on one fill layer.
344
+ */
345
+ function buildFillFilter(hatched) {
346
+ const isHatched = [
347
+ "in",
348
+ fillPatternField(),
349
+ ["literal", [...HATCH_PATTERNS]]
350
+ ];
351
+ return [
352
+ "all",
353
+ [
354
+ "==",
355
+ ["geometry-type"],
356
+ "Polygon"
357
+ ],
358
+ hatched ? isHatched : ["!", isHatched]
359
+ ];
360
+ }
361
+ /**
362
+ * Paint for the hatch-pattern fill layer. The image id is baked per-feature
363
+ * into `properties._fillPatternImageId` (see `promoteFeature`), so the paint
364
+ * is a plain `["get", ...]` rather than re-deriving the id as a GL expression,
365
+ * keeping `fillPatternImageId` the single source of truth for the id string.
366
+ */
367
+ const FILL_PATTERN_PAINT = {
368
+ "fill-pattern": ["get", FILL_PATTERN_IMAGE_ID_PROPERTY],
369
+ "fill-opacity": 1
370
+ };
312
371
  function buildLinePaint(style) {
313
372
  const colorDefault = style?.strokeColor ?? "#000";
314
373
  const widthDefault = style?.strokeWidth ?? 2;
@@ -367,14 +426,19 @@ var MapLibreAdapter = class extends BaseMapAdapter {
367
426
  id: fillId,
368
427
  type: "fill",
369
428
  source: sourceId,
370
- filter: [
371
- "==",
372
- "$type",
373
- "Polygon"
374
- ],
429
+ filter: buildFillFilter(false),
375
430
  paint: buildFillPaint(style)
376
431
  });
377
432
  mlLayerIds.push(fillId);
433
+ const fillPatternId = `${layerId}-fill-pattern`;
434
+ this.map.addLayer({
435
+ id: fillPatternId,
436
+ type: "fill",
437
+ source: sourceId,
438
+ filter: buildFillFilter(true),
439
+ paint: FILL_PATTERN_PAINT
440
+ });
441
+ mlLayerIds.push(fillPatternId);
378
442
  const lineId = `${layerId}-line`;
379
443
  this.map.addLayer({
380
444
  id: lineId,
@@ -449,13 +513,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
449
513
  if (!handle) return;
450
514
  const source = this.map.getSource(handle.sourceId);
451
515
  if (!source) return;
452
- const promotedFeatures = features.map((f) => f.id !== void 0 ? {
453
- ...f,
454
- properties: {
455
- ...f.properties ?? {},
456
- _fid: f.id
457
- }
458
- } : f);
516
+ const promotedFeatures = features.map((f) => this.promoteFeature(f));
459
517
  let resolveSourceData;
460
518
  const sourceDataCompletion = new Promise((resolve) => {
461
519
  resolveSourceData = resolve;
@@ -492,6 +550,48 @@ var MapLibreAdapter = class extends BaseMapAdapter {
492
550
  if (!pending || !this.layers.has(layerId)) return;
493
551
  this.deferredLayerRemovals.set(layerId, { after: pending.completion });
494
552
  }
553
+ /**
554
+ * Promote a source feature for MapLibre: stamp `_fid` (so data-driven paint
555
+ * and picks can key on the caller's feature id) and, for a hatch-patterned
556
+ * Polygon, `_fillPatternImageId` (registering the tile image on demand). At
557
+ * most one object spread — a feature needing neither passes through
558
+ * untouched.
559
+ */
560
+ promoteFeature(feature) {
561
+ const extra = {};
562
+ if (feature.id !== void 0) extra._fid = feature.id;
563
+ const imageId = this.resolveFillPatternImage(feature);
564
+ if (imageId !== void 0) extra[FILL_PATTERN_IMAGE_ID_PROPERTY] = imageId;
565
+ if (Object.keys(extra).length === 0) return feature;
566
+ return {
567
+ ...feature,
568
+ properties: {
569
+ ...feature.properties ?? {},
570
+ ...extra
571
+ }
572
+ };
573
+ }
574
+ /**
575
+ * Resolve a hatch-patterned Polygon's `style.fillPattern`/`fillColor` to its
576
+ * hatch-tile image id, registering the image on demand. Returns `undefined`
577
+ * for features without a hatch pattern, so `fillPatternImageId` stays the
578
+ * single source of truth for the id baked into `_fillPatternImageId`.
579
+ */
580
+ resolveFillPatternImage(feature) {
581
+ const props = feature.properties;
582
+ const pattern = props?.style?.fillPattern;
583
+ if (!isHatchPattern(pattern)) return void 0;
584
+ const color = props?.fillColor ?? props?.style?.fillColor ?? "rgba(0,0,0,0)";
585
+ const imageId = fillPatternImageId(color, pattern);
586
+ this.ensureFillPatternImage(imageId, color, pattern);
587
+ return imageId;
588
+ }
589
+ /** Register a hatch-tile image once per (color, pattern) pair, keyed by `fillPatternImageId`. */
590
+ ensureFillPatternImage(imageId, color, pattern) {
591
+ if (this.map.hasImage(imageId)) return;
592
+ const image = makeFillPatternImage(color, pattern, 2);
593
+ this.map.addImage(imageId, image, { pixelRatio: 2 });
594
+ }
495
595
  applyIconResources(resources) {
496
596
  if (typeof Image === "undefined") return;
497
597
  for (const resource of resources) {
@@ -664,8 +764,10 @@ var MapLibreAdapter = class extends BaseMapAdapter {
664
764
  const layer = this.map.getLayer(mlId);
665
765
  if (!layer) continue;
666
766
  const type = layer.type;
667
- if (type === "fill") this.map.setPaintProperty(mlId, "fill-color", buildFillPaint(style)["fill-color"]);
668
- else if (type === "line") {
767
+ if (type === "fill") {
768
+ if (mlId.endsWith("-fill-pattern")) continue;
769
+ this.map.setPaintProperty(mlId, "fill-color", buildFillPaint(style)["fill-color"]);
770
+ } else if (type === "line") {
669
771
  if (style.strokeColor !== void 0 || style.strokeWidth !== void 0) {
670
772
  const paint = buildLinePaint(style);
671
773
  if (style.strokeColor !== void 0) this.map.setPaintProperty(mlId, "line-color", paint["line-color"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw-adapter-maplibre",
3
- "version": "0.2.0-alpha.12",
3
+ "version": "0.2.0-alpha.13",
4
4
  "description": "MapLibre GL JS engine adapter for @orbat-mapper/tactical-draw. Draw and edit tactical control measures (MIL-STD-2525 / APP-6) on a MapLibre map.",
5
5
  "license": "MIT",
6
6
  "author": "Orbat Mapper",
@@ -37,8 +37,8 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@orbat-mapper/tactical-draw": "^0.2.0-alpha.27",
41
- "@orbat-mapper/control-measures": "^0.2.0-alpha.16"
40
+ "@orbat-mapper/control-measures": "^0.2.0-alpha.20",
41
+ "@orbat-mapper/tactical-draw": "^0.2.0-alpha.28"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "maplibre-gl": ">=4.0.0 <6.0.0"