@opendfieldmap/map 0.2.1-beta → 0.2.2-beta

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,5 +1,5 @@
1
- // packages/map/src/runtime.ts
2
- import L4 from "leaflet";
1
+ // packages/map/src/runtime/oem.ts
2
+ import L5 from "leaflet";
3
3
  import "leaflet.markercluster";
4
4
  import {
5
5
  createOEMPointUrl,
@@ -13,25 +13,8 @@ import {
13
13
  toOEMLeafletPosition
14
14
  } from "@opendfieldmap/core";
15
15
 
16
- // packages/map/src/atlos/smoothTileLayer.ts
17
- import L from "leaflet";
18
- var SmoothTileLayer = class extends L.TileLayer {
19
- _setZoomTransform(level, center, zoom) {
20
- const map = this._map;
21
- if (!map)
22
- return;
23
- const scale = map.getZoomScale(zoom, level.zoom);
24
- const translate = level.origin.multiplyBy(scale).subtract(map._getNewPixelOrigin(center, zoom));
25
- if (L.Browser.any3d) {
26
- L.DomUtil.setTransform(level.el, translate, scale);
27
- } else {
28
- L.DomUtil.setPosition(level.el, translate);
29
- }
30
- }
31
- };
32
-
33
16
  // packages/map/src/atlos/smoothWheelZoom.ts
34
- import L2 from "leaflet";
17
+ import L from "leaflet";
35
18
  import { WHEEL_GESTURE_IDLE_MS, WheelInputRouter } from "trackpad-input";
36
19
  var ZOOM_PER_WHEEL_PIXEL = 3e-3;
37
20
  var TRACKPAD_PINCH_ZOOM_PER_PIXEL = 0.01;
@@ -74,7 +57,7 @@ var SmoothWheelZoom = class {
74
57
  lastZoomInputTime = null;
75
58
  inertiaStep = 0;
76
59
  lastFrameTime = null;
77
- pendingPanOffset = L2.point(0, 0);
60
+ pendingPanOffset = L.point(0, 0);
78
61
  panRawCenterPoint = null;
79
62
  gestureMode = null;
80
63
  gestureActive = false;
@@ -138,7 +121,7 @@ var SmoothWheelZoom = class {
138
121
  this.handleZoom(event);
139
122
  }
140
123
  handleZoom(event) {
141
- const wheelDelta = L2.DomEvent.getWheelDelta(event);
124
+ const wheelDelta = L.DomEvent.getWheelDelta(event);
142
125
  if (!wheelDelta)
143
126
  return;
144
127
  const zoomDelta = event.ctrlKey && event.deltaMode === 0 ? -event.deltaY * TRACKPAD_PINCH_ZOOM_PER_PIXEL : wheelDelta * ZOOM_PER_WHEEL_PIXEL;
@@ -160,7 +143,7 @@ var SmoothWheelZoom = class {
160
143
  this.scheduleGestureEnd();
161
144
  }
162
145
  handleTrackpadPan(event) {
163
- const offset = L2.point(event.deltaX, event.deltaY);
146
+ const offset = L.point(event.deltaX, event.deltaY);
164
147
  if (offset.x === 0 && offset.y === 0) {
165
148
  if (this.gestureActive && !this.overdragSettling) {
166
149
  this.scheduleGestureEnd();
@@ -251,7 +234,7 @@ var SmoothWheelZoom = class {
251
234
  if (!this.gestureActive || this.gestureMode !== "pan")
252
235
  return;
253
236
  const offset = this.pendingPanOffset;
254
- this.pendingPanOffset = L2.point(0, 0);
237
+ this.pendingPanOffset = L.point(0, 0);
255
238
  if (offset.x !== 0 || offset.y !== 0) {
256
239
  const overdragAmount = this.applyConstrainedPan(offset);
257
240
  this.map.fire("move").fire("drag");
@@ -277,12 +260,12 @@ var SmoothWheelZoom = class {
277
260
  this.map._rawPanBy(offset);
278
261
  return 0;
279
262
  }
280
- const maxBounds = configuredBounds instanceof L2.LatLngBounds ? configuredBounds : L2.latLngBounds(configuredBounds);
263
+ const maxBounds = configuredBounds instanceof L.LatLngBounds ? configuredBounds : L.latLngBounds(configuredBounds);
281
264
  const rawCenter = this.map.unproject(this.panRawCenterPoint, zoom);
282
265
  const limitedCenter = this.map._limitCenter(rawCenter, zoom, maxBounds);
283
266
  const limitedCenterPoint = this.map.project(limitedCenter, zoom);
284
267
  const rawOverdrag = this.panRawCenterPoint.subtract(limitedCenterPoint);
285
- const visualOverdrag = L2.point(this.resistOverdrag(rawOverdrag.x), this.resistOverdrag(rawOverdrag.y));
268
+ const visualOverdrag = L.point(this.resistOverdrag(rawOverdrag.x), this.resistOverdrag(rawOverdrag.y));
286
269
  const visualCenterPoint = limitedCenterPoint.add(visualOverdrag);
287
270
  const visualOffset = visualCenterPoint.subtract(currentCenterPoint);
288
271
  if (visualOffset.x !== 0 || visualOffset.y !== 0) {
@@ -329,7 +312,7 @@ var SmoothWheelZoom = class {
329
312
  let center = this.map.unproject(this.map.project(this.anchorLatLng, zoom).subtract(cursorOffset), zoom);
330
313
  const configuredBounds = this.map.options.maxBounds;
331
314
  if (configuredBounds) {
332
- const maxBounds = configuredBounds instanceof L2.LatLngBounds ? configuredBounds : L2.latLngBounds(configuredBounds);
315
+ const maxBounds = configuredBounds instanceof L.LatLngBounds ? configuredBounds : L.latLngBounds(configuredBounds);
333
316
  center = this.map._limitCenter(center, zoom, maxBounds);
334
317
  }
335
318
  this.map._move(center, zoom, CONTINUOUS_ZOOM_EVENT_DATA);
@@ -384,7 +367,7 @@ var SmoothWheelZoom = class {
384
367
  this.lastZoomInputTime = null;
385
368
  this.inertiaStep = 0;
386
369
  this.lastFrameTime = null;
387
- this.pendingPanOffset = L2.point(0, 0);
370
+ this.pendingPanOffset = L.point(0, 0);
388
371
  this.panRawCenterPoint = null;
389
372
  this.gestureMode = null;
390
373
  this.trackpadDragging = false;
@@ -411,13 +394,13 @@ var SmoothWheelZoom = class {
411
394
  var enableSmoothWheelZoom = (map, options) => new SmoothWheelZoom(map, options);
412
395
 
413
396
  // packages/map/src/atlos/mapOverdrag.ts
414
- import L3 from "leaflet";
397
+ import L2 from "leaflet";
415
398
  var toMapBounds = (bounds) => {
416
399
  if (!bounds)
417
400
  return null;
418
- if (bounds instanceof L3.LatLngBounds)
401
+ if (bounds instanceof L2.LatLngBounds)
419
402
  return bounds;
420
- return Array.isArray(bounds) && bounds.length === 2 ? L3.latLngBounds(bounds[0], bounds[1]) : null;
403
+ return Array.isArray(bounds) && bounds.length === 2 ? L2.latLngBounds(bounds[0], bounds[1]) : null;
421
404
  };
422
405
  var isMapOverdragged = (map, bounds) => {
423
406
  const constrainedMap = map;
@@ -426,6 +409,64 @@ var isMapOverdragged = (map, bounds) => {
426
409
  return map.project(center, map.getZoom()).distanceTo(map.project(constrainedCenter, map.getZoom())) > 1;
427
410
  };
428
411
 
412
+ // packages/map/src/assets/ghicon.svg
413
+ var ghicon_default = '<svg width="83" height="81" viewBox="0 0 83 81" xmlns="http://www.w3.org/2000/svg">\n<path fill-rule="evenodd" clip-rule="evenodd" d="M41.3763 0C18.4963 0 0 18.5625 0 41.5268C0 59.8835 11.8512 75.422 28.292 80.9215C30.3475 81.335 31.1004 80.028 31.1004 78.9286C31.1004 77.9659 31.0327 74.666 31.0327 71.2277C19.5228 73.7032 17.1259 66.2774 17.1259 66.2774C15.2762 61.4647 12.5355 60.2277 12.5355 60.2277C8.76836 57.6838 12.8099 57.6838 12.8099 57.6838C16.9887 57.9589 19.1815 61.9464 19.1815 61.9464C22.8801 68.2712 28.84 66.4841 31.2376 65.3839C31.5798 62.7024 32.6766 60.8462 33.8411 59.8151C24.6612 58.8524 15.0027 55.2774 15.0027 39.3263C15.0027 34.7887 16.6457 31.0762 19.2492 28.1888C18.8385 27.1578 17.3995 22.8943 19.6608 17.188C19.6608 17.188 23.1545 16.0878 31.0318 21.4507C34.4044 20.5417 37.8825 20.0792 41.3763 20.0753C44.87 20.0753 48.4314 20.5571 51.72 21.4507C59.5982 16.0878 63.0919 17.188 63.0919 17.188C65.3532 22.8943 63.9134 27.1578 63.5026 28.1888C66.1747 31.0762 67.75 34.7887 67.75 39.3263C67.75 55.2774 58.0915 58.7832 48.843 59.8151C50.3505 61.1213 51.6514 63.596 51.6514 67.5152C51.6514 73.0839 51.5837 77.5533 51.5837 78.9277C51.5837 80.028 52.3374 81.335 54.3921 80.9224C70.8329 75.4211 82.6841 59.8835 82.6841 41.5268C82.7518 18.5625 64.1878 0 41.3763 0Z"/>\n</svg>\n';
414
+
415
+ // packages/map/src/runtime/geometry.ts
416
+ var pointOnSegment = (point, start, end) => {
417
+ const cross = (point.x - start.x) * (end.z - start.z) - (point.z - start.z) * (end.x - start.x);
418
+ if (Math.abs(cross) > 1e-7) return false;
419
+ return point.x >= Math.min(start.x, end.x) - 1e-7 && point.x <= Math.max(start.x, end.x) + 1e-7 && point.z >= Math.min(start.z, end.z) - 1e-7 && point.z <= Math.max(start.z, end.z) + 1e-7;
420
+ };
421
+ var pointInRing = (point, ring) => {
422
+ let inside = false;
423
+ for (let index = 0, previous = ring.length - 1; index < ring.length; previous = index++) {
424
+ const current = ring[index];
425
+ const prior = ring[previous];
426
+ if (pointOnSegment(point, prior, current)) return true;
427
+ const crosses = current.z > point.z !== prior.z > point.z;
428
+ if (crosses && point.x < (prior.x - current.x) * (point.z - current.z) / (prior.z - current.z) + current.x) inside = !inside;
429
+ }
430
+ return inside;
431
+ };
432
+ var distanceSquaredToSegment = (point, start, end) => {
433
+ const dx = end.x - start.x;
434
+ const dz = end.z - start.z;
435
+ const lengthSquared = dx * dx + dz * dz;
436
+ if (!lengthSquared) return (point.x - start.x) ** 2 + (point.z - start.z) ** 2;
437
+ const projection = Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.z - start.z) * dz) / lengthSquared));
438
+ const nearestX = start.x + projection * dx;
439
+ const nearestZ = start.z + projection * dz;
440
+ return (point.x - nearestX) ** 2 + (point.z - nearestZ) ** 2;
441
+ };
442
+ var boundaryScore = (boundary, point) => {
443
+ const rings = boundary.rings.map((ring) => ring);
444
+ const distances = rings.flatMap((ring) => ring.map((start, index) => distanceSquaredToSegment(point, start, ring[(index + 1) % ring.length])));
445
+ const insideOuter = rings.length > 0 && pointInRing(point, rings[0]);
446
+ const insideHole = rings.slice(1).some((ring) => pointInRing(point, ring));
447
+ return { id: boundary.id, inside: insideOuter && !insideHole, distance: Math.min(...distances, Infinity) };
448
+ };
449
+
450
+ // packages/map/src/runtime/layers.ts
451
+ import L4 from "leaflet";
452
+
453
+ // packages/map/src/atlos/smoothTileLayer.ts
454
+ import L3 from "leaflet";
455
+ var SmoothTileLayer = class extends L3.TileLayer {
456
+ _setZoomTransform(level, center, zoom) {
457
+ const map = this._map;
458
+ if (!map)
459
+ return;
460
+ const scale = map.getZoomScale(zoom, level.zoom);
461
+ const translate = level.origin.multiplyBy(scale).subtract(map._getNewPixelOrigin(center, zoom));
462
+ if (L3.Browser.any3d) {
463
+ L3.DomUtil.setTransform(level.el, translate, scale);
464
+ } else {
465
+ L3.DomUtil.setPosition(level.el, translate);
466
+ }
467
+ }
468
+ };
469
+
429
470
  // packages/map/src/tileVersion.ts
430
471
  var lookupOEMTile = (coverage, floor, zoom, x, y) => {
431
472
  const ranges = coverage[String(zoom)]?.[floor.id]?.[String(y)] ?? [];
@@ -445,32 +486,7 @@ var lookupOEMTile = (coverage, floor, zoom, x, y) => {
445
486
  };
446
487
  var appendOEMTileVersion = (url, version) => version ? `${url}${url.includes("?") ? "&" : "?"}v=${encodeURIComponent(version)}` : url;
447
488
 
448
- // packages/map/src/assets/ghicon.svg
449
- var ghicon_default = '<svg width="83" height="81" viewBox="0 0 83 81" xmlns="http://www.w3.org/2000/svg">\n<path fill-rule="evenodd" clip-rule="evenodd" d="M41.3763 0C18.4963 0 0 18.5625 0 41.5268C0 59.8835 11.8512 75.422 28.292 80.9215C30.3475 81.335 31.1004 80.028 31.1004 78.9286C31.1004 77.9659 31.0327 74.666 31.0327 71.2277C19.5228 73.7032 17.1259 66.2774 17.1259 66.2774C15.2762 61.4647 12.5355 60.2277 12.5355 60.2277C8.76836 57.6838 12.8099 57.6838 12.8099 57.6838C16.9887 57.9589 19.1815 61.9464 19.1815 61.9464C22.8801 68.2712 28.84 66.4841 31.2376 65.3839C31.5798 62.7024 32.6766 60.8462 33.8411 59.8151C24.6612 58.8524 15.0027 55.2774 15.0027 39.3263C15.0027 34.7887 16.6457 31.0762 19.2492 28.1888C18.8385 27.1578 17.3995 22.8943 19.6608 17.188C19.6608 17.188 23.1545 16.0878 31.0318 21.4507C34.4044 20.5417 37.8825 20.0792 41.3763 20.0753C44.87 20.0753 48.4314 20.5571 51.72 21.4507C59.5982 16.0878 63.0919 17.188 63.0919 17.188C65.3532 22.8943 63.9134 27.1578 63.5026 28.1888C66.1747 31.0762 67.75 34.7887 67.75 39.3263C67.75 55.2774 58.0915 58.7832 48.843 59.8151C50.3505 61.1213 51.6514 63.596 51.6514 67.5152C51.6514 73.0839 51.5837 77.5533 51.5837 78.9277C51.5837 80.028 52.3374 81.335 54.3921 80.9224C70.8329 75.4211 82.6841 59.8835 82.6841 41.5268C82.7518 18.5625 64.1878 0 41.3763 0Z"/>\n</svg>\n';
450
-
451
- // packages/map/src/runtime.ts
452
- var mounted = /* @__PURE__ */ new WeakSet();
453
- var CLUSTER_SUBCATEGORIES = /* @__PURE__ */ new Set(["boss", "collection", "mob", "natural", "valuable", "exploration"]);
454
- var FEATURE_NAMES = ["points", "labels", "boundaries"];
455
- var BRAND_URL = "https://oem.re/";
456
- var GITHUB_URL = "https://github.com/Terra-Online/OEM-SDK";
457
- var TERMS_URL = "https://blog.opendfieldmap.org/docs/tos#intellectual-property-and-copyright";
458
- var cloneFilter = (filter) => ({
459
- types: filter.types ? [...filter.types] : void 0,
460
- subregions: filter.subregions ? [...filter.subregions] : void 0,
461
- floorOnly: filter.floorOnly
462
- });
463
- var sameList = (left, right) => left === right || !!left && !!right && left.length === right.length && left.every((value, index) => value === right[index]);
464
- var sameFilter = (left, right) => left.floorOnly === right.floorOnly && sameList(left.types, right.types) && sameList(left.subregions, right.subregions);
465
- var clonePoint = (point) => ({
466
- ...point,
467
- raw: { ...point.raw },
468
- position: { ...point.position }
469
- });
470
- var cloneCustomPoint = (point) => ({
471
- ...point,
472
- position: { ...point.position }
473
- });
489
+ // packages/map/src/runtime/layers.ts
474
490
  var OEMMarker = class extends L4.Marker {
475
491
  update() {
476
492
  const marker = this;
@@ -497,6 +513,56 @@ var CoveredTileLayer = class extends SmoothTileLayer {
497
513
  return prototype._isValidTile.call(this, coords) && lookupOEMTile(this.coverage, this.floor, coords.z, coords.x, coords.y).covered;
498
514
  }
499
515
  };
516
+
517
+ // packages/map/src/runtime/helpers.ts
518
+ var cloneFilter = (filter) => ({
519
+ types: filter.types ? [...filter.types] : void 0,
520
+ subregions: filter.subregions ? [...filter.subregions] : void 0,
521
+ floorOnly: filter.floorOnly
522
+ });
523
+ var sameList = (left, right) => left === right || !!left && !!right && left.length === right.length && left.every((value, index) => value === right[index]);
524
+ var sameFilter = (left, right) => left.floorOnly === right.floorOnly && sameList(left.types, right.types) && sameList(left.subregions, right.subregions);
525
+ var clonePoint = (point) => ({
526
+ ...point,
527
+ raw: { ...point.raw },
528
+ position: { ...point.position }
529
+ });
530
+ var cloneCustomPoint = (point) => ({
531
+ ...point,
532
+ position: { ...point.position }
533
+ });
534
+ var CLUSTER_SUBCATEGORIES = /* @__PURE__ */ new Set(["boss", "collection", "mob", "natural", "valuable", "exploration"]);
535
+ var FEATURE_NAMES = ["points", "labels", "boundaries"];
536
+ var BRAND_URL = "https://oem.re/";
537
+ var GITHUB_URL = "https://github.com/Terra-Online/OEM-SDK";
538
+ var TERMS_URL = "https://blog.opendfieldmap.org/docs/tos#intellectual-property-and-copyright";
539
+
540
+ // packages/map/src/runtime/oem.ts
541
+ var mounted = /* @__PURE__ */ new WeakSet();
542
+ var decodeAtlosPoint = (raw, region, fallbackSubregionId) => {
543
+ if (!Array.isArray(raw) && raw && "position" in raw && "raw" in raw) return raw;
544
+ const value = Array.isArray(raw) ? { id: raw[0], z: raw[1], x: raw[2], y: raw[3], tier: raw[4], type: raw[5] } : raw;
545
+ if (value?.id == null) return void 0;
546
+ const x = value.x ?? value.pos?.[1] ?? 0;
547
+ const z = value.z ?? value.pos?.[0] ?? 0;
548
+ const y = value.y ?? value.pos?.[2] ?? 0;
549
+ const tier = value.tier ?? 0;
550
+ if (![x, y, z, tier].every(Number.isFinite)) return void 0;
551
+ const subregionId = value.subregId ?? fallbackSubregionId;
552
+ const floorId = tier === 0 ? "M" : `${tier < 0 ? "B" : "L"}${Math.abs(Math.trunc(tier))}`;
553
+ const mapPosition = { regionId: region.id, subregionId, x, z, floorId };
554
+ const game = mapToGameXZPosition(mapPosition, region, subregionId);
555
+ const scale = 2 ** region.maxNativeZoom;
556
+ return {
557
+ id: String(value.id),
558
+ regionId: region.id,
559
+ subregionId,
560
+ type: value.type ?? "",
561
+ tier,
562
+ raw: { x: game.x, y, z: game.z },
563
+ position: { ...mapPosition, x: x * scale, z: z * scale }
564
+ };
565
+ };
500
566
  var OEM = class {
501
567
  constructor(container, manifest, options) {
502
568
  this.container = container;
@@ -518,8 +584,8 @@ var OEM = class {
518
584
  this.root.dataset.theme = options.theme ?? "light";
519
585
  container.append(this.root);
520
586
  mounted.add(container);
521
- this.map = L4.map(this.root, {
522
- crs: L4.CRS.Simple,
587
+ this.map = L5.map(this.root, {
588
+ crs: L5.CRS.Simple,
523
589
  minZoom: 0,
524
590
  maxZoom: 3,
525
591
  zoomControl: false,
@@ -599,11 +665,11 @@ var OEM = class {
599
665
  requests = /* @__PURE__ */ new Map();
600
666
  baseTiles;
601
667
  floorTiles;
602
- pointsLayer = L4.layerGroup();
603
- customPointsLayer = L4.layerGroup();
668
+ pointsLayer = L5.layerGroup();
669
+ customPointsLayer = L5.layerGroup();
604
670
  pointClusters = /* @__PURE__ */ new Map();
605
- labelsLayer = L4.layerGroup();
606
- boundariesLayer = L4.layerGroup();
671
+ labelsLayer = L5.layerGroup();
672
+ boundariesLayer = L5.layerGroup();
607
673
  points = [];
608
674
  customPoints = [];
609
675
  clickPoints = [];
@@ -613,6 +679,8 @@ var OEM = class {
613
679
  pointIndex;
614
680
  pointIndexRequest;
615
681
  pointShardRequests = /* @__PURE__ */ new Map();
682
+ boundaryData = /* @__PURE__ */ new Map();
683
+ boundaryDataRequests = /* @__PURE__ */ new Map();
616
684
  types = {};
617
685
  labels = [];
618
686
  visibleLabelType;
@@ -651,21 +719,22 @@ var OEM = class {
651
719
  if (!point.position || typeof point.position.regionId !== "string") {
652
720
  throw new Error(`Invalid custom point position: ${id}`);
653
721
  }
722
+ const position = point.position;
654
723
  if (point.style !== "framed" && point.style !== "no-frame") {
655
724
  throw new Error(`Invalid custom point style: ${id}`);
656
725
  }
657
726
  if (typeof point.icon !== "string" || !point.icon) {
658
727
  throw new Error(`Custom point icon must be a non-empty URL: ${id}`);
659
728
  }
660
- const pointRegion = getOEMRegion(this.manifest, point.position.regionId);
661
- toOEMLeafletMapPosition(point.position);
662
- if (point.position.subregionId && !pointRegion.subregions.some((subregion) => subregion.id === point.position.subregionId)) {
663
- throw new Error(`Unknown custom point subregion ${point.position.subregionId} in ${point.position.regionId}`);
729
+ const pointRegion = getOEMRegion(this.manifest, position.regionId);
730
+ toOEMLeafletMapPosition(position);
731
+ if (position.subregionId && !pointRegion.subregions.some((subregion) => subregion.id === position.subregionId)) {
732
+ throw new Error(`Unknown custom point subregion ${position.subregionId} in ${position.regionId}`);
664
733
  }
665
- if (point.position.floorId && !pointRegion.floors.some((floor) => floor.id === point.position.floorId)) {
666
- throw new Error(`Unknown custom point floor ${point.position.floorId} in ${point.position.regionId}`);
734
+ if (position.floorId && !pointRegion.floors.some((floor) => floor.id === position.floorId)) {
735
+ throw new Error(`Unknown custom point floor ${position.floorId} in ${position.regionId}`);
667
736
  }
668
- return { ...cloneCustomPoint(point), id };
737
+ return { ...cloneCustomPoint({ ...point, position }), id };
669
738
  });
670
739
  }
671
740
  normalizeClickPointOptions(options) {
@@ -684,25 +753,70 @@ var OEM = class {
684
753
  position(latlng) {
685
754
  return fromOEMLeafletPosition(latlng.lat, latlng.lng, this.region, this.floorId);
686
755
  }
756
+ loadOEMBoundaryData(reference) {
757
+ const cached = this.boundaryData.get(reference.path);
758
+ if (cached) return Promise.resolve(cached);
759
+ const pending = this.boundaryDataRequests.get(reference.path);
760
+ if (pending) return pending;
761
+ const request = fetchOEMJson(resolveOEMAsset(this.options.resources.baseUrl, reference.path), this.options.signal).then((value) => {
762
+ if (!Array.isArray(value)) throw new Error(`Invalid OEM boundary data: ${reference.path}`);
763
+ const boundaries = value;
764
+ this.boundaryData.set(reference.path, boundaries);
765
+ return boundaries;
766
+ }).finally(() => {
767
+ if (this.boundaryDataRequests.get(reference.path) === request) this.boundaryDataRequests.delete(reference.path);
768
+ });
769
+ this.boundaryDataRequests.set(reference.path, request);
770
+ return request;
771
+ }
772
+ /** Preloads OEM subregion geometry so map clicks can resolve an exact subregion. */
773
+ async prepareSubregionBoundaries() {
774
+ if (!this.region.boundaries) return;
775
+ try {
776
+ await this.loadOEMBoundaryData(this.region.boundaries);
777
+ } catch {
778
+ }
779
+ }
687
780
  inferSubregionId(x, z) {
688
781
  const selected = this.filter.subregions?.filter((id) => this.region.subregions.some((subregion) => subregion.id === id));
689
- if (selected?.length === 1) {
690
- const subregion = this.region.subregions.find((entry) => entry.id === selected[0]);
691
- if (!subregion?.bounds) return subregion?.id;
692
- const scale2 = 2 ** this.region.maxNativeZoom;
693
- const [[minX, minY], [maxX, maxY]] = subregion.bounds;
694
- const pixelX2 = x * scale2;
695
- const pixelZ2 = z * scale2;
696
- if (pixelX2 >= minX && pixelX2 <= maxX && pixelZ2 >= minY && pixelZ2 <= maxY) return subregion.id;
697
- }
698
782
  const scale = 2 ** this.region.maxNativeZoom;
699
- const pixelX = x * scale;
700
- const pixelZ = z * scale;
701
- return this.region.subregions.find((subregion) => {
702
- if (!subregion.bounds) return false;
703
- const [[minX, minY], [maxX, maxY]] = subregion.bounds;
704
- return pixelX >= minX && pixelX <= maxX && pixelZ >= minY && pixelZ <= maxY;
705
- })?.id;
783
+ const point = { x: x * scale, z: z * scale };
784
+ const allowed = selected?.length ? new Set(selected) : void 0;
785
+ const geometries = this.region.boundaries ? this.boundaryData.get(this.region.boundaries.path) ?? [] : [];
786
+ const scores = geometries.filter((boundary) => this.region.subregions.some((subregion) => subregion.id === boundary.id) && (!allowed || allowed.has(boundary.id))).map((boundary) => boundaryScore(boundary, point));
787
+ const inside = scores.filter((score) => score.inside);
788
+ if (inside.length) {
789
+ const ranked = inside.map((score) => ({ ...score, wins: 0, margin: 0 }));
790
+ for (let leftIndex = 0; leftIndex < ranked.length; leftIndex += 1) {
791
+ for (let rightIndex = leftIndex + 1; rightIndex < ranked.length; rightIndex += 1) {
792
+ const left = ranked[leftIndex];
793
+ const right = ranked[rightIndex];
794
+ const delta = left.distance - right.distance;
795
+ if (Math.abs(delta) <= 1e-9) continue;
796
+ if (delta > 0) {
797
+ left.wins += 1;
798
+ left.margin += delta;
799
+ right.margin -= delta;
800
+ } else {
801
+ right.wins += 1;
802
+ right.margin -= delta;
803
+ left.margin += delta;
804
+ }
805
+ }
806
+ }
807
+ ranked.sort((left, right) => right.wins - left.wins || right.margin - left.margin || right.distance - left.distance || left.id.localeCompare(right.id));
808
+ return ranked[0].id;
809
+ }
810
+ const bounds = this.region.subregions.filter((subregion) => subregion.bounds && (!allowed || allowed.has(subregion.id))).map((subregion) => {
811
+ const [[minX, minZ], [maxX, maxZ]] = subregion.bounds;
812
+ const insideX = point.x >= minX && point.x <= maxX;
813
+ const insideZ = point.z >= minZ && point.z <= maxZ;
814
+ const dx = insideX ? Math.min(point.x - minX, maxX - point.x) : Math.min(Math.abs(point.x - minX), Math.abs(point.x - maxX));
815
+ const dz = insideZ ? Math.min(point.z - minZ, maxZ - point.z) : Math.min(Math.abs(point.z - minZ), Math.abs(point.z - maxZ));
816
+ return { id: subregion.id, inside: insideX && insideZ, distance: dx * dx + dz * dz };
817
+ }).sort((left, right) => Number(right.inside) - Number(left.inside) || left.distance - right.distance || left.id.localeCompare(right.id));
818
+ if (bounds.length) return bounds[0].id;
819
+ return scores.sort((left, right) => left.distance - right.distance || left.id.localeCompare(right.id))[0]?.id;
706
820
  }
707
821
  emitMapClick = (event) => {
708
822
  if (this.destroyed) return;
@@ -781,7 +895,7 @@ var OEM = class {
781
895
  this.floorTiles?.remove();
782
896
  this.floorTiles = void 0;
783
897
  this.floorId = "M";
784
- this.map.setMaxBounds(L4.latLngBounds([]));
898
+ this.map.setMaxBounds(L5.latLngBounds([]));
785
899
  this.map.setMinZoom(this.region.minZoom);
786
900
  this.map.setMaxZoom(this.region.maxZoom);
787
901
  const target = view ?? this.region.initialView;
@@ -793,7 +907,7 @@ var OEM = class {
793
907
  /** Converts the region's published pixel extent to Simple CRS bounds. */
794
908
  regionBounds() {
795
909
  const { x, z } = this.region.boundsOffset;
796
- return L4.latLngBounds(
910
+ return L5.latLngBounds(
797
911
  toOEMLeafletPosition({ regionId: this.region.id, x, z }, this.region),
798
912
  toOEMLeafletPosition({ regionId: this.region.id, x: x + this.region.dimensions[0], z: z + this.region.dimensions[1] }, this.region)
799
913
  );
@@ -840,7 +954,7 @@ var OEM = class {
840
954
  /** Fits the map to an OEM pixel-coordinate extent. */
841
955
  fitBounds(bounds) {
842
956
  this.assertAlive();
843
- this.map.fitBounds(L4.latLngBounds(toOEMLeafletPosition(bounds[0], this.region), toOEMLeafletPosition(bounds[1], this.region)), { animate: false });
957
+ this.map.fitBounds(L5.latLngBounds(toOEMLeafletPosition(bounds[0], this.region), toOEMLeafletPosition(bounds[1], this.region)), { animate: false });
844
958
  }
845
959
  /** Switches regions and reloads only the features currently enabled. */
846
960
  async setRegion(regionId) {
@@ -860,6 +974,7 @@ var OEM = class {
860
974
  this.emit("regionchange", { regionId });
861
975
  this.emit("floorchange", { floorId: "M" });
862
976
  this.emitView();
977
+ await this.prepareSubregionBoundaries();
863
978
  await this.loadFeatures();
864
979
  }
865
980
  /** Switches the rendered floor while retaining the current region view. */
@@ -926,7 +1041,7 @@ var OEM = class {
926
1041
  this.labelsLayer.clearLayers();
927
1042
  } else this.boundariesLayer.clearLayers();
928
1043
  }
929
- await Promise.all(loads);
1044
+ await Promise.allSettled(loads);
930
1045
  }
931
1046
  /** Replaces host-defined points without reloading static map data. */
932
1047
  setCustomPoints(points) {
@@ -1047,8 +1162,9 @@ var OEM = class {
1047
1162
  const cached = this.pointShardRequests.get(path);
1048
1163
  if (cached) return cached;
1049
1164
  const request = fetchOEMJson(resolveOEMAsset(this.options.resources.baseUrl, path), this.options.signal).then((value) => {
1050
- if (!Array.isArray(value)) throw new Error(`Invalid OEM point shard: ${path}`);
1051
- return value;
1165
+ const region = this.manifest.regions.find((entry) => entry.points.some((ref) => ref.path === path));
1166
+ if (!region || !Array.isArray(value)) throw new Error(`Invalid OEM point shard: ${path}`);
1167
+ return this.decodePointShard(value, path, region);
1052
1168
  }).catch((error) => {
1053
1169
  if (this.pointShardRequests.get(path) === request) this.pointShardRequests.delete(path);
1054
1170
  throw error;
@@ -1056,6 +1172,10 @@ var OEM = class {
1056
1172
  this.pointShardRequests.set(path, request);
1057
1173
  return request;
1058
1174
  }
1175
+ decodePointShard(value, shardPath, region = this.region) {
1176
+ const fallbackSubregionId = shardPath.split("/").at(-1)?.replace(/\.json$/, "") ?? "";
1177
+ return value.map((entry) => decodeAtlosPoint(entry, region, fallbackSubregionId)).filter((entry) => !!entry);
1178
+ }
1059
1179
  cancelRequest(feature) {
1060
1180
  if (!this.requests.has(feature)) return;
1061
1181
  this.requests.get(feature).abort();
@@ -1066,7 +1186,7 @@ var OEM = class {
1066
1186
  for (const feature of this.requests.keys()) this.cancelRequest(feature);
1067
1187
  }
1068
1188
  async loadFeatures() {
1069
- await Promise.all(FEATURE_NAMES.filter((feature) => this.features[feature]).map((feature) => this.loadFeature(feature)));
1189
+ await Promise.allSettled(FEATURE_NAMES.filter((feature) => this.features[feature]).map((feature) => this.loadFeature(feature)));
1070
1190
  }
1071
1191
  /** Loads one feature with a request token so stale responses are ignored. */
1072
1192
  async loadFeature(feature) {
@@ -1078,7 +1198,11 @@ var OEM = class {
1078
1198
  try {
1079
1199
  if (feature === "points") {
1080
1200
  const [groups, types] = await Promise.all([
1081
- Promise.all(this.region.points.map((ref) => read(ref))),
1201
+ Promise.all(this.region.points.map(async (ref) => {
1202
+ const value = await read(ref);
1203
+ if (!Array.isArray(value)) throw new Error(`Invalid OEM point shard: ${ref.path}`);
1204
+ return this.decodePointShard(value, ref.path);
1205
+ })),
1082
1206
  Object.keys(this.types).length ? this.types : read(this.manifest.types)
1083
1207
  ]);
1084
1208
  if (request.signal.aborted) return;
@@ -1096,7 +1220,7 @@ var OEM = class {
1096
1220
  this.renderLabels(true);
1097
1221
  } else {
1098
1222
  const reference = this.boundarySource === "game" ? this.region.gameBoundaries : this.region.boundaries;
1099
- const boundaries = reference ? await read(reference) : [];
1223
+ const boundaries = reference ? this.boundarySource === "oem" ? await this.loadOEMBoundaryData(reference) : await read(reference) : [];
1100
1224
  if (request.signal.aborted) return;
1101
1225
  this.renderBoundaries(boundaries);
1102
1226
  }
@@ -1164,7 +1288,7 @@ var OEM = class {
1164
1288
  interactive: false,
1165
1289
  keyboard: false,
1166
1290
  bubblingMouseEvents: false,
1167
- icon: L4.divIcon({
1291
+ icon: L5.divIcon({
1168
1292
  html: this.createCustomPointVisual(point),
1169
1293
  className: `${point.style === "no-frame" ? "noFrameMarkerIcon" : "frameMarkerIcon"} incompleteMarker`,
1170
1294
  iconSize: point.style === "no-frame" ? [50, 50] : [32, 32],
@@ -1223,7 +1347,7 @@ var OEM = class {
1223
1347
  interactive: true,
1224
1348
  keyboard: false,
1225
1349
  bubblingMouseEvents: false,
1226
- icon: L4.divIcon({
1350
+ icon: L5.divIcon({
1227
1351
  html: this.createMarkerVisual(type, point),
1228
1352
  className: `${type.noFrame ? "noFrameMarkerIcon" : "frameMarkerIcon"} incompleteMarker`,
1229
1353
  iconSize: type.noFrame ? [50, 50] : [32, 32],
@@ -1232,13 +1356,13 @@ var OEM = class {
1232
1356
  });
1233
1357
  }
1234
1358
  createPointCluster(type) {
1235
- return L4.markerClusterGroup({
1359
+ return L5.markerClusterGroup({
1236
1360
  showCoverageOnHover: false,
1237
1361
  zoomToBoundsOnClick: !this.options.lockZoom,
1238
1362
  spiderfyOnMaxZoom: !this.options.lockZoom,
1239
1363
  disableClusteringAtZoom: 2,
1240
1364
  maxClusterRadius: 60,
1241
- iconCreateFunction: (cluster) => L4.divIcon({
1365
+ iconCreateFunction: (cluster) => L5.divIcon({
1242
1366
  html: this.createMarkerVisual(type, void 0, cluster.getChildCount()),
1243
1367
  className: `${type.noFrame ? "noFrameMarkerIcon" : "frameMarkerIcon"} markerClusterCustom`,
1244
1368
  iconSize: type.noFrame ? [50, 50] : [32, 32],
@@ -1274,13 +1398,13 @@ var OEM = class {
1274
1398
  this.boundariesLayer.clearLayers();
1275
1399
  for (const boundary of boundaries) {
1276
1400
  const rings = boundary.rings.map((ring) => ring.map((position) => toOEMLeafletPosition(position, this.region)));
1277
- L4.polygon(rings, {
1401
+ L5.polygon(rings, {
1278
1402
  color: "transparent",
1279
1403
  fillOpacity: 0.2,
1280
1404
  interactive: false,
1281
1405
  className: "subregionBoundaryFill"
1282
1406
  }).addTo(this.boundariesLayer);
1283
- L4.polygon(rings, {
1407
+ L5.polygon(rings, {
1284
1408
  weight: 2,
1285
1409
  opacity: 0.8,
1286
1410
  fill: false,
@@ -1305,7 +1429,7 @@ var OEM = class {
1305
1429
  pane: "placeLabels",
1306
1430
  interactive: false,
1307
1431
  keyboard: false,
1308
- icon: L4.divIcon({ className: "mapLabel", html: inner, iconSize: [0, 0] })
1432
+ icon: L5.divIcon({ className: "mapLabel", html: inner, iconSize: [0, 0] })
1309
1433
  }).addTo(this.labelsLayer);
1310
1434
  }
1311
1435
  }
@@ -1339,4 +1463,4 @@ var OEM = class {
1339
1463
  export {
1340
1464
  OEM
1341
1465
  };
1342
- //# sourceMappingURL=runtime-YB3JS7EZ.js.map
1466
+ //# sourceMappingURL=runtime-6E7MGGCD.js.map