@angular-helpers/openlayers 22.0.1 → 22.1.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.
@@ -341,17 +341,29 @@ class OlGeometryService {
341
341
  if (segments < 8) {
342
342
  throw new RangeError('segments must be >= 8');
343
343
  }
344
- const cosR = Math.cos(rotation);
345
- const sinR = Math.sin(rotation);
344
+ const rRadius = 6371008.8; // Standard Earth radius used by OpenLayers
345
+ const alpha = semiMajor / rRadius;
346
+ const beta = semiMinor / rRadius;
347
+ // Failsafe for strategic-scale inputs exceeding spherical boundaries (quarter of Earth)
348
+ if (alpha >= Math.PI / 2 || beta >= Math.PI / 2) {
349
+ throw new RangeError('semiMajor and semiMinor must be less than quarter of Earth circumference');
350
+ }
351
+ const tanAlpha = Math.tan(alpha);
352
+ const tanBeta = Math.tan(beta);
353
+ const tanAlphaSq = tanAlpha * tanAlpha;
354
+ const tanBetaSq = tanBeta * tanBeta;
346
355
  const ring = [];
347
356
  for (let i = 0; i < segments; i++) {
348
357
  const theta = (i / segments) * Math.PI * 2;
349
- // Ellipse in local axis-aligned frame, then rotated by `rotation`.
350
- const ax = Math.cos(theta) * semiMajor;
351
- const ay = Math.sin(theta) * semiMinor;
352
- const dx = ax * cosR - ay * sinR;
353
- const dy = ax * sinR + ay * cosR;
354
- ring.push(this.offsetMetersToLonLat(center, dx, dy));
358
+ const cosT = Math.cos(theta);
359
+ // Spherical ellipse polar equation:
360
+ // tan^2(rho) = tan^2(beta) / (1 - cos^2(theta) * (1 - tan^2(beta) / tan^2(alpha)))
361
+ const denom = 1 - cosT * cosT * (1 - tanBetaSq / tanAlphaSq);
362
+ const tanSqRho = tanBetaSq / denom;
363
+ const rho = Math.atan(Math.sqrt(tanSqRho));
364
+ const distance = rho * rRadius;
365
+ const bearing = Math.PI / 2 - (rotation + theta);
366
+ ring.push(offset(center, distance, bearing));
355
367
  }
356
368
  ring.push(ring[0]); // close the ring
357
369
  return {
@@ -382,9 +394,8 @@ class OlGeometryService {
382
394
  const span = endAngle - startAngle;
383
395
  for (let i = 0; i <= segments; i++) {
384
396
  const theta = startAngle + (i / segments) * span;
385
- const dx = Math.cos(theta) * radius;
386
- const dy = Math.sin(theta) * radius;
387
- ring.push(this.offsetMetersToLonLat(center, dx, dy));
397
+ const bearing = Math.PI / 2 - theta;
398
+ ring.push(offset(center, radius, bearing));
388
399
  }
389
400
  ring.push(center); // close back to apex
390
401
  return {
@@ -413,13 +424,9 @@ class OlGeometryService {
413
424
  const inner = [];
414
425
  for (let i = 0; i < segments; i++) {
415
426
  const theta = (i / segments) * Math.PI * 2;
416
- const cosT = Math.cos(theta);
417
- const sinT = Math.sin(theta);
418
- // Outer ring: CCW (theta increasing)
419
- outer.push(this.offsetMetersToLonLat(center, cosT * outerRadius, sinT * outerRadius));
420
- // Inner ring: CW — sample the SAME thetas but we'll reverse the
421
- // accumulator below so the ring is traversed in the opposite sense.
422
- inner.push(this.offsetMetersToLonLat(center, cosT * innerRadius, sinT * innerRadius));
427
+ const bearing = Math.PI / 2 - theta;
428
+ outer.push(offset(center, outerRadius, bearing));
429
+ inner.push(offset(center, innerRadius, bearing));
423
430
  }
424
431
  inner.reverse();
425
432
  outer.push(outer[0]);
@@ -1279,6 +1279,9 @@ class OlWebGLTileLayerComponent {
1279
1279
  /** Preload low-res tiles up to this many zoom levels */
1280
1280
  preload = input(0, /* @ts-ignore */
1281
1281
  ...(ngDevMode ? [{ debugName: "preload" }] : /* istanbul ignore next */ []));
1282
+ /** Style variables for dynamic expressions (e.g. `['var', 'brightness']`) */
1283
+ variables = input(/* @ts-ignore */
1284
+ ...(ngDevMode ? [undefined, { debugName: "variables" }] : /* istanbul ignore next */ []));
1282
1285
  layer = null;
1283
1286
  constructor() {
1284
1287
  afterNextRender(() => {
@@ -1299,6 +1302,7 @@ class OlWebGLTileLayerComponent {
1299
1302
  opacity: this.opacity(),
1300
1303
  zIndex: this.zIndex(),
1301
1304
  style: this.tileStyle() || {},
1305
+ ...(this.variables() ? { variables: this.variables() } : {}),
1302
1306
  });
1303
1307
  break;
1304
1308
  case 'xyz':
@@ -1313,6 +1317,7 @@ class OlWebGLTileLayerComponent {
1313
1317
  zIndex: this.zIndex(),
1314
1318
  preload: this.preload(),
1315
1319
  ...(this.tileStyle() ? { style: this.tileStyle() } : {}),
1320
+ ...(this.variables() ? { variables: this.variables() } : {}),
1316
1321
  });
1317
1322
  break;
1318
1323
  case 'osm':
@@ -1327,6 +1332,7 @@ class OlWebGLTileLayerComponent {
1327
1332
  zIndex: this.zIndex(),
1328
1333
  preload: this.preload(),
1329
1334
  ...(this.tileStyle() ? { style: this.tileStyle() } : {}),
1335
+ ...(this.variables() ? { variables: this.variables() } : {}),
1330
1336
  });
1331
1337
  break;
1332
1338
  }
@@ -1355,6 +1361,12 @@ class OlWebGLTileLayerComponent {
1355
1361
  }
1356
1362
  }
1357
1363
  });
1364
+ effect(() => {
1365
+ const vars = this.variables();
1366
+ if (vars && this.layer) {
1367
+ this.layer.updateStyleVariables(vars);
1368
+ }
1369
+ });
1358
1370
  // WebGL tile layers also need manual dispose
1359
1371
  this.destroyRef.onDestroy(() => {
1360
1372
  const map = this.mapService.getMap();
@@ -1364,8 +1376,17 @@ class OlWebGLTileLayerComponent {
1364
1376
  }
1365
1377
  });
1366
1378
  }
1379
+ /**
1380
+ * Imperatively update style variables without triggering Angular change detection.
1381
+ * Ideal for 60FPS animations where you don't want to use the declarative [variables] input.
1382
+ */
1383
+ updateVariables(vars) {
1384
+ if (this.layer) {
1385
+ this.layer.updateStyleVariables(vars);
1386
+ }
1387
+ }
1367
1388
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: OlWebGLTileLayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1368
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.0", type: OlWebGLTileLayerComponent, isStandalone: true, selector: "ol-webgl-tile-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, attributions: { classPropertyName: "attributions", publicName: "attributions", isSignal: true, isRequired: false, transformFunction: null }, tileStyle: { classPropertyName: "tileStyle", publicName: "tileStyle", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
1389
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.0", type: OlWebGLTileLayerComponent, isStandalone: true, selector: "ol-webgl-tile-layer", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: false, transformFunction: null }, attributions: { classPropertyName: "attributions", publicName: "attributions", isSignal: true, isRequired: false, transformFunction: null }, tileStyle: { classPropertyName: "tileStyle", publicName: "tileStyle", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null }, variables: { classPropertyName: "variables", publicName: "variables", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
1369
1390
  }
1370
1391
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: OlWebGLTileLayerComponent, decorators: [{
1371
1392
  type: Component,
@@ -1373,7 +1394,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImpor
1373
1394
  selector: 'ol-webgl-tile-layer',
1374
1395
  template: '',
1375
1396
  }]
1376
- }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: true }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], attributions: [{ type: i0.Input, args: [{ isSignal: true, alias: "attributions", required: false }] }], tileStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "tileStyle", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }] } });
1397
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: true }] }], url: [{ type: i0.Input, args: [{ isSignal: true, alias: "url", required: false }] }], attributions: [{ type: i0.Input, args: [{ isSignal: true, alias: "attributions", required: false }] }], tileStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "tileStyle", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }], variables: [{ type: i0.Input, args: [{ isSignal: true, alias: "variables", required: false }] }] } });
1377
1398
 
1378
1399
  // Provider functions
1379
1400
  function withLayers() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-helpers/openlayers",
3
- "version": "22.0.1",
3
+ "version": "22.1.0",
4
4
  "description": "Modern Angular wrapper for OpenLayers with modular architecture, standalone components, and hybrid template/programmatic API",
5
5
  "homepage": "https://gaspar1992.github.io/angular-helpers/docs/openlayers",
6
6
  "repository": {
@@ -241,10 +241,17 @@ declare class OlWebGLTileLayerComponent {
241
241
  visible: _angular_core.InputSignal<boolean>;
242
242
  /** Preload low-res tiles up to this many zoom levels */
243
243
  preload: _angular_core.InputSignal<number>;
244
+ /** Style variables for dynamic expressions (e.g. `['var', 'brightness']`) */
245
+ variables: _angular_core.InputSignal<Record<string, string | number | boolean | number[]> | undefined>;
244
246
  private layer;
245
247
  constructor();
248
+ /**
249
+ * Imperatively update style variables without triggering Angular change detection.
250
+ * Ideal for 60FPS animations where you don't want to use the declarative [variables] input.
251
+ */
252
+ updateVariables(vars: Record<string, string | number | boolean | number[]>): void;
246
253
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlWebGLTileLayerComponent, never>;
247
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlWebGLTileLayerComponent, "ol-webgl-tile-layer", never, { "id": { "alias": "id"; "required": true; "isSignal": true; }; "source": { "alias": "source"; "required": true; "isSignal": true; }; "url": { "alias": "url"; "required": false; "isSignal": true; }; "attributions": { "alias": "attributions"; "required": false; "isSignal": true; }; "tileStyle": { "alias": "tileStyle"; "required": false; "isSignal": true; }; "zIndex": { "alias": "zIndex"; "required": false; "isSignal": true; }; "opacity": { "alias": "opacity"; "required": false; "isSignal": true; }; "visible": { "alias": "visible"; "required": false; "isSignal": true; }; "preload": { "alias": "preload"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
254
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlWebGLTileLayerComponent, "ol-webgl-tile-layer", never, { "id": { "alias": "id"; "required": true; "isSignal": true; }; "source": { "alias": "source"; "required": true; "isSignal": true; }; "url": { "alias": "url"; "required": false; "isSignal": true; }; "attributions": { "alias": "attributions"; "required": false; "isSignal": true; }; "tileStyle": { "alias": "tileStyle"; "required": false; "isSignal": true; }; "zIndex": { "alias": "zIndex"; "required": false; "isSignal": true; }; "opacity": { "alias": "opacity"; "required": false; "isSignal": true; }; "visible": { "alias": "visible"; "required": false; "isSignal": true; }; "preload": { "alias": "preload"; "required": false; "isSignal": true; }; "variables": { "alias": "variables"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
248
255
  }
249
256
 
250
257
  interface LayerInfo {