1- import React , { useCallback , useMemo , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
22import type { LayerEventPayload } from '../../schema/types' ;
33import { LineLayer } from '../Layer/LineLayer' ;
44import { PointLayer } from '../Layer/PointLayer' ;
@@ -41,6 +41,29 @@ export interface RouteSegment {
4141 width ?: number ;
4242}
4343
44+ /** 路径类型 */
45+ export type RouteType = 'straight' | 'arc' | 'walking' | 'cycling' | 'driving' | 'transit' ;
46+
47+ /** 交通路线查询参数 */
48+ export interface RouteQueryParams {
49+ origin : [ number , number ] ;
50+ destination : [ number , number ] ;
51+ waypoints ?: [ number , number ] [ ] ;
52+ routeType : 'walking' | 'cycling' | 'driving' | 'transit' ;
53+ }
54+
55+ /** 交通路线查询结果 */
56+ export interface RouteQueryResult {
57+ /** 完整路径坐标 */
58+ path : [ number , number ] [ ] ;
59+ /** 分段路径(可选,如返回多段路况着色路径) */
60+ segments ?: RouteSegment [ ] ;
61+ /** 途中补充站点(可选,如公交换乘站) */
62+ stops ?: RouteStop [ ] ;
63+ /** 路线信息 */
64+ info ?: { distance ?: number ; duration ?: number ; description ?: string } ;
65+ }
66+
4467export interface RouteLayerProps {
4568 /** 路径坐标 — 完整线坐标或分段 */
4669 path ?: [ number , number ] [ ] ;
@@ -49,6 +72,14 @@ export interface RouteLayerProps {
4972 /** 途经点列表 */
5073 stops ?: RouteStop [ ] ;
5174
75+ // ===== 路径模式 =====
76+ /** 路径类型,默认 'straight' */
77+ routeType ?: RouteType ;
78+ /** 路线查询回调 — routeType 为 walking/cycling/driving/transit 时使用 */
79+ onRouteQuery ?: ( params : RouteQueryParams ) => Promise < RouteQueryResult > ;
80+ /** 路线查询完成回调 */
81+ onRouteResult ?: ( result : RouteQueryResult ) => void ;
82+
5283 // ===== 路径视觉 =====
5384 /** 路径颜色,默认 '#2563eb' */
5485 color ?: string ;
@@ -122,6 +153,9 @@ export function RouteLayer({
122153 path,
123154 segments,
124155 stops = [ ] ,
156+ routeType = 'straight' ,
157+ onRouteQuery,
158+ onRouteResult,
125159 color = '#2563eb' ,
126160 lineWidth = 4 ,
127161 opacity = 0.9 ,
@@ -143,46 +177,84 @@ export function RouteLayer({
143177 onPathClick,
144178 onStopClick,
145179} : RouteLayerProps ) {
180+ // 交通路线查询结果
181+ const [ routeQueryResult , setRouteQueryResult ] = useState < RouteQueryResult | null > ( null ) ;
182+ const queryVersionRef = useRef ( 0 ) ;
183+
184+ const isTransportMode = routeType === 'walking' || routeType === 'cycling' || routeType === 'driving' || routeType === 'transit' ;
185+
186+ useEffect ( ( ) => {
187+ if ( ! isTransportMode || ! onRouteQuery || stops . length < 2 ) {
188+ setRouteQueryResult ( null ) ;
189+ return ;
190+ }
191+ const origin : [ number , number ] = [ stops [ 0 ] . lng , stops [ 0 ] . lat ] ;
192+ const destination : [ number , number ] = [ stops [ stops . length - 1 ] . lng , stops [ stops . length - 1 ] . lat ] ;
193+ const waypoints : [ number , number ] [ ] = stops . length > 2
194+ ? stops . slice ( 1 , - 1 ) . map ( ( s ) => [ s . lng , s . lat ] )
195+ : [ ] ;
196+
197+ const version = ++ queryVersionRef . current ;
198+ onRouteQuery ( { origin, destination, waypoints : waypoints . length > 0 ? waypoints : undefined , routeType } )
199+ . then ( ( result ) => {
200+ if ( queryVersionRef . current !== version ) return ;
201+ setRouteQueryResult ( result ) ;
202+ onRouteResult ?.( result ) ;
203+ } )
204+ . catch ( ( ) => {
205+ if ( queryVersionRef . current !== version ) return ;
206+ setRouteQueryResult ( null ) ;
207+ } ) ;
208+ } , [ isTransportMode , onRouteQuery , stops , routeType ] ) ;
209+
210+ // 实际使用的路径和分段数据
211+ const effectivePath = isTransportMode && routeQueryResult ? routeQueryResult . path : path ;
212+ const effectiveSegments = isTransportMode && routeQueryResult ?. segments ? routeQueryResult . segments : segments ;
213+ const extraStops = isTransportMode && routeQueryResult ?. stops ? routeQueryResult . stops : [ ] ;
214+
215+ // 线形状:arc 模式用 'arc',其余用 'line'
216+ const lineShape = routeType === 'arc' ? 'arc' : 'line' ;
217+
146218 // 构建路径 GeoJSON
147219 const pathGeoJSON = useMemo ( ( ) => {
148- if ( segments && segments . length > 0 ) {
220+ if ( effectiveSegments && effectiveSegments . length > 0 ) {
149221 return {
150222 type : 'FeatureCollection' as const ,
151- features : segments . map ( ( seg , idx ) => ( {
223+ features : effectiveSegments . map ( ( seg , idx ) => ( {
152224 type : 'Feature' as const ,
153225 properties : { color : seg . color || color , width : seg . width || lineWidth , index : idx } ,
154226 geometry : { type : 'LineString' as const , coordinates : seg . coordinates } ,
155227 } ) ) ,
156228 } ;
157229 }
158- if ( path && path . length > 1 ) {
230+ if ( effectivePath && effectivePath . length > 1 ) {
159231 return {
160232 type : 'FeatureCollection' as const ,
161233 features : [ {
162234 type : 'Feature' as const ,
163235 properties : { color, width : lineWidth } ,
164- geometry : { type : 'LineString' as const , coordinates : path } ,
236+ geometry : { type : 'LineString' as const , coordinates : effectivePath } ,
165237 } ] ,
166238 } ;
167239 }
168240 return null ;
169- } , [ path , segments , color , lineWidth ] ) ;
241+ } , [ effectivePath , effectiveSegments , color , lineWidth ] ) ;
170242
171243 // 发光层 GeoJSON(同路径但宽度更大)
172- const hasSegmentColors = segments && segments . some ( ( s ) => s . color ) ;
244+ const hasSegmentColors = effectiveSegments && effectiveSegments . some ( ( s ) => s . color ) ;
173245
174246 // 途经点数据(增加序号和类型)
175247 // 自动补全路径起终点:如果 stops 中没有覆盖 path 的首尾坐标,则自动添加
176248 const stopsWithIndex = useMemo ( ( ) => {
177- const effectivePath = path ?? ( segments && segments . length > 0
178- ? [ ...segments [ 0 ] . coordinates . slice ( 0 , 1 ) , ...segments [ segments . length - 1 ] . coordinates . slice ( - 1 ) ]
249+ const effectivePathForStops = effectivePath ?? ( effectiveSegments && effectiveSegments . length > 0
250+ ? [ ...effectiveSegments [ 0 ] . coordinates . slice ( 0 , 1 ) , ...effectiveSegments [ effectiveSegments . length - 1 ] . coordinates . slice ( - 1 ) ]
179251 : null ) ;
180252
181- let merged = [ ...stops ] ;
253+ let merged = [ ...stops , ... extraStops ] ;
182254
183- if ( effectivePath && effectivePath . length >= 2 ) {
184- const [ startLng , startLat ] = effectivePath [ 0 ] ;
185- const [ endLng , endLat ] = effectivePath [ effectivePath . length - 1 ] ;
255+ if ( effectivePathForStops && effectivePathForStops . length >= 2 ) {
256+ const [ startLng , startLat ] = effectivePathForStops [ 0 ] ;
257+ const [ endLng , endLat ] = effectivePathForStops [ effectivePathForStops . length - 1 ] ;
186258 const hasStart = stops . some ( ( s ) => Math . abs ( s . lng - startLng ) < 1e-6 && Math . abs ( s . lat - startLat ) < 1e-6 ) ;
187259 const hasEnd = stops . some ( ( s ) => Math . abs ( s . lng - endLng ) < 1e-6 && Math . abs ( s . lat - endLat ) < 1e-6 ) ;
188260
@@ -205,7 +277,7 @@ export function RouteLayer({
205277 indexLabel : String ( stop . index ?? idx + 1 ) ,
206278 markerColorValue : stop . markerColor ?? resolveMarkerColor ( stop . type ?? ( idx === 0 ? 'start' : idx === merged . length - 1 ? 'end' : 'waypoint' ) ) ,
207279 } ) ) ;
208- } , [ stops , path , segments , color , stopColor , endColor ] ) ;
280+ } , [ stops , extraStops , effectivePath , effectiveSegments , color , stopColor , endColor ] ) ;
209281
210282 const resolvedStopIconMap = useMemo ( ( ) => {
211283 if ( stopRenderer !== 'icon' ) return undefined ;
@@ -248,10 +320,10 @@ export function RouteLayer({
248320 < LineLayer
249321 source = { pathGeoJSON }
250322 sourceType = "geojson"
251- shape = "line"
323+ shape = { lineShape }
252324 color = { hasSegmentColors ? undefined : color }
253325 colorField = { hasSegmentColors ? 'color' : undefined }
254- colorValues = { hasSegmentColors ? segments ! . map ( ( s ) => s . color || color ) : undefined }
326+ colorValues = { hasSegmentColors ? effectiveSegments ! . map ( ( s ) => s . color || color ) : undefined }
255327 size = { lineWidth * 2.5 }
256328 style = { { opacity : 0.15 } }
257329 />
@@ -261,13 +333,13 @@ export function RouteLayer({
261333 < LineLayer
262334 source = { pathGeoJSON }
263335 sourceType = "geojson"
264- shape = "line"
336+ shape = { lineShape }
265337 color = { hasSegmentColors ? undefined : color }
266338 colorField = { hasSegmentColors ? 'color' : undefined }
267- colorValues = { hasSegmentColors ? segments ! . map ( ( s ) => s . color || color ) : undefined }
339+ colorValues = { hasSegmentColors ? effectiveSegments ! . map ( ( s ) => s . color || color ) : undefined }
268340 size = { hasSegmentColors ? undefined : lineWidth }
269341 sizeField = { hasSegmentColors ? 'width' : undefined }
270- sizeValues = { hasSegmentColors ? segments ! . map ( ( s ) => s . width || lineWidth ) : undefined }
342+ sizeValues = { hasSegmentColors ? effectiveSegments ! . map ( ( s ) => s . width || lineWidth ) : undefined }
271343 style = { { opacity } }
272344 active = { activeColor ? { color : activeColor } : false }
273345 onClick = { onPathClick }
0 commit comments