feat(map地图): 初始化地图配置,引入相关js文件作为参考
This commit is contained in:
parent
1f6c7ccb18
commit
e3e3c05ae0
@ -1,40 +1,43 @@
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import Map from 'react-map-gl';
|
||||
import './index.less';
|
||||
import React from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import { MapProps } from './interface';
|
||||
import { merge } from './utils';
|
||||
import { MAP_CENTER, defaultMapConfig } from './constants';
|
||||
|
||||
const MapBox: React.FC<MapProps> = (props) => {
|
||||
export interface MapRefProps {
|
||||
|
||||
}
|
||||
|
||||
const MapBox = forwardRef<MapRefProps, MapProps>((props, ref) => {
|
||||
const {
|
||||
style = {},
|
||||
children,
|
||||
mapRef,
|
||||
onLoad,
|
||||
mapCenter = MAP_CENTER,
|
||||
mapConfig = {},
|
||||
height = 600,
|
||||
width = '100%',
|
||||
...others
|
||||
} = props || {};
|
||||
const mapRef = useRef(null)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
return (
|
||||
//@ts-ignore
|
||||
<Map
|
||||
ref={(e) => {
|
||||
if (mapRef) {
|
||||
mapRef.current = e!;
|
||||
}
|
||||
}}
|
||||
ref={mapRef}
|
||||
initialViewState={{ ...mapCenter, zoom: 10 }}
|
||||
onLoad={(e) => {
|
||||
onLoad && onLoad(e);
|
||||
}}
|
||||
style={{ width: '100%', height: 600, ...style }}
|
||||
{...merge(defaultMapConfig, mapConfig)}
|
||||
initialViewState={{ ...mapCenter, zoom: 10 }}
|
||||
{...others}
|
||||
style={{ width: width, height: height, ...style }}
|
||||
{...merge(defaultMapConfig, others)}
|
||||
>
|
||||
{children}
|
||||
</Map>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default MapBox;
|
||||
|
@ -1,9 +1,11 @@
|
||||
export const mapboxAccessToken =
|
||||
'pk.eyJ1IjoiZGluZ2xpMTIzIiwiYSI6ImNra204ODhjczBobTgyeHJ6MmJpZHMxNWgifQ.NbKrXh_hb2gvjr5CEMDnyQ';
|
||||
export const MAP_CENTER = {
|
||||
|
||||
export const MAP_CENTER = {
|
||||
longitude: 120.2667694313269,
|
||||
latitude: 30.180942826533766,
|
||||
}; //地图中心
|
||||
|
||||
const MapUrl = 'http://10.0.0.120:30003/map';
|
||||
export const defaultMapConfig = {
|
||||
mapboxAccessToken,
|
||||
@ -39,4 +41,4 @@ export const defaultMapConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -13,12 +13,14 @@ const demo = () => {
|
||||
zoom: map?.getMaxZoom(),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MapBox onLoad={handleMapLoad}
|
||||
mapRef={mapRef}
|
||||
style={{ width: '100%', height: 300 }}/>
|
||||
</div>
|
||||
<MapBox
|
||||
onLoad={handleMapLoad}
|
||||
ref={mapRef}
|
||||
width='100%'
|
||||
height='100vh'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ title: 快速上手
|
||||
|
||||
<embed src="../README.md" ></embed>
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
|
||||
## API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
@ -18,3 +19,9 @@ title: 快速上手
|
||||
| children | 内部元素 | JSX.Element或JSX.Element[]或Array<JSX.Element或undefined> | {} | - |
|
||||
| mapConfig | 地图配置 | MapConfigProps | defaultMapConfig | - |
|
||||
| onLoad | 地图加载事件 | function | ()=>{} | - |
|
||||
|
||||
## 参考文档
|
||||
|
||||
[官方文档](https://docs.mapbox.com/mapbox-gl-js/example)
|
||||
[react-map-gl](https://visgl.github.io/react-map-gl/examples)
|
||||
[mapbox-gl-draw](https://github.com/mapbox/mapbox-gl-draw)
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { MapRef, MapStyle } from "react-map-gl";
|
||||
import { MapboxMap, MapRef, MapStyle } from "react-map-gl";
|
||||
|
||||
export interface MapProps {
|
||||
onLoad?: (e: mapboxgl.MapboxEvent<undefined>) => void;
|
||||
mapRef?: React.MutableRefObject<MapRef | undefined>;
|
||||
style?: CSSProperties;
|
||||
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
|
||||
mapConfig?: MapConfigProps
|
||||
mapCenter: {longitude: number, latitude: number}
|
||||
}
|
||||
|
||||
export interface MapConfigProps {
|
||||
export interface MapProps extends Partial<MapboxMap> {
|
||||
mapboxAccessToken?: string; //token
|
||||
minZoom?: number; //最小层级
|
||||
maxZoom?: number; //最大层级
|
||||
dragRotate?: boolean; //是否支持拖拽旋转
|
||||
mapStyle?: MapStyle; //地图样式
|
||||
}
|
||||
height?: number | string;
|
||||
width?: string | number;
|
||||
onLoad?: (e: mapboxgl.MapboxEvent<undefined>) => void;
|
||||
mapRef?: MapRef;
|
||||
style?: CSSProperties;
|
||||
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
|
||||
mapCenter?: {
|
||||
longitude: number, latitude: number
|
||||
}
|
||||
}
|
||||
|
100
packages/map/src/utils/Draw/constants.js
Normal file
100
packages/map/src/utils/Draw/constants.js
Normal file
@ -0,0 +1,100 @@
|
||||
export const classes = {
|
||||
CONTROL_BASE: 'mapboxgl-ctrl',
|
||||
CONTROL_PREFIX: 'mapboxgl-ctrl-',
|
||||
CONTROL_BUTTON: 'mapbox-gl-draw_ctrl-draw-btn',
|
||||
CONTROL_BUTTON_LINE: 'mapbox-gl-draw_line',
|
||||
CONTROL_BUTTON_POLYGON: 'mapbox-gl-draw_polygon',
|
||||
CONTROL_BUTTON_POINT: 'mapbox-gl-draw_point',
|
||||
CONTROL_BUTTON_TRASH: 'mapbox-gl-draw_trash',
|
||||
CONTROL_BUTTON_COMBINE_FEATURES: 'mapbox-gl-draw_combine',
|
||||
CONTROL_BUTTON_UNCOMBINE_FEATURES: 'mapbox-gl-draw_uncombine',
|
||||
CONTROL_GROUP: 'mapboxgl-ctrl-group',
|
||||
ATTRIBUTION: 'mapboxgl-ctrl-attrib',
|
||||
ACTIVE_BUTTON: 'active',
|
||||
BOX_SELECT: 'mapbox-gl-draw_boxselect',
|
||||
};
|
||||
|
||||
export const sources = {
|
||||
HOT: 'mapbox-gl-draw-hot',
|
||||
COLD: 'mapbox-gl-draw-cold',
|
||||
};
|
||||
|
||||
export const cursors = {
|
||||
ADD: 'add',
|
||||
MOVE: 'move',
|
||||
DRAG: 'drag',
|
||||
POINTER: 'pointer',
|
||||
NONE: 'none',
|
||||
};
|
||||
|
||||
export const types = {
|
||||
POLYGON: 'polygon',
|
||||
LINE: 'line_string',
|
||||
POINT: 'point',
|
||||
};
|
||||
|
||||
export const geojsonTypes = {
|
||||
FEATURE: 'Feature',
|
||||
POLYGON: 'Polygon',
|
||||
LINE_STRING: 'LineString',
|
||||
POINT: 'Point',
|
||||
FEATURE_COLLECTION: 'FeatureCollection',
|
||||
MULTI_PREFIX: 'Multi',
|
||||
MULTI_POINT: 'MultiPoint',
|
||||
MULTI_LINE_STRING: 'MultiLineString',
|
||||
MULTI_POLYGON: 'MultiPolygon',
|
||||
};
|
||||
|
||||
export const modes = {
|
||||
DRAW_LINE_STRING: 'draw_line_string',
|
||||
DRAW_POLYGON: 'draw_polygon',
|
||||
DRAW_POINT: 'draw_point',
|
||||
SIMPLE_SELECT: 'simple_select',
|
||||
DIRECT_SELECT: 'direct_select',
|
||||
STATIC: 'static',
|
||||
};
|
||||
|
||||
export const events = {
|
||||
CREATE: 'draw.create',
|
||||
DELETE: 'draw.delete',
|
||||
UPDATE: 'draw.update',
|
||||
SELECTION_CHANGE: 'draw.selectionchange',
|
||||
MODE_CHANGE: 'draw.modechange',
|
||||
ACTIONABLE: 'draw.actionable',
|
||||
RENDER: 'draw.render',
|
||||
COMBINE_FEATURES: 'draw.combine',
|
||||
UNCOMBINE_FEATURES: 'draw.uncombine',
|
||||
};
|
||||
|
||||
export const updateActions = {
|
||||
MOVE: 'move',
|
||||
CHANGE_COORDINATES: 'change_coordinates',
|
||||
};
|
||||
|
||||
export const meta = {
|
||||
FEATURE: 'feature',
|
||||
MIDPOINT: 'midpoint',
|
||||
VERTEX: 'vertex',
|
||||
};
|
||||
|
||||
export const activeStates = {
|
||||
ACTIVE: 'true',
|
||||
INACTIVE: 'false',
|
||||
};
|
||||
|
||||
export const interactions = [
|
||||
'scrollZoom',
|
||||
'boxZoom',
|
||||
'dragRotate',
|
||||
'dragPan',
|
||||
'keyboard',
|
||||
'doubleClickZoom',
|
||||
'touchZoomRotate',
|
||||
];
|
||||
|
||||
export const LAT_MIN = -90;
|
||||
export const LAT_RENDERED_MIN = -85;
|
||||
export const LAT_MAX = 90;
|
||||
export const LAT_RENDERED_MAX = 85;
|
||||
export const LNG_MIN = -270;
|
||||
export const LNG_MAX = 270;
|
24
packages/map/src/utils/Draw/doubleClickZoom.js
Normal file
24
packages/map/src/utils/Draw/doubleClickZoom.js
Normal file
@ -0,0 +1,24 @@
|
||||
const doubleClickZoom = {
|
||||
enable(ctx) {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
!ctx.map ||
|
||||
!ctx.map.doubleClickZoom ||
|
||||
!ctx._ctx ||
|
||||
!ctx._ctx.store ||
|
||||
!ctx._ctx.store.getInitialConfigValue
|
||||
)
|
||||
return;
|
||||
if (!ctx._ctx.store.getInitialConfigValue('doubleClickZoom')) return;
|
||||
ctx.map.doubleClickZoom.enable();
|
||||
}, 0);
|
||||
},
|
||||
disable(ctx) {
|
||||
setTimeout(() => {
|
||||
if (!ctx.map || !ctx.map.doubleClickZoom) return;
|
||||
ctx.map.doubleClickZoom.disable();
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
|
||||
export default doubleClickZoom;
|
69
packages/map/src/utils/Draw/drawCircleMode.draw.js
Normal file
69
packages/map/src/utils/Draw/drawCircleMode.draw.js
Normal file
@ -0,0 +1,69 @@
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import doubleClickZoom from './doubleClickZoom';
|
||||
import * as turf from '@turf/turf';
|
||||
const { circle, distance, helpers: turfHelpers } = turf;
|
||||
const drawCircleMode = { ...MapboxDraw.modes.draw_polygon };
|
||||
drawCircleMode.onSetup = function () {
|
||||
const polygon = this.newFeature({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
isCircle: true,
|
||||
center: [],
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [],
|
||||
},
|
||||
});
|
||||
|
||||
this.addFeature(polygon);
|
||||
|
||||
this.clearSelectedFeatures();
|
||||
doubleClickZoom.disable(this);
|
||||
// dragPan.disable(this);
|
||||
this.updateUIClasses({ mouse: 'add' });
|
||||
this.activateUIButton('Polygon');
|
||||
this.setActionableState({
|
||||
trash: true,
|
||||
});
|
||||
|
||||
return {
|
||||
polygon,
|
||||
currentVertexPosition: 0,
|
||||
};
|
||||
};
|
||||
drawCircleMode.onClick = drawCircleMode.onTap = function (state, e) {
|
||||
const currentCenter = state.polygon.properties.center;
|
||||
if (currentCenter.length === 0) {
|
||||
// dragPan.disable(this)
|
||||
state.polygon.properties.center = [e.lngLat.lng, e.lngLat.lat];
|
||||
} else {
|
||||
// dragPan.enable(this);
|
||||
return this.changeMode('simple_select', { featureIds: [state.polygon.id] });
|
||||
}
|
||||
};
|
||||
drawCircleMode.onDrag = drawCircleMode.onMouseMove = function (state, e) {
|
||||
const center = state.polygon.properties.center;
|
||||
if (center.length > 0) {
|
||||
const distanceInKm = distance(
|
||||
turfHelpers.point(center),
|
||||
turfHelpers.point([e.lngLat.lng, e.lngLat.lat]),
|
||||
{
|
||||
units: 'kilometers',
|
||||
}
|
||||
);
|
||||
const circleFeature = circle(center, distanceInKm);
|
||||
state.polygon.incomingCoords(circleFeature.geometry.coordinates);
|
||||
state.polygon.properties.radiusInKm = distanceInKm;
|
||||
state.polygon.properties.lastClickCoord = [e.lngLat.lng, e.lngLat.lat];
|
||||
}
|
||||
};
|
||||
//它决定当前 Drew 数据存储中的哪些特性将在地图上呈现。
|
||||
//所有传递给“显示”的特性都将被渲染,因此可以为每个内部特性传递多个显示特性。
|
||||
//有关如何制作显示特性的建议,请参阅‘ styling-pull’in‘ API.md’
|
||||
drawCircleMode.toDisplayFeatures = function (state, geojson, display) {
|
||||
const isActivePolygon = geojson.properties.id === state.polygon.id;
|
||||
geojson.properties.active = isActivePolygon ? 'true' : 'false';
|
||||
display(geojson);
|
||||
};
|
||||
export default drawCircleMode;
|
131
packages/map/src/utils/Draw/drawDirectMode.draw.js
Normal file
131
packages/map/src/utils/Draw/drawDirectMode.draw.js
Normal file
@ -0,0 +1,131 @@
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import createSupplementaryPoints from '@mapbox/mapbox-gl-draw';
|
||||
import moveFeatures from '@mapbox/mapbox-gl-draw';
|
||||
import constrainFeatureMovement from '@mapbox/mapbox-gl-draw';
|
||||
import createVertex from '@mapbox/mapbox-gl-draw';
|
||||
import * as turf from '@turf/turf';
|
||||
const { constants } = MapboxDraw;
|
||||
const { circle, distance, helpers: turfHelpers } = turf;
|
||||
|
||||
function createSupplementaryPointsForCircle(geojson) {
|
||||
const { properties, geometry } = geojson;
|
||||
|
||||
if (!properties.user_isCircle) return null;
|
||||
|
||||
const supplementaryPoints = [];
|
||||
const vertices = geometry.coordinates[0].slice(0, -1);
|
||||
for (let index = 0; index < vertices.length; index += Math.round(vertices.length / 4)) {
|
||||
supplementaryPoints.push(createVertex(properties.id, vertices[index], `0.${index}`, false));
|
||||
}
|
||||
return supplementaryPoints;
|
||||
}
|
||||
const drawDirectMode = { ...MapboxDraw.modes.direct_select };
|
||||
|
||||
drawDirectMode.dragFeature = function (state, e, delta) {
|
||||
moveFeatures(this.getSelected(), delta);
|
||||
this.getSelected()
|
||||
.filter((feature) => feature.properties.isCircle)
|
||||
.map((circle) => circle.properties.center)
|
||||
.forEach((center) => {
|
||||
center[0] += delta.lng;
|
||||
center[1] += delta.lat;
|
||||
});
|
||||
state.dragMoveLocation = e.lngLat;
|
||||
};
|
||||
|
||||
drawDirectMode.dragVertex = function (state, e, delta) {
|
||||
//圆处理
|
||||
if (state.feature.properties.isCircle) {
|
||||
const center = state.feature.properties.center;
|
||||
const movedVertex = [e.lngLat.lng, e.lngLat.lat];
|
||||
const radius = distance(turfHelpers.point(center), turfHelpers.point(movedVertex), {
|
||||
units: 'kilometers',
|
||||
});
|
||||
const circleFeature = circle(center, radius);
|
||||
state.feature.incomingCoords(circleFeature.geometry.coordinates);
|
||||
state.feature.properties.radiusInKm = radius;
|
||||
return;
|
||||
}
|
||||
//矩形处理
|
||||
if (state.feature.properties.isRect) {
|
||||
state.selectedCoordPaths.forEach((coordPath) => {
|
||||
const selectCoord = state.feature.getCoordinate(coordPath);
|
||||
//更新边缘2点
|
||||
const [featureIndex, coordIndex] = coordPath.split('.');
|
||||
const coordinates = state.feature.getCoordinates()[featureIndex];
|
||||
//对立点判断
|
||||
const coordPosMap = {
|
||||
1: '3',
|
||||
2: '0',
|
||||
3: '1',
|
||||
0: '2',
|
||||
};
|
||||
const mapCoord = state.feature.getCoordinate(`${featureIndex}.${coordPosMap[coordIndex]}`);
|
||||
//如果对立点和坐标x||y 一致 则返回
|
||||
if (mapCoord[0] === e.lngLat.lng || mapCoord[1] === e.lngLat.lat) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const coord = coordinates[i];
|
||||
if (coordIndex == i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (coord[0] === selectCoord[0]) {
|
||||
state.feature.updateCoordinate(`${featureIndex}.${i}`, e.lngLat.lng, coord[1]);
|
||||
|
||||
continue;
|
||||
}
|
||||
if (coord[1] === selectCoord[1]) {
|
||||
state.feature.updateCoordinate(`${featureIndex}.${i}`, coord[0], e.lngLat.lat);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//更新拖拽的点
|
||||
state.feature.updateCoordinate(coordPath, e.lngLat.lng, e.lngLat.lat);
|
||||
});
|
||||
return;
|
||||
}
|
||||
//其他走回默认
|
||||
const selectedCoords = state.selectedCoordPaths.map((coordPath) =>
|
||||
state.feature.getCoordinate(coordPath)
|
||||
);
|
||||
const selectedCoordPoints = selectedCoords.map((coords) => ({
|
||||
type: constants.geojsonTypes.FEATURE,
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: constants.geojsonTypes.POINT,
|
||||
coordinates: coords,
|
||||
},
|
||||
}));
|
||||
|
||||
const constrainedDelta = constrainFeatureMovement(selectedCoordPoints, delta);
|
||||
for (let i = 0; i < selectedCoords.length; i++) {
|
||||
const coord = selectedCoords[i];
|
||||
state.feature.updateCoordinate(
|
||||
state.selectedCoordPaths[i],
|
||||
coord[0] + constrainedDelta.lng,
|
||||
coord[1] + constrainedDelta.lat
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
drawDirectMode.toDisplayFeatures = function (state, geojson, push) {
|
||||
if (state.featureId === geojson.properties.id) {
|
||||
geojson.properties.active = constants.activeStates.ACTIVE;
|
||||
push(geojson);
|
||||
const supplementaryPoints = geojson.properties.user_isCircle
|
||||
? createSupplementaryPointsForCircle(geojson)
|
||||
: createSupplementaryPoints(geojson, {
|
||||
map: this.map,
|
||||
midpoints: true,
|
||||
selectedPaths: state.selectedCoordPaths,
|
||||
});
|
||||
supplementaryPoints.forEach(push);
|
||||
} else {
|
||||
geojson.properties.active = constants.activeStates.INACTIVE;
|
||||
push(geojson);
|
||||
}
|
||||
this.fireActionable(state);
|
||||
};
|
||||
export default drawDirectMode;
|
165
packages/map/src/utils/Draw/drawLineSelectMode.draw.js
Normal file
165
packages/map/src/utils/Draw/drawLineSelectMode.draw.js
Normal file
@ -0,0 +1,165 @@
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import doubleClickZoom from './doubleClickZoom';
|
||||
const { constants, lib } = MapboxDraw;
|
||||
const drawLineSelectMode = {
|
||||
//当模式启动时,这个函数将被调用。
|
||||
//draw.changeMode(drawLineSelectMode,params}); 切换模式时,params = opts
|
||||
//返回的值应该是一个对象,并将传递给所有其他生命周期函数
|
||||
onSetup: function (opts) {
|
||||
const featureId = opts.featureId;
|
||||
let line;
|
||||
let currentVertexPosition = 0;
|
||||
let direction = 'forward';
|
||||
if (featureId) {
|
||||
line = this.getFeature(featureId);
|
||||
if (!line) {
|
||||
throw new Error('Could not find a feature with the provided featureId');
|
||||
}
|
||||
let from = opts.from;
|
||||
if (from && from.type === 'Feature' && from.geometry && from.geometry.type === 'Point') {
|
||||
from = from.geometry;
|
||||
}
|
||||
if (from && from.type === 'Point' && from.coordinates && from.coordinates.length === 2) {
|
||||
from = from.coordinates;
|
||||
}
|
||||
if (!from || !Array.isArray(from)) {
|
||||
throw new Error(
|
||||
'Please use the `from` property to indicate which point to continue the line from'
|
||||
);
|
||||
}
|
||||
const lastCoord = line.coordinates.length - 1;
|
||||
if (
|
||||
line.coordinates[lastCoord][0] === from[0] &&
|
||||
line.coordinates[lastCoord][1] === from[1]
|
||||
) {
|
||||
currentVertexPosition = lastCoord + 1;
|
||||
line.addCoordinate(currentVertexPosition, ...line.coordinates[lastCoord]);
|
||||
} else if (line.coordinates[0][0] === from[0] && line.coordinates[0][1] === from[1]) {
|
||||
direction = 'backwards';
|
||||
currentVertexPosition = 0;
|
||||
line.addCoordinate(currentVertexPosition, ...line.coordinates[0]);
|
||||
} else {
|
||||
throw new Error(
|
||||
'`from` should match the point at either the start or the end of the provided LineString'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
line = this.newFeature({
|
||||
type: constants.geojsonTypes.FEATURE,
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: constants.geojsonTypes.LINE_STRING,
|
||||
coordinates: [],
|
||||
},
|
||||
});
|
||||
currentVertexPosition = 0;
|
||||
this.addFeature(line);
|
||||
}
|
||||
this.clearSelectedFeatures();
|
||||
doubleClickZoom.disable(this);
|
||||
this.updateUIClasses({ mouse: 'add' }); //"+"
|
||||
this.setActionableState({
|
||||
//添加地图事件'draw.actionable'
|
||||
trash: true,
|
||||
combineFeatures: false,
|
||||
uncombineFeatures: false,
|
||||
});
|
||||
return {
|
||||
line,
|
||||
currentVertexPosition,
|
||||
direction,
|
||||
};
|
||||
},
|
||||
clickAnywhere: function (state, e) {
|
||||
if (
|
||||
(state.currentVertexPosition > 0 &&
|
||||
lib.isEventAtCoordinates(e, state.line.coordinates[state.currentVertexPosition - 1])) ||
|
||||
(state.direction === 'backwards' &&
|
||||
lib.isEventAtCoordinates(e, state.line.coordinates[state.currentVertexPosition + 1]))
|
||||
) {
|
||||
return this.changeMode(constants.modes.SIMPLE_SELECT, {
|
||||
featureIds: [state.line.id],
|
||||
});
|
||||
}
|
||||
|
||||
this.updateUIClasses({ mouse: constants.cursors.ADD });
|
||||
state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat);
|
||||
if (state.direction === 'forward') {
|
||||
state.currentVertexPosition++;
|
||||
state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat);
|
||||
} else {
|
||||
state.line.addCoordinate(0, e.lngLat.lng, e.lngLat.lat);
|
||||
}
|
||||
},
|
||||
clickOnVertex: function (state) {
|
||||
//点击在已经有点上
|
||||
state.line.properties.name = 'line_select';
|
||||
return this.changeMode(constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] });
|
||||
},
|
||||
onMouseMove: function (state, e) {
|
||||
state.line.updateCoordinate(state.currentVertexPosition, e.lngLat.lng, e.lngLat.lat);
|
||||
if (lib.CommonSelectors.isVertex(e)) {
|
||||
//再次将鼠标放在之前已经有的点上,改变鼠标样式
|
||||
this.updateUIClasses({ mouse: constants.cursors.POINTER });
|
||||
}
|
||||
},
|
||||
onClick: function (state, e) {
|
||||
//再次将鼠标放在之前已经有的点上
|
||||
if (lib.CommonSelectors.isVertex(e)) return this.clickOnVertex(state, e);
|
||||
this.clickAnywhere(state, e);
|
||||
},
|
||||
onKeyUp: function (state, e) {
|
||||
if (lib.CommonSelectors.isEnterKey(e)) {
|
||||
this.changeMode(constants.modes.SIMPLE_SELECT, { featureIds: [state.line.id] });
|
||||
} else if (lib.CommonSelectors.isEscapeKey(e)) {
|
||||
this.deleteFeature([state.line.id], { silent: true });
|
||||
this.changeMode(constants.modes.SIMPLE_SELECT);
|
||||
}
|
||||
},
|
||||
onStop: function (state) {
|
||||
doubleClickZoom.enable(this);
|
||||
if (this.getFeature(state.line.id) === undefined) return;
|
||||
state.line.removeCoordinate(`${state.currentVertexPosition}`); //双击停止时,最后两个点位是一样的
|
||||
if (state.line.isValid()) {
|
||||
//'draw.create'
|
||||
this.map.fire(constants.events.CREATE, {
|
||||
features: [state.line.toGeoJSON()],
|
||||
});
|
||||
} else {
|
||||
this.deleteFeature([state.line.id], { silent: true });
|
||||
this.changeMode(constants.modes.SIMPLE_SELECT, {}, { silent: true });
|
||||
}
|
||||
},
|
||||
onTrash: function (state) {
|
||||
this.deleteFeature([state.line.id], { silent: true });
|
||||
this.changeMode(constants.modes.SIMPLE_SELECT);
|
||||
},
|
||||
//它决定当前 Drew 数据存储中的哪些特性将在地图上呈现。
|
||||
//所有传递给“显示”的特性都将被渲染,因此可以为每个内部特性传递多个显示特性。
|
||||
//有关如何制作显示特性的建议,请参阅‘ styling-pull’in‘ API.md’
|
||||
toDisplayFeatures: function (state, geojson, display) {
|
||||
const isActiveLine = geojson.properties.id === state.line.id;
|
||||
geojson.properties.active = isActiveLine
|
||||
? constants.activeStates.ACTIVE
|
||||
: constants.activeStates.INACTIVE;
|
||||
if (!isActiveLine) {
|
||||
display(geojson);
|
||||
return;
|
||||
}
|
||||
if (geojson.geometry.coordinates.length < 2) return;
|
||||
geojson.properties.meta = 'line_distance';
|
||||
geojson.properties.name = 'line_distance';
|
||||
display(
|
||||
lib.createVertex(
|
||||
state.line.id,
|
||||
geojson.geometry.coordinates[
|
||||
state.direction === 'forward' ? geojson.geometry.coordinates.length - 2 : 1
|
||||
],
|
||||
`${state.direction === 'forward' ? geojson.geometry.coordinates.length - 2 : 1}`,
|
||||
false
|
||||
)
|
||||
);
|
||||
display(geojson);
|
||||
},
|
||||
};
|
||||
export default drawLineSelectMode;
|
125
packages/map/src/utils/Draw/drawRectMode.draw.js
Normal file
125
packages/map/src/utils/Draw/drawRectMode.draw.js
Normal file
@ -0,0 +1,125 @@
|
||||
const doubleClickZoom = {
|
||||
enable: (ctx) => {
|
||||
setTimeout(() => {
|
||||
// First check we've got a map and some context.
|
||||
if (
|
||||
!ctx.map ||
|
||||
!ctx.map.doubleClickZoom ||
|
||||
!ctx._ctx ||
|
||||
!ctx._ctx.store ||
|
||||
!ctx._ctx.store.getInitialConfigValue
|
||||
)
|
||||
return;
|
||||
// Now check initial state wasn't false (we leave it disabled if so)
|
||||
if (!ctx._ctx.store.getInitialConfigValue('doubleClickZoom')) return;
|
||||
ctx.map.doubleClickZoom.enable();
|
||||
}, 0);
|
||||
},
|
||||
disable(ctx) {
|
||||
setTimeout(() => {
|
||||
if (!ctx.map || !ctx.map.doubleClickZoom) return;
|
||||
// Always disable here, as it's necessary in some cases.
|
||||
ctx.map.doubleClickZoom.disable();
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
|
||||
const DrawRectangle = {
|
||||
// When the mode starts this function will be called.
|
||||
onSetup: function () {
|
||||
const rectangle = this.newFeature({
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
isRect: true,
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [[]],
|
||||
},
|
||||
});
|
||||
this.addFeature(rectangle);
|
||||
this.clearSelectedFeatures();
|
||||
doubleClickZoom.disable(this);
|
||||
this.updateUIClasses({ mouse: 'add' });
|
||||
this.setActionableState({
|
||||
trash: true,
|
||||
});
|
||||
return {
|
||||
rectangle,
|
||||
};
|
||||
},
|
||||
// support mobile taps
|
||||
onTap: function (state, e) {
|
||||
// emulate 'move mouse' to update feature coords
|
||||
if (state.startPoint) this.onMouseMove(state, e);
|
||||
// emulate onClick
|
||||
this.onClick(state, e);
|
||||
},
|
||||
// Whenever a user clicks on the map, Draw will call `onClick`
|
||||
onClick: function (state, e) {
|
||||
// if state.startPoint exist, means its second click
|
||||
//change to simple_select mode
|
||||
if (
|
||||
state.startPoint &&
|
||||
state.startPoint[0] !== e.lngLat.lng &&
|
||||
state.startPoint[1] !== e.lngLat.lat
|
||||
) {
|
||||
this.updateUIClasses({ mouse: 'pointer' });
|
||||
state.endPoint = [e.lngLat.lng, e.lngLat.lat];
|
||||
this.changeMode('simple_select', { featuresId: state.rectangle.id });
|
||||
}
|
||||
// on first click, save clicked point coords as starting for rectangle
|
||||
const startPoint = [e.lngLat.lng, e.lngLat.lat];
|
||||
state.startPoint = startPoint;
|
||||
},
|
||||
onMouseMove: function (state, e) {
|
||||
// if startPoint, update the feature coordinates, using the bounding box concept
|
||||
// we are simply using the startingPoint coordinates and the current Mouse Position
|
||||
// coordinates to calculate the bounding box on the fly, which will be our rectangle
|
||||
if (state.startPoint) {
|
||||
state.rectangle.updateCoordinate('0.0', state.startPoint[0], state.startPoint[1]); //minX, minY - the starting point
|
||||
state.rectangle.updateCoordinate('0.1', e.lngLat.lng, state.startPoint[1]); // maxX, minY
|
||||
state.rectangle.updateCoordinate('0.2', e.lngLat.lng, e.lngLat.lat); // maxX, maxY
|
||||
state.rectangle.updateCoordinate('0.3', state.startPoint[0], e.lngLat.lat); // minX,maxY
|
||||
state.rectangle.updateCoordinate('0.4', state.startPoint[0], state.startPoint[1]); //minX,minY - ending point (equals to starting point)
|
||||
}
|
||||
},
|
||||
// Whenever a user clicks on a key while focused on the map, it will be sent here
|
||||
onKeyUp: function (state, e) {
|
||||
if (e.keyCode === 27) return this.changeMode('simple_select');
|
||||
},
|
||||
onStop: function (state) {
|
||||
doubleClickZoom.enable(this);
|
||||
this.updateUIClasses({ mouse: 'none' });
|
||||
this.activateUIButton();
|
||||
|
||||
// check to see if we've deleted this feature
|
||||
if (this.getFeature(state.rectangle.id) === undefined) return;
|
||||
|
||||
//remove last added coordinate
|
||||
state.rectangle.removeCoordinate('0.4');
|
||||
if (state.rectangle.isValid()) {
|
||||
this.map.fire('draw.create', {
|
||||
features: [state.rectangle.toGeoJSON()],
|
||||
});
|
||||
} else {
|
||||
this.deleteFeature([state.rectangle.id], { silent: true });
|
||||
this.changeMode('simple_select', {}, { silent: true });
|
||||
}
|
||||
},
|
||||
toDisplayFeatures: function (state, geojson, display) {
|
||||
const isActivePolygon = geojson.properties.id === state.rectangle.id;
|
||||
geojson.properties.active = isActivePolygon ? 'true' : 'false';
|
||||
if (!isActivePolygon) return display(geojson);
|
||||
|
||||
// Only render the rectangular polygon if it has the starting point
|
||||
if (!state.startPoint) return;
|
||||
return display(geojson);
|
||||
},
|
||||
onTrash: function (state) {
|
||||
this.deleteFeature([state.rectangle.id], { silent: true });
|
||||
this.changeMode('simple_select');
|
||||
},
|
||||
};
|
||||
|
||||
export default DrawRectangle;
|
86
packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js
Normal file
86
packages/map/src/utils/Draw/drawSimpleSelectMode.draw.js
Normal file
@ -0,0 +1,86 @@
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import createSupplementaryPoints from '@mapbox/mapbox-gl-draw';
|
||||
import moveFeatures from '@mapbox/mapbox-gl-draw';
|
||||
import createVertex from '@mapbox/mapbox-gl-draw';
|
||||
import { lineToPoly } from './utils';
|
||||
const { constants } = MapboxDraw;
|
||||
|
||||
function createSupplementaryPointsForCircle(geojson) {
|
||||
const { properties, geometry } = geojson;
|
||||
|
||||
if (!properties.user_isCircle) return null;
|
||||
|
||||
const supplementaryPoints = [];
|
||||
const vertices = geometry.coordinates[0].slice(0, -1);
|
||||
for (let index = 0; index < vertices.length; index += Math.round(vertices.length / 4)) {
|
||||
supplementaryPoints.push(createVertex(properties.id, vertices[index], `0.${index}`, false));
|
||||
}
|
||||
return supplementaryPoints;
|
||||
}
|
||||
const drawSimpleSelectMode = { ...MapboxDraw.modes.simple_select };
|
||||
|
||||
drawSimpleSelectMode.dragMove = function (state, e) {
|
||||
// Dragging when drag move is enabled
|
||||
state.dragMoving = true;
|
||||
e.originalEvent.stopPropagation();
|
||||
|
||||
const delta = {
|
||||
lng: e.lngLat.lng - state.dragMoveLocation.lng,
|
||||
lat: e.lngLat.lat - state.dragMoveLocation.lat,
|
||||
};
|
||||
|
||||
moveFeatures(this.getSelected(), delta);
|
||||
|
||||
this.getSelected()
|
||||
.filter((feature) => feature.properties.isCircle)
|
||||
.map((circle) => circle.properties.center)
|
||||
.forEach((center) => {
|
||||
center[0] += delta.lng;
|
||||
center[1] += delta.lat;
|
||||
});
|
||||
|
||||
state.dragMoveLocation = e.lngLat;
|
||||
};
|
||||
|
||||
drawSimpleSelectMode.toDisplayFeatures = function (state, geojson, display) {
|
||||
geojson.properties.active = this.isSelected(geojson.properties.id)
|
||||
? constants.activeStates.ACTIVE
|
||||
: constants.activeStates.INACTIVE;
|
||||
|
||||
if (geojson.properties.user_name === 'line_select') {
|
||||
const union = lineToPoly(geojson);
|
||||
display(union);
|
||||
}
|
||||
display(geojson);
|
||||
this.fireActionable();
|
||||
//如果是线 每次都创建点
|
||||
if (
|
||||
geojson?.properties.active !== constants.activeStates.ACTIVE &&
|
||||
geojson.geometry.type === constants.geojsonTypes.LINE_STRING &&
|
||||
geojson.properties.user_name !== 'line_select'
|
||||
) {
|
||||
const points = createSupplementaryPoints(geojson);
|
||||
points.forEach(display);
|
||||
}
|
||||
|
||||
if (
|
||||
geojson.properties.active !== constants.activeStates.ACTIVE ||
|
||||
geojson.geometry.type === constants.geojsonTypes.POINT
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let supplementaryPoints;
|
||||
if (geojson.properties.user_isCircle) {
|
||||
supplementaryPoints = createSupplementaryPointsForCircle(geojson);
|
||||
} else {
|
||||
supplementaryPoints = createSupplementaryPoints(geojson);
|
||||
}
|
||||
supplementaryPoints.forEach(display);
|
||||
|
||||
// if(geojson.properties.)
|
||||
};
|
||||
|
||||
//阻止框选图形拖拽
|
||||
drawSimpleSelectMode.onTap = drawSimpleSelectMode.onClick = function () {};
|
||||
export default drawSimpleSelectMode;
|
14
packages/map/src/utils/Draw/drawStaticMode.draw.js
Normal file
14
packages/map/src/utils/Draw/drawStaticMode.draw.js
Normal file
@ -0,0 +1,14 @@
|
||||
import doubleClickZoom from './doubleClickZoom';
|
||||
|
||||
var StaticMode = {};
|
||||
|
||||
StaticMode.onSetup = function () {
|
||||
this.setActionableState(); // default actionable state is false for all actions
|
||||
doubleClickZoom.disable(this); //静态model 不运行双击
|
||||
return {};
|
||||
};
|
||||
|
||||
StaticMode.toDisplayFeatures = function (state, geojson, display) {
|
||||
display(geojson);
|
||||
};
|
||||
export default StaticMode;
|
144
packages/map/src/utils/Draw/drawStyle.ts
Normal file
144
packages/map/src/utils/Draw/drawStyle.ts
Normal file
@ -0,0 +1,144 @@
|
||||
//自定义画框样式
|
||||
const mapboxDrawStyle = [
|
||||
// ACTIVE (being drawn)
|
||||
// line stroke
|
||||
|
||||
{
|
||||
id: 'gl-draw-line',
|
||||
type: 'line',
|
||||
filter: [
|
||||
'all',
|
||||
['==', '$type', 'LineString'],
|
||||
['!=', 'mode', 'static'],
|
||||
['==', 'active', 'true'],
|
||||
],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': 'rgba(246,67,72,1)',
|
||||
'line-dasharray': [0.2, 2],
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-line-not-active',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'LineString'], ['!=', 'active', 'true']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': 'rgba(246,67,72,1)',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
|
||||
// polygon fill
|
||||
{
|
||||
id: 'gl-draw-polygon-fill',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'fill-color': 'rgba(246,67,72,0.2)',
|
||||
'fill-outline-color': 'rgba(246,67,72,0.2)',
|
||||
},
|
||||
},
|
||||
// polygon outline stroke
|
||||
// This doesn't style the first edge of the polygon, which uses the line stroke styling instead
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-active',
|
||||
type: 'line',
|
||||
filter: [
|
||||
'all',
|
||||
['==', '$type', 'Polygon'],
|
||||
['!=', 'mode', 'static'],
|
||||
['==', 'active', 'false'],
|
||||
],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': 'rgba(246,67,72,1)',
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-active-select',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static'], ['==', 'active', 'true']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': 'rgba(246,67,72,1)',
|
||||
'line-dasharray': [0.2, 2],
|
||||
'line-width': 2,
|
||||
},
|
||||
},
|
||||
// vertex point halos
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-halo-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#FFF',
|
||||
},
|
||||
},
|
||||
// vertex points
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'circle-radius': 3,
|
||||
'circle-color': 'rgba(246,67,72,1)',
|
||||
},
|
||||
},
|
||||
|
||||
// INACTIVE (static, already drawn)
|
||||
// line stroke
|
||||
{
|
||||
id: 'gl-draw-line-static',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'LineString'], ['==', 'mode', 'static']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#E13F3F',
|
||||
'line-width': 3,
|
||||
},
|
||||
},
|
||||
// polygon fill
|
||||
{
|
||||
id: 'gl-draw-polygon-fill-static',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['==', 'mode', 'static']],
|
||||
paint: {
|
||||
'fill-color': 'rgba(225, 63, 63, 0.2)',
|
||||
'fill-outline-color': 'rgba(225, 63, 63, 0.2)',
|
||||
},
|
||||
},
|
||||
// polygon outline
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-static',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['==', 'mode', 'static']],
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
paint: {
|
||||
'line-color': '#E13F3F',
|
||||
'line-width': 3,
|
||||
},
|
||||
},
|
||||
];
|
||||
export default mapboxDrawStyle;
|
74
packages/map/src/utils/Draw/index.ts
Normal file
74
packages/map/src/utils/Draw/index.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
import type { DrawModes } from '@mapbox/mapbox-gl-draw';
|
||||
import type { MapboxMap } from 'react-map-gl';
|
||||
import drawLineSelectMode from './drawLineSelectMode.draw.js';
|
||||
import drawCircleMode from './drawCircleMode.draw.js';
|
||||
import drawRectMode from './drawRectMode.draw.js';
|
||||
import drawDirectMode from './drawDirectMode.draw.js';
|
||||
import drawSimpleSelectMode from './drawSimpleSelectMode.draw.js';
|
||||
import drawStaticMode from './drawStaticMode.draw.js';
|
||||
import mapboxDrawStyle from './drawStyle';
|
||||
|
||||
interface DrawControl {
|
||||
modes?: DrawModes;
|
||||
}
|
||||
function noop(): void {
|
||||
/* do nothing */
|
||||
}
|
||||
//mapbox-gl-draw api
|
||||
//https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/MODES.md#life-cycle-functions
|
||||
|
||||
function drawControl(
|
||||
map: MapboxMap,
|
||||
params: DrawControl = {},
|
||||
defaultMode = 'static',
|
||||
onDrawCreate = () => {}
|
||||
) {
|
||||
const { modes: paramModes = {} } = params;
|
||||
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* MapboxDraw 里面有的
|
||||
* draw_line_string 画线
|
||||
* draw_polygon 多边形
|
||||
* draw_point
|
||||
* simple_select
|
||||
* direct_select
|
||||
*
|
||||
* 需要自定义的
|
||||
* draw_line_select; 路径框选1
|
||||
* direct_select;
|
||||
* simple_select;
|
||||
* draw_circle;1
|
||||
* draw_rect;1
|
||||
*
|
||||
* 用MapboxDraw的
|
||||
* draw_polygon 多边形
|
||||
* draw_line_string 画线
|
||||
*/
|
||||
|
||||
const draw = new MapboxDraw({
|
||||
defaultMode: defaultMode,
|
||||
displayControlsDefault: false, // 取消默认的按钮
|
||||
styles: mapboxDrawStyle,
|
||||
modes: {
|
||||
...MapboxDraw.modes,
|
||||
draw_line_select: drawLineSelectMode,
|
||||
draw_line_string: drawLineSelectMode,
|
||||
draw_circle: drawCircleMode,
|
||||
draw_rect: drawRectMode,
|
||||
direct_select: drawDirectMode,
|
||||
simple_select: drawSimpleSelectMode,
|
||||
static: drawStaticMode,
|
||||
...paramModes,
|
||||
},
|
||||
});
|
||||
map.addControl(draw);
|
||||
map.on('draw.create', onDrawCreate || noop);
|
||||
return draw;
|
||||
}
|
||||
|
||||
export default drawControl;
|
162
packages/map/src/utils/Draw/utils.js
Normal file
162
packages/map/src/utils/Draw/utils.js
Normal file
@ -0,0 +1,162 @@
|
||||
import * as turf from '@turf/turf';
|
||||
import union from '@turf/union';
|
||||
|
||||
// 获取2个点的距离
|
||||
export const getDistance = (point1, point2) => {
|
||||
const { x: x1, y: y1 } = point1;
|
||||
const { x: x2, y: y2 } = point2;
|
||||
return Math.abs(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)));
|
||||
};
|
||||
|
||||
//获取两点(格式例如:[120,30])间物理像素距离
|
||||
export function getPixelDistance(map, pointA, pointB) {
|
||||
const pointAPixel = map.transform.locationPoint(pointA); // A点的像素坐标位置
|
||||
const pointBPixel = map.transform.locationPoint(pointB); // B点的像素坐标位置
|
||||
// A、B两点的像素坐标距离
|
||||
return getDistance(pointAPixel, pointBPixel);
|
||||
}
|
||||
|
||||
//判断是否是0值 给一个偏移量
|
||||
export const isZero = (floatValue) => {
|
||||
return floatValue > -0.00001 && floatValue < 0.00001;
|
||||
};
|
||||
|
||||
//判断2个向量是否平行
|
||||
export const isParallel = (vectorA, vectorB) => {
|
||||
const { x: x1, y: y1 } = vectorA;
|
||||
const { x: x2, y: y2 } = vectorB;
|
||||
return isZero(x1 * y2 - y1 * x2);
|
||||
};
|
||||
|
||||
//判断一个点是否在线段上 判断向量叉乘是否为0 且x坐标在线段2端点间
|
||||
export const isPointOnLine = (vectorA, path) => {
|
||||
const [vectorB, vectorC] = path;
|
||||
|
||||
//与端点重合
|
||||
if (
|
||||
(vectorA.x === vectorB.x && vectorA.y === vectorB.y) ||
|
||||
(vectorA.x === vectorC.x && vectorA.y === vectorC.y)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//在同一竖直方向,线段竖直,点在该线段所在的直线上
|
||||
if (isZero(vectorB.x - vectorC.x) && isZero(vectorB.x - vectorA.x)) {
|
||||
//已判定点在直线上,若点在两端点中间,即点在线段上
|
||||
if (
|
||||
(vectorA.y < vectorC.y && vectorA.y > vectorB.y) ||
|
||||
(vectorA.y < vectorB.y && vectorA.y > vectorC.y)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//在同一水平方向
|
||||
if (isZero(vectorB.y - vectorC.y) && isZero(vectorB.y - vectorA.y)) {
|
||||
if (
|
||||
(vectorA.x < vectorC.x && vectorA.x > vectorB.x) ||
|
||||
(vectorA.x < vectorB.x && vectorA.x > vectorC.x)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 线段倾斜,此时线段所在直线存在斜率
|
||||
// 点在直线上,AB与AC斜率相等,且有共同点A,此时AC与AB重合,即点A在直线BC上
|
||||
if (
|
||||
isZero(
|
||||
(vectorB.y - vectorA.y) / (vectorB.x - vectorA.x) -
|
||||
(vectorA.y - vectorC.y) / (vectorA.x - vectorC.x)
|
||||
)
|
||||
) {
|
||||
if (
|
||||
(vectorB.y - vectorA.y) * (vectorA.y - vectorC.y) > 0 &&
|
||||
(vectorB.x - vectorA.x) * (vectorA.x - vectorC.x) > 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//获取线段重合部分,如果重合返回合并后的线段 如果不重合 返回传入的线段
|
||||
//see https://blog.csdn.net/qq_39108767/article/details/81673921
|
||||
//see https://www.cnblogs.com/tuyang1129/p/9390376.html
|
||||
export const getLineCoincide = (path1, path2) => {
|
||||
let paths = [];
|
||||
const [vectorA, vectorB] = path1;
|
||||
const [vectorC, vectorD] = path2;
|
||||
|
||||
const isOnLineA = isPointOnLine(vectorA, [vectorC, vectorD]);
|
||||
const isOnLineB = isPointOnLine(vectorB, [vectorC, vectorD]);
|
||||
const isOnLineC = isPointOnLine(vectorC, [vectorA, vectorB]);
|
||||
const isOnLineD = isPointOnLine(vectorD, [vectorA, vectorB]);
|
||||
|
||||
const isCollinear = isParallel(
|
||||
{ x: vectorA.x - vectorB.x, y: vectorA.y - vectorB.y },
|
||||
{ x: vectorC.x - vectorD.x, y: vectorC.y - vectorD.y }
|
||||
);
|
||||
//下面6中情况代表合并
|
||||
if (isOnLineA && isOnLineC && isCollinear) {
|
||||
paths = [[vectorB, vectorD]];
|
||||
}
|
||||
if (isOnLineA && isOnLineD && isCollinear) {
|
||||
paths = [[vectorB, vectorC]];
|
||||
}
|
||||
if (isOnLineB && isOnLineC && isCollinear) {
|
||||
paths = [[vectorA, vectorD]];
|
||||
}
|
||||
if (isOnLineB && isOnLineD && isCollinear) {
|
||||
paths = [[vectorA, vectorC]];
|
||||
}
|
||||
if (isOnLineA && isOnLineB && isCollinear) {
|
||||
paths = [[vectorC, vectorD]];
|
||||
}
|
||||
if (isOnLineC && isOnLineD && isCollinear) {
|
||||
paths = [[vectorA, vectorB]];
|
||||
}
|
||||
//未匹配到上述情况,则没有重合 原样返回
|
||||
if (paths.length === 0) {
|
||||
paths = [path1, path2];
|
||||
}
|
||||
return paths;
|
||||
};
|
||||
|
||||
// 计算与纬线的角度,正方向向上
|
||||
const calcAng = (point, p) => (Math.atan2(point[1] - p[1], point[0] - p[0]) * 180) / Math.PI + 180;
|
||||
|
||||
// 多段折线转polygon,直线左右默认范围50米
|
||||
export const lineToPoly = (geojson, r = 50) => {
|
||||
const linesPolygon = [];
|
||||
const circlePolygon = [];
|
||||
for (let i = 0; i < geojson.geometry.coordinates.length - 1; i++) {
|
||||
const [pointA, pointB] = [geojson.geometry.coordinates[i], geojson.geometry.coordinates[i + 1]];
|
||||
const line = turf.lineString([pointA, pointB]);
|
||||
const ang = calcAng(pointA, pointB);
|
||||
// 与经线的夹角为偏移方向,右正左负, 右偏移-左偏移 = 180
|
||||
const translatedPolyA = turf.transformTranslate(line, r / 1000, -ang);
|
||||
const translatedPolyB = turf.transformTranslate(line, r / 1000, -ang + 180);
|
||||
const _line = turf.lineString([
|
||||
// 逆时针闭合
|
||||
...translatedPolyA.geometry.coordinates.reverse(),
|
||||
...translatedPolyB.geometry.coordinates,
|
||||
]);
|
||||
const linePolygon = turf.lineToPolygon(_line);
|
||||
linesPolygon.push(linePolygon);
|
||||
circlePolygon.push(turf.circle(pointA, r / 1000));
|
||||
if (i == geojson.geometry.coordinates.length - 2) {
|
||||
circlePolygon.push(turf.circle(pointB, r / 1000));
|
||||
}
|
||||
}
|
||||
let _union;
|
||||
try {
|
||||
//todo: 新版本union和老版本行为不一致 先用老版本 后续观察原因
|
||||
// _union = turf.union(...circlePolygon, ...linesPolygon);
|
||||
_union = union(...circlePolygon, ...linesPolygon);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return _union;
|
||||
};
|
Loading…
Reference in New Issue
Block a user