@orbat-mapper/tactical-draw-adapter-maplibre 0.6.0 → 0.7.0

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
@@ -39,7 +39,11 @@ declare class PinResources {
39
39
  private invalidateDepth;
40
40
  private restore;
41
41
  constructor(map: Map$1);
42
- setRoster(pins: readonly PinRender[]): void;
42
+ /**
43
+ * `pixelRatio` is the device pixels per CSS pixel the atlas is rasterized
44
+ * at; it is part of the roster key, so a change rebuilds the atlas.
45
+ */
46
+ setRoster(pins: readonly PinRender[], pixelRatio?: number): void;
43
47
  load(gl: WebGL2RenderingContext): Promise<PinResourceSnapshot | undefined>;
44
48
  dispose(): void;
45
49
  }
@@ -162,6 +166,8 @@ interface MapLibreLayerHandle {
162
166
  * skip calling it at all rather than pay for that every drag frame.
163
167
  */
164
168
  lastPinBatch?: readonly PinRender[];
169
+ /** The pixel ratio passed alongside `lastPinBatch`. */
170
+ lastPinPixelRatio?: number;
165
171
  }
166
172
  type PaintChannel = "fill" | "fill-pattern" | "line" | "circle" | "icon" | "text";
167
173
  interface MapLibreGroupBundle {
@@ -228,6 +234,13 @@ interface MapLibreAdapterOptions {
228
234
  * one draping pass per graphic. @default true
229
235
  */
230
236
  terrainBanding?: boolean;
237
+ /**
238
+ * Device pixels per CSS pixel at which pin symbols are rasterized into their
239
+ * texture atlas. Defaults to the map's pixel ratio capped at 2, which bounds
240
+ * texture memory on dense screens. Set it (e.g. to the map's `pixelRatio`)
241
+ * for offscreen renders above 2× so pins stay as sharp as the output.
242
+ */
243
+ pinPixelRatio?: number;
231
244
  }
232
245
  declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLibreEventToken, MapLibreViewToken> {
233
246
  private map;
@@ -236,6 +249,7 @@ declare class MapLibreAdapter extends BaseMapAdapter<MapLibreLayerHandle, MapLib
236
249
  readonly scalesGroundPointSymbolsNatively = true;
237
250
  private terrainActive;
238
251
  private terrainBanding;
252
+ private readonly pinPixelRatio?;
239
253
  private readonly handleTerrainChange;
240
254
  constructor(map: maplibregl.Map, options?: MapLibreAdapterOptions);
241
255
  /** Switches `terrainBanding` at runtime, re-banding every group if the effective state changes. */
package/dist/index.mjs CHANGED
@@ -81,11 +81,15 @@ var PinResources = class {
81
81
  map.on("webglcontextrestored", this.restore);
82
82
  map.on("move", this.invalidateDepth);
83
83
  }
84
- setRoster(pins) {
84
+ /**
85
+ * `pixelRatio` is the device pixels per CSS pixel the atlas is rasterized
86
+ * at; it is part of the roster key, so a change rebuilds the atlas.
87
+ */
88
+ setRoster(pins, pixelRatio = 1) {
85
89
  this.needsDepth = pins.some((pin) => pin.presentation.occlusion === "terrain");
86
90
  const resources = /* @__PURE__ */ new Map();
87
91
  for (const pin of pins) {
88
- const scale = pin.presentation.sizePixels / pin.octagonSize * Math.min(devicePixelRatio, 2);
92
+ const scale = pin.presentation.sizePixels / pin.octagonSize * pixelRatio;
89
93
  if (scale > (resources.get(pin.resource.id)?.scale ?? 0)) resources.set(pin.resource.id, {
90
94
  pin,
91
95
  scale
@@ -239,14 +243,15 @@ void main() {
239
243
  else if(kind < 1.5) {
240
244
  vec2 delta = tp-gp; float len = length(delta);
241
245
  vec2 dir = len > .0001 ? delta/len : vec2(0,1);
242
- vec2 p = mix(gp,tp,c.y) + vec2(-dir.y,dir.x) * (c.x-.5)*a_shape.x;
246
+ // Widen by 1px so the fragment edge fade has room to run.
247
+ vec2 p = mix(gp,tp,c.y) + vec2(-dir.y,dir.x) * (c.x-.5)*(a_shape.x+1.);
243
248
  clip = vec4(p/u_view*2., mix(ground.z/ground.w,target.z/target.w,c.y), 1.);
244
249
  offset = vec2(0);
245
250
  } else { offset = (c - vec2(.5,0)) * a_shape.xy + a_shape.zw; }
246
251
  clip.xy += offset * 2. / u_view * clip.w;
247
252
  if (ground.w <= 0. || target.w <= 0.) clip = vec4(2,2,2,1);
248
253
  gl_Position=clip;
249
- v_local=(c-.5)*a_shape.xy;
254
+ v_local=(c-.5)*(a_shape.xy + (kind > .5 && kind < 1.5 ? vec2(1,0) : vec2(0)));
250
255
  v_uv=mix(a_uv.xy,a_uv.zw,vec2(c.x,1.-c.y));
251
256
  v_bounds=a_uv; v_color=a_color; v_shape=a_shape; v_flags=a_flags; v_radius=a_target.w;
252
257
  }`;
@@ -288,6 +293,10 @@ void main() {
288
293
  if(kind < .5) {
289
294
  float edge=length(v_local)-v_shape.x*.5;
290
295
  paint.a *= 1.-smoothstep(-1.,0.,edge);
296
+ } else if(kind < 1.5) {
297
+ // World-mode stems lean with perspective; fade the long edges over one device pixel.
298
+ float aa=max(fwidth(v_local.x),1e-4);
299
+ paint.a *= clamp((v_shape.x*.5-abs(v_local.x))/aa+.5,0.,1.);
291
300
  } else if(kind > 4.5) {
292
301
  // One optional halo instance; reuse the canonical atlas, including text and HQ staff.
293
302
  vec2 size = v_shape.xy - vec2(2. * v_radius);
@@ -1948,6 +1957,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1948
1957
  scalesGroundPointSymbolsNatively = true;
1949
1958
  terrainActive = false;
1950
1959
  terrainBanding;
1960
+ pinPixelRatio;
1951
1961
  handleTerrainChange = () => {
1952
1962
  const active = this.terrainBanding && !!this.map.getTerrain?.();
1953
1963
  if (active === this.terrainActive) return;
@@ -1959,6 +1969,7 @@ var MapLibreAdapter = class extends BaseMapAdapter {
1959
1969
  this.map = map;
1960
1970
  this.viewChangeMode = options.viewChangeMode ?? "continuous";
1961
1971
  this.terrainBanding = options.terrainBanding ?? true;
1972
+ this.pinPixelRatio = options.pinPixelRatio;
1962
1973
  this.handleTerrainChange();
1963
1974
  this.map.on("terrain", this.handleTerrainChange);
1964
1975
  }
@@ -2098,10 +2109,12 @@ var MapLibreAdapter = class extends BaseMapAdapter {
2098
2109
  const allPins = groups.flatMap((group) => group.pinBatch ?? []);
2099
2110
  if (allPins.length) {
2100
2111
  handle.pinResources ??= new PinResources(this.map);
2112
+ const pixelRatio = this.pinPixelRatio ?? Math.min(this.map.getPixelRatio(), 2);
2101
2113
  const previousBatch = handle.lastPinBatch;
2102
- if (!(previousBatch !== void 0 && previousBatch.length === allPins.length && allPins.every((pin, index) => pin === previousBatch[index]))) {
2103
- handle.pinResources.setRoster(allPins);
2114
+ if (!(previousBatch !== void 0 && handle.lastPinPixelRatio === pixelRatio && previousBatch.length === allPins.length && allPins.every((pin, index) => pin === previousBatch[index]))) {
2115
+ handle.pinResources.setRoster(allPins, pixelRatio);
2104
2116
  handle.lastPinBatch = allPins;
2117
+ handle.lastPinPixelRatio = pixelRatio;
2105
2118
  }
2106
2119
  }
2107
2120
  const desiredGroupIds = new Set(groups.map((group) => group.id));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbat-mapper/tactical-draw-adapter-maplibre",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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",