You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
5.4 KiB
185 lines
5.4 KiB
/**
|
|
* 测距尺
|
|
*/
|
|
import mapbox from 'mapbox-gl';
|
|
import * as turf from '@turf/turf';
|
|
|
|
const SOURCE_LINE = 'ruler-source-line';
|
|
const SOURCE_SYMBOL = 'ruler-source-symbol';
|
|
const LAYER_LINE = 'ruler-layer-line';
|
|
const LAYER_SYMBOL = 'ruler-layer-symbol';
|
|
const labelFormat = n => (n < 1000 ? `${Number(n).toFixed(2)}米` : `${(n / 1000).toFixed(2)}千米`);
|
|
|
|
class Ruler {
|
|
// 地图实例
|
|
map = null;
|
|
|
|
// 点坐标
|
|
coordinates = [];
|
|
|
|
// 距离值(含单位)
|
|
labels = [];
|
|
|
|
// 操作点
|
|
markers = [];
|
|
|
|
// 视觉选项
|
|
option = {
|
|
mainColor: 'rgba(255,0,0,0.8)',
|
|
secondaryColor: '#fff',
|
|
fontSize: 12,
|
|
};
|
|
|
|
constructor(map, option = {}) {
|
|
this.map = map;
|
|
this.option = {
|
|
...this.option,
|
|
...option,
|
|
};
|
|
}
|
|
|
|
// 开启测量(外部调用)
|
|
onMeasuringOn() {
|
|
this.map.getCanvas().style.cursor = 'crosshair';
|
|
this.coordinates = [];
|
|
this.markers = [];
|
|
this.labels = [];
|
|
this.map.on('click', this.onClickMap);
|
|
this.map.on('style.load', this.draw);
|
|
this.map.fire('ruler.on');
|
|
}
|
|
|
|
// 关闭测量(外部调用)
|
|
onMeasuringOff() {
|
|
this.map.getCanvas().style.cursor = '';
|
|
if (this.map.getSource(SOURCE_LINE)) {
|
|
this.map.removeLayer(LAYER_LINE);
|
|
this.map.removeSource(SOURCE_LINE);
|
|
}
|
|
if (this.map.getSource(SOURCE_SYMBOL)) {
|
|
this.map.removeLayer(LAYER_SYMBOL);
|
|
this.map.removeSource(SOURCE_SYMBOL);
|
|
}
|
|
this.markers.forEach(m => m.remove());
|
|
this.map.off('click', this.onClickMap);
|
|
this.map.off('style.load', this.draw);
|
|
this.map.fire('ruler.off');
|
|
}
|
|
|
|
// 生成线段geojson
|
|
genLineFeature() {
|
|
return turf.lineString(this.coordinates);
|
|
}
|
|
|
|
// 生成点geojson
|
|
genPointFeature() {
|
|
const pointFeatures = this.coordinates.map((coordinate, index) => turf.point(coordinate, {
|
|
text: this.labels[index],
|
|
}));
|
|
return turf.featureCollection(pointFeatures);
|
|
}
|
|
|
|
// 绘制
|
|
draw = () => {
|
|
if (this.coordinates.length >= 2) {
|
|
const sourceLine = this.map.getSource(SOURCE_LINE);
|
|
if (!sourceLine) {
|
|
this.map.addSource(SOURCE_LINE, {
|
|
type: 'geojson',
|
|
data: this.genLineFeature(),
|
|
});
|
|
|
|
this.map.addLayer({
|
|
id: LAYER_LINE,
|
|
type: 'line',
|
|
source: SOURCE_LINE,
|
|
paint: {
|
|
'line-color': this.option.mainColor,
|
|
'line-width': 2,
|
|
},
|
|
});
|
|
} else {
|
|
sourceLine.setData(this.genLineFeature());
|
|
}
|
|
}
|
|
|
|
const sourceSymbol = this.map.getSource(SOURCE_SYMBOL);
|
|
if (!sourceSymbol) {
|
|
this.map.addSource(SOURCE_SYMBOL, {
|
|
type: 'geojson',
|
|
data: this.genPointFeature(),
|
|
});
|
|
|
|
this.map.addLayer({
|
|
id: LAYER_SYMBOL,
|
|
type: 'symbol',
|
|
source: SOURCE_SYMBOL,
|
|
layout: {
|
|
'text-field': '{text}',
|
|
'text-anchor': 'top',
|
|
'text-size': this.option.fontSize,
|
|
'text-offset': [0, 0.8],
|
|
'text-allow-overlap': true,
|
|
},
|
|
paint: {
|
|
'text-color': this.option.mainColor,
|
|
'text-halo-color': this.option.secondaryColor,
|
|
'text-halo-width': 1,
|
|
},
|
|
});
|
|
} else {
|
|
sourceSymbol.setData(this.genPointFeature());
|
|
}
|
|
};
|
|
|
|
// 生成操作点
|
|
genMarkerNode() {
|
|
const node = document.createElement('div');
|
|
node.style.width = '12px';
|
|
node.style.height = '12px';
|
|
node.style.borderRadius = '50%';
|
|
node.style.background = this.option.secondaryColor;
|
|
node.style.boxSizing = 'border-box';
|
|
node.style.border = `2px solid ${this.option.mainColor}`;
|
|
return node;
|
|
}
|
|
|
|
onClickMap = e => {
|
|
const marker = new mapbox.Marker({
|
|
element: this.genMarkerNode(),
|
|
draggable: true,
|
|
}).setLngLat(e.lngLat).addTo(this.map);
|
|
|
|
const { lng, lat } = e.lngLat;
|
|
this.coordinates.push([lng, lat]);
|
|
this.updateLabels();
|
|
this.draw();
|
|
this.markers.push(marker);
|
|
this.map.fire('ruler.change', { coordinates: this.coordinates });
|
|
|
|
marker.on('drag', () => {
|
|
const index = this.markers.indexOf(marker);
|
|
const { lng: newLng, lat: newLat } = marker.getLngLat();
|
|
this.coordinates[index] = [newLng, newLat];
|
|
this.updateLabels();
|
|
this.draw();
|
|
});
|
|
|
|
marker.on('dragend', () => {
|
|
this.map.fire('ruler.change', { coordinates: this.coordinates });
|
|
});
|
|
};
|
|
|
|
// 更新测量结果值
|
|
updateLabels() {
|
|
const { coordinates } = this;
|
|
let sum = 0;
|
|
this.labels = coordinates.map((coordinate, index) => {
|
|
if (index === 0) return labelFormat(0);
|
|
sum += turf.distance(coordinates[index - 1], coordinates[index], { units: 'meters' });
|
|
return labelFormat(sum);
|
|
});
|
|
}
|
|
}
|
|
|
|
export default Ruler;
|
|
|