leaflet-polydraw 0.8.5 → 0.8.8

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.
@@ -16,6 +16,7 @@ const polygonOptions = { "smoothFactor": 0.3, "color": "#50622b", "fillColor": "
16
16
  const holeOptions = { "color": "#aa0000", "fillColor": "#ffcccc", "weight": 2, "opacity": 1, "fillOpacity": 0.5 };
17
17
  const polygonCreation = { "method": "concaveman", "simplification": { "mode": "simple", "tolerance": 1e-5, "highQuality": false } };
18
18
  const simplification = { "simplifyTolerance": { "tolerance": 1e-4, "highQuality": false, "mutate": false }, "dynamicMode": { "fractionGuard": 0.9, "multipiler": 2 } };
19
+ const menuOperations = { "simplify": { "processHoles": true }, "doubleElbows": { "processHoles": true }, "bbox": { "processHoles": true } };
19
20
  const boundingBox = { "addMidPointMarkers": true };
20
21
  const bezier$1 = { "resolution": 1e4, "sharpness": 0.75 };
21
22
  const defaultConfig = {
@@ -32,6 +33,7 @@ const defaultConfig = {
32
33
  holeOptions,
33
34
  polygonCreation,
34
35
  simplification,
36
+ menuOperations,
35
37
  boundingBox,
36
38
  bezier: bezier$1
37
39
  };
@@ -17141,520 +17143,142 @@ class ModeManager {
17141
17143
  this.updateStateForMode(this.state.currentMode);
17142
17144
  }
17143
17145
  }
17144
- class PolygonGeometryManager {
17146
+ class PolygonDrawManager {
17145
17147
  constructor(dependencies) {
17146
17148
  __publicField(this, "turfHelper");
17149
+ __publicField(this, "map");
17147
17150
  __publicField(this, "config");
17151
+ __publicField(this, "modeManager");
17152
+ __publicField(this, "tracer");
17153
+ __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
17154
+ // Point-to-Point drawing state
17155
+ __publicField(this, "p2pMarkers", []);
17156
+ __publicField(this, "isModifierKeyHeld", false);
17148
17157
  this.turfHelper = dependencies.turfHelper;
17158
+ this.map = dependencies.map;
17149
17159
  this.config = dependencies.config;
17160
+ this.modeManager = dependencies.modeManager;
17161
+ this.tracer = dependencies.tracer;
17150
17162
  }
17151
17163
  /**
17152
- * Check if two polygons intersect using multiple detection methods
17164
+ * Add event listener
17153
17165
  */
17154
- checkPolygonIntersection(polygon1, polygon2) {
17155
- try {
17156
- const poly1WithinPoly2 = this.turfHelper.isPolygonCompletelyWithin(polygon1, polygon2);
17157
- const poly2WithinPoly1 = this.turfHelper.isPolygonCompletelyWithin(polygon2, polygon1);
17158
- if (poly1WithinPoly2 || poly2WithinPoly1) {
17159
- return true;
17160
- }
17161
- } catch (error) {
17162
- }
17163
- try {
17164
- const result = this.turfHelper.polygonIntersect(polygon1, polygon2);
17165
- if (result) {
17166
- return true;
17167
- }
17168
- } catch (error) {
17169
- }
17170
- try {
17171
- const intersection3 = this.turfHelper.getIntersection(polygon1, polygon2);
17172
- if (intersection3 && intersection3.geometry && (intersection3.geometry.type === "Polygon" || intersection3.geometry.type === "MultiPolygon")) {
17173
- const intersectionArea2 = this.turfHelper.getPolygonArea(intersection3);
17174
- if (intersectionArea2 > 1e-6) {
17175
- return true;
17176
- }
17177
- }
17178
- } catch (error) {
17179
- }
17180
- try {
17181
- const coords1 = this.turfHelper.getCoords(polygon1);
17182
- const coords2 = this.turfHelper.getCoords(polygon2);
17183
- for (const ring2 of coords2) {
17184
- for (const coord of ring2[0]) {
17185
- const point2 = {
17186
- type: "Feature",
17187
- geometry: { type: "Point", coordinates: coord },
17188
- properties: {}
17189
- };
17190
- if (this.turfHelper.isPolygonCompletelyWithin(point2, polygon1)) {
17191
- return true;
17192
- }
17193
- }
17194
- }
17195
- for (const ring1 of coords1) {
17196
- for (const coord of ring1[0]) {
17197
- const point2 = {
17198
- type: "Feature",
17199
- geometry: { type: "Point", coordinates: coord },
17200
- properties: {}
17201
- };
17202
- if (this.turfHelper.isPolygonCompletelyWithin(point2, polygon2)) {
17203
- return true;
17204
- }
17205
- }
17206
- }
17207
- } catch (error) {
17166
+ on(event, callback) {
17167
+ if (!this.eventListeners.has(event)) {
17168
+ this.eventListeners.set(event, []);
17208
17169
  }
17209
- return false;
17170
+ this.eventListeners.get(event).push(callback);
17210
17171
  }
17211
17172
  /**
17212
- * Perform union operation on multiple polygons
17173
+ * Emit event to all listeners
17213
17174
  */
17214
- unionPolygons(polygons, newPolygon) {
17215
- try {
17216
- let result = newPolygon;
17217
- for (const polygon2 of polygons) {
17218
- const shouldCreateDonut = this.shouldCreateDonutPolygon(result, polygon2);
17219
- if (shouldCreateDonut) {
17220
- const donutPolygon = this.createDonutPolygon(result, polygon2);
17221
- if (donutPolygon) {
17222
- result = donutPolygon;
17223
- } else {
17224
- const union3 = this.turfHelper.union(result, polygon2);
17225
- result = union3;
17226
- }
17227
- } else {
17228
- const union3 = this.turfHelper.union(result, polygon2);
17229
- result = union3;
17230
- }
17231
- }
17232
- return {
17233
- success: true,
17234
- result
17235
- };
17236
- } catch (error) {
17237
- return {
17238
- success: false,
17239
- error: error instanceof Error ? error.message : "Unknown error in unionPolygons"
17240
- };
17175
+ emit(event, data) {
17176
+ const listeners = this.eventListeners.get(event);
17177
+ if (listeners) {
17178
+ listeners.forEach((callback) => callback(data));
17241
17179
  }
17242
17180
  }
17243
17181
  /**
17244
- * Perform subtraction operation
17182
+ * Handle mouse move during freehand drawing
17245
17183
  */
17246
- subtractPolygon(existingPolygon, subtractPolygon) {
17247
- try {
17248
- const result = this.turfHelper.polygonDifference(existingPolygon, subtractPolygon);
17249
- if (result) {
17250
- const coords = this.turfHelper.getCoords(result);
17251
- const results = [];
17252
- for (const value of coords) {
17253
- results.push(this.turfHelper.getMultiPolygon([value]));
17254
- }
17255
- return {
17256
- success: true,
17257
- results
17258
- };
17259
- } else {
17260
- return {
17261
- success: true,
17262
- results: []
17263
- };
17264
- }
17265
- } catch (error) {
17266
- return {
17267
- success: false,
17268
- error: error instanceof Error ? error.message : "Unknown error in subtractPolygon"
17269
- };
17184
+ mouseMove(event) {
17185
+ if ("latlng" in event && event.latlng) {
17186
+ this.tracer.addLatLng(event.latlng);
17187
+ } else if ("touches" in event && event.touches && event.touches.length > 0) {
17188
+ const latlng = this.map.containerPointToLatLng([
17189
+ event.touches[0].clientX,
17190
+ event.touches[0].clientY
17191
+ ]);
17192
+ this.tracer.addLatLng(latlng);
17270
17193
  }
17271
17194
  }
17272
17195
  /**
17273
- * Simplify a polygon by removing every other point
17196
+ * Handle mouse up/leave to complete freehand drawing
17274
17197
  */
17275
- simplifyPolygon(polygon2) {
17276
- try {
17277
- const coords = this.turfHelper.getCoords(polygon2);
17278
- if (!coords || coords.length === 0) {
17279
- return { success: false, error: "Invalid polygon coordinates" };
17280
- }
17281
- const simplifiedCoords = coords.map((ring) => {
17282
- const outerRing = ring[0];
17283
- if (outerRing.length <= 4) {
17284
- return ring;
17285
- }
17286
- const simplified = [];
17287
- for (let i = 0; i < outerRing.length; i += 2) {
17288
- simplified.push(outerRing[i]);
17289
- }
17290
- const firstPoint = simplified[0];
17291
- const lastPoint = simplified[simplified.length - 1];
17292
- if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
17293
- simplified.push(firstPoint);
17294
- }
17295
- if (simplified.length < 4) {
17296
- return ring;
17297
- }
17298
- return [simplified, ...ring.slice(1)];
17299
- });
17300
- const result = this.turfHelper.getMultiPolygon(simplifiedCoords);
17301
- return {
17302
- success: true,
17303
- result: this.turfHelper.getTurfPolygon(result)
17304
- };
17305
- } catch (error) {
17198
+ async mouseUpLeave(event) {
17199
+ const tracerGeoJSON = this.tracer.toGeoJSON();
17200
+ if (!tracerGeoJSON || !tracerGeoJSON.geometry || !tracerGeoJSON.geometry.coordinates || tracerGeoJSON.geometry.coordinates.length < 3) {
17306
17201
  return {
17307
17202
  success: false,
17308
- error: error instanceof Error ? error.message : "Unknown error in simplifyPolygon"
17203
+ error: "Not enough points to form a valid polygon"
17309
17204
  };
17310
17205
  }
17311
- }
17312
- /**
17313
- * Convert polygon to bounding box
17314
- */
17315
- convertToBoundingBox(polygon2) {
17206
+ let geoPos;
17316
17207
  try {
17317
- const result = this.turfHelper.convertToBoundingBoxPolygon(polygon2);
17318
- return {
17319
- success: true,
17320
- result: this.turfHelper.getTurfPolygon(result)
17321
- };
17208
+ geoPos = this.turfHelper.createPolygonFromTrace(tracerGeoJSON);
17322
17209
  } catch (error) {
17323
17210
  return {
17324
17211
  success: false,
17325
- error: error instanceof Error ? error.message : "Unknown error in convertToBoundingBox"
17212
+ error: error instanceof Error ? error.message : "Failed to create polygon from trace"
17326
17213
  };
17327
17214
  }
17328
- }
17329
- /**
17330
- * Apply bezier curve to polygon
17331
- */
17332
- bezierifyPolygon(polygon2) {
17333
- try {
17334
- const coords = this.turfHelper.getCoords(polygon2);
17335
- const result = this.turfHelper.getBezierMultiPolygon(coords);
17336
- return {
17337
- success: true,
17338
- result: this.turfHelper.getTurfPolygon(result)
17339
- };
17340
- } catch (error) {
17215
+ if (!geoPos || !geoPos.geometry || !geoPos.geometry.coordinates || geoPos.geometry.coordinates.length === 0) {
17341
17216
  return {
17342
17217
  success: false,
17343
- error: error instanceof Error ? error.message : "Unknown error in bezierifyPolygon"
17218
+ error: "Invalid polygon created from trace"
17344
17219
  };
17345
17220
  }
17221
+ this.emit("drawCompleted", {
17222
+ polygon: geoPos,
17223
+ mode: this.modeManager.getCurrentMode()
17224
+ });
17225
+ return {
17226
+ success: true,
17227
+ polygon: geoPos
17228
+ };
17346
17229
  }
17347
17230
  /**
17348
- * Double the elbows of a polygon
17231
+ * Handle point-to-point click
17349
17232
  */
17350
- doubleElbowsPolygon(latlngs) {
17351
- try {
17352
- const doubleLatLngs = this.turfHelper.getDoubleElbowLatLngs(latlngs);
17353
- const coords = [
17354
- [doubleLatLngs.map((latlng) => [latlng.lng, latlng.lat])]
17355
- ];
17356
- const result = this.turfHelper.getMultiPolygon(coords);
17357
- return {
17358
- success: true,
17359
- result: this.turfHelper.getTurfPolygon(result)
17360
- };
17361
- } catch (error) {
17362
- return {
17363
- success: false,
17364
- error: error instanceof Error ? error.message : "Unknown error in doubleElbowsPolygon"
17365
- };
17366
- }
17233
+ /**
17234
+ * Set the modifier key status
17235
+ * @param isHeld - Whether the modifier key is held down
17236
+ */
17237
+ setModifierKey(isHeld) {
17238
+ this.isModifierKeyHeld = isHeld;
17367
17239
  }
17368
17240
  /**
17369
- * Helper method to get polygon center
17241
+ * Handle point-to-point click
17370
17242
  */
17371
- getPolygonCenter(polygon2) {
17372
- try {
17373
- if (!polygon2 || !polygon2.geometry || !polygon2.geometry.coordinates) {
17374
- return null;
17375
- }
17376
- let coordinates;
17377
- if (polygon2.geometry.type === "Polygon") {
17378
- coordinates = polygon2.geometry.coordinates[0];
17379
- } else if (polygon2.geometry.type === "MultiPolygon") {
17380
- coordinates = polygon2.geometry.coordinates[0][0];
17381
- } else {
17382
- return null;
17383
- }
17384
- if (!Array.isArray(coordinates) || coordinates.length === 0) {
17385
- return null;
17243
+ handlePointToPointClick(clickLatLng) {
17244
+ if (!clickLatLng) {
17245
+ return;
17246
+ }
17247
+ if (this.p2pMarkers.length >= 3) {
17248
+ const firstPoint = this.p2pMarkers[0].getLatLng();
17249
+ const isClickingFirst = this.isClickingFirstPoint(clickLatLng, firstPoint);
17250
+ if (isClickingFirst) {
17251
+ this.completePointToPointPolygon();
17252
+ return;
17386
17253
  }
17387
- let latSum = 0;
17388
- let lngSum = 0;
17389
- let count = 0;
17390
- for (const coord of coordinates) {
17391
- if (Array.isArray(coord) && coord.length >= 2) {
17392
- const lng = coord[0];
17393
- const lat = coord[1];
17394
- if (typeof lng === "number" && typeof lat === "number" && !isNaN(lng) && !isNaN(lat)) {
17395
- lngSum += lng;
17396
- latSum += lat;
17397
- count++;
17398
- }
17399
- }
17400
- }
17401
- if (count === 0) {
17402
- return null;
17403
- }
17404
- return {
17405
- lat: latSum / count,
17406
- lng: lngSum / count
17407
- };
17408
- } catch (error) {
17409
- return null;
17410
- }
17411
- }
17412
- /**
17413
- * Helper method to get bounding box from polygon
17414
- */
17415
- getBoundingBox(polygon2) {
17416
- try {
17417
- if (!polygon2 || !polygon2.geometry || !polygon2.geometry.coordinates) {
17418
- return null;
17419
- }
17420
- let coordinates;
17421
- if (polygon2.geometry.type === "Polygon") {
17422
- coordinates = polygon2.geometry.coordinates[0];
17423
- } else if (polygon2.geometry.type === "MultiPolygon") {
17424
- coordinates = polygon2.geometry.coordinates[0][0];
17425
- } else {
17426
- return null;
17427
- }
17428
- if (!Array.isArray(coordinates) || coordinates.length === 0) {
17429
- return null;
17430
- }
17431
- let minLat = Infinity;
17432
- let maxLat = -Infinity;
17433
- let minLng = Infinity;
17434
- let maxLng = -Infinity;
17435
- for (const coord of coordinates) {
17436
- if (Array.isArray(coord) && coord.length >= 2) {
17437
- const lng = coord[0];
17438
- const lat = coord[1];
17439
- if (typeof lng === "number" && typeof lat === "number" && !isNaN(lng) && !isNaN(lat)) {
17440
- minLng = Math.min(minLng, lng);
17441
- maxLng = Math.max(maxLng, lng);
17442
- minLat = Math.min(minLat, lat);
17443
- maxLat = Math.max(maxLat, lat);
17444
- }
17445
- }
17446
- }
17447
- if (minLat === Infinity || maxLat === -Infinity || minLng === Infinity || maxLng === -Infinity) {
17448
- return null;
17449
- }
17450
- return { minLat, maxLat, minLng, maxLng };
17451
- } catch (error) {
17452
- return null;
17453
- }
17454
- }
17455
- /**
17456
- * Determine if two polygons should create a donut instead of a regular union
17457
- */
17458
- shouldCreateDonutPolygon(polygon1, polygon2) {
17459
- try {
17460
- return false;
17461
- } catch (error) {
17462
- console.warn("Error in shouldCreateDonutPolygon:", error.message);
17463
- return false;
17464
- }
17465
- }
17466
- /**
17467
- * Create a donut polygon from two intersecting polygons
17468
- */
17469
- createDonutPolygon(polygon1, polygon2) {
17470
- try {
17471
- const area1 = this.turfHelper.getPolygonArea(polygon1);
17472
- const area2 = this.turfHelper.getPolygonArea(polygon2);
17473
- let outerPolygon;
17474
- let innerPolygon;
17475
- if (area1 > area2) {
17476
- outerPolygon = polygon1;
17477
- innerPolygon = polygon2;
17478
- } else {
17479
- outerPolygon = polygon2;
17480
- innerPolygon = polygon1;
17481
- }
17482
- const innerWithinOuter = this.turfHelper.isPolygonCompletelyWithin(
17483
- innerPolygon,
17484
- outerPolygon
17485
- );
17486
- if (innerWithinOuter) {
17487
- return this.createDonutFromContainment(outerPolygon, innerPolygon);
17488
- } else {
17489
- return this.createDonutFromIntersection(outerPolygon, innerPolygon);
17490
- }
17491
- } catch (error) {
17492
- console.warn("Error in createDonutPolygon:", error.message);
17493
- return null;
17494
- }
17495
- }
17496
- /**
17497
- * Create donut when one polygon is completely within another
17498
- */
17499
- createDonutFromContainment(outerPolygon, innerPolygon) {
17500
- try {
17501
- const outerCoords = this.turfHelper.getCoords(outerPolygon);
17502
- const innerCoords = this.turfHelper.getCoords(innerPolygon);
17503
- const donutCoords = [
17504
- outerCoords[0][0],
17505
- // Outer ring
17506
- innerCoords[0][0]
17507
- // Inner ring as hole
17508
- ];
17509
- return {
17510
- type: "Feature",
17511
- geometry: {
17512
- type: "Polygon",
17513
- coordinates: donutCoords
17514
- },
17515
- properties: {}
17516
- };
17517
- } catch (error) {
17518
- console.warn("Error in createDonutFromContainment:", error.message);
17519
- return null;
17520
- }
17521
- }
17522
- /**
17523
- * Create donut from intersecting polygons (C-to-O scenario)
17524
- */
17525
- createDonutFromIntersection(polygon1, polygon2) {
17526
- try {
17527
- const union3 = this.turfHelper.union(polygon1, polygon2);
17528
- if (!union3) {
17529
- return null;
17530
- }
17531
- const intersection3 = this.turfHelper.getIntersection(polygon1, polygon2);
17532
- if (!intersection3) {
17533
- return union3;
17534
- }
17535
- const donut = this.turfHelper.polygonDifference(union3, intersection3);
17536
- return donut;
17537
- } catch (error) {
17538
- console.warn("Error in createDonutFromIntersection:", error.message);
17539
- return null;
17540
- }
17541
- }
17542
- }
17543
- class PolygonDrawManager {
17544
- constructor(dependencies) {
17545
- __publicField(this, "turfHelper");
17546
- __publicField(this, "map");
17547
- __publicField(this, "config");
17548
- __publicField(this, "modeManager");
17549
- __publicField(this, "tracer");
17550
- __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
17551
- // Point-to-Point drawing state
17552
- __publicField(this, "p2pMarkers", []);
17553
- this.turfHelper = dependencies.turfHelper;
17554
- this.map = dependencies.map;
17555
- this.config = dependencies.config;
17556
- this.modeManager = dependencies.modeManager;
17557
- this.tracer = dependencies.tracer;
17558
- }
17559
- /**
17560
- * Add event listener
17561
- */
17562
- on(event, callback) {
17563
- if (!this.eventListeners.has(event)) {
17564
- this.eventListeners.set(event, []);
17565
- }
17566
- this.eventListeners.get(event).push(callback);
17567
- }
17568
- /**
17569
- * Emit event to all listeners
17570
- */
17571
- emit(event, data) {
17572
- const listeners = this.eventListeners.get(event);
17573
- if (listeners) {
17574
- listeners.forEach((callback) => callback(data));
17575
- }
17576
- }
17577
- /**
17578
- * Handle mouse move during freehand drawing
17579
- */
17580
- mouseMove(event) {
17581
- if ("latlng" in event && event.latlng) {
17582
- this.tracer.addLatLng(event.latlng);
17583
- } else if ("touches" in event && event.touches && event.touches.length > 0) {
17584
- const latlng = this.map.containerPointToLatLng([
17585
- event.touches[0].clientX,
17586
- event.touches[0].clientY
17587
- ]);
17588
- this.tracer.addLatLng(latlng);
17589
17254
  }
17590
- }
17591
- /**
17592
- * Handle mouse up/leave to complete freehand drawing
17593
- */
17594
- async mouseUpLeave(event) {
17595
- const tracerGeoJSON = this.tracer.toGeoJSON();
17596
- if (!tracerGeoJSON || !tracerGeoJSON.geometry || !tracerGeoJSON.geometry.coordinates || tracerGeoJSON.geometry.coordinates.length < 3) {
17597
- return {
17598
- success: false,
17599
- error: "Not enough points to form a valid polygon"
17600
- };
17601
- }
17602
- let geoPos;
17603
- try {
17604
- geoPos = this.turfHelper.createPolygonFromTrace(tracerGeoJSON);
17605
- } catch (error) {
17606
- return {
17607
- success: false,
17608
- error: error instanceof Error ? error.message : "Failed to create polygon from trace"
17609
- };
17610
- }
17611
- if (!geoPos || !geoPos.geometry || !geoPos.geometry.coordinates || geoPos.geometry.coordinates.length === 0) {
17612
- return {
17613
- success: false,
17614
- error: "Invalid polygon created from trace"
17615
- };
17616
- }
17617
- this.emit("drawCompleted", {
17618
- polygon: geoPos,
17619
- mode: this.modeManager.getCurrentMode()
17620
- });
17621
- return {
17622
- success: true,
17623
- polygon: geoPos
17624
- };
17625
- }
17626
- /**
17627
- * Handle point-to-point click
17628
- */
17629
- handlePointToPointClick(clickLatLng) {
17630
- if (!clickLatLng) {
17631
- return;
17632
- }
17633
- const currentPoints = this.tracer.getLatLngs();
17634
- if (currentPoints.length >= 3 && this.p2pMarkers.length > 0) {
17635
- const firstPoint = this.p2pMarkers[0].getLatLng();
17636
- const isClickingFirst = this.isClickingFirstPoint(clickLatLng, firstPoint);
17637
- if (isClickingFirst) {
17638
- this.completePointToPointPolygon();
17639
- return;
17640
- }
17641
- }
17642
- this.tracer.addLatLng(clickLatLng);
17643
17255
  try {
17644
17256
  const isFirstMarker = this.p2pMarkers.length === 0;
17645
17257
  const markerClassName = isFirstMarker ? "leaflet-polydraw-p2p-marker leaflet-polydraw-p2p-first-marker" : "leaflet-polydraw-p2p-marker";
17646
17258
  const pointMarker = new L.Marker(clickLatLng, {
17647
17259
  icon: L.divIcon({
17648
17260
  className: markerClassName,
17649
- iconSize: isFirstMarker ? [20, 20] : [10, 10]
17650
- })
17261
+ iconSize: isFirstMarker ? [20, 20] : [16, 16]
17262
+ }),
17263
+ draggable: this.config.modes.dragElbow
17651
17264
  }).addTo(this.map);
17652
17265
  pointMarker.on("mousedown", (e) => {
17653
17266
  L.DomEvent.stopPropagation(e);
17654
17267
  });
17268
+ pointMarker.on("drag", () => {
17269
+ this.updateP2PTracer();
17270
+ });
17271
+ pointMarker.on("click", (e) => {
17272
+ if (this.isModifierKeyHeld && this.config.modes.edgeDeletion) {
17273
+ this.deleteP2PMarker(pointMarker);
17274
+ L.DomEvent.stopPropagation(e);
17275
+ }
17276
+ });
17277
+ pointMarker.on("mouseover", () => this.onMarkerHoverForEdgeDeletion(pointMarker, true));
17278
+ pointMarker.on("mouseout", () => this.onMarkerHoverForEdgeDeletion(pointMarker, false));
17655
17279
  if (isFirstMarker) {
17656
17280
  pointMarker.on("mouseover", () => {
17657
- if (this.tracer.getLatLngs().length >= 3) {
17281
+ if (this.p2pMarkers.length >= 3) {
17658
17282
  const element = pointMarker.getElement();
17659
17283
  if (element) {
17660
17284
  element.style.backgroundColor = "#4CAF50";
@@ -17674,24 +17298,21 @@ class PolygonDrawManager {
17674
17298
  }
17675
17299
  });
17676
17300
  pointMarker.on("click", (e) => {
17677
- if (this.tracer.getLatLngs().length >= 3) {
17301
+ if (this.isModifierKeyHeld && this.config.modes.edgeDeletion) {
17302
+ this.deleteP2PMarker(pointMarker);
17303
+ L.DomEvent.stopPropagation(e);
17304
+ return;
17305
+ }
17306
+ if (this.p2pMarkers.length >= 3) {
17678
17307
  L.DomEvent.stopPropagation(e);
17679
17308
  this.completePointToPointPolygon();
17680
17309
  }
17681
17310
  });
17682
17311
  }
17683
17312
  this.p2pMarkers.push(pointMarker);
17313
+ this.updateP2PTracer();
17684
17314
  } catch (error) {
17685
17315
  }
17686
- if (this.tracer.getLatLngs().length >= 2) {
17687
- try {
17688
- this.tracer.setStyle({
17689
- color: this.config.polyLineOptions.color,
17690
- dashArray: "5, 5"
17691
- });
17692
- } catch (error) {
17693
- }
17694
- }
17695
17316
  }
17696
17317
  /**
17697
17318
  * Handle double-click to complete point-to-point polygon
@@ -17700,8 +17321,7 @@ class PolygonDrawManager {
17700
17321
  if (this.modeManager.getCurrentMode() !== DrawMode.PointToPoint) {
17701
17322
  return;
17702
17323
  }
17703
- const currentPoints = this.tracer.getLatLngs();
17704
- if (currentPoints.length >= 3) {
17324
+ if (this.p2pMarkers.length >= 3) {
17705
17325
  this.completePointToPointPolygon();
17706
17326
  }
17707
17327
  }
@@ -17709,7 +17329,7 @@ class PolygonDrawManager {
17709
17329
  * Complete point-to-point polygon drawing
17710
17330
  */
17711
17331
  completePointToPointPolygon() {
17712
- const points = this.tracer.getLatLngs();
17332
+ const points = this.p2pMarkers.map((marker) => marker.getLatLng());
17713
17333
  if (points.length < 3) {
17714
17334
  return;
17715
17335
  }
@@ -17755,6 +17375,12 @@ class PolygonDrawManager {
17755
17375
  */
17756
17376
  resetTracer() {
17757
17377
  this.tracer.setLatLngs([]);
17378
+ try {
17379
+ this.tracer.setStyle({
17380
+ dashArray: null
17381
+ });
17382
+ } catch (error) {
17383
+ }
17758
17384
  }
17759
17385
  /**
17760
17386
  * Check if clicking on the first point to close polygon
@@ -17769,6 +17395,43 @@ class PolygonDrawManager {
17769
17395
  const isClicking = latDiff < tolerance && lngDiff < tolerance;
17770
17396
  return isClicking;
17771
17397
  }
17398
+ /**
17399
+ * Update the tracer polyline based on P2P markers
17400
+ */
17401
+ updateP2PTracer() {
17402
+ const latlngs = this.p2pMarkers.map((marker) => marker.getLatLng());
17403
+ this.tracer.setLatLngs(latlngs);
17404
+ if (this.p2pMarkers.length >= 2) {
17405
+ try {
17406
+ this.tracer.setStyle({
17407
+ color: this.config.polyLineOptions.color,
17408
+ dashArray: "5, 5"
17409
+ });
17410
+ } catch (error) {
17411
+ }
17412
+ } else {
17413
+ try {
17414
+ this.tracer.setStyle({
17415
+ dashArray: null
17416
+ });
17417
+ } catch (error) {
17418
+ }
17419
+ }
17420
+ }
17421
+ /**
17422
+ * Delete a P2P marker
17423
+ */
17424
+ deleteP2PMarker(markerToDelete) {
17425
+ const markerIndex = this.p2pMarkers.findIndex((marker) => marker === markerToDelete);
17426
+ if (markerIndex > -1) {
17427
+ this.p2pMarkers.splice(markerIndex, 1);
17428
+ this.map.removeLayer(markerToDelete);
17429
+ this.updateP2PTracer();
17430
+ if (markerIndex === 0 && this.p2pMarkers.length > 0) {
17431
+ this.setupFirstMarker();
17432
+ }
17433
+ }
17434
+ }
17772
17435
  /**
17773
17436
  * Get current P2P markers (for external access)
17774
17437
  */
@@ -17776,17 +17439,597 @@ class PolygonDrawManager {
17776
17439
  return [...this.p2pMarkers];
17777
17440
  }
17778
17441
  /**
17779
- * Check if currently in point-to-point drawing mode
17442
+ * Check if currently in point-to-point drawing mode
17443
+ */
17444
+ isInPointToPointMode() {
17445
+ return this.modeManager.getCurrentMode() === DrawMode.PointToPoint;
17446
+ }
17447
+ /**
17448
+ * Get current tracer points count
17449
+ */
17450
+ getTracerPointsCount() {
17451
+ const points = this.tracer.getLatLngs();
17452
+ return points.length;
17453
+ }
17454
+ /**
17455
+ * Set up the first marker with special properties
17456
+ */
17457
+ setupFirstMarker() {
17458
+ if (this.p2pMarkers.length === 0) return;
17459
+ const firstMarker = this.p2pMarkers[0];
17460
+ const element = firstMarker.getElement();
17461
+ if (element) {
17462
+ element.classList.add("leaflet-polydraw-p2p-first-marker");
17463
+ firstMarker.setIcon(
17464
+ L.divIcon({
17465
+ className: "leaflet-polydraw-p2p-marker leaflet-polydraw-p2p-first-marker",
17466
+ iconSize: [20, 20]
17467
+ })
17468
+ );
17469
+ }
17470
+ firstMarker.off("mouseover");
17471
+ firstMarker.off("mouseout");
17472
+ firstMarker.off("click");
17473
+ firstMarker.on("mouseover", () => {
17474
+ if (this.p2pMarkers.length >= 3) {
17475
+ const element2 = firstMarker.getElement();
17476
+ if (element2) {
17477
+ element2.style.backgroundColor = "#4CAF50";
17478
+ element2.style.borderColor = "#4CAF50";
17479
+ element2.style.cursor = "pointer";
17480
+ element2.title = "Click to close polygon";
17481
+ }
17482
+ }
17483
+ });
17484
+ firstMarker.on("mouseout", () => {
17485
+ const element2 = firstMarker.getElement();
17486
+ if (element2) {
17487
+ element2.style.backgroundColor = "";
17488
+ element2.style.borderColor = "";
17489
+ element2.style.cursor = "";
17490
+ element2.title = "";
17491
+ }
17492
+ });
17493
+ firstMarker.on("click", (e) => {
17494
+ if (this.isModifierKeyHeld && this.config.modes.edgeDeletion) {
17495
+ this.deleteP2PMarker(firstMarker);
17496
+ L.DomEvent.stopPropagation(e);
17497
+ return;
17498
+ }
17499
+ if (this.p2pMarkers.length >= 3) {
17500
+ L.DomEvent.stopPropagation(e);
17501
+ this.completePointToPointPolygon();
17502
+ }
17503
+ });
17504
+ }
17505
+ /**
17506
+ * Handle marker hover for edge deletion feedback
17507
+ */
17508
+ onMarkerHoverForEdgeDeletion(marker, isHovering) {
17509
+ const element = marker.getElement();
17510
+ if (!element) return;
17511
+ if (isHovering) {
17512
+ const checkModifierAndUpdate = (e) => {
17513
+ if (this.isModifierKeyHeld && this.config.modes.edgeDeletion) {
17514
+ element.classList.add("edge-deletion-hover");
17515
+ try {
17516
+ const container = this.map.getContainer();
17517
+ container.style.cursor = "pointer";
17518
+ } catch (error) {
17519
+ }
17520
+ } else {
17521
+ element.classList.remove("edge-deletion-hover");
17522
+ try {
17523
+ const container = this.map.getContainer();
17524
+ container.style.cursor = "";
17525
+ } catch (error) {
17526
+ }
17527
+ }
17528
+ };
17529
+ marker._polydrawModifierHandler = checkModifierAndUpdate;
17530
+ document.addEventListener("keydown", checkModifierAndUpdate);
17531
+ document.addEventListener("keyup", checkModifierAndUpdate);
17532
+ element.addEventListener("mousemove", checkModifierAndUpdate);
17533
+ } else {
17534
+ element.classList.remove("edge-deletion-hover");
17535
+ try {
17536
+ const container = this.map.getContainer();
17537
+ container.style.cursor = "";
17538
+ } catch (error) {
17539
+ }
17540
+ const handler = marker._polydrawModifierHandler;
17541
+ if (handler) {
17542
+ document.removeEventListener("keydown", handler);
17543
+ document.removeEventListener("keyup", handler);
17544
+ element.removeEventListener("mousemove", handler);
17545
+ delete marker._polydrawModifierHandler;
17546
+ }
17547
+ }
17548
+ }
17549
+ }
17550
+ class PolygonGeometryManager {
17551
+ constructor(dependencies) {
17552
+ __publicField(this, "turfHelper");
17553
+ __publicField(this, "config");
17554
+ this.turfHelper = dependencies.turfHelper;
17555
+ this.config = dependencies.config;
17556
+ }
17557
+ /**
17558
+ * Check if two polygons intersect using multiple detection methods
17559
+ */
17560
+ checkPolygonIntersection(polygon1, polygon2) {
17561
+ try {
17562
+ const poly1WithinPoly2 = this.turfHelper.isPolygonCompletelyWithin(polygon1, polygon2);
17563
+ const poly2WithinPoly1 = this.turfHelper.isPolygonCompletelyWithin(polygon2, polygon1);
17564
+ if (poly1WithinPoly2 || poly2WithinPoly1) {
17565
+ return true;
17566
+ }
17567
+ } catch (error) {
17568
+ }
17569
+ try {
17570
+ const result = this.turfHelper.polygonIntersect(polygon1, polygon2);
17571
+ if (result) {
17572
+ return true;
17573
+ }
17574
+ } catch (error) {
17575
+ }
17576
+ try {
17577
+ const intersection3 = this.turfHelper.getIntersection(polygon1, polygon2);
17578
+ if (intersection3 && intersection3.geometry && (intersection3.geometry.type === "Polygon" || intersection3.geometry.type === "MultiPolygon")) {
17579
+ const intersectionArea2 = this.turfHelper.getPolygonArea(intersection3);
17580
+ if (intersectionArea2 > 1e-6) {
17581
+ return true;
17582
+ }
17583
+ }
17584
+ } catch (error) {
17585
+ }
17586
+ try {
17587
+ const coords1 = this.turfHelper.getCoords(polygon1);
17588
+ const coords2 = this.turfHelper.getCoords(polygon2);
17589
+ for (const ring2 of coords2) {
17590
+ for (const coord of ring2[0]) {
17591
+ const point2 = {
17592
+ type: "Feature",
17593
+ geometry: { type: "Point", coordinates: coord },
17594
+ properties: {}
17595
+ };
17596
+ if (this.turfHelper.isPolygonCompletelyWithin(point2, polygon1)) {
17597
+ return true;
17598
+ }
17599
+ }
17600
+ }
17601
+ for (const ring1 of coords1) {
17602
+ for (const coord of ring1[0]) {
17603
+ const point2 = {
17604
+ type: "Feature",
17605
+ geometry: { type: "Point", coordinates: coord },
17606
+ properties: {}
17607
+ };
17608
+ if (this.turfHelper.isPolygonCompletelyWithin(point2, polygon2)) {
17609
+ return true;
17610
+ }
17611
+ }
17612
+ }
17613
+ } catch (error) {
17614
+ }
17615
+ return false;
17616
+ }
17617
+ /**
17618
+ * Perform union operation on multiple polygons
17619
+ */
17620
+ unionPolygons(polygons, newPolygon) {
17621
+ try {
17622
+ let result = newPolygon;
17623
+ for (const polygon2 of polygons) {
17624
+ const shouldCreateDonut = this.shouldCreateDonutPolygon(result, polygon2);
17625
+ if (shouldCreateDonut) {
17626
+ const donutPolygon = this.createDonutPolygon(result, polygon2);
17627
+ if (donutPolygon) {
17628
+ result = donutPolygon;
17629
+ } else {
17630
+ const union3 = this.turfHelper.union(result, polygon2);
17631
+ result = union3;
17632
+ }
17633
+ } else {
17634
+ const union3 = this.turfHelper.union(result, polygon2);
17635
+ result = union3;
17636
+ }
17637
+ }
17638
+ return {
17639
+ success: true,
17640
+ result
17641
+ };
17642
+ } catch (error) {
17643
+ return {
17644
+ success: false,
17645
+ error: error instanceof Error ? error.message : "Unknown error in unionPolygons"
17646
+ };
17647
+ }
17648
+ }
17649
+ /**
17650
+ * Perform subtraction operation
17651
+ */
17652
+ subtractPolygon(existingPolygon, subtractPolygon) {
17653
+ try {
17654
+ const result = this.turfHelper.polygonDifference(existingPolygon, subtractPolygon);
17655
+ if (result) {
17656
+ const coords = this.turfHelper.getCoords(result);
17657
+ const results = [];
17658
+ for (const value of coords) {
17659
+ results.push(this.turfHelper.getMultiPolygon([value]));
17660
+ }
17661
+ return {
17662
+ success: true,
17663
+ results
17664
+ };
17665
+ } else {
17666
+ return {
17667
+ success: true,
17668
+ results: []
17669
+ };
17670
+ }
17671
+ } catch (error) {
17672
+ return {
17673
+ success: false,
17674
+ error: error instanceof Error ? error.message : "Unknown error in subtractPolygon"
17675
+ };
17676
+ }
17677
+ }
17678
+ /**
17679
+ * Simplify a polygon by removing every other point
17680
+ */
17681
+ simplifyPolygon(polygon2) {
17682
+ var _a2, _b2;
17683
+ try {
17684
+ const coords = this.turfHelper.getCoords(polygon2);
17685
+ if (!coords || coords.length === 0) {
17686
+ return { success: false, error: "Invalid polygon coordinates" };
17687
+ }
17688
+ const processHoles = ((_b2 = (_a2 = this.config.menuOperations) == null ? void 0 : _a2.simplify) == null ? void 0 : _b2.processHoles) ?? true;
17689
+ const simplifiedCoords = coords.map((ring) => {
17690
+ const simplifiedRings = [];
17691
+ for (let ringIndex = 0; ringIndex < ring.length; ringIndex++) {
17692
+ const currentRing = ring[ringIndex];
17693
+ const isOuterRing = ringIndex === 0;
17694
+ if (isOuterRing || processHoles) {
17695
+ if (currentRing.length <= 4) {
17696
+ simplifiedRings.push(currentRing);
17697
+ continue;
17698
+ }
17699
+ const simplified = [];
17700
+ for (let i = 0; i < currentRing.length; i += 2) {
17701
+ simplified.push(currentRing[i]);
17702
+ }
17703
+ const firstPoint = simplified[0];
17704
+ const lastPoint = simplified[simplified.length - 1];
17705
+ if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
17706
+ simplified.push(firstPoint);
17707
+ }
17708
+ if (simplified.length < 4) {
17709
+ simplifiedRings.push(currentRing);
17710
+ } else {
17711
+ simplifiedRings.push(simplified);
17712
+ }
17713
+ } else {
17714
+ simplifiedRings.push(currentRing);
17715
+ }
17716
+ }
17717
+ return simplifiedRings;
17718
+ });
17719
+ const result = this.turfHelper.getMultiPolygon(simplifiedCoords);
17720
+ return {
17721
+ success: true,
17722
+ result: this.turfHelper.getTurfPolygon(result)
17723
+ };
17724
+ } catch (error) {
17725
+ return {
17726
+ success: false,
17727
+ error: error instanceof Error ? error.message : "Unknown error in simplifyPolygon"
17728
+ };
17729
+ }
17730
+ }
17731
+ /**
17732
+ * Convert polygon to bounding box
17733
+ */
17734
+ convertToBoundingBox(polygon2) {
17735
+ var _a2, _b2;
17736
+ try {
17737
+ const coords = this.turfHelper.getCoords(polygon2);
17738
+ if (!coords || coords.length === 0) {
17739
+ return { success: false, error: "Invalid polygon coordinates" };
17740
+ }
17741
+ const processHoles = ((_b2 = (_a2 = this.config.menuOperations) == null ? void 0 : _a2.bbox) == null ? void 0 : _b2.processHoles) ?? true;
17742
+ if (!processHoles) {
17743
+ const result2 = this.turfHelper.convertToBoundingBoxPolygon(polygon2);
17744
+ return {
17745
+ success: true,
17746
+ result: this.turfHelper.getTurfPolygon(result2)
17747
+ };
17748
+ }
17749
+ const bboxCoords = coords.map((ring) => {
17750
+ const bboxRings = [];
17751
+ for (let ringIndex = 0; ringIndex < ring.length; ringIndex++) {
17752
+ const currentRing = ring[ringIndex];
17753
+ let minLat = Infinity;
17754
+ let maxLat = -Infinity;
17755
+ let minLng = Infinity;
17756
+ let maxLng = -Infinity;
17757
+ for (const coord of currentRing) {
17758
+ if (Array.isArray(coord) && coord.length >= 2) {
17759
+ const lng = coord[0];
17760
+ const lat = coord[1];
17761
+ if (typeof lng === "number" && typeof lat === "number" && !isNaN(lng) && !isNaN(lat)) {
17762
+ minLng = Math.min(minLng, lng);
17763
+ maxLng = Math.max(maxLng, lng);
17764
+ minLat = Math.min(minLat, lat);
17765
+ maxLat = Math.max(maxLat, lat);
17766
+ }
17767
+ }
17768
+ }
17769
+ if (minLat !== Infinity && maxLat !== -Infinity && minLng !== Infinity && maxLng !== -Infinity) {
17770
+ const rectangularRing = [
17771
+ [minLng, minLat],
17772
+ // Bottom-left
17773
+ [minLng, maxLat],
17774
+ // Top-left
17775
+ [maxLng, maxLat],
17776
+ // Top-right
17777
+ [maxLng, minLat],
17778
+ // Bottom-right
17779
+ [minLng, minLat]
17780
+ // Close the ring
17781
+ ];
17782
+ bboxRings.push(rectangularRing);
17783
+ } else {
17784
+ bboxRings.push(currentRing);
17785
+ }
17786
+ }
17787
+ return bboxRings;
17788
+ });
17789
+ const result = this.turfHelper.getMultiPolygon(bboxCoords);
17790
+ return {
17791
+ success: true,
17792
+ result: this.turfHelper.getTurfPolygon(result)
17793
+ };
17794
+ } catch (error) {
17795
+ return {
17796
+ success: false,
17797
+ error: error instanceof Error ? error.message : "Unknown error in convertToBoundingBox"
17798
+ };
17799
+ }
17800
+ }
17801
+ /**
17802
+ * Apply bezier curve to polygon
17803
+ */
17804
+ bezierifyPolygon(polygon2) {
17805
+ try {
17806
+ const coords = this.turfHelper.getCoords(polygon2);
17807
+ const result = this.turfHelper.getBezierMultiPolygon(coords);
17808
+ return {
17809
+ success: true,
17810
+ result: this.turfHelper.getTurfPolygon(result)
17811
+ };
17812
+ } catch (error) {
17813
+ return {
17814
+ success: false,
17815
+ error: error instanceof Error ? error.message : "Unknown error in bezierifyPolygon"
17816
+ };
17817
+ }
17818
+ }
17819
+ /**
17820
+ * Double the elbows of a polygon
17821
+ */
17822
+ doubleElbowsPolygon(polygon2) {
17823
+ var _a2, _b2;
17824
+ try {
17825
+ const coords = this.turfHelper.getCoords(polygon2);
17826
+ if (!coords || coords.length === 0) {
17827
+ return { success: false, error: "Invalid polygon coordinates" };
17828
+ }
17829
+ const processHoles = ((_b2 = (_a2 = this.config.menuOperations) == null ? void 0 : _a2.doubleElbows) == null ? void 0 : _b2.processHoles) ?? true;
17830
+ const doubleElbowCoords = coords.map((ring) => {
17831
+ const processedRings = [];
17832
+ for (let ringIndex = 0; ringIndex < ring.length; ringIndex++) {
17833
+ const currentRing = ring[ringIndex];
17834
+ const isOuterRing = ringIndex === 0;
17835
+ if (isOuterRing || processHoles) {
17836
+ const latlngs = currentRing.map((coord) => ({ lat: coord[1], lng: coord[0] }));
17837
+ const doubleLatLngs = this.turfHelper.getDoubleElbowLatLngs(latlngs);
17838
+ const doubledCoords = doubleLatLngs.map(
17839
+ (latlng) => [latlng.lng, latlng.lat]
17840
+ );
17841
+ processedRings.push(doubledCoords);
17842
+ } else {
17843
+ processedRings.push(currentRing);
17844
+ }
17845
+ }
17846
+ return processedRings;
17847
+ });
17848
+ const result = this.turfHelper.getMultiPolygon(doubleElbowCoords);
17849
+ return {
17850
+ success: true,
17851
+ result: this.turfHelper.getTurfPolygon(result)
17852
+ };
17853
+ } catch (error) {
17854
+ return {
17855
+ success: false,
17856
+ error: error instanceof Error ? error.message : "Unknown error in doubleElbowsPolygon"
17857
+ };
17858
+ }
17859
+ }
17860
+ /**
17861
+ * Helper method to get polygon center
17862
+ */
17863
+ getPolygonCenter(polygon2) {
17864
+ try {
17865
+ if (!polygon2 || !polygon2.geometry || !polygon2.geometry.coordinates) {
17866
+ return null;
17867
+ }
17868
+ let coordinates;
17869
+ if (polygon2.geometry.type === "Polygon") {
17870
+ coordinates = polygon2.geometry.coordinates[0];
17871
+ } else if (polygon2.geometry.type === "MultiPolygon") {
17872
+ coordinates = polygon2.geometry.coordinates[0][0];
17873
+ } else {
17874
+ return null;
17875
+ }
17876
+ if (!Array.isArray(coordinates) || coordinates.length === 0) {
17877
+ return null;
17878
+ }
17879
+ let latSum = 0;
17880
+ let lngSum = 0;
17881
+ let count = 0;
17882
+ for (const coord of coordinates) {
17883
+ if (Array.isArray(coord) && coord.length >= 2) {
17884
+ const lng = coord[0];
17885
+ const lat = coord[1];
17886
+ if (typeof lng === "number" && typeof lat === "number" && !isNaN(lng) && !isNaN(lat)) {
17887
+ lngSum += lng;
17888
+ latSum += lat;
17889
+ count++;
17890
+ }
17891
+ }
17892
+ }
17893
+ if (count === 0) {
17894
+ return null;
17895
+ }
17896
+ return {
17897
+ lat: latSum / count,
17898
+ lng: lngSum / count
17899
+ };
17900
+ } catch (error) {
17901
+ return null;
17902
+ }
17903
+ }
17904
+ /**
17905
+ * Helper method to get bounding box from polygon
17906
+ */
17907
+ getBoundingBox(polygon2) {
17908
+ try {
17909
+ if (!polygon2 || !polygon2.geometry || !polygon2.geometry.coordinates) {
17910
+ return null;
17911
+ }
17912
+ let coordinates;
17913
+ if (polygon2.geometry.type === "Polygon") {
17914
+ coordinates = polygon2.geometry.coordinates[0];
17915
+ } else if (polygon2.geometry.type === "MultiPolygon") {
17916
+ coordinates = polygon2.geometry.coordinates[0][0];
17917
+ } else {
17918
+ return null;
17919
+ }
17920
+ if (!Array.isArray(coordinates) || coordinates.length === 0) {
17921
+ return null;
17922
+ }
17923
+ let minLat = Infinity;
17924
+ let maxLat = -Infinity;
17925
+ let minLng = Infinity;
17926
+ let maxLng = -Infinity;
17927
+ for (const coord of coordinates) {
17928
+ if (Array.isArray(coord) && coord.length >= 2) {
17929
+ const lng = coord[0];
17930
+ const lat = coord[1];
17931
+ if (typeof lng === "number" && typeof lat === "number" && !isNaN(lng) && !isNaN(lat)) {
17932
+ minLng = Math.min(minLng, lng);
17933
+ maxLng = Math.max(maxLng, lng);
17934
+ minLat = Math.min(minLat, lat);
17935
+ maxLat = Math.max(maxLat, lat);
17936
+ }
17937
+ }
17938
+ }
17939
+ if (minLat === Infinity || maxLat === -Infinity || minLng === Infinity || maxLng === -Infinity) {
17940
+ return null;
17941
+ }
17942
+ return { minLat, maxLat, minLng, maxLng };
17943
+ } catch (error) {
17944
+ return null;
17945
+ }
17946
+ }
17947
+ /**
17948
+ * Determine if two polygons should create a donut instead of a regular union
17949
+ */
17950
+ shouldCreateDonutPolygon(polygon1, polygon2) {
17951
+ try {
17952
+ return false;
17953
+ } catch (error) {
17954
+ console.warn("Error in shouldCreateDonutPolygon:", error.message);
17955
+ return false;
17956
+ }
17957
+ }
17958
+ /**
17959
+ * Create a donut polygon from two intersecting polygons
17960
+ */
17961
+ createDonutPolygon(polygon1, polygon2) {
17962
+ try {
17963
+ const area1 = this.turfHelper.getPolygonArea(polygon1);
17964
+ const area2 = this.turfHelper.getPolygonArea(polygon2);
17965
+ let outerPolygon;
17966
+ let innerPolygon;
17967
+ if (area1 > area2) {
17968
+ outerPolygon = polygon1;
17969
+ innerPolygon = polygon2;
17970
+ } else {
17971
+ outerPolygon = polygon2;
17972
+ innerPolygon = polygon1;
17973
+ }
17974
+ const innerWithinOuter = this.turfHelper.isPolygonCompletelyWithin(
17975
+ innerPolygon,
17976
+ outerPolygon
17977
+ );
17978
+ if (innerWithinOuter) {
17979
+ return this.createDonutFromContainment(outerPolygon, innerPolygon);
17980
+ } else {
17981
+ return this.createDonutFromIntersection(outerPolygon, innerPolygon);
17982
+ }
17983
+ } catch (error) {
17984
+ console.warn("Error in createDonutPolygon:", error.message);
17985
+ return null;
17986
+ }
17987
+ }
17988
+ /**
17989
+ * Create donut when one polygon is completely within another
17780
17990
  */
17781
- isInPointToPointMode() {
17782
- return this.modeManager.getCurrentMode() === DrawMode.PointToPoint;
17991
+ createDonutFromContainment(outerPolygon, innerPolygon) {
17992
+ try {
17993
+ const outerCoords = this.turfHelper.getCoords(outerPolygon);
17994
+ const innerCoords = this.turfHelper.getCoords(innerPolygon);
17995
+ const donutCoords = [
17996
+ outerCoords[0][0],
17997
+ // Outer ring
17998
+ innerCoords[0][0]
17999
+ // Inner ring as hole
18000
+ ];
18001
+ return {
18002
+ type: "Feature",
18003
+ geometry: {
18004
+ type: "Polygon",
18005
+ coordinates: donutCoords
18006
+ },
18007
+ properties: {}
18008
+ };
18009
+ } catch (error) {
18010
+ console.warn("Error in createDonutFromContainment:", error.message);
18011
+ return null;
18012
+ }
17783
18013
  }
17784
18014
  /**
17785
- * Get current tracer points count
18015
+ * Create donut from intersecting polygons (C-to-O scenario)
17786
18016
  */
17787
- getTracerPointsCount() {
17788
- const points = this.tracer.getLatLngs();
17789
- return points.length;
18017
+ createDonutFromIntersection(polygon1, polygon2) {
18018
+ try {
18019
+ const union3 = this.turfHelper.union(polygon1, polygon2);
18020
+ if (!union3) {
18021
+ return null;
18022
+ }
18023
+ const intersection3 = this.turfHelper.getIntersection(polygon1, polygon2);
18024
+ if (!intersection3) {
18025
+ return union3;
18026
+ }
18027
+ const donut = this.turfHelper.polygonDifference(union3, intersection3);
18028
+ return donut;
18029
+ } catch (error) {
18030
+ console.warn("Error in createDonutFromIntersection:", error.message);
18031
+ return null;
18032
+ }
17790
18033
  }
17791
18034
  }
17792
18035
  class IconFactory {
@@ -19125,6 +19368,10 @@ class PolygonInteractionManager {
19125
19368
  const bezier2 = document.createElement("div");
19126
19369
  bezier2.classList.add("marker-menu-button", "bezier");
19127
19370
  bezier2.title = "Curve";
19371
+ const alphaBanner = document.createElement("span");
19372
+ alphaBanner.classList.add("alpha-banner");
19373
+ alphaBanner.textContent = "ALPHA";
19374
+ bezier2.appendChild(alphaBanner);
19128
19375
  const separator = document.createElement("div");
19129
19376
  separator.classList.add("separator");
19130
19377
  outerWrapper.appendChild(wrapper);
@@ -19361,39 +19608,24 @@ class PolygonMutationManager {
19361
19608
  */
19362
19609
  async handleMenuAction(data) {
19363
19610
  const { action, latLngs, featureGroup } = data;
19611
+ const completePolygonGeoJSON = this.getCompletePolygonFromFeatureGroup(featureGroup);
19364
19612
  this.removeFeatureGroupInternal(featureGroup);
19365
19613
  let result;
19366
19614
  switch (action) {
19367
19615
  case "simplify": {
19368
- const coords = [
19369
- [latLngs.map((latlng) => [latlng.lng, latlng.lat])]
19370
- ];
19371
- const polygon2 = this.turfHelper.getMultiPolygon(coords);
19372
- result = this.geometryManager.simplifyPolygon(this.turfHelper.getTurfPolygon(polygon2));
19616
+ result = this.geometryManager.simplifyPolygon(completePolygonGeoJSON);
19373
19617
  break;
19374
19618
  }
19375
19619
  case "bbox": {
19376
- const bboxCoords = [
19377
- [latLngs.map((latlng) => [latlng.lng, latlng.lat])]
19378
- ];
19379
- const bboxPolygon2 = this.turfHelper.getMultiPolygon(bboxCoords);
19380
- result = this.geometryManager.convertToBoundingBox(
19381
- this.turfHelper.getTurfPolygon(bboxPolygon2)
19382
- );
19620
+ result = this.geometryManager.convertToBoundingBox(completePolygonGeoJSON);
19383
19621
  break;
19384
19622
  }
19385
19623
  case "doubleElbows": {
19386
- result = this.geometryManager.doubleElbowsPolygon(latLngs);
19624
+ result = this.geometryManager.doubleElbowsPolygon(completePolygonGeoJSON);
19387
19625
  break;
19388
19626
  }
19389
19627
  case "bezier": {
19390
- const bezierCoords = [
19391
- [latLngs.map((latlng) => [latlng.lng, latlng.lat])]
19392
- ];
19393
- const bezierPolygon = this.turfHelper.getMultiPolygon(bezierCoords);
19394
- result = this.geometryManager.bezierifyPolygon(
19395
- this.turfHelper.getTurfPolygon(bezierPolygon)
19396
- );
19628
+ result = this.geometryManager.bezierifyPolygon(completePolygonGeoJSON);
19397
19629
  break;
19398
19630
  }
19399
19631
  default:
@@ -19717,6 +19949,41 @@ class PolygonMutationManager {
19717
19949
  }
19718
19950
  this.map.removeLayer(featureGroup);
19719
19951
  }
19952
+ /**
19953
+ * Get complete polygon GeoJSON including holes from a feature group
19954
+ */
19955
+ getCompletePolygonFromFeatureGroup(featureGroup) {
19956
+ try {
19957
+ let polygon2 = null;
19958
+ featureGroup.eachLayer((layer) => {
19959
+ if (layer instanceof L.Polygon) {
19960
+ polygon2 = layer;
19961
+ }
19962
+ });
19963
+ if (!polygon2) {
19964
+ throw new Error("No polygon found in feature group");
19965
+ }
19966
+ return polygon2.toGeoJSON();
19967
+ } catch (error) {
19968
+ console.warn("Error getting complete polygon GeoJSON from feature group:", error.message);
19969
+ return {
19970
+ type: "Feature",
19971
+ geometry: {
19972
+ type: "Polygon",
19973
+ coordinates: [
19974
+ [
19975
+ [0, 0],
19976
+ [0, 1],
19977
+ [1, 1],
19978
+ [1, 0],
19979
+ [0, 0]
19980
+ ]
19981
+ ]
19982
+ },
19983
+ properties: {}
19984
+ };
19985
+ }
19986
+ }
19720
19987
  // Public methods that delegate to interaction manager
19721
19988
  /**
19722
19989
  * Update marker draggable state
@@ -19971,9 +20238,9 @@ class Polydraw extends L.Control {
19971
20238
  __publicField(this, "mapStateService");
19972
20239
  __publicField(this, "polygonInformation");
19973
20240
  __publicField(this, "modeManager");
20241
+ __publicField(this, "polygonDrawManager");
19974
20242
  __publicField(this, "polygonMutationManager");
19975
20243
  __publicField(this, "arrayOfFeatureGroups", []);
19976
- __publicField(this, "p2pMarkers", []);
19977
20244
  __publicField(this, "drawMode", DrawMode.Off);
19978
20245
  __publicField(this, "drawModeListeners", []);
19979
20246
  __publicField(this, "_boundKeyDownHandler");
@@ -20044,6 +20311,7 @@ class Polydraw extends L.Control {
20044
20311
  });
20045
20312
  this._boundKeyDownHandler = this.handleKeyDown.bind(this);
20046
20313
  this.polygonMutationManager = null;
20314
+ this.polygonDrawManager = null;
20047
20315
  }
20048
20316
  onAdd(_map) {
20049
20317
  this.map = _map;
@@ -20151,6 +20419,13 @@ class Polydraw extends L.Control {
20151
20419
  this.tracer.addTo(this.map);
20152
20420
  } catch (error) {
20153
20421
  }
20422
+ this.polygonDrawManager = new PolygonDrawManager({
20423
+ turfHelper: this.turfHelper,
20424
+ map: this.map,
20425
+ config: this.config,
20426
+ modeManager: this.modeManager,
20427
+ tracer: this.tracer
20428
+ });
20154
20429
  this.polygonMutationManager = new PolygonMutationManager({
20155
20430
  turfHelper: this.turfHelper,
20156
20431
  polygonInformation: this.polygonInformation,
@@ -20184,6 +20459,22 @@ class Polydraw extends L.Control {
20184
20459
  this.polygonMutationManager.on("polygonDeleted", () => {
20185
20460
  this.updateActivateButtonIndicator();
20186
20461
  });
20462
+ this.polygonDrawManager.on("drawCompleted", async (data) => {
20463
+ this.stopDraw();
20464
+ if (data.isPointToPoint) {
20465
+ await this.polygonMutationManager.addPolygon(data.polygon, {
20466
+ simplify: false,
20467
+ noMerge: false
20468
+ });
20469
+ } else {
20470
+ await this.handleFreehandDrawCompletion(data.polygon);
20471
+ }
20472
+ this.polygonInformation.createPolygonInformationStorage(this.arrayOfFeatureGroups);
20473
+ });
20474
+ this.polygonDrawManager.on("drawCancelled", () => {
20475
+ this.stopDraw();
20476
+ this.setDrawMode(DrawMode.Off);
20477
+ });
20187
20478
  return container;
20188
20479
  }
20189
20480
  addTo(map) {
@@ -20243,7 +20534,7 @@ class Polydraw extends L.Control {
20243
20534
  this.emitDrawModeChanged();
20244
20535
  this.updateMarkerDraggableState();
20245
20536
  if (previousMode === DrawMode.PointToPoint && mode !== DrawMode.PointToPoint) {
20246
- this.clearP2pMarkers();
20537
+ this.polygonDrawManager.clearP2pMarkers();
20247
20538
  this.stopDraw();
20248
20539
  } else if (mode === DrawMode.Off) {
20249
20540
  this.stopDraw();
@@ -20363,14 +20654,22 @@ class Polydraw extends L.Control {
20363
20654
  this.map[onoroff]("mouseup", this.mouseUpLeave, this);
20364
20655
  if (onoff) {
20365
20656
  try {
20366
- this.map.getContainer().addEventListener("touchmove", (e) => this.mouseMove(e));
20367
- this.map.getContainer().addEventListener("touchend", (e) => this.mouseUpLeave(e));
20657
+ this.map.getContainer().addEventListener("touchmove", (e) => this.mouseMove(e), {
20658
+ passive: true
20659
+ });
20660
+ this.map.getContainer().addEventListener("touchend", (e) => this.mouseUpLeave(e), {
20661
+ passive: true
20662
+ });
20368
20663
  } catch (error) {
20369
20664
  }
20370
20665
  } else {
20371
20666
  try {
20372
- this.map.getContainer().removeEventListener("touchmove", (e) => this.mouseMove(e), true);
20373
- this.map.getContainer().removeEventListener("touchend", (e) => this.mouseUpLeave(e), true);
20667
+ this.map.getContainer().removeEventListener("touchmove", (e) => this.mouseMove(e), {
20668
+ passive: true
20669
+ });
20670
+ this.map.getContainer().removeEventListener("touchend", (e) => this.mouseUpLeave(e), {
20671
+ passive: true
20672
+ });
20374
20673
  } catch (error) {
20375
20674
  }
20376
20675
  }
@@ -20386,6 +20685,33 @@ class Polydraw extends L.Control {
20386
20685
  this.tracer.addLatLng(latlng);
20387
20686
  }
20388
20687
  }
20688
+ async handleFreehandDrawCompletion(geoPos) {
20689
+ try {
20690
+ switch (this.modeManager.getCurrentMode()) {
20691
+ case DrawMode.Add: {
20692
+ const result = await this.polygonMutationManager.addPolygon(geoPos, {
20693
+ simplify: true,
20694
+ noMerge: false
20695
+ });
20696
+ if (!result.success) {
20697
+ console.error("Error adding polygon via manager:", result.error);
20698
+ }
20699
+ break;
20700
+ }
20701
+ case DrawMode.Subtract: {
20702
+ const subtractResult = await this.polygonMutationManager.subtractPolygon(geoPos);
20703
+ if (!subtractResult.success) {
20704
+ console.error("Error subtracting polygon via manager:", subtractResult.error);
20705
+ }
20706
+ break;
20707
+ }
20708
+ default:
20709
+ break;
20710
+ }
20711
+ } catch (error) {
20712
+ console.error("Error in mouseUpLeave polygon operation:", error);
20713
+ }
20714
+ }
20389
20715
  async mouseUpLeave(event) {
20390
20716
  this.polygonInformation.deletePolygonInformationStorage();
20391
20717
  const tracerGeoJSON = this.tracer.toGeoJSON();
@@ -20438,12 +20764,16 @@ class Polydraw extends L.Control {
20438
20764
  this.map[onoroff]("dblclick", this.handleDoubleClick, this);
20439
20765
  if (onoff) {
20440
20766
  try {
20441
- this.map.getContainer().addEventListener("touchstart", (e) => this.mouseDown(e));
20767
+ this.map.getContainer().addEventListener("touchstart", (e) => this.mouseDown(e), {
20768
+ passive: true
20769
+ });
20442
20770
  } catch (error) {
20443
20771
  }
20444
20772
  } else {
20445
20773
  try {
20446
- this.map.getContainer().removeEventListener("touchstart", (e) => this.mouseDown(e), true);
20774
+ this.map.getContainer().removeEventListener("touchstart", (e) => this.mouseDown(e), {
20775
+ passive: true
20776
+ });
20447
20777
  } catch (error) {
20448
20778
  }
20449
20779
  }
@@ -20465,7 +20795,7 @@ class Polydraw extends L.Control {
20465
20795
  return;
20466
20796
  }
20467
20797
  if (this.modeManager.getCurrentMode() === DrawMode.PointToPoint) {
20468
- this.handlePointToPointClick(clickLatLng);
20798
+ this.polygonDrawManager.handlePointToPointClick(clickLatLng);
20469
20799
  return;
20470
20800
  }
20471
20801
  this.tracer.setLatLngs([clickLatLng]);
@@ -20486,12 +20816,13 @@ class Polydraw extends L.Control {
20486
20816
  handleKeyDown(e) {
20487
20817
  if (e.key === "Escape") {
20488
20818
  if (this.modeManager.getCurrentMode() === DrawMode.PointToPoint) {
20489
- this.cancelPointToPointDrawing();
20819
+ this.polygonDrawManager.cancelPointToPointDrawing();
20490
20820
  }
20491
20821
  }
20492
20822
  const isModifierPressed = this.isModifierKeyPressed(e);
20493
20823
  if (isModifierPressed && !this.isModifierKeyHeld) {
20494
20824
  this.isModifierKeyHeld = true;
20825
+ this.polygonDrawManager.setModifierKey(true);
20495
20826
  this.updateAllMarkersForEdgeDeletion(true);
20496
20827
  }
20497
20828
  }
@@ -20499,6 +20830,7 @@ class Polydraw extends L.Control {
20499
20830
  const isModifierPressed = this.isModifierKeyPressed(e);
20500
20831
  if (!isModifierPressed && this.isModifierKeyHeld) {
20501
20832
  this.isModifierKeyHeld = false;
20833
+ this.polygonDrawManager.setModifierKey(false);
20502
20834
  this.updateAllMarkersForEdgeDeletion(false);
20503
20835
  }
20504
20836
  }
@@ -20530,125 +20862,11 @@ class Polydraw extends L.Control {
20530
20862
  element.style.borderColor = "";
20531
20863
  }
20532
20864
  }
20533
- cancelPointToPointDrawing() {
20534
- this.clearP2pMarkers();
20535
- this.stopDraw();
20536
- this.setDrawMode(DrawMode.Off);
20537
- }
20538
- // Point-to-Point state management
20539
- handlePointToPointClick(clickLatLng) {
20540
- if (!clickLatLng) {
20541
- return;
20542
- }
20543
- const currentPoints = this.tracer.getLatLngs();
20544
- if (currentPoints.length >= 3 && this.p2pMarkers.length > 0) {
20545
- const firstPoint = this.p2pMarkers[0].getLatLng();
20546
- const isClickingFirst = this.isClickingFirstPoint(clickLatLng, firstPoint);
20547
- if (isClickingFirst) {
20548
- this.completePointToPointPolygon();
20549
- return;
20550
- }
20551
- }
20552
- this.tracer.addLatLng(clickLatLng);
20553
- try {
20554
- const isFirstMarker = this.p2pMarkers.length === 0;
20555
- const markerClassName = isFirstMarker ? "leaflet-polydraw-p2p-marker leaflet-polydraw-p2p-first-marker" : "leaflet-polydraw-p2p-marker";
20556
- const pointMarker = new L.Marker(clickLatLng, {
20557
- icon: L.divIcon({
20558
- className: markerClassName,
20559
- iconSize: isFirstMarker ? [20, 20] : [10, 10]
20560
- })
20561
- }).addTo(this.map);
20562
- pointMarker.on("mousedown", (e) => {
20563
- L.DomEvent.stopPropagation(e);
20564
- });
20565
- if (isFirstMarker) {
20566
- pointMarker.on("mouseover", () => {
20567
- if (this.tracer.getLatLngs().length >= 3) {
20568
- const element = pointMarker.getElement();
20569
- if (element) {
20570
- element.style.backgroundColor = "#4CAF50";
20571
- element.style.borderColor = "#4CAF50";
20572
- element.style.cursor = "pointer";
20573
- element.title = "Click to close polygon";
20574
- }
20575
- }
20576
- });
20577
- pointMarker.on("mouseout", () => {
20578
- const element = pointMarker.getElement();
20579
- if (element) {
20580
- element.style.backgroundColor = "";
20581
- element.style.borderColor = "";
20582
- element.style.cursor = "";
20583
- element.title = "";
20584
- }
20585
- });
20586
- pointMarker.on("click", (e) => {
20587
- if (this.tracer.getLatLngs().length >= 3) {
20588
- L.DomEvent.stopPropagation(e);
20589
- this.completePointToPointPolygon();
20590
- }
20591
- });
20592
- }
20593
- this.p2pMarkers.push(pointMarker);
20594
- } catch (error) {
20595
- }
20596
- if (this.tracer.getLatLngs().length >= 2) {
20597
- try {
20598
- this.tracer.setStyle({
20599
- color: defaultConfig.polyLineOptions.color,
20600
- dashArray: "5, 5"
20601
- });
20602
- } catch (error) {
20603
- }
20604
- }
20605
- }
20606
- isClickingFirstPoint(clickLatLng, firstPoint) {
20607
- if (!firstPoint) return false;
20608
- const zoom = this.map.getZoom();
20609
- const baseTolerance = 5e-4;
20610
- const tolerance = baseTolerance / Math.pow(2, Math.max(0, zoom - 10));
20611
- const latDiff = Math.abs(clickLatLng.lat - firstPoint.lat);
20612
- const lngDiff = Math.abs(clickLatLng.lng - firstPoint.lng);
20613
- const isClicking = latDiff < tolerance && lngDiff < tolerance;
20614
- return isClicking;
20615
- }
20616
20865
  handleDoubleClick(e) {
20617
20866
  if (this.modeManager.getCurrentMode() !== DrawMode.PointToPoint) {
20618
20867
  return;
20619
20868
  }
20620
- const currentPoints = this.tracer.getLatLngs();
20621
- if (currentPoints.length >= 3) {
20622
- this.completePointToPointPolygon();
20623
- }
20624
- }
20625
- completePointToPointPolygon() {
20626
- const points = this.tracer.getLatLngs();
20627
- if (points.length < 3) {
20628
- return;
20629
- }
20630
- const closedPoints = [...points];
20631
- const firstPoint = points[0];
20632
- const lastPoint = points[points.length - 1];
20633
- if (firstPoint.lat !== lastPoint.lat || firstPoint.lng !== lastPoint.lng) {
20634
- closedPoints.push(firstPoint);
20635
- }
20636
- try {
20637
- const coordinates = closedPoints.map((point2) => [point2.lng, point2.lat]);
20638
- const geoPos = this.turfHelper.getMultiPolygon([[coordinates]]);
20639
- this.clearP2pMarkers();
20640
- this.stopDraw();
20641
- this.polygonMutationManager.addPolygon(geoPos, { simplify: false, noMerge: false });
20642
- this.polygonInformation.createPolygonInformationStorage(this.arrayOfFeatureGroups);
20643
- } catch (error) {
20644
- console.warn("Error completing point-to-point polygon:", error);
20645
- this.clearP2pMarkers();
20646
- this.stopDraw();
20647
- }
20648
- }
20649
- clearP2pMarkers() {
20650
- this.p2pMarkers.forEach((marker) => this.map.removeLayer(marker));
20651
- this.p2pMarkers = [];
20869
+ this.polygonDrawManager.handleDoubleClick(e);
20652
20870
  }
20653
20871
  /**
20654
20872
  * Detect if modifier key is pressed (Ctrl on Windows/Linux, Cmd on Mac)