zjk
2026-07-29 429208957f30ab9eedc5533a81aaa284048d09f5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import Taro from '@tarojs/taro';
import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE } from '../../../../robotaxi/constants/map';
import {
  HOME_START_SITE_SOURCE_MANUAL_SELECTED,
  writeHomeStartSiteDisplayState
} from '../../../../robotaxi/hooks/use-home-start-site-display';
 
type Deps = {
  mapState: any;
  siteState: any;
  toOrigin: () => void;
  getMarkerCommitDelayMs?: () => number;
  onRegionBegin?: () => void;
  onRegionSettled?: (payload: { lat: number; lon: number; isInside: boolean }) => void;
  onScaleChange?: (scale: number) => void;
};
 
/**
 * 地图 region-change 编排:处理地图拖拽/缩放后的吸附、回填和自动归位。
 */
export default function useRegionChange({ mapState, siteState, toOrigin, getMarkerCommitDelayMs, onRegionBegin, onRegionSettled, onScaleChange }: Deps) {
  /**
   * 查询完成后提交站点 marker:命中吸附时立即提交,未吸附时避让车辆动画窗口再提交。
   */
  const commitQueriedMarkers = (newMarkers: any[], isInside: boolean, adsorbed: boolean) => {
    if (!isInside || adsorbed) {
      mapState.commitMarkers(newMarkers);
      return;
    }
    const delayMs = typeof getMarkerCommitDelayMs === 'function' ? getMarkerCommitDelayMs() : 0;
    mapState.scheduleMarkerCommit(newMarkers, delayMs);
  };
 
  /**
   * 将用户显式换点后的吸附结果同步为首页确认上车点。
   */
  const syncManualSelectedStartSite = (site: any) => {
    if (!site?.latitude || !site?.longitude) {
      return;
    }
    writeHomeStartSiteDisplayState(site, HOME_START_SITE_SOURCE_MANUAL_SELECTED);
  };
 
  /**
   * 按最终落点回传地图结算结果,保证场站围栏与吸附后的中心点一致。
   */
  const settleRegionResult = (payload: { lat: number; lon: number; isInside: boolean }) => {
    if (typeof onRegionSettled === 'function') {
      onRegionSettled(payload);
    }
  };
 
  /**
   * 通过中心点主动触发吸附判断。
   * @param options.allowAdsorb 是否允许吸附到最近点位,默认允许;查看运营区域等仅浏览场景需传 false 以保持视野不被吸附打断。
   */
  const queryPointerByCenter = async (lat: number, lng: number, options?: { allowAdsorb?: boolean }) => {
    const allowAdsorb = options?.allowAdsorb !== false;
    siteState.setBounce(true);
    siteState.setShowToolTip(false);
    try {
      const { isInside, newMarkers, sites } = await mapState.updateMapData(lat, lng, undefined, { commitMarkers: false });
      if (!isInside) {
        siteState.clearCachedStartSiteSelection();
        commitQueriedMarkers(newMarkers, false, false);
        settleRegionResult({ lat, lon: lng, isInside: false });
        siteState.setIsAdsorbed(false);
        if (allowAdsorb) {
          siteState.setShowToolTip(true); // 运营区外恢复气泡提示
        }
        siteState.setBounce(false);
        return;
      }
      const nearestCandidates = sites.length > 0 ? sites : newMarkers;
      const { adsorbed, marker } = allowAdsorb
        ? siteState.tryAdsorb(lat, lng, nearestCandidates)
        : { adsorbed: false, marker: null }; // 仅浏览运营区域时不吸附最近点位
      commitQueriedMarkers(newMarkers, true, adsorbed);
      const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度
      const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度
      if (allowAdsorb) {
        // 仅浏览运营区域时不回写地图中心,避免 Map 组件按旧 scale 重绘导致刚展开的运营区视野被收回
        mapState.setLatitude(finalLat);
        mapState.setLongitude(finalLng);
      }
      settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true });
      setTimeout(async () => {
        siteState.setBounce(false);
        if (!adsorbed) {
          await siteState.resolveAddress(lat, lng);
        }
        if (!allowAdsorb) {
          siteState.setShowToolTip(false); // 查看运营区域期间隐藏气泡,避免遮挡围栏,拖动地图后自动恢复
        }
      }, 600);
    } catch (error) {
      siteState.setBounce(false);
    }
  };
 
  const handleRegionEnd = (causedBy: string, wasReturningToOrigin: boolean) => {
    siteState.setBounce(true);
    const mapCtx = Taro.createMapContext('map');
    mapCtx.getScale({
      success: (res) => {
        if (res.scale <= 3 || res.scale >= 20) {
          if (!mapState.resetTimer.current) {
            mapState.setMapScale(res.scale);
            mapState.resetTimer.current = setTimeout(() => {
              toOrigin();
            }, 6000);
          }
        }
        if (typeof onScaleChange === 'function') {
          onScaleChange(res.scale);
        }
      }
    });
    mapCtx.getCenterLocation({
      success: async (res) => {
        const { latitude: lat, longitude: lng } = res;
        if (
          !lat ||
          !lng ||
          (Math.abs(lat - DEFAULT_LATITUDE) < 0.0001 && Math.abs(lng - DEFAULT_LONGITUDE) < 0.0001)
        ) {
          siteState.setBounce(false);
          return;
        }
        
        if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale' || wasReturningToOrigin) {
          // 只有发生显著位移时才同步经纬度,避免微小抖动引发的二次重绘
          if (Math.abs(mapState.latitude - lat) > 0.0001 || Math.abs(mapState.longitude - lng) > 0.0001) {
            mapState.setLatitude(lat);
            mapState.setLongitude(lng);
          }
        }
 
        try {
          const { isInside, newMarkers, sites } = await mapState.updateMapData(lat, lng, undefined, { commitMarkers: false });
          if (!isInside) {
            siteState.clearCachedStartSiteSelection();
            commitQueriedMarkers(newMarkers, false, false);
            settleRegionResult({ lat, lon: lng, isInside: false });
            siteState.setIsAdsorbed(false);
            siteState.setShowToolTip(true); // 运营区外恢复气泡提示
            siteState.setBounce(false);
            return;
          }
          const nearestCandidates = sites.length > 0 ? sites : newMarkers;
          const { adsorbed, marker } = siteState.tryAdsorb(lat, lng, nearestCandidates);
          commitQueriedMarkers(newMarkers, true, adsorbed);
          const finalLat = adsorbed && marker ? marker.latitude : lat; // 最终纬度
          const finalLng = adsorbed && marker ? marker.longitude : lng; // 最终经度
          if (adsorbed && marker && (causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin)) {
            syncManualSelectedStartSite(marker);
          }
          settleRegionResult({ lat: finalLat, lon: finalLng, isInside: true });
          if ((causedBy === 'gesture' || causedBy === 'drag' || wasReturningToOrigin) && marker) {
            mapCtx.moveToLocation({
              latitude: marker.latitude,
              longitude: marker.longitude
            });
            mapState.setLatitude(marker.latitude);
            mapState.setLongitude(marker.longitude);
          }
          setTimeout(async () => {
            siteState.setBounce(false);
            if (!adsorbed) {
              await siteState.resolveAddress(lat, lng);
            }
          }, 600);
        } catch (error) {
          siteState.setBounce(false);
        }
      }
    });
  };
 
  /**
   * region-change 事件入口。
   */
  const handleRegionChange = (e: any) => {
    const type = e?.type || e?.detail?.type;
    const causedBy = e?.causedBy || e?.detail?.causedBy;
    if (mapState.isLocationAuthDenied) return;
    if (causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale') {
      mapState.shouldIgnoreRegionChange.current = false;
      if (mapState.resetTimer.current) {
        clearTimeout(mapState.resetTimer.current);
        mapState.resetTimer.current = null;
      }
    } else if (mapState.isReturningToOrigin.current) {
      mapState.shouldIgnoreRegionChange.current = false;
    } else {
      mapState.shouldIgnoreRegionChange.current = true;
      return;
    }
    if (type === 'begin') {
      siteState.setShowToolTip(false);
      if (typeof onRegionBegin === 'function') {
        onRegionBegin();
      }
      return;
    }
    if (type !== 'end') return;
    const wasReturningToOrigin = mapState.isReturningToOrigin.current;
    if (mapState.isReturningToOrigin.current) {
      mapState.isReturningToOrigin.current = false;
    }
    if (mapState.shouldIgnoreRegionChange.current) {
      mapState.shouldIgnoreRegionChange.current = false;
      return;
    }
    handleRegionEnd(causedBy, wasReturningToOrigin);
  };
 
  return { handleRegionChange, queryPointerByCenter };
}