@expofp/offline 3.7.16 → 3.8.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.
@@ -1,3 +1,4 @@
1
+ import { Box, projectLocalToGps } from '@expofp/geometry';
1
2
  import { resolve } from '@expofp/resolve';
2
3
  import debug from 'debug';
3
4
  import { execScriptInSandbox } from './exec-script-in-sandbox.js';
@@ -103,12 +104,16 @@ async function derivePaddedVenueBbox(manifest, fpSvgContext, signal) {
103
104
  throw new Error('Offline map generation requires fpGeo.properties.config.');
104
105
  }
105
106
  const svgArea = deriveSvgArea(ctx);
107
+ const [anchorA, anchorB] = geoAnchors(config);
106
108
  const corners = [
107
- [svgArea.x1, svgArea.y1],
108
- [svgArea.x2, svgArea.y1],
109
- [svgArea.x2, svgArea.y2],
110
- [svgArea.x1, svgArea.y2],
111
- ].map(([x, y]) => convertLocalToGps(x, y, config));
109
+ [svgArea.min.x, svgArea.min.y],
110
+ [svgArea.max.x, svgArea.min.y],
111
+ [svgArea.max.x, svgArea.max.y],
112
+ [svgArea.min.x, svgArea.max.y],
113
+ ].map(([x, y]) => {
114
+ const { lat, lng } = projectLocalToGps({ x, y }, anchorA, anchorB);
115
+ return [lng, lat];
116
+ });
112
117
  if (!corners.every(([lng, lat]) => Number.isFinite(lng) && Number.isFinite(lat))) {
113
118
  throw new Error('Offline map generation produced invalid venue GPS bounds.');
114
119
  }
@@ -134,7 +139,7 @@ async function resolveFpSvgContext(manifest, signal) {
134
139
  function deriveSvgArea(ctx) {
135
140
  const viewboxObj = ctx.__viewbox;
136
141
  if (viewboxObj) {
137
- return fromXywh(viewboxObj.x, viewboxObj.y, viewboxObj.width, viewboxObj.height);
142
+ return new Box(viewboxObj.x, viewboxObj.y, viewboxObj.width, viewboxObj.height);
138
143
  }
139
144
  const fpSvg = ctx.__fp;
140
145
  if (typeof fpSvg !== 'string') {
@@ -147,7 +152,10 @@ function deriveSvgArea(ctx) {
147
152
  if (!svgViewBox) {
148
153
  throw new Error('Offline map generation could not derive the SVG viewBox.');
149
154
  }
150
- return withPadding(svgViewBox, -width(svgViewBox) * 0.05, -height(svgViewBox) * 0.05);
155
+ // Inflate the viewBox by 10% about its centre (a 5% margin per side), matching
156
+ // the legacy withPadding(svgViewBox, -w*0.05, -h*0.05): a NEGATIVE padding GREW
157
+ // the extent per side.
158
+ return svgViewBox.scale(1.1);
151
159
  }
152
160
  function getSvgViewBox(svg) {
153
161
  const match = svg.match(/\bviewBox\s*=\s*["']([^"']+)["']/i);
@@ -159,7 +167,7 @@ function getSvgViewBox(svg) {
159
167
  .map(Number);
160
168
  if (![x, y, w, h].every(Number.isFinite))
161
169
  return null;
162
- return fromXywh(x, y, w, h);
170
+ return new Box(x, y, w, h);
163
171
  }
164
172
  function getSvgRectById(svg, id) {
165
173
  const rectMatch = svg.match(new RegExp(`<rect\\b(?=[^>]*\\bid=["']${id}["'])[^>]*/?>`, 'i'));
@@ -172,7 +180,7 @@ function getSvgRectById(svg, id) {
172
180
  const h = Number(getSvgAttribute(rect, 'height'));
173
181
  if (![x, y, w, h].every(Number.isFinite))
174
182
  return null;
175
- return fromXywh(x, y, w, h);
183
+ return new Box(x, y, w, h);
176
184
  }
177
185
  function getSvgAttribute(element, name) {
178
186
  return element.match(new RegExp(`\\b${name}\\s*=\\s*["']([^"']+)["']`, 'i'))?.[1];
@@ -404,100 +412,13 @@ function createMapStyle() {
404
412
  ],
405
413
  };
406
414
  }
407
- function fromXywh(x, y, w, h) {
408
- return normalizeRect({ x1: x, y1: y, x2: x + w, y2: y + h });
409
- }
410
- function normalizeRect(rect) {
411
- return {
412
- x1: Math.min(rect.x1, rect.x2),
413
- y1: Math.min(rect.y1, rect.y2),
414
- x2: Math.max(rect.x1, rect.x2),
415
- y2: Math.max(rect.y1, rect.y2),
416
- };
417
- }
418
- function width(rect) {
419
- return Math.abs(rect.x2 - rect.x1);
420
- }
421
- function height(rect) {
422
- return Math.abs(rect.y2 - rect.y1);
423
- }
424
- function withPadding(rect, x, y = x) {
425
- if (width(rect) < x * 2 || height(rect) < y * 2)
426
- return rect;
427
- const cx = (rect.x1 + rect.x2) / 2;
428
- const cy = (rect.y1 + rect.y2) / 2;
429
- const w = width(rect) - x * 2;
430
- const h = height(rect) - y * 2;
431
- return normalizeRect({ x1: cx - w / 2, y1: cy - h / 2, x2: cx + w / 2, y2: cy + h / 2 });
432
- }
433
- const earthRadius = 6371000.0;
434
- const minLongitude = -180.0;
435
- const maxLongitude = 180.0;
436
- const toRad = (value) => (value * Math.PI) / 180;
437
- const toDeg = (value) => (value * 180) / Math.PI;
438
- function convertLocalToGps(x, y, geoConfig) {
439
- const diagAngle = -getAngle(geoConfig.p0, geoConfig.p2, {
440
- x: geoConfig.p0.x + 10000,
441
- y: geoConfig.p0.y,
442
- });
443
- const pointAngle = -getAngle(geoConfig.p0, { x, y }, { x: geoConfig.p0.x + 10000, y: geoConfig.p0.y });
444
- const delta = pointAngle - diagAngle;
445
- const diagLen = lineLength(geoConfig.p2, geoConfig.p0);
446
- const pointLen = lineLength(geoConfig.p0, { x, y });
447
- const perc = pointLen / diagLen;
448
- const fullDistance = distance(geoConfig.p0.lat, geoConfig.p0.lng, geoConfig.p2.lat, geoConfig.p2.lng);
449
- const pointDist = perc * fullDistance;
450
- const baseBearing = bearing(geoConfig.p0.lat, geoConfig.p0.lng, geoConfig.p2.lat, geoConfig.p2.lng);
451
- return destinationPoint(geoConfig.p0.lat, geoConfig.p0.lng, pointDist, baseBearing + delta);
452
- }
453
- function lineLength(p1, p2) {
454
- return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
455
- }
456
- function getAngle(centerPoint, startPoint, endPoint) {
457
- const a = lineLength(startPoint, centerPoint);
458
- const b = lineLength(endPoint, centerPoint);
459
- const c = lineLength(startPoint, endPoint);
460
- const cos = (a ** 2 + b ** 2 - c ** 2) / (2 * a * b);
461
- const direction = getDirection(centerPoint, startPoint, endPoint);
462
- return (direction * (Math.acos(Math.max(-1, Math.min(1, cos))) * 180)) / Math.PI || 0;
463
- }
464
- function getDirection(centerPoint, startPoint, endPoint) {
465
- return (startPoint.x - centerPoint.x) * (endPoint.y - centerPoint.y) -
466
- (startPoint.y - centerPoint.y) * (endPoint.x - centerPoint.x) <
467
- 0
468
- ? -1
469
- : 1;
470
- }
471
- function distance(lat1, lng1, lat2, lng2) {
472
- const phi1 = toRad(lat1);
473
- const phi2 = toRad(lat2);
474
- const deltaPhi = toRad(lat2 - lat1);
475
- const deltaLambda = toRad(lng2 - lng1);
476
- const a = Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +
477
- Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2);
478
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
479
- return earthRadius * c;
480
- }
481
- function bearing(lat1, lng1, lat2, lng2) {
482
- const phi1 = toRad(lat1);
483
- const phi2 = toRad(lat2);
484
- const deltaLambda = toRad(lng2 - lng1);
485
- const y = Math.sin(deltaLambda) * Math.cos(phi2);
486
- const x = Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(deltaLambda);
487
- return (toDeg(Math.atan2(y, x)) + 360) % 360;
488
- }
489
- function destinationPoint(lat, lng, distanceMeters, pointBearing) {
490
- const delta = distanceMeters / earthRadius;
491
- const theta = toRad(pointBearing);
492
- const phi1 = toRad(lat);
493
- const lambda1 = toRad(lng);
494
- const phi2 = Math.asin(Math.sin(phi1) * Math.cos(delta) + Math.cos(phi1) * Math.sin(delta) * Math.cos(theta));
495
- let lambda2 = lambda1 +
496
- Math.atan2(Math.sin(theta) * Math.sin(delta) * Math.cos(phi1), Math.cos(delta) - Math.sin(phi1) * Math.sin(phi2));
497
- let longitude = toDeg(lambda2);
498
- if (longitude < minLongitude || longitude > maxLongitude) {
499
- lambda2 = ((lambda2 + 3 * Math.PI) % (2 * Math.PI)) - Math.PI;
500
- longitude = toDeg(lambda2);
501
- }
502
- return [longitude, toDeg(phi2)];
415
+ // Builds the two-anchor affine bridge (p0 -> anchor a, p2 -> anchor b) used to
416
+ // project the SVG-area corners into GPS. All GeoConfig parsing / SVG extraction /
417
+ // anchor validation stays at the callsite; only the pure projection math lives in
418
+ // @expofp/geometry's geo module.
419
+ function geoAnchors(config) {
420
+ return [
421
+ { local: { x: config.p0.x, y: config.p0.y }, geo: { lat: config.p0.lat, lng: config.p0.lng } },
422
+ { local: { x: config.p2.x, y: config.p2.y }, geo: { lat: config.p2.lat, lng: config.p2.lng } },
423
+ ];
503
424
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/offline",
3
- "version": "3.7.16",
3
+ "version": "3.8.0",
4
4
  "type": "module",
5
5
  "description": "CLI tool for creating offline copies of ExpoFP floor plans",
6
6
  "homepage": "https://developer.expofp.com/",
@@ -31,9 +31,10 @@
31
31
  "dependencies": {
32
32
  "debug": "^4.4.3",
33
33
  "tslib": "^2.3.0",
34
- "@expofp/data": "3.7.16",
35
- "@expofp/floorplan": "3.7.16",
36
- "@expofp/resolve": "3.7.16",
37
- "@expofp/utils": "3.7.16"
34
+ "@expofp/floorplan": "3.8.0",
35
+ "@expofp/data": "3.8.0",
36
+ "@expofp/geometry": "3.8.0",
37
+ "@expofp/resolve": "3.8.0",
38
+ "@expofp/utils": "3.8.0"
38
39
  }
39
40
  }