套索工具(超叼版)
This commit is contained in:
203
src/utils/lassoSelect.ts
Normal file
203
src/utils/lassoSelect.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { Map as OLMap } from 'ol';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import { LineString, Polygon } from 'ol/geom';
|
||||
import { Feature } from 'ol';
|
||||
import { Style, Stroke, Fill } from 'ol/style';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { polygon as turfPolygon, booleanIntersects } from '@turf/turf';
|
||||
import { toLonLat } from 'ol/proj';
|
||||
import DragPan from 'ol/interaction/DragPan';
|
||||
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
|
||||
|
||||
export class LassoSelector {
|
||||
private map: OLMap;
|
||||
private drawLayer: VectorLayer<VectorSource>;
|
||||
private drawSource: VectorSource;
|
||||
private overlaySource: VectorSource;
|
||||
private overlayLayer: VectorLayer<VectorSource>;
|
||||
private drawing = false;
|
||||
private coordinates: [number, number][] = [];
|
||||
private targetSource: VectorSource;
|
||||
private isShiftKeyDown = false;
|
||||
private onSelectCallback: (selected: Feature[], isInvert?: boolean) => void;
|
||||
private dragPanInteraction: DragPan | null = null;
|
||||
private mouseWheelZoomInteraction: MouseWheelZoom | null = null;
|
||||
|
||||
constructor(
|
||||
map: OLMap,
|
||||
targetSource: VectorSource,
|
||||
onSelect: (selected: Feature[], isInvert?: boolean) => void
|
||||
) {
|
||||
this.map = map;
|
||||
this.targetSource = targetSource;
|
||||
this.onSelectCallback = onSelect;
|
||||
|
||||
// 找出拖动和滚轮缩放交互
|
||||
this.dragPanInteraction = this.map
|
||||
.getInteractions()
|
||||
.getArray()
|
||||
.find((interaction) => interaction instanceof DragPan) as DragPan;
|
||||
|
||||
this.mouseWheelZoomInteraction = this.map
|
||||
.getInteractions()
|
||||
.getArray()
|
||||
.find((interaction) => interaction instanceof MouseWheelZoom) as MouseWheelZoom;
|
||||
|
||||
this.drawSource = new VectorSource();
|
||||
this.drawLayer = new VectorLayer({
|
||||
source: this.drawSource,
|
||||
style: new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#ff0000',
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
this.map.addLayer(this.drawLayer);
|
||||
|
||||
this.overlaySource = new VectorSource();
|
||||
this.overlayLayer = new VectorLayer({
|
||||
source: this.overlaySource,
|
||||
style: new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(255, 0, 0, 0.8)',
|
||||
width: 2,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 0, 0, 0.3)',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
this.map.addLayer(this.overlayLayer);
|
||||
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
private bindEvents() {
|
||||
// 禁用默认右键菜单
|
||||
this.map.getViewport().addEventListener('contextmenu', (e) => e.preventDefault());
|
||||
|
||||
// pointerdown 捕获左键按下
|
||||
this.map.getViewport().addEventListener('pointerdown', (e) => {
|
||||
if (e.button === 0 && !this.drawing) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.isShiftKeyDown = e.shiftKey;
|
||||
this.drawing = true;
|
||||
this.coordinates = [];
|
||||
this.drawSource.clear();
|
||||
this.overlaySource.clear();
|
||||
|
||||
// 禁用拖动和缩放
|
||||
if (this.dragPanInteraction) this.dragPanInteraction.setActive(false);
|
||||
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(false);
|
||||
}
|
||||
});
|
||||
|
||||
// pointermove 画线
|
||||
this.map.on('pointermove', (evt) => {
|
||||
if (!this.drawing) return;
|
||||
const coord = evt.coordinate as [number, number];
|
||||
this.coordinates.push(coord);
|
||||
this.renderLine();
|
||||
this.renderPolygon();
|
||||
});
|
||||
|
||||
// pointerup 捕获左键抬起
|
||||
this.map.getViewport().addEventListener('pointerup', (e) => {
|
||||
if (e.button === 0 && this.drawing) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.drawing = false;
|
||||
this.handleDrawEnd();
|
||||
|
||||
// 恢复拖动和缩放
|
||||
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
|
||||
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
|
||||
}
|
||||
});
|
||||
|
||||
// 防止拖动导致意外事件
|
||||
this.map.getViewport().addEventListener('pointercancel', (e) => {
|
||||
if (this.drawing) {
|
||||
this.drawing = false;
|
||||
this.drawSource.clear();
|
||||
this.overlaySource.clear();
|
||||
|
||||
if (this.dragPanInteraction) this.dragPanInteraction.setActive(true);
|
||||
if (this.mouseWheelZoomInteraction) this.mouseWheelZoomInteraction.setActive(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private renderLine() {
|
||||
this.drawSource.clear();
|
||||
if (this.coordinates.length >= 2) {
|
||||
const line = new LineString(this.coordinates);
|
||||
const feature = new Feature({ geometry: line });
|
||||
this.drawSource.addFeature(feature);
|
||||
}
|
||||
}
|
||||
|
||||
private renderPolygon() {
|
||||
this.overlaySource.clear();
|
||||
if (this.coordinates.length < 3) return;
|
||||
|
||||
const polygonCoords = [...this.coordinates, this.coordinates[0]];
|
||||
const polygon = new Polygon([polygonCoords]);
|
||||
const feature = new Feature({ geometry: polygon });
|
||||
this.overlaySource.addFeature(feature);
|
||||
}
|
||||
|
||||
private handleDrawEnd() {
|
||||
if (this.coordinates.length < 3) {
|
||||
this.drawSource.clear();
|
||||
this.overlaySource.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const first = this.coordinates[0];
|
||||
const last = this.coordinates[this.coordinates.length - 1];
|
||||
if (first[0] !== last[0] || first[1] !== last[1]) {
|
||||
this.coordinates.push([...first]);
|
||||
}
|
||||
|
||||
const coords4326 = this.coordinates.map((c) => toLonLat(c));
|
||||
const turfPoly = turfPolygon([coords4326]);
|
||||
|
||||
const geojson = new GeoJSON();
|
||||
const selected: Feature[] = [];
|
||||
|
||||
this.targetSource.getFeatures().forEach((feature) => {
|
||||
const geom = feature.getGeometry();
|
||||
if (!geom) return;
|
||||
|
||||
const geomObj = geojson.writeGeometryObject(geom, {
|
||||
featureProjection: 'EPSG:3857',
|
||||
dataProjection: 'EPSG:4326',
|
||||
}) as any;
|
||||
|
||||
if (
|
||||
(geomObj.type === 'Polygon' || geomObj.type === 'MultiPolygon') &&
|
||||
booleanIntersects(turfPoly, geomObj)
|
||||
) {
|
||||
selected.push(feature);
|
||||
}
|
||||
});
|
||||
|
||||
if (selected.length) {
|
||||
this.onSelectCallback(selected, this.isShiftKeyDown);
|
||||
}
|
||||
|
||||
this.drawSource.clear();
|
||||
this.overlaySource.clear();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.map.removeLayer(this.drawLayer);
|
||||
this.map.removeLayer(this.overlayLayer);
|
||||
}
|
||||
}
|
58
src/utils/setMapCenter.ts
Normal file
58
src/utils/setMapCenter.ts
Normal file
@ -0,0 +1,58 @@
|
||||
// MapViewFitter.ts
|
||||
import { Map as OlMap } from 'ol';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { FeatureCollection } from 'geojson';
|
||||
import { bbox as turfBbox, bboxPolygon as turfBboxPolygon } from '@turf/turf';
|
||||
import type { Geometry } from 'ol/geom';
|
||||
|
||||
export class MapViewFitter {
|
||||
private map: OlMap;
|
||||
private format: GeoJSON;
|
||||
|
||||
constructor(map: OlMap) {
|
||||
this.map = map;
|
||||
this.format = new GeoJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使地图视图自动适应传入的 GeoJSON FeatureCollection 范围
|
||||
* @param featureCollection GeoJSON FeatureCollection
|
||||
* @param padding 四周留白,默认为 [10, 10, 10, 10]
|
||||
* @param duration 动画持续时间,默认为 1000 毫秒
|
||||
*/
|
||||
fit(featureCollection: FeatureCollection, padding: number[] = [10, 10, 10, 10], duration: number = 1000) {
|
||||
if (!featureCollection?.features?.length) return;
|
||||
|
||||
const bbox = turfBbox(featureCollection); // [minX, minY, maxX, maxY]
|
||||
const bboxPolygon = turfBboxPolygon(bbox); // Feature<Polygon>
|
||||
|
||||
const geometry: Geometry = this.format.readGeometry(bboxPolygon.geometry, {
|
||||
dataProjection: 'EPSG:4326',
|
||||
featureProjection: 'EPSG:3857'
|
||||
});
|
||||
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
this.map.getView().fit(extent, {
|
||||
padding,
|
||||
duration
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//示例
|
||||
// import { MapViewFitter } from '@/utils/setMapCenter'; // 确保路径正确
|
||||
|
||||
// const fitter = new MapViewFitter(map); // 传入你的 OpenLayers 地图实例
|
||||
// const features = xxx;//features数组
|
||||
|
||||
// if (features?.length) {
|
||||
// const featureCollection = {
|
||||
// type: 'FeatureCollection',
|
||||
// features
|
||||
// };
|
||||
|
||||
// fitter.fit(featureCollection);
|
||||
// }
|
||||
|
Reference in New Issue
Block a user