|
|
@@ -167,12 +167,22 @@ class MapManage {
|
|
|
}
|
|
|
|
|
|
initMap(location, target, options = {}) {
|
|
|
- const { editable = true, constrainedDrawing = false, onDrawOutsideBoundary, zoneStyle } = options;
|
|
|
+ const {
|
|
|
+ editable = true,
|
|
|
+ constrainedDrawing = false,
|
|
|
+ onDrawOutsideBoundary,
|
|
|
+ zoneStyle,
|
|
|
+ preventOverlapExisting = false,
|
|
|
+ onDrawOverlap,
|
|
|
+ } = options;
|
|
|
this.zoneStyle = zoneStyle ? { ...DEFAULT_ZONE_STYLE, ...zoneStyle } : { ...DEFAULT_ZONE_STYLE };
|
|
|
this.editable = editable;
|
|
|
this.constrainedDrawing = constrainedDrawing;
|
|
|
+ this.preventOverlapExisting = !!preventOverlapExisting;
|
|
|
this.onDrawOutsideBoundary = typeof onDrawOutsideBoundary === "function" ? onDrawOutsideBoundary : null;
|
|
|
+ this.onDrawOverlap = typeof onDrawOverlap === "function" ? onDrawOverlap : null;
|
|
|
this.constrainedDrawingReady = false;
|
|
|
+ this.overlapConstraintReady = false;
|
|
|
this.boundaryGeometry = null;
|
|
|
let level = 16;
|
|
|
let coordinate = util.wktCastGeom(location).getFirstCoordinate();
|
|
|
@@ -185,8 +195,13 @@ class MapManage {
|
|
|
this.kmap.addLayer(this.gridLayer.layer);
|
|
|
|
|
|
if (this.editable) {
|
|
|
- this.kmap.initDraw(() => {});
|
|
|
+ this.kmap.initDraw((e) => {
|
|
|
+ if (this.preventOverlapExisting && e?.feature) {
|
|
|
+ this.handleDrawEndOverlap(e.feature);
|
|
|
+ }
|
|
|
+ });
|
|
|
this.kmap.modifyDraw();
|
|
|
+ this.setupOverlapConstraintListeners();
|
|
|
this.setRegionDrawingActive(false);
|
|
|
} else if (this.constrainedDrawing) {
|
|
|
this.setupConstrainedDrawing();
|
|
|
@@ -194,6 +209,169 @@ class MapManage {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ notifyDrawOverlap() {
|
|
|
+ this.onDrawOverlap?.();
|
|
|
+ }
|
|
|
+
|
|
|
+ toTurfFeature(geometry) {
|
|
|
+ if (!geometry || !this.kmap) return null;
|
|
|
+ const geoJson = new GeoJSON();
|
|
|
+ const projection = this.kmap.map.getView().getProjection();
|
|
|
+ try {
|
|
|
+ return turf.feature(
|
|
|
+ geoJson.writeGeometryObject(geometry, {
|
|
|
+ dataProjection: "EPSG:4326",
|
|
|
+ featureProjection: projection,
|
|
|
+ })
|
|
|
+ );
|
|
|
+ } catch {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 点是否落在已有品种范围内 */
|
|
|
+ isCoordinateInExistingVariety(coordinate) {
|
|
|
+ if (!coordinate || !this.existingVarietyLayer?.source) return false;
|
|
|
+ return this.existingVarietyLayer.source.getFeatures().some((feature) => {
|
|
|
+ try {
|
|
|
+ return !!feature.getGeometry()?.intersectsCoordinate(coordinate);
|
|
|
+ } catch {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 几何是否与禁止区域重叠(已有品种 + 其他已勾画面,可排除自身)
|
|
|
+ * 仅面积相交算重叠,边界相接允许
|
|
|
+ */
|
|
|
+ geometryOverlapsForbidden(geometry, excludeFeature = null) {
|
|
|
+ if (!geometry || !this.kmap) return false;
|
|
|
+ const drawn = this.toTurfFeature(geometry);
|
|
|
+ if (!drawn) return false;
|
|
|
+
|
|
|
+ const overlaps = (otherFeature) => {
|
|
|
+ const otherGeom = otherFeature.getGeometry();
|
|
|
+ if (!otherGeom) return false;
|
|
|
+ const other = this.toTurfFeature(otherGeom);
|
|
|
+ if (!other) return false;
|
|
|
+ try {
|
|
|
+ let result = null;
|
|
|
+ try {
|
|
|
+ result = turf.intersect(turf.featureCollection([drawn, other]));
|
|
|
+ } catch {
|
|
|
+ result = turf.intersect(drawn, other);
|
|
|
+ }
|
|
|
+ if (!result?.geometry) return false;
|
|
|
+ return turf.area(result) > 0.5;
|
|
|
+ } catch {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const checkFeatures = (features) =>
|
|
|
+ features.some((feature) => {
|
|
|
+ if (excludeFeature && feature === excludeFeature) return false;
|
|
|
+ return overlaps(feature);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (checkFeatures(this.existingVarietyLayer?.source?.getFeatures?.() || [])) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (checkFeatures(this.kmap.polygonLayer?.source?.getFeatures?.() || [])) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ handleDrawEndOverlap(feature) {
|
|
|
+ if (!feature) return;
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
+ if (!this.geometryOverlapsForbidden(geometry, feature)) return;
|
|
|
+ if (this.kmap?.polygonLayer?.source) {
|
|
|
+ this.kmap.polygonLayer.source.removeFeature(feature);
|
|
|
+ }
|
|
|
+ this.notifyDrawOverlap();
|
|
|
+ }
|
|
|
+
|
|
|
+ handleModifyEndOverlap(feature) {
|
|
|
+ if (!feature) return;
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
+ if (!this.geometryOverlapsForbidden(geometry, feature)) {
|
|
|
+ feature.unset("_preModifyGeom");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const prev = feature.get("_preModifyGeom");
|
|
|
+ if (prev) {
|
|
|
+ feature.setGeometry(prev);
|
|
|
+ } else if (this.kmap?.polygonLayer?.source) {
|
|
|
+ this.kmap.polygonLayer.source.removeFeature(feature);
|
|
|
+ }
|
|
|
+ feature.unset("_preModifyGeom");
|
|
|
+ this.notifyDrawOverlap();
|
|
|
+ }
|
|
|
+
|
|
|
+ bindSketchOverlapConstraint(sketch) {
|
|
|
+ if (!sketch) return;
|
|
|
+ const onSketchChange = () => {
|
|
|
+ if (!this.preventOverlapExisting) return;
|
|
|
+ const geometry = sketch.getGeometry();
|
|
|
+ if (!geometry) return;
|
|
|
+ const type = geometry.getType();
|
|
|
+ if (type !== "LineString" && type !== "Point") return;
|
|
|
+ const last = this.getLastCoordinateFromGeometry(geometry);
|
|
|
+ if (last && this.isCoordinateInExistingVariety(last)) {
|
|
|
+ sketch.getGeometry()?.un("change", onSketchChange);
|
|
|
+ if (this.kmap?.draw && typeof this.kmap.draw.abortDrawing === "function") {
|
|
|
+ this.kmap.draw.abortDrawing();
|
|
|
+ }
|
|
|
+ this.notifyDrawOverlap();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ sketch.getGeometry()?.on("change", onSketchChange);
|
|
|
+ const cleanup = () => sketch.getGeometry()?.un("change", onSketchChange);
|
|
|
+ this.kmap.draw.once("drawend", cleanup);
|
|
|
+ this.kmap.draw.once("drawabort", cleanup);
|
|
|
+ }
|
|
|
+
|
|
|
+ setupOverlapConstraintListeners() {
|
|
|
+ if (!this.kmap?.draw || this.overlapConstraintReady) return;
|
|
|
+ this.overlapConstraintReady = true;
|
|
|
+ this.kmap.draw.on("drawstart", (e) => {
|
|
|
+ if (!this.preventOverlapExisting) return;
|
|
|
+ const coordinate = e.coordinate || this.getLastCoordinateFromGeometry(e.feature?.getGeometry());
|
|
|
+ if (coordinate && this.isCoordinateInExistingVariety(coordinate)) {
|
|
|
+ if (this.kmap?.draw && typeof this.kmap.draw.abortDrawing === "function") {
|
|
|
+ this.kmap.draw.abortDrawing();
|
|
|
+ }
|
|
|
+ this.notifyDrawOverlap();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.bindSketchOverlapConstraint(e.feature);
|
|
|
+ });
|
|
|
+ if (this.kmap.modify) {
|
|
|
+ this.kmap.modify.on("modifystart", (e) => {
|
|
|
+ if (!this.preventOverlapExisting) return;
|
|
|
+ e.features?.forEach((feature) => {
|
|
|
+ const geom = feature.getGeometry();
|
|
|
+ if (geom) feature.set("_preModifyGeom", geom.clone());
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.kmap.modify.on("modifyend", (e) => {
|
|
|
+ if (!this.preventOverlapExisting) return;
|
|
|
+ e.features?.forEach((feature) => this.handleModifyEndOverlap(feature));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 当前勾画面是否与已有品种范围重叠(确认前提交校验) */
|
|
|
+ hasDrawnOverlapWithExisting() {
|
|
|
+ if (!this.kmap?.polygonLayer?.source) return false;
|
|
|
+ return this.kmap.polygonLayer.source.getFeatures().some((feature) =>
|
|
|
+ this.geometryOverlapsForbidden(feature.getGeometry(), feature)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
isCoordinateInsideBoundary(coordinate) {
|
|
|
if (!this.boundaryGeometry || !coordinate || !this.kmap) return false;
|
|
|
try {
|
|
|
@@ -855,8 +1033,11 @@ class MapManage {
|
|
|
this.regionDrawingActive = false;
|
|
|
this.constrainedDrawing = false;
|
|
|
this.constrainedDrawingReady = false;
|
|
|
+ this.overlapConstraintReady = false;
|
|
|
+ this.preventOverlapExisting = false;
|
|
|
this.boundaryGeometry = null;
|
|
|
this.onDrawOutsideBoundary = null;
|
|
|
+ this.onDrawOverlap = null;
|
|
|
this.selectedGridIds?.clear();
|
|
|
}
|
|
|
|