leaflet-polydraw 0.8.5 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/leaflet-polydraw.css +1 -1
- package/dist/polydraw.es.js +843 -625
- package/dist/polydraw.es.js.map +1 -1
- package/dist/polydraw.umd.min.js +1 -1
- package/dist/polydraw.umd.min.js.map +1 -1
- package/dist/styles/polydraw.css +32 -0
- package/dist/types/managers/polygon-draw-manager.d.ts +25 -0
- package/dist/types/managers/polygon-draw-manager.d.ts.map +1 -1
- package/dist/types/managers/polygon-geometry-manager.d.ts +1 -1
- package/dist/types/managers/polygon-geometry-manager.d.ts.map +1 -1
- package/dist/types/managers/polygon-interaction-manager.d.ts.map +1 -1
- package/dist/types/managers/polygon-mutation-manager.d.ts +4 -0
- package/dist/types/managers/polygon-mutation-manager.d.ts.map +1 -1
- package/dist/types/polydraw.d.ts +2 -6
- package/dist/types/polydraw.d.ts.map +1 -1
- package/dist/types/types/polydraw-interfaces.d.ts +15 -0
- package/dist/types/types/polydraw-interfaces.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/polydraw.es.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
17164
|
+
* Add event listener
|
|
17153
17165
|
*/
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
|
|
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
|
-
|
|
17170
|
+
this.eventListeners.get(event).push(callback);
|
|
17210
17171
|
}
|
|
17211
17172
|
/**
|
|
17212
|
-
*
|
|
17173
|
+
* Emit event to all listeners
|
|
17213
17174
|
*/
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
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
|
-
*
|
|
17182
|
+
* Handle mouse move during freehand drawing
|
|
17245
17183
|
*/
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17252
|
-
|
|
17253
|
-
|
|
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
|
-
*
|
|
17196
|
+
* Handle mouse up/leave to complete freehand drawing
|
|
17274
17197
|
*/
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
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:
|
|
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
|
-
|
|
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 : "
|
|
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:
|
|
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
|
-
*
|
|
17231
|
+
* Handle point-to-point click
|
|
17349
17232
|
*/
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
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
|
-
*
|
|
17241
|
+
* Handle point-to-point click
|
|
17370
17242
|
*/
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
17379
|
-
|
|
17380
|
-
|
|
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] : [
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
17782
|
-
|
|
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
|
-
*
|
|
18015
|
+
* Create donut from intersecting polygons (C-to-O scenario)
|
|
17786
18016
|
*/
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
19624
|
+
result = this.geometryManager.doubleElbowsPolygon(completePolygonGeoJSON);
|
|
19387
19625
|
break;
|
|
19388
19626
|
}
|
|
19389
19627
|
case "bezier": {
|
|
19390
|
-
|
|
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
|
-
|
|
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),
|
|
20373
|
-
|
|
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),
|
|
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
|
-
|
|
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)
|