mirror of https://gitee.com/antv-l7/antv-l7
feat: 提供线图层偏移点位计算的通用方法 (#1293)
* feat: 增加线偏移点的计算 * style: lint style * feat: 支持对 greatcircle 的偏移点位的计算 * feat: 完善弧线偏移点的 featureId 过滤 * style: lint style * style: lint style * feat: 支持对普通线偏移点的计算支持 * feat: 增加线图层偏移点位计算的测试用例 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
This commit is contained in:
parent
02ac2ab4a1
commit
4de2369a1b
|
@ -0,0 +1,2 @@
|
|||
### Arc Line
|
||||
<code src="./arcline.tsx"></code>
|
|
@ -0,0 +1,139 @@
|
|||
// @ts-ignore
|
||||
import {
|
||||
LineLayer,
|
||||
Scene,
|
||||
Source,
|
||||
lineAtOffset,
|
||||
lineAtOffsetAsyc,
|
||||
PointLayer,
|
||||
} from '@antv/l7';
|
||||
// @ts-ignore
|
||||
import { GaodeMap } from '@antv/l7-maps';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [105, 32],
|
||||
zoom: 4,
|
||||
// pitch: 60
|
||||
}),
|
||||
});
|
||||
|
||||
const geoData = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.3,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[99.228515625, 37.43997405227057],
|
||||
[100.72265625, 27.994401411046148],
|
||||
],
|
||||
[
|
||||
[108.544921875, 37.71859032558816],
|
||||
[112.412109375, 32.84267363195431],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.8,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[120, 30],
|
||||
[120, 40],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const source = new Source(geoData);
|
||||
|
||||
// scene.on('zoom', e => console.log(e))
|
||||
|
||||
const layer = new LineLayer({ blend: 'normal' })
|
||||
.source(source)
|
||||
.size(1)
|
||||
.shape('arc')
|
||||
.color('#f00')
|
||||
.style({
|
||||
// thetaOffset: 'offset'
|
||||
// segmentNumber: 10,
|
||||
thetaOffset: 0.5,
|
||||
});
|
||||
|
||||
source.on('sourceUpdate', () => {
|
||||
const midPoints = lineAtOffset(source, {
|
||||
offset: 0.1,
|
||||
shape: 'arc',
|
||||
thetaOffset: 0.5,
|
||||
mapVersion: scene.getMapService().version,
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(10)
|
||||
.color('#ff0');
|
||||
scene.addLayer(point);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
// const midPoints = await lineAtOffsetAsyc(source, 0.1, 'arc', 'offset');
|
||||
const midPoints = await lineAtOffsetAsyc(source, {
|
||||
offset: 0.3,
|
||||
shape: 'arc',
|
||||
thetaOffset: 0.5,
|
||||
mapVersion: scene.getMapService().version,
|
||||
featureId: 1,
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(5)
|
||||
.color('#0f0')
|
||||
.style({
|
||||
opacity: 0.8,
|
||||
});
|
||||
scene.addLayer(point);
|
||||
})();
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addLayer(layer);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
### GreatCircle Line
|
||||
<code src="./greatcircleline.tsx"></code>
|
|
@ -0,0 +1,143 @@
|
|||
// @ts-ignore
|
||||
import {
|
||||
LineLayer,
|
||||
Scene,
|
||||
Source,
|
||||
lineAtOffset,
|
||||
lineAtOffsetAsyc,
|
||||
PointLayer,
|
||||
} from '@antv/l7';
|
||||
// @ts-ignore
|
||||
import { GaodeMap } from '@antv/l7-maps';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [105, 32],
|
||||
zoom: 4,
|
||||
// pitch: 60
|
||||
}),
|
||||
});
|
||||
|
||||
const geoData = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.3,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[99.228515625, 37.43997405227057],
|
||||
[100.72265625, 27.994401411046148],
|
||||
],
|
||||
[
|
||||
[108.544921875, 37.71859032558816],
|
||||
[112.412109375, 32.84267363195431],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.8,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[120, 30],
|
||||
[120, 40],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const source = new Source(geoData);
|
||||
|
||||
// scene.on('zoom', e => console.log(e))
|
||||
|
||||
const layer = new LineLayer({ blend: 'normal' })
|
||||
.source(source)
|
||||
.size(1)
|
||||
.shape('greatcircle')
|
||||
.color('#f00')
|
||||
.style({
|
||||
// thetaOffset: 'offset'
|
||||
// segmentNumber: 10,
|
||||
// thetaOffset: 0.5,
|
||||
});
|
||||
|
||||
source.on('sourceUpdate', () => {
|
||||
// console.log(scene.getMapService().version)
|
||||
// const midPoints = lineAtOffset(source, 0.3, 'arc', 'offset');
|
||||
const midPoints = lineAtOffset(source, {
|
||||
offset: 2 / 30,
|
||||
shape: 'greatcircle',
|
||||
// thetaOffset: 0.5,
|
||||
mapVersion: scene.getMapService().version,
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(10)
|
||||
.color('#ff0');
|
||||
scene.addLayer(point);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
// const midPoints = await lineAtOffsetAsyc(source, 0.1, 'arc', 'offset');
|
||||
const midPoints = await lineAtOffsetAsyc(source, {
|
||||
// offset: 12.681/30,
|
||||
// offset: 12/31,
|
||||
// offset: 48/186,
|
||||
offset: 0.3,
|
||||
shape: 'greatcircle',
|
||||
// thetaOffset: 0.5,
|
||||
mapVersion: scene.getMapService().version,
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(5)
|
||||
.color('#0f0')
|
||||
.style({
|
||||
opacity: 0.8,
|
||||
});
|
||||
scene.addLayer(point);
|
||||
})();
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addLayer(layer);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
### Line
|
||||
<code src="./line.tsx"></code>
|
|
@ -0,0 +1,138 @@
|
|||
// @ts-ignore
|
||||
import {
|
||||
LineLayer,
|
||||
Scene,
|
||||
Source,
|
||||
lineAtOffset,
|
||||
lineAtOffsetAsyc,
|
||||
PointLayer,
|
||||
} from '@antv/l7';
|
||||
// @ts-ignore
|
||||
import { GaodeMap } from '@antv/l7-maps';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [105, 32],
|
||||
zoom: 4,
|
||||
// pitch: 60
|
||||
}),
|
||||
});
|
||||
|
||||
const geoData = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.3,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[99.228515625, 37.43997405227057],
|
||||
[100.72265625, 27.994401411046148],
|
||||
[110, 27.994401411046148],
|
||||
[110, 25],
|
||||
[100, 25],
|
||||
],
|
||||
[
|
||||
[108.544921875, 37.71859032558816],
|
||||
[112.412109375, 32.84267363195431],
|
||||
[115, 32.84267363195431],
|
||||
[115, 35],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
offset: 0.8,
|
||||
},
|
||||
geometry: {
|
||||
type: 'MultiLineString',
|
||||
coordinates: [
|
||||
[
|
||||
[110, 30],
|
||||
[120, 30],
|
||||
[120, 40],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const source = new Source(geoData);
|
||||
|
||||
// scene.on('zoom', e => console.log(e))
|
||||
|
||||
const layer = new LineLayer({ blend: 'normal' })
|
||||
.source(source)
|
||||
.size(1)
|
||||
.shape('line')
|
||||
.color('#f00')
|
||||
.style({});
|
||||
|
||||
source.on('sourceUpdate', () => {
|
||||
console.log(source);
|
||||
const midPoints = lineAtOffset(source, {
|
||||
offset: 0.1,
|
||||
shape: 'line',
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(10)
|
||||
.color('#ff0');
|
||||
scene.addLayer(point);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
// const midPoints = await lineAtOffsetAsyc(source, 0.1, 'arc', 'offset');
|
||||
const midPoints = await lineAtOffsetAsyc(source, {
|
||||
offset: 0.5,
|
||||
shape: 'line',
|
||||
featureId: 1,
|
||||
});
|
||||
const point = new PointLayer({ blend: 'normal', zIndex: 1 })
|
||||
.source(midPoints, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: '_lng',
|
||||
y: '_lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.size(5)
|
||||
.color('#0f0')
|
||||
.style({
|
||||
opacity: 0.8,
|
||||
});
|
||||
scene.addLayer(point);
|
||||
})();
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addLayer(layer);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -19,13 +19,14 @@ export default () => {
|
|||
zIndex: 1,
|
||||
});
|
||||
layerTile.source(
|
||||
'//t3.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=f1f2021a42d110057042177cd22d856f',
|
||||
// '//t3.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=f1f2021a42d110057042177cd22d856f',
|
||||
'http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
||||
{
|
||||
parser: {
|
||||
type: 'rasterTile',
|
||||
tileSize: 256,
|
||||
// zoomOffset: 0
|
||||
zoomOffset: 1,
|
||||
// zoomOffset: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
"release": "lerna publish from-package --force-publish && yarn sync",
|
||||
"release-cdn": "antv-bin upload -n @antv/l7",
|
||||
"test": "umi-test",
|
||||
"test-cover": "umi-test --coverage",
|
||||
"test-cover": "yarn run worker && umi-test --coverage",
|
||||
"test-cover-viewer": "umi-test --coverage && cd coverage && http-server -p 6001 -o",
|
||||
"test-live": "umi-test --watch",
|
||||
"tsc": "tsc",
|
||||
|
|
|
@ -52,7 +52,8 @@ vec2 midPoint(vec2 source, vec2 target, float arcThetaOffset) {
|
|||
// return mid;
|
||||
}
|
||||
float getSegmentRatio(float index) {
|
||||
return smoothstep(0.0, 1.0, index / (segmentNumber - 1.));
|
||||
// return smoothstep(0.0, 1.0, index / (segmentNumber - 1.));
|
||||
return index / (segmentNumber - 1.);
|
||||
}
|
||||
vec2 interpolate (vec2 source, vec2 target, float t, float arcThetaOffset) {
|
||||
// if the angularDist is PI, linear interpolation is applied. otherwise, use spherical interpolation
|
||||
|
@ -130,8 +131,9 @@ void main() {
|
|||
styleMappingMat[3].b = d_distance_ratio;
|
||||
|
||||
// styleMappingMat[0][1] - arcThetaOffset
|
||||
vec4 curr = project_position(vec4(interpolate(source, target, segmentRatio, styleMappingMat[0][1]), 0.0, 1.0));
|
||||
vec4 next = project_position(vec4(interpolate(source, target, nextSegmentRatio, styleMappingMat[0][1]), 0.0, 1.0));
|
||||
float arcThetaOffset = styleMappingMat[0][1];
|
||||
vec4 curr = project_position(vec4(interpolate(source, target, segmentRatio, arcThetaOffset), 0.0, 1.0));
|
||||
vec4 next = project_position(vec4(interpolate(source, target, nextSegmentRatio, arcThetaOffset), 0.0, 1.0));
|
||||
// v_normal = getNormal((next.xy - curr.xy) * indexDir, a_Position.y);
|
||||
//unProjCustomCoord
|
||||
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import { Version } from '../src/interface/map';
|
||||
import { lineAtOffset, lineAtOffsetAsyc } from '../src/lineAtOffset/index';
|
||||
const arcSource = {
|
||||
inited: true,
|
||||
data: {
|
||||
dataArray: [
|
||||
{
|
||||
coordinates: [
|
||||
[99.228515625, 37.43997405227057],
|
||||
[100.72265625, 27.994401411046148],
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe('lineAtOffset', () => {
|
||||
(async () => {
|
||||
const asyncOffsetPoint = await lineAtOffsetAsyc(arcSource, {
|
||||
shape: 'arc',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE1.x']
|
||||
})
|
||||
it('asyncOffsetPoint', () => {
|
||||
expect(asyncOffsetPoint).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.66757224332352,
|
||||
_lat: 36.507847734146466,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
})()
|
||||
const arcOffsetPoint = lineAtOffset(arcSource, {
|
||||
shape: 'arc',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE1.x']
|
||||
})
|
||||
|
||||
const arcOffsetPoint2 = lineAtOffset(arcSource, {
|
||||
shape: 'arc',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE2.x'],
|
||||
thetaOffset: 0.314
|
||||
})
|
||||
|
||||
const arcOffsetPoint3 = lineAtOffset(arcSource, {
|
||||
shape: 'arc',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE2.x'],
|
||||
thetaOffset: 0.314,
|
||||
featureId: 0
|
||||
})
|
||||
|
||||
const greatCircleOffsetPoint = lineAtOffset(arcSource, {
|
||||
shape: 'greatcircle',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE1.x']
|
||||
})
|
||||
|
||||
const greatCircleOffsetPoint2 = lineAtOffset(arcSource, {
|
||||
shape: 'greatcircle',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE2.x']
|
||||
})
|
||||
|
||||
const lineOffsetPoint = lineAtOffset(arcSource, {
|
||||
shape: 'line',
|
||||
offset: 0.1,
|
||||
mapVersion: Version['GAODE1.x']
|
||||
})
|
||||
|
||||
it('arcOffsetPoint', () => {
|
||||
expect(arcOffsetPoint).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.66757224332352,
|
||||
_lat: 36.507847734146466,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('arcOffsetPoint2', () => {
|
||||
expect(arcOffsetPoint2).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.72191962749187,
|
||||
_lat: 36.54640697914567,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('arcOffsetPoint3', () => {
|
||||
expect(arcOffsetPoint3).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.72191962749187,
|
||||
_lat: 36.54640697914567,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('greatCircleOffsetPoint', () => {
|
||||
expect(greatCircleOffsetPoint).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.39897617955312,
|
||||
_lat: 36.46373143064039,
|
||||
_height: undefined
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('greatCircleOffsetPoint2', () => {
|
||||
expect(greatCircleOffsetPoint2).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 1.7395272931153063,
|
||||
_lat: 0.6371821457776072,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('lineOffsetPoint', () => {
|
||||
expect(lineOffsetPoint).toEqual( [
|
||||
{
|
||||
coordinates: [
|
||||
[ 99.228515625, 37.43997405227057 ],
|
||||
[ 100.72265625, 27.994401411046148 ]
|
||||
],
|
||||
offset: 0.3,
|
||||
_id: 0,
|
||||
_lng: 99.3779296875,
|
||||
_lat: 36.495416788148134,
|
||||
_height: 0
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -191,6 +191,15 @@ export function amap2Project(lng: number, lat: number): [number, number] {
|
|||
return [lng * Tg, lat * Tg];
|
||||
}
|
||||
|
||||
export function amap2UnProject(x: number, y: number): [number, number] {
|
||||
const Rg = Math.PI / 180;
|
||||
const Tg = 6378137;
|
||||
|
||||
const lng = (x/Tg) / Rg;
|
||||
const lat = (2 * (Math.atan(Math.exp((y / Tg))) - Math.PI/4))/Rg;
|
||||
return [lng, lat];
|
||||
}
|
||||
|
||||
export function lnglatDistance(
|
||||
coordinates1: [number, number],
|
||||
coordinates2: [number, number],
|
||||
|
|
|
@ -19,3 +19,4 @@ export * from './cull';
|
|||
export * from './env';
|
||||
export * from './tileset-manager';
|
||||
export * from './workers/triangulation';
|
||||
export * from './lineAtOffset'
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export enum Version {
|
||||
'GAODE1.x' = 'GAODE1.x',
|
||||
'GAODE2.x' = 'GAODE2.x',
|
||||
'MAPBOX' = 'MAPBOX',
|
||||
'L7MAP' = 'L7MAP',
|
||||
'SIMPLE' = 'SIMPLE',
|
||||
'GLOBEL' = 'GLOBEL',
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import { Point } from './interface';
|
||||
import { amap2Project, amap2UnProject } from '../geo';
|
||||
import { Version } from '../interface/map';
|
||||
// arc
|
||||
export function arcLineAtOffset(
|
||||
source: Point,
|
||||
target: Point,
|
||||
offset: number,
|
||||
thetaOffset: number | undefined,
|
||||
mapVersion: Version | undefined,
|
||||
segmentNumber: number = 30,
|
||||
autoFit: boolean,
|
||||
) {
|
||||
let pointOffset = offset;
|
||||
|
||||
if (autoFit) {
|
||||
// Tip: 自动偏移到线的节点位置
|
||||
pointOffset =
|
||||
Math.round(offset * (segmentNumber - 1)) / (segmentNumber - 1);
|
||||
}
|
||||
|
||||
if (!thetaOffset) {
|
||||
return interpolate(source, target, pointOffset, 0.314, mapVersion);
|
||||
} else {
|
||||
return interpolate(source, target, pointOffset, thetaOffset, mapVersion);
|
||||
}
|
||||
}
|
||||
|
||||
function bezier3(arr: Point, t: number) {
|
||||
const ut = 1 - t;
|
||||
return (arr[0] * ut + arr[1] * t) * ut + (arr[1] * ut + arr[2] * t) * t;
|
||||
}
|
||||
|
||||
function calDistance(p1: Point, p2: Point) {
|
||||
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
|
||||
}
|
||||
|
||||
function midPoint(source: Point, target: Point, thetaOffset: number) {
|
||||
const center = [target[0] - source[0], target[1] - source[1]]; //target - source;
|
||||
const r = calDistance(center, [0, 0]);
|
||||
const theta = Math.atan2(center[1], center[0]);
|
||||
const r2 = r / 2.0 / Math.cos(thetaOffset);
|
||||
const theta2 = theta + thetaOffset;
|
||||
const mid = [
|
||||
r2 * Math.cos(theta2) + source[0],
|
||||
r2 * Math.sin(theta2) + source[1],
|
||||
];
|
||||
return mid;
|
||||
}
|
||||
|
||||
function interpolate(
|
||||
source: Point,
|
||||
target: Point,
|
||||
offset: number,
|
||||
thetaOffset: number,
|
||||
mapVersion?: Version,
|
||||
) {
|
||||
if (mapVersion === Version['GAODE2.x']) {
|
||||
// amap2
|
||||
const sourceFlat = amap2Project(source[0], source[1]);
|
||||
const targetFlat = amap2Project(target[0], target[1]);
|
||||
|
||||
const mid = midPoint(sourceFlat, targetFlat, thetaOffset);
|
||||
|
||||
const x = [sourceFlat[0], mid[0], targetFlat[0]];
|
||||
const y = [sourceFlat[1], mid[1], targetFlat[1]];
|
||||
|
||||
return [...amap2UnProject(bezier3(x, offset), bezier3(y, offset)), 0];
|
||||
} else {
|
||||
// amap
|
||||
const mid = midPoint(source, target, thetaOffset);
|
||||
const x = [source[0], mid[0], target[0]];
|
||||
const y = [source[1], mid[1], target[1]];
|
||||
return [bezier3(x, offset), bezier3(y, offset), 0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import { Point } from './interface';
|
||||
import { calDistance } from '../geo';
|
||||
import { Version } from '../interface/map';
|
||||
import { degreesToRadians, radiansToDegrees } from '@turf/helpers';
|
||||
// arc
|
||||
export function greatCircleLineAtOffset(
|
||||
source: Point,
|
||||
target: Point,
|
||||
offset: number,
|
||||
thetaOffset: number | undefined,
|
||||
mapVersion: Version | undefined,
|
||||
segmentNumber: number = 30,
|
||||
autoFit: boolean,
|
||||
) {
|
||||
let pointOffset = offset;
|
||||
if (autoFit) {
|
||||
// Tip: 自动偏移到线的节点位置
|
||||
// greatcircle 暂时不支持配置 segmentNumber 和 thetaOffset
|
||||
pointOffset = Math.round(offset * 29) / 29;
|
||||
}
|
||||
return interpolate(source, target, pointOffset, mapVersion);
|
||||
}
|
||||
|
||||
function midPoint(source: Point, target: Point) {
|
||||
const center = [target[0] - source[0], target[1] - source[1]]; //target - source;
|
||||
const r = calDistance(center, [0, 0]);
|
||||
const theta = Math.atan2(center[1], center[0]);
|
||||
const thetaOffset = 0.314;
|
||||
const r2 = r / 2.0 / Math.cos(thetaOffset);
|
||||
const theta2 = theta + thetaOffset;
|
||||
const mid = [
|
||||
r2 * Math.cos(theta2) + source[0],
|
||||
r2 * Math.sin(theta2) + source[1],
|
||||
];
|
||||
return mid;
|
||||
}
|
||||
|
||||
function bezier3(arr: Point, t: number) {
|
||||
const ut = 1 - t;
|
||||
return (arr[0] * ut + arr[1] * t) * ut + (arr[1] * ut + arr[2] * t) * t;
|
||||
}
|
||||
|
||||
function getAngularDist(source: Point, target: Point) {
|
||||
const delta = [source[0] - target[0], source[1] - target[1]];
|
||||
const sin_half_delta = [Math.sin(delta[0] / 2.0), Math.sin(delta[1] / 2.0)]; //Math.sin(delta / 2.0);
|
||||
const a =
|
||||
sin_half_delta[1] * sin_half_delta[1] +
|
||||
Math.cos(source[1]) *
|
||||
Math.cos(target[1]) *
|
||||
sin_half_delta[0] *
|
||||
sin_half_delta[0];
|
||||
return 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
|
||||
}
|
||||
|
||||
export function interpolate(
|
||||
s: Point,
|
||||
t: Point,
|
||||
offset: number,
|
||||
mapVersion: string | undefined,
|
||||
) {
|
||||
const source = [degreesToRadians(s[0]), degreesToRadians(s[1])];
|
||||
const target = [degreesToRadians(t[0]), degreesToRadians(t[1])];
|
||||
if (mapVersion === 'GAODE2.x') {
|
||||
// gaode2.x
|
||||
const mid = midPoint(source, target);
|
||||
const x = [source[0], mid[0], target[0]];
|
||||
const y = [source[1], mid[1], target[1]];
|
||||
return [bezier3(x, offset), bezier3(y, offset), 0];
|
||||
} else {
|
||||
const angularDist = getAngularDist(source, target);
|
||||
if (Math.abs(angularDist - Math.PI) < 0.001) {
|
||||
return [
|
||||
(1.0 - offset) * source[0] + offset * target[0],
|
||||
(1.0 - offset) * source[1] + offset * target[1],
|
||||
];
|
||||
}
|
||||
const a = Math.sin((1.0 - offset) * angularDist) / Math.sin(angularDist);
|
||||
const b = Math.sin(offset * angularDist) / Math.sin(angularDist);
|
||||
const sin_source = [Math.sin(source[0]), Math.sin(source[1])];
|
||||
const cos_source = [Math.cos(source[0]), Math.cos(source[1])];
|
||||
const sin_target = [Math.sin(target[0]), Math.sin(target[1])];
|
||||
const cos_target = [Math.cos(target[0]), Math.cos(target[1])];
|
||||
const x =
|
||||
a * cos_source[1] * cos_source[0] + b * cos_target[1] * cos_target[0];
|
||||
const y =
|
||||
a * cos_source[1] * sin_source[0] + b * cos_target[1] * sin_target[0];
|
||||
const z = a * sin_source[1] + b * sin_target[1];
|
||||
return [
|
||||
radiansToDegrees(Math.atan2(y, x)),
|
||||
radiansToDegrees(Math.atan2(z, Math.sqrt(x * x + y * y))),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { Source, ILineAtOffset } from './interface';
|
||||
import { arcLineAtOffset } from './arc';
|
||||
import { greatCircleLineAtOffset } from './greatCircle';
|
||||
import { pathLineAtOffset } from './line';
|
||||
|
||||
export function lineAtOffset(source: Source, option: ILineAtOffset) {
|
||||
const { featureId } = option;
|
||||
let features = source.data.dataArray;
|
||||
if (typeof featureId === 'number') {
|
||||
features = features.filter(({ _id }: { _id: number }) => _id === featureId);
|
||||
}
|
||||
return features.map((feature: any) => {
|
||||
const position = getLineOffsetPosition(feature, option);
|
||||
return {
|
||||
...feature,
|
||||
...position,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function lineAtOffsetAsyc(source: Source, option: ILineAtOffset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (source.inited) {
|
||||
resolve(lineAtOffset(source, option));
|
||||
} else {
|
||||
source.once('sourceUpdate', () => {
|
||||
resolve(lineAtOffset(source, option));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getLineOffsetPosition(feature: any, option: ILineAtOffset) {
|
||||
const {
|
||||
offset,
|
||||
shape,
|
||||
thetaOffset,
|
||||
mapVersion,
|
||||
segmentNumber = 30,
|
||||
autoFit = true,
|
||||
} = option;
|
||||
const { coordinates } = feature;
|
||||
if (shape === 'line') {
|
||||
return pathLineAtOffset(coordinates, offset);
|
||||
}
|
||||
|
||||
const source = coordinates[0];
|
||||
const target = coordinates[1];
|
||||
let linetheatOffset;
|
||||
if (typeof thetaOffset === 'string') {
|
||||
linetheatOffset = feature[thetaOffset] || 0;
|
||||
} else {
|
||||
linetheatOffset = thetaOffset;
|
||||
}
|
||||
|
||||
let calFunc;
|
||||
switch (shape) {
|
||||
case 'arc':
|
||||
calFunc = arcLineAtOffset;
|
||||
break;
|
||||
case 'greatcircle':
|
||||
calFunc = greatCircleLineAtOffset;
|
||||
break;
|
||||
default:
|
||||
calFunc = arcLineAtOffset;
|
||||
}
|
||||
const [lng, lat, height] = calFunc(
|
||||
source,
|
||||
target,
|
||||
offset,
|
||||
linetheatOffset,
|
||||
mapVersion,
|
||||
segmentNumber,
|
||||
autoFit,
|
||||
);
|
||||
|
||||
return {
|
||||
_lng: lng,
|
||||
_lat: lat,
|
||||
_height: height,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { Version } from '../interface/map';
|
||||
export type Source = any;
|
||||
export type ILineShape = 'line' | 'arc' | 'arc3d' | 'greatcircle';
|
||||
export type IThetaOffset = string | number | undefined;
|
||||
export type Point = number[];
|
||||
|
||||
export interface ILineAtOffset {
|
||||
offset: number;
|
||||
shape: ILineShape;
|
||||
mapVersion: Version;
|
||||
thetaOffset?: IThetaOffset;
|
||||
featureId?: number | undefined;
|
||||
segmentNumber?: number;
|
||||
autoFit?: boolean;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { Point } from './interface';
|
||||
|
||||
export function pathLineAtOffset(coords: Point[], offset: number) {
|
||||
let totalDistance = 0;
|
||||
const cachePoints = [];
|
||||
for (let i = 0; i < coords.length - 1; i++) {
|
||||
const p1 = coords[i] as [number, number];
|
||||
const p2 = coords[i + 1] as [number, number];
|
||||
const distance = calDistance(p1, p2);
|
||||
const lastTotalDistance = totalDistance;
|
||||
totalDistance += distance;
|
||||
cachePoints.push({
|
||||
p1,
|
||||
p2,
|
||||
totalDistance,
|
||||
distance,
|
||||
lastTotalDistance,
|
||||
});
|
||||
}
|
||||
const offsetDistance = totalDistance * offset;
|
||||
let _lng, _lat;
|
||||
for (let i = 0; i < cachePoints.length; i++) {
|
||||
const currentDistance = cachePoints[i].totalDistance;
|
||||
if (currentDistance > offsetDistance) {
|
||||
const p1 = cachePoints[i].p1;
|
||||
const p2 = cachePoints[i].p2;
|
||||
const radius =
|
||||
(offsetDistance - cachePoints[i].lastTotalDistance) /
|
||||
cachePoints[i].distance;
|
||||
const offsetPoint = mixPoint(p2, p1, radius);
|
||||
_lng = offsetPoint[0];
|
||||
_lat = offsetPoint[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
_lng,
|
||||
_lat,
|
||||
_height: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function mixPoint(p1: Point, p2: Point, r: number) {
|
||||
return [p1[0] * r + p2[0] * (1 - r), p1[1] * r + p2[1] * (1 - r)];
|
||||
}
|
||||
|
||||
function calDistance(p1: Point, p2: Point) {
|
||||
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
|
||||
}
|
Loading…
Reference in New Issue