expo-gaode-map 2.2.32-next.0 → 2.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,11 +49,14 @@ export function useRoutePlayback(points, options = {}) {
49
49
  const map = React.useContext(MapContext);
50
50
  const optionsRef = React.useRef(options);
51
51
  const [state, setState] = React.useState(DEFAULT_STATE);
52
+ const stateRef = React.useRef(DEFAULT_STATE);
52
53
  const [speedMultiplier, setSpeedMultiplierState] = React.useState(options.speedMultiplier ?? 1);
53
54
  const timerRef = React.useRef(null);
54
55
  const startAtRef = React.useRef(0);
55
56
  const elapsedBeforePauseRef = React.useRef(0);
56
57
  const lastAngleRef = React.useRef(0);
58
+ const previousPathRef = React.useRef(null);
59
+ const previousDurationRef = React.useRef(null);
57
60
  React.useEffect(() => {
58
61
  optionsRef.current = options;
59
62
  }, [options]);
@@ -91,6 +94,7 @@ export function useRoutePlayback(points, options = {}) {
91
94
  }, []);
92
95
  const publishState = React.useCallback((nextState) => {
93
96
  // 统一从这里下发状态,避免不同控制分支各自维护回调时机。
97
+ stateRef.current = nextState;
94
98
  setState(nextState);
95
99
  optionsRef.current.onProgress?.(nextState);
96
100
  }, []);
@@ -124,7 +128,7 @@ export function useRoutePlayback(points, options = {}) {
124
128
  smoothMoveDuration: keepPlaying ? durationSeconds : undefined,
125
129
  };
126
130
  publishState(nextState);
127
- if (optionsRef.current.followCamera !== false && nextState.currentPosition && map) {
131
+ if (optionsRef.current.followCamera === true && nextState.currentPosition && map) {
128
132
  await map.moveCamera({
129
133
  target: nextState.currentPosition,
130
134
  zoom: optionsRef.current.followZoom ?? 17,
@@ -139,6 +143,26 @@ export function useRoutePlayback(points, options = {}) {
139
143
  publishState,
140
144
  totalDistance,
141
145
  ]);
146
+ const runPlaybackTick = React.useCallback(async () => {
147
+ const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;
148
+ const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);
149
+ if (progress >= 1) {
150
+ stopTimer();
151
+ elapsedBeforePauseRef.current = 0;
152
+ startAtRef.current = 0;
153
+ const completedState = await syncProgress(1, false);
154
+ optionsRef.current.onComplete?.(completedState);
155
+ return;
156
+ }
157
+ await syncProgress(progress, true);
158
+ }, [durationSeconds, stopTimer, syncProgress]);
159
+ const startTimer = React.useCallback(() => {
160
+ stopTimer();
161
+ const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);
162
+ timerRef.current = setInterval(() => {
163
+ void runPlaybackTick();
164
+ }, interval);
165
+ }, [runPlaybackTick, stopTimer]);
142
166
  const stop = React.useCallback(() => {
143
167
  stopTimer();
144
168
  elapsedBeforePauseRef.current = 0;
@@ -156,30 +180,15 @@ export function useRoutePlayback(points, options = {}) {
156
180
  elapsedBeforePauseRef.current = 0;
157
181
  startAtRef.current = Date.now();
158
182
  lastAngleRef.current = 0;
159
- if (optionsRef.current.autoFit !== false && map && normalizedPath.length > 0) {
183
+ if (optionsRef.current.autoFit === true && map && normalizedPath.length > 0) {
160
184
  await fitCameraToCoordinates(map, normalizedPath, optionsRef.current.fitOptions);
161
185
  }
162
186
  await syncProgress(0, true);
163
- // 使用固定间隔推进进度,保证回放速度与 UI 反馈稳定。
164
- const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);
165
- timerRef.current = setInterval(async () => {
166
- const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;
167
- const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);
168
- if (progress >= 1) {
169
- stopTimer();
170
- elapsedBeforePauseRef.current = 0;
171
- startAtRef.current = 0;
172
- const completedState = await syncProgress(1, false);
173
- optionsRef.current.onComplete?.(completedState);
174
- return;
175
- }
176
- await syncProgress(progress, true);
177
- }, interval);
187
+ startTimer();
178
188
  }, [
179
- durationSeconds,
180
189
  map,
181
190
  normalizedPath,
182
- stopTimer,
191
+ startTimer,
183
192
  syncProgress,
184
193
  ]);
185
194
  const pause = React.useCallback(() => {
@@ -188,49 +197,37 @@ export function useRoutePlayback(points, options = {}) {
188
197
  }
189
198
  stopTimer();
190
199
  elapsedBeforePauseRef.current += Date.now() - startAtRef.current;
200
+ startAtRef.current = 0;
201
+ const currentState = stateRef.current;
191
202
  publishState({
192
- ...state,
203
+ ...currentState,
193
204
  isPlaying: false,
194
205
  isPaused: true,
195
206
  smoothMovePath: undefined,
196
207
  smoothMoveDuration: undefined,
197
208
  });
198
- }, [durationSeconds, publishState, state, stopTimer]);
209
+ }, [durationSeconds, publishState, stopTimer]);
199
210
  const resume = React.useCallback(async () => {
200
- if (state.progress >= 1) {
211
+ const currentState = stateRef.current;
212
+ if (currentState.progress >= 1) {
201
213
  await start();
202
214
  return;
203
215
  }
204
216
  startAtRef.current = Date.now();
205
217
  publishState({
206
- ...state,
218
+ ...currentState,
207
219
  isPlaying: true,
208
220
  isPaused: false,
209
221
  smoothMovePath: normalizedPath,
210
222
  smoothMoveDuration: durationSeconds,
211
223
  });
212
- const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);
213
- timerRef.current = setInterval(async () => {
214
- const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;
215
- const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);
216
- if (progress >= 1) {
217
- stopTimer();
218
- elapsedBeforePauseRef.current = 0;
219
- startAtRef.current = 0;
220
- const completedState = await syncProgress(1, false);
221
- optionsRef.current.onComplete?.(completedState);
222
- return;
223
- }
224
- await syncProgress(progress, true);
225
- }, interval);
224
+ startTimer();
226
225
  }, [
227
226
  durationSeconds,
228
227
  normalizedPath,
229
228
  publishState,
230
229
  start,
231
- state,
232
- stopTimer,
233
- syncProgress,
230
+ startTimer,
234
231
  ]);
235
232
  const seek = React.useCallback((progress) => {
236
233
  // seek 只重算“已经播放过的时间”,其余状态由 syncProgress 统一刷新。
@@ -244,13 +241,56 @@ export function useRoutePlayback(points, options = {}) {
244
241
  }, []);
245
242
  React.useEffect(() => () => stopTimer(), [stopTimer]);
246
243
  React.useEffect(() => {
244
+ const pathChanged = previousPathRef.current !== normalizedPath;
245
+ const durationChanged = previousDurationRef.current !== null && previousDurationRef.current !== durationSeconds;
246
+ previousPathRef.current = normalizedPath;
247
+ previousDurationRef.current = durationSeconds;
248
+ if (pathChanged) {
249
+ stopTimer();
250
+ elapsedBeforePauseRef.current = 0;
251
+ startAtRef.current = 0;
252
+ lastAngleRef.current = 0;
253
+ publishState({
254
+ ...DEFAULT_STATE,
255
+ currentPosition: normalizedPath[0] ?? null,
256
+ totalDistance,
257
+ durationSeconds,
258
+ });
259
+ return;
260
+ }
261
+ if (!durationChanged) {
262
+ return;
263
+ }
264
+ const currentState = stateRef.current;
265
+ if (currentState.isPlaying) {
266
+ elapsedBeforePauseRef.current = currentState.progress * durationSeconds * 1000;
267
+ startAtRef.current = Date.now();
268
+ publishState({
269
+ ...currentState,
270
+ totalDistance,
271
+ durationSeconds,
272
+ smoothMoveDuration: durationSeconds,
273
+ });
274
+ startTimer();
275
+ return;
276
+ }
277
+ if (currentState.isPaused) {
278
+ elapsedBeforePauseRef.current = currentState.progress * durationSeconds * 1000;
279
+ startAtRef.current = 0;
280
+ publishState({
281
+ ...currentState,
282
+ totalDistance,
283
+ durationSeconds,
284
+ });
285
+ return;
286
+ }
247
287
  publishState({
248
- ...DEFAULT_STATE,
249
- currentPosition: normalizedPath[0] ?? null,
288
+ ...currentState,
289
+ currentPosition: currentState.progress > 0 ? currentState.currentPosition : normalizedPath[0] ?? null,
250
290
  totalDistance,
251
291
  durationSeconds,
252
292
  });
253
- }, [durationSeconds, normalizedPath, publishState, totalDistance]);
293
+ }, [durationSeconds, normalizedPath, publishState, startTimer, stopTimer, totalDistance]);
254
294
  return {
255
295
  ...state,
256
296
  totalDistance,
@@ -1 +1 @@
1
- {"version":3,"file":"useRoutePlayback.js","sourceRoot":"","sources":["../../src/hooks/useRoutePlayback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,kBAAkB,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,aAAa,GAAuB;IACxC,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,CAAC;IACX,eAAe,EAAE,IAAI;IACrB,YAAY,EAAE,CAAC;IACf,aAAa,EAAE,CAAC;IAChB,gBAAgB,EAAE,CAAC;IACnB,eAAe,EAAE,CAAC;CACnB,CAAC;AAEF,SAAS,cAAc,CACrB,IAAc,EACd,aAAqB,EACrB,cAAsB,EACtB,SAAiB,EACjB,OAA6B;IAE7B,wBAAwB;IACxB,yBAAyB;IACzB,MAAM,SAAS,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC9E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,MAAM,iBAAiB,GAAG,OAAO,CAAC,uBAAuB,IAAI,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,kBAAkB,CAAC,kBAAkB,CACvD,IAAI,EACJ,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,cAAc,GAAG,iBAAiB,CAAC,CAC5D,CAAC;IAEF,IAAI,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC;IAClC,IAAI,WAAW,IAAI,cAAc,GAAG,iBAAiB,GAAG,aAAa,EAAE,CAAC;QACtE,IAAI,QAAQ,GAAG,WAAW,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;QACnD,IAAI,QAAQ,GAAG,GAAG;YAAE,QAAQ,IAAI,GAAG,CAAC;QACpC,IAAI,QAAQ,GAAG,CAAC,GAAG;YAAE,QAAQ,IAAI,GAAG,CAAC;QACrC,WAAW,GAAG,SAAS,CAAC,KAAK,GAAG,QAAQ,GAAG,GAAG,CAAC;IACjD,CAAC;IAED,IAAI,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;IACnC,IAAI,IAAI,GAAG,GAAG;QAAE,IAAI,IAAI,GAAG,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,GAAG;QAAE,IAAI,IAAI,GAAG,CAAC;IAE7B,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,GAAG,CAAC;IACzD,OAAO;QACL,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,gBAAgB;QAC1C,KAAK,EAAE;YACL,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,SAAS,EAAE,SAAS,CAAC,SAAS;SAC/B;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAmD,EACnD,UAAgC,EAAE;IAElC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAqB,aAAa,CAAC,CAAC;IAC5E,MAAM,CAAC,eAAe,EAAE,uBAAuB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC;IAChG,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAwC,IAAI,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAErC,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACxC,2BAA2B;QAC3B,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAa,CAAC;QAC3D,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,CAAC;YAC/D,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC1F,CAAC,EAAE,CAAC,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,CAAC;IAE9C,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CACjC,GAAG,EAAE,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,cAAc,CAAC,EAC5D,CAAC,cAAc,CAAC,CACjB,CAAC;IAEF,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACzC,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,OAAO,OAAO,CAAC,eAAe,CAAC;QACjC,CAAC;QAED,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;QAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC,CAAC;QACpD,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,aAAa,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC,EAAE;QACD,OAAO,CAAC,YAAY;QACpB,OAAO,CAAC,eAAe;QACvB,OAAO,CAAC,kBAAkB;QAC1B,eAAe;QACf,aAAa;KACd,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACvC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrB,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CACpC,CAAC,SAA6B,EAAE,EAAE;QAChC,8BAA8B;QAC9B,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpB,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CACpC,KAAK,EAAE,QAAgB,EAAE,WAAoB,EAAE,EAAE;QAC/C,qCAAqC;QACrC,sCAAsC;QACtC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,IAAI,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YAC9E,MAAM,SAAS,GAAG;gBAChB,GAAG,aAAa;gBAChB,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI;gBAC1C,aAAa;gBACb,eAAe;aAChB,CAAC;YACF,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC3D,MAAM,gBAAgB,GAAG,aAAa,GAAG,eAAe,CAAC;QACzD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,cAAc,CACrC,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,CAAC,OAAO,EACpB,UAAU,CAAC,OAAO,CACnB,CAAC;QACF,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC;QAE7B,MAAM,SAAS,GAAuB;YACpC,SAAS,EAAE,WAAW;YACtB,QAAQ,EAAE,CAAC,WAAW,IAAI,eAAe,GAAG,CAAC,IAAI,eAAe,GAAG,CAAC;YACpE,QAAQ,EAAE,eAAe;YACzB,eAAe,EAAE,KAAK,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI;YACnD,YAAY,EAAE,KAAK;YACnB,aAAa;YACb,gBAAgB;YAChB,eAAe;YACf,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS;YACxD,kBAAkB,EAAE,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;SAC9D,CAAC;QAEF,YAAY,CAAC,SAAS,CAAC,CAAC;QAExB,IAAI,UAAU,CAAC,OAAO,CAAC,YAAY,KAAK,KAAK,IAAI,SAAS,CAAC,eAAe,IAAI,GAAG,EAAE,CAAC;YAClF,MAAM,GAAG,CAAC,UAAU,CAClB;gBACE,MAAM,EAAE,SAAS,CAAC,eAAe;gBACjC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE;gBACzC,OAAO,EAAE,KAAK;aACf,EACD,UAAU,CAAC,OAAO,CAAC,gBAAgB,IAAI,GAAG,CAC3C,CAAC;QACJ,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC,EACD;QACE,eAAe;QACf,GAAG;QACH,cAAc;QACd,YAAY;QACZ,aAAa;KACd,CACF,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QAClC,SAAS,EAAE,CAAC;QACZ,qBAAqB,CAAC,OAAO,GAAG,CAAC,CAAC;QAClC,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;QACvB,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC;QACzB,YAAY,CAAC;YACX,GAAG,aAAa;YAChB,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI;YAC1C,aAAa;YACb,eAAe;SAChB,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;IAE9E,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,SAAS,EAAE,CAAC;QACZ,qBAAqB,CAAC,OAAO,GAAG,CAAC,CAAC;QAClC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,YAAY,CAAC,OAAO,GAAG,CAAC,CAAC;QAEzB,IAAI,UAAU,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,IAAI,GAAG,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7E,MAAM,sBAAsB,CAAC,GAAG,EAAE,cAAc,EAAE,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAE5B,8BAA8B;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,gBAAgB,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAC1E,QAAQ,CAAC,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAClF,MAAM,QAAQ,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;YAEjF,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;gBAClB,SAAS,EAAE,CAAC;gBACZ,qBAAqB,CAAC,OAAO,GAAG,CAAC,CAAC;gBAClC,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;gBACvB,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBACpD,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,cAAc,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,MAAM,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACf,CAAC,EAAE;QACD,eAAe;QACf,GAAG;QACH,cAAc;QACd,SAAS;QACT,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACnC,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,SAAS,EAAE,CAAC;QACZ,qBAAqB,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC;QACjE,YAAY,CAAC;YACX,GAAG,KAAK;YACR,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,IAAI;YACd,cAAc,EAAE,SAAS;YACzB,kBAAkB,EAAE,SAAS;SAC9B,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,eAAe,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE;QAC1C,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;YACxB,MAAM,KAAK,EAAE,CAAC;YACd,OAAO;QACT,CAAC;QAED,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,YAAY,CAAC;YACX,GAAG,KAAK;YACR,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,KAAK;YACf,cAAc,EAAE,cAAc;YAC9B,kBAAkB,EAAE,eAAe;SACpC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,gBAAgB,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAC1E,QAAQ,CAAC,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,GAAG,qBAAqB,CAAC,OAAO,CAAC;YAClF,MAAM,QAAQ,GAAG,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;YAEjF,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;gBAClB,SAAS,EAAE,CAAC;gBACZ,qBAAqB,CAAC,OAAO,GAAG,CAAC,CAAC;gBAClC,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;gBACvB,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBACpD,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,cAAc,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,MAAM,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACf,CAAC,EAAE;QACD,eAAe;QACf,cAAc;QACd,YAAY;QACZ,KAAK;QACL,KAAK;QACL,SAAS;QACT,YAAY;KACb,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAC5B,CAAC,QAAgB,EAAE,EAAE;QACnB,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnD,qBAAqB,CAAC,OAAO,GAAG,OAAO,GAAG,eAAe,GAAG,IAAI,CAAC;QACjE,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,EACD,CAAC,eAAe,EAAE,YAAY,CAAC,CAChC,CAAC;IAEF,MAAM,kBAAkB,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,UAAkB,EAAE,EAAE;QAClE,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEtD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,YAAY,CAAC;YACX,GAAG,aAAa;YAChB,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI;YAC1C,aAAa;YACb,eAAe;SAChB,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,GAAG,KAAK;QACR,aAAa;QACb,eAAe;QACf,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,KAAK,EAAE,CAAC;QACf,CAAC;QACD,KAAK;QACL,MAAM,EAAE,GAAG,EAAE;YACX,KAAK,MAAM,EAAE,CAAC;QAChB,CAAC;QACD,IAAI;QACJ,IAAI;QACJ,kBAAkB;KACnB,CAAC;AACJ,CAAC","sourcesContent":["import * as React from 'react';\n\nimport ExpoGaodeMapModule from '../ExpoGaodeMapModule';\nimport { MapContext } from '../components/MapContext';\nimport type { LatLng } from '../types/common.types';\nimport type {\n RoutePlaybackController,\n RoutePlaybackOptions,\n RoutePlaybackState,\n} from '../types/route-playback.types';\nimport { normalizeLatLngList } from '../utils/GeoUtils';\nimport { fitCameraToCoordinates } from '../utils/RouteUtils';\n\nconst DEFAULT_STATE: RoutePlaybackState = {\n isPlaying: false,\n isPaused: false,\n progress: 0,\n currentPosition: null,\n currentAngle: 0,\n totalDistance: 0,\n traveledDistance: 0,\n durationSeconds: 0,\n};\n\nfunction getMarkerAngle(\n path: LatLng[],\n totalDistance: number,\n targetDistance: number,\n lastAngle: number,\n options: RoutePlaybackOptions\n): { angle: number; point: LatLng | null } {\n // 先取当前距离上的点,再额外预读一个前瞻点,\n // 让车辆朝向在转弯时更自然,不会出现生硬折角。\n const pointInfo = ExpoGaodeMapModule.getPointAtDistance(path, targetDistance);\n if (!pointInfo) {\n return { angle: lastAngle, point: null };\n }\n\n const lookAheadDistance = options.lookAheadDistanceMeters ?? 5;\n const futurePoint = ExpoGaodeMapModule.getPointAtDistance(\n path,\n Math.min(totalDistance, targetDistance + lookAheadDistance)\n );\n\n let targetAngle = pointInfo.angle;\n if (futurePoint && targetDistance + lookAheadDistance < totalDistance) {\n let diffNext = futurePoint.angle - pointInfo.angle;\n if (diffNext > 180) diffNext -= 360;\n if (diffNext < -180) diffNext += 360;\n targetAngle = pointInfo.angle + diffNext * 0.4;\n }\n\n let diff = targetAngle - lastAngle;\n if (diff > 180) diff -= 360;\n if (diff < -180) diff += 360;\n\n const bearingSmoothing = options.bearingSmoothing ?? 0.2;\n return {\n angle: lastAngle + diff * bearingSmoothing,\n point: {\n latitude: pointInfo.latitude,\n longitude: pointInfo.longitude,\n },\n };\n}\n\nexport function useRoutePlayback(\n points: Array<LatLng | [number, number] | number[]>,\n options: RoutePlaybackOptions = {}\n): RoutePlaybackController {\n const map = React.useContext(MapContext);\n const optionsRef = React.useRef(options);\n const [state, setState] = React.useState<RoutePlaybackState>(DEFAULT_STATE);\n const [speedMultiplier, setSpeedMultiplierState] = React.useState(options.speedMultiplier ?? 1);\n const timerRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n const startAtRef = React.useRef(0);\n const elapsedBeforePauseRef = React.useRef(0);\n const lastAngleRef = React.useRef(0);\n\n React.useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const normalizedPath = React.useMemo(() => {\n // 长路径可选走一次抽稀,减少轨迹回放期间的计算量。\n const normalized = normalizeLatLngList(points) as LatLng[];\n if (normalized.length <= 2 || !options.simplificationTolerance) {\n return normalized;\n }\n return ExpoGaodeMapModule.simplifyPolyline(normalized, options.simplificationTolerance);\n }, [options.simplificationTolerance, points]);\n\n const totalDistance = React.useMemo(\n () => ExpoGaodeMapModule.calculatePathLength(normalizedPath),\n [normalizedPath]\n );\n\n const durationSeconds = React.useMemo(() => {\n if (options.durationSeconds) {\n return options.durationSeconds;\n }\n\n const baseSpeed = options.baseSpeedMps ?? 15;\n const minDuration = options.minDurationSeconds ?? 5;\n if (totalDistance <= 0) {\n return 0;\n }\n\n return Math.max(minDuration, totalDistance / (baseSpeed * Math.max(speedMultiplier, 0.1)));\n }, [\n options.baseSpeedMps,\n options.durationSeconds,\n options.minDurationSeconds,\n speedMultiplier,\n totalDistance,\n ]);\n\n const stopTimer = React.useCallback(() => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const publishState = React.useCallback(\n (nextState: RoutePlaybackState) => {\n // 统一从这里下发状态,避免不同控制分支各自维护回调时机。\n setState(nextState);\n optionsRef.current.onProgress?.(nextState);\n },\n []\n );\n\n const syncProgress = React.useCallback(\n async (progress: number, keepPlaying: boolean) => {\n // 根据“当前进度 -> 已行驶距离 -> 路径上的点”推导出车辆位置,\n // 再同步决定 smoothMovePath / 相机跟随 / 对外状态。\n if (normalizedPath.length === 0 || totalDistance <= 0 || durationSeconds <= 0) {\n const nextState = {\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n };\n publishState(nextState);\n return nextState;\n }\n\n const clampedProgress = Math.max(0, Math.min(1, progress));\n const traveledDistance = totalDistance * clampedProgress;\n const { angle, point } = getMarkerAngle(\n normalizedPath,\n totalDistance,\n traveledDistance,\n lastAngleRef.current,\n optionsRef.current\n );\n lastAngleRef.current = angle;\n\n const nextState: RoutePlaybackState = {\n isPlaying: keepPlaying,\n isPaused: !keepPlaying && clampedProgress > 0 && clampedProgress < 1,\n progress: clampedProgress,\n currentPosition: point ?? normalizedPath[0] ?? null,\n currentAngle: angle,\n totalDistance,\n traveledDistance,\n durationSeconds,\n smoothMovePath: keepPlaying ? normalizedPath : undefined,\n smoothMoveDuration: keepPlaying ? durationSeconds : undefined,\n };\n\n publishState(nextState);\n\n if (optionsRef.current.followCamera !== false && nextState.currentPosition && map) {\n await map.moveCamera(\n {\n target: nextState.currentPosition,\n zoom: optionsRef.current.followZoom ?? 17,\n bearing: angle,\n },\n optionsRef.current.updateIntervalMs ?? 100\n );\n }\n\n return nextState;\n },\n [\n durationSeconds,\n map,\n normalizedPath,\n publishState,\n totalDistance,\n ]\n );\n\n const stop = React.useCallback(() => {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n lastAngleRef.current = 0;\n publishState({\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n });\n }, [durationSeconds, normalizedPath, publishState, stopTimer, totalDistance]);\n\n const start = React.useCallback(async () => {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = Date.now();\n lastAngleRef.current = 0;\n\n if (optionsRef.current.autoFit !== false && map && normalizedPath.length > 0) {\n await fitCameraToCoordinates(map, normalizedPath, optionsRef.current.fitOptions);\n }\n\n await syncProgress(0, true);\n\n // 使用固定间隔推进进度,保证回放速度与 UI 反馈稳定。\n const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);\n timerRef.current = setInterval(async () => {\n const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;\n const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);\n\n if (progress >= 1) {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n const completedState = await syncProgress(1, false);\n optionsRef.current.onComplete?.(completedState);\n return;\n }\n\n await syncProgress(progress, true);\n }, interval);\n }, [\n durationSeconds,\n map,\n normalizedPath,\n stopTimer,\n syncProgress,\n ]);\n\n const pause = React.useCallback(() => {\n if (!timerRef.current || durationSeconds <= 0) {\n return;\n }\n\n stopTimer();\n elapsedBeforePauseRef.current += Date.now() - startAtRef.current;\n publishState({\n ...state,\n isPlaying: false,\n isPaused: true,\n smoothMovePath: undefined,\n smoothMoveDuration: undefined,\n });\n }, [durationSeconds, publishState, state, stopTimer]);\n\n const resume = React.useCallback(async () => {\n if (state.progress >= 1) {\n await start();\n return;\n }\n\n startAtRef.current = Date.now();\n publishState({\n ...state,\n isPlaying: true,\n isPaused: false,\n smoothMovePath: normalizedPath,\n smoothMoveDuration: durationSeconds,\n });\n\n const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);\n timerRef.current = setInterval(async () => {\n const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;\n const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);\n\n if (progress >= 1) {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n const completedState = await syncProgress(1, false);\n optionsRef.current.onComplete?.(completedState);\n return;\n }\n\n await syncProgress(progress, true);\n }, interval);\n }, [\n durationSeconds,\n normalizedPath,\n publishState,\n start,\n state,\n stopTimer,\n syncProgress,\n ]);\n\n const seek = React.useCallback(\n (progress: number) => {\n // seek 只重算“已经播放过的时间”,其余状态由 syncProgress 统一刷新。\n const clamped = Math.max(0, Math.min(1, progress));\n elapsedBeforePauseRef.current = clamped * durationSeconds * 1000;\n startAtRef.current = Date.now();\n void syncProgress(clamped, !!timerRef.current);\n },\n [durationSeconds, syncProgress]\n );\n\n const setSpeedMultiplier = React.useCallback((multiplier: number) => {\n setSpeedMultiplierState(Math.max(multiplier, 0.1));\n }, []);\n\n React.useEffect(() => () => stopTimer(), [stopTimer]);\n\n React.useEffect(() => {\n publishState({\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n });\n }, [durationSeconds, normalizedPath, publishState, totalDistance]);\n\n return {\n ...state,\n totalDistance,\n durationSeconds,\n start: () => {\n void start();\n },\n pause,\n resume: () => {\n void resume();\n },\n stop,\n seek,\n setSpeedMultiplier,\n };\n}\n"]}
1
+ {"version":3,"file":"useRoutePlayback.js","sourceRoot":"","sources":["../../src/hooks/useRoutePlayback.ts"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport ExpoGaodeMapModule from '../ExpoGaodeMapModule';\nimport { MapContext } from '../components/MapContext';\nimport type { LatLng } from '../types/common.types';\nimport type {\n RoutePlaybackController,\n RoutePlaybackOptions,\n RoutePlaybackState,\n} from '../types/route-playback.types';\nimport { normalizeLatLngList } from '../utils/GeoUtils';\nimport { fitCameraToCoordinates } from '../utils/RouteUtils';\n\nconst DEFAULT_STATE: RoutePlaybackState = {\n isPlaying: false,\n isPaused: false,\n progress: 0,\n currentPosition: null,\n currentAngle: 0,\n totalDistance: 0,\n traveledDistance: 0,\n durationSeconds: 0,\n};\n\nfunction getMarkerAngle(\n path: LatLng[],\n totalDistance: number,\n targetDistance: number,\n lastAngle: number,\n options: RoutePlaybackOptions\n): { angle: number; point: LatLng | null } {\n // 先取当前距离上的点,再额外预读一个前瞻点,\n // 让车辆朝向在转弯时更自然,不会出现生硬折角。\n const pointInfo = ExpoGaodeMapModule.getPointAtDistance(path, targetDistance);\n if (!pointInfo) {\n return { angle: lastAngle, point: null };\n }\n\n const lookAheadDistance = options.lookAheadDistanceMeters ?? 5;\n const futurePoint = ExpoGaodeMapModule.getPointAtDistance(\n path,\n Math.min(totalDistance, targetDistance + lookAheadDistance)\n );\n\n let targetAngle = pointInfo.angle;\n if (futurePoint && targetDistance + lookAheadDistance < totalDistance) {\n let diffNext = futurePoint.angle - pointInfo.angle;\n if (diffNext > 180) diffNext -= 360;\n if (diffNext < -180) diffNext += 360;\n targetAngle = pointInfo.angle + diffNext * 0.4;\n }\n\n let diff = targetAngle - lastAngle;\n if (diff > 180) diff -= 360;\n if (diff < -180) diff += 360;\n\n const bearingSmoothing = options.bearingSmoothing ?? 0.2;\n return {\n angle: lastAngle + diff * bearingSmoothing,\n point: {\n latitude: pointInfo.latitude,\n longitude: pointInfo.longitude,\n },\n };\n}\n\nexport function useRoutePlayback(\n points: Array<LatLng | [number, number] | number[]>,\n options: RoutePlaybackOptions = {}\n): RoutePlaybackController {\n const map = React.useContext(MapContext);\n const optionsRef = React.useRef(options);\n const [state, setState] = React.useState<RoutePlaybackState>(DEFAULT_STATE);\n const stateRef = React.useRef<RoutePlaybackState>(DEFAULT_STATE);\n const [speedMultiplier, setSpeedMultiplierState] = React.useState(options.speedMultiplier ?? 1);\n const timerRef = React.useRef<ReturnType<typeof setInterval> | null>(null);\n const startAtRef = React.useRef(0);\n const elapsedBeforePauseRef = React.useRef(0);\n const lastAngleRef = React.useRef(0);\n const previousPathRef = React.useRef<LatLng[] | null>(null);\n const previousDurationRef = React.useRef<number | null>(null);\n\n React.useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const normalizedPath = React.useMemo(() => {\n // 长路径可选走一次抽稀,减少轨迹回放期间的计算量。\n const normalized = normalizeLatLngList(points) as LatLng[];\n if (normalized.length <= 2 || !options.simplificationTolerance) {\n return normalized;\n }\n return ExpoGaodeMapModule.simplifyPolyline(normalized, options.simplificationTolerance);\n }, [options.simplificationTolerance, points]);\n\n const totalDistance = React.useMemo(\n () => ExpoGaodeMapModule.calculatePathLength(normalizedPath),\n [normalizedPath]\n );\n\n const durationSeconds = React.useMemo(() => {\n if (options.durationSeconds) {\n return options.durationSeconds;\n }\n\n const baseSpeed = options.baseSpeedMps ?? 15;\n const minDuration = options.minDurationSeconds ?? 5;\n if (totalDistance <= 0) {\n return 0;\n }\n\n return Math.max(minDuration, totalDistance / (baseSpeed * Math.max(speedMultiplier, 0.1)));\n }, [\n options.baseSpeedMps,\n options.durationSeconds,\n options.minDurationSeconds,\n speedMultiplier,\n totalDistance,\n ]);\n\n const stopTimer = React.useCallback(() => {\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const publishState = React.useCallback(\n (nextState: RoutePlaybackState) => {\n // 统一从这里下发状态,避免不同控制分支各自维护回调时机。\n stateRef.current = nextState;\n setState(nextState);\n optionsRef.current.onProgress?.(nextState);\n },\n []\n );\n\n const syncProgress = React.useCallback(\n async (progress: number, keepPlaying: boolean) => {\n // 根据“当前进度 -> 已行驶距离 -> 路径上的点”推导出车辆位置,\n // 再同步决定 smoothMovePath / 相机跟随 / 对外状态。\n if (normalizedPath.length === 0 || totalDistance <= 0 || durationSeconds <= 0) {\n const nextState = {\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n };\n publishState(nextState);\n return nextState;\n }\n\n const clampedProgress = Math.max(0, Math.min(1, progress));\n const traveledDistance = totalDistance * clampedProgress;\n const { angle, point } = getMarkerAngle(\n normalizedPath,\n totalDistance,\n traveledDistance,\n lastAngleRef.current,\n optionsRef.current\n );\n lastAngleRef.current = angle;\n\n const nextState: RoutePlaybackState = {\n isPlaying: keepPlaying,\n isPaused: !keepPlaying && clampedProgress > 0 && clampedProgress < 1,\n progress: clampedProgress,\n currentPosition: point ?? normalizedPath[0] ?? null,\n currentAngle: angle,\n totalDistance,\n traveledDistance,\n durationSeconds,\n smoothMovePath: keepPlaying ? normalizedPath : undefined,\n smoothMoveDuration: keepPlaying ? durationSeconds : undefined,\n };\n\n publishState(nextState);\n\n if (optionsRef.current.followCamera === true && nextState.currentPosition && map) {\n await map.moveCamera(\n {\n target: nextState.currentPosition,\n zoom: optionsRef.current.followZoom ?? 17,\n bearing: angle,\n },\n optionsRef.current.updateIntervalMs ?? 100\n );\n }\n\n return nextState;\n },\n [\n durationSeconds,\n map,\n normalizedPath,\n publishState,\n totalDistance,\n ]\n );\n\n const runPlaybackTick = React.useCallback(async () => {\n const elapsedMs = Date.now() - startAtRef.current + elapsedBeforePauseRef.current;\n const progress = durationSeconds <= 0 ? 1 : elapsedMs / (durationSeconds * 1000);\n\n if (progress >= 1) {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n const completedState = await syncProgress(1, false);\n optionsRef.current.onComplete?.(completedState);\n return;\n }\n\n await syncProgress(progress, true);\n }, [durationSeconds, stopTimer, syncProgress]);\n\n const startTimer = React.useCallback(() => {\n stopTimer();\n const interval = Math.max(optionsRef.current.updateIntervalMs ?? 100, 16);\n timerRef.current = setInterval(() => {\n void runPlaybackTick();\n }, interval);\n }, [runPlaybackTick, stopTimer]);\n\n const stop = React.useCallback(() => {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n lastAngleRef.current = 0;\n publishState({\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n });\n }, [durationSeconds, normalizedPath, publishState, stopTimer, totalDistance]);\n\n const start = React.useCallback(async () => {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = Date.now();\n lastAngleRef.current = 0;\n\n if (optionsRef.current.autoFit === true && map && normalizedPath.length > 0) {\n await fitCameraToCoordinates(map, normalizedPath, optionsRef.current.fitOptions);\n }\n\n await syncProgress(0, true);\n startTimer();\n }, [\n map,\n normalizedPath,\n startTimer,\n syncProgress,\n ]);\n\n const pause = React.useCallback(() => {\n if (!timerRef.current || durationSeconds <= 0) {\n return;\n }\n\n stopTimer();\n elapsedBeforePauseRef.current += Date.now() - startAtRef.current;\n startAtRef.current = 0;\n const currentState = stateRef.current;\n publishState({\n ...currentState,\n isPlaying: false,\n isPaused: true,\n smoothMovePath: undefined,\n smoothMoveDuration: undefined,\n });\n }, [durationSeconds, publishState, stopTimer]);\n\n const resume = React.useCallback(async () => {\n const currentState = stateRef.current;\n if (currentState.progress >= 1) {\n await start();\n return;\n }\n\n startAtRef.current = Date.now();\n publishState({\n ...currentState,\n isPlaying: true,\n isPaused: false,\n smoothMovePath: normalizedPath,\n smoothMoveDuration: durationSeconds,\n });\n startTimer();\n }, [\n durationSeconds,\n normalizedPath,\n publishState,\n start,\n startTimer,\n ]);\n\n const seek = React.useCallback(\n (progress: number) => {\n // seek 只重算“已经播放过的时间”,其余状态由 syncProgress 统一刷新。\n const clamped = Math.max(0, Math.min(1, progress));\n elapsedBeforePauseRef.current = clamped * durationSeconds * 1000;\n startAtRef.current = Date.now();\n void syncProgress(clamped, !!timerRef.current);\n },\n [durationSeconds, syncProgress]\n );\n\n const setSpeedMultiplier = React.useCallback((multiplier: number) => {\n setSpeedMultiplierState(Math.max(multiplier, 0.1));\n }, []);\n\n React.useEffect(() => () => stopTimer(), [stopTimer]);\n\n React.useEffect(() => {\n const pathChanged = previousPathRef.current !== normalizedPath;\n const durationChanged =\n previousDurationRef.current !== null && previousDurationRef.current !== durationSeconds;\n\n previousPathRef.current = normalizedPath;\n previousDurationRef.current = durationSeconds;\n\n if (pathChanged) {\n stopTimer();\n elapsedBeforePauseRef.current = 0;\n startAtRef.current = 0;\n lastAngleRef.current = 0;\n publishState({\n ...DEFAULT_STATE,\n currentPosition: normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n });\n return;\n }\n\n if (!durationChanged) {\n return;\n }\n\n const currentState = stateRef.current;\n\n if (currentState.isPlaying) {\n elapsedBeforePauseRef.current = currentState.progress * durationSeconds * 1000;\n startAtRef.current = Date.now();\n publishState({\n ...currentState,\n totalDistance,\n durationSeconds,\n smoothMoveDuration: durationSeconds,\n });\n startTimer();\n return;\n }\n\n if (currentState.isPaused) {\n elapsedBeforePauseRef.current = currentState.progress * durationSeconds * 1000;\n startAtRef.current = 0;\n publishState({\n ...currentState,\n totalDistance,\n durationSeconds,\n });\n return;\n }\n\n publishState({\n ...currentState,\n currentPosition:\n currentState.progress > 0 ? currentState.currentPosition : normalizedPath[0] ?? null,\n totalDistance,\n durationSeconds,\n });\n }, [durationSeconds, normalizedPath, publishState, startTimer, stopTimer, totalDistance]);\n\n return {\n ...state,\n totalDistance,\n durationSeconds,\n start: () => {\n void start();\n },\n pause,\n resume: () => {\n void resume();\n },\n stop,\n seek,\n setSpeedMultiplier,\n };\n}\n"]}
@@ -170,24 +170,24 @@ export declare enum MapType {
170
170
  /**
171
171
  * 标准地图
172
172
  */
173
- Standard = 0,
173
+ Standard = 1,
174
174
  /**
175
175
  * 卫星地图
176
176
  */
177
- Satellite = 1,
177
+ Satellite = 2,
178
178
  /**
179
179
  * 夜间地图
180
180
  */
181
- Night = 2,
181
+ Night = 3,
182
182
  /**
183
183
  * 导航地图
184
184
  */
185
- Navi = 3,
185
+ Navi = 4,
186
186
  /**
187
187
  * 公交地图
188
188
  * @platform android
189
189
  */
190
- Bus = 4
190
+ Bus = 5
191
191
  }
192
192
  /**
193
193
  * 颜色值类型
@@ -6,23 +6,23 @@ export var MapType;
6
6
  /**
7
7
  * 标准地图
8
8
  */
9
- MapType[MapType["Standard"] = 0] = "Standard";
9
+ MapType[MapType["Standard"] = 1] = "Standard";
10
10
  /**
11
11
  * 卫星地图
12
12
  */
13
- MapType[MapType["Satellite"] = 1] = "Satellite";
13
+ MapType[MapType["Satellite"] = 2] = "Satellite";
14
14
  /**
15
15
  * 夜间地图
16
16
  */
17
- MapType[MapType["Night"] = 2] = "Night";
17
+ MapType[MapType["Night"] = 3] = "Night";
18
18
  /**
19
19
  * 导航地图
20
20
  */
21
- MapType[MapType["Navi"] = 3] = "Navi";
21
+ MapType[MapType["Navi"] = 4] = "Navi";
22
22
  /**
23
23
  * 公交地图
24
24
  * @platform android
25
25
  */
26
- MapType[MapType["Bus"] = 4] = "Bus";
26
+ MapType[MapType["Bus"] = 5] = "Bus";
27
27
  })(MapType || (MapType = {}));
28
28
  //# sourceMappingURL=common.types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"common.types.js","sourceRoot":"","sources":["../../src/types/common.types.ts"],"names":[],"mappings":"AA8LA;;GAEG;AACH,MAAM,CAAN,IAAY,OA0BX;AA1BD,WAAY,OAAO;IACjB;;OAEG;IACH,6CAAY,CAAA;IAEZ;;OAEG;IACH,+CAAa,CAAA;IAEb;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,qCAAQ,CAAA;IAER;;;OAGG;IACH,mCAAO,CAAA;AACT,CAAC,EA1BW,OAAO,KAAP,OAAO,QA0BlB","sourcesContent":["/**\n * 高德地图通用类型定义\n * 基于 Expo Modules API\n */\nimport { PermissionResponse } from 'expo-modules-core';\n\n/**\n * SDK 配置参数\n */\nexport interface SDKConfig {\n /** Android 平台的高德地图 API Key */\n androidKey?: string;\n /** iOS 平台的高德地图 API Key */\n iosKey?: string;\n /** web api key 如果要使用expo-gaode-map-web-api相关的功能,需要配置web api key*/\n webKey?: string;\n}\n\n/**\n * 隐私同意配置\n * 推荐优先使用该配置对象一次性完成隐私状态同步\n */\nexport interface PrivacyConfig {\n /** 是否已经向用户展示隐私弹窗或隐私说明 */\n hasShow: boolean;\n /** 展示内容中是否包含隐私政策条款 */\n hasContainsPrivacy: boolean;\n /** 用户是否已经同意隐私政策 */\n hasAgree: boolean;\n /** 可选的隐私协议版本号;变更后会要求重新同意 */\n privacyVersion?: string;\n}\n/**\n * 隐私政策状态\n */\nexport interface PrivacyStatus {\n hasShow: boolean;\n hasContainsPrivacy: boolean;\n hasAgree: boolean;\n isReady: boolean;\n privacyVersion?: string | null;\n agreedPrivacyVersion?: string | null;\n restoredFromStorage?: boolean;\n}\n\n/**\n * 权限状态(增强版,支持 Android 14+ 和 iOS 17+)\n */\nexport interface PermissionStatus extends PermissionResponse{\n /** 是否已授权(前台位置权限) */\n granted: boolean;\n \n // Android 专用字段\n /** Android 精确位置权限 */\n fineLocation?: boolean;\n /** Android 粗略位置权限 */\n coarseLocation?: boolean;\n /** Android 后台位置权限(Android 10+) */\n backgroundLocation?: boolean;\n /** 是否应显示权限说明(Android) */\n shouldShowRationale?: boolean;\n /** 权限是否被永久拒绝(Android) */\n isPermanentlyDenied?: boolean;\n /** 是否为 Android 14+(Android) */\n isAndroid14Plus?: boolean;\n \n // 其他字段\n /** 额外的消息说明 */\n message?: string;\n}\n\n/**\n * 点坐标(屏幕坐标)\n */\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * 地理坐标\n */\nexport interface LatLng {\n /**\n * 纬度\n */\n latitude: number;\n\n /**\n * 经度\n */\n longitude: number;\n}\n\n/**\n * 坐标点类型\n * 支持对象格式 { latitude, longitude }\n * 或数组格式 [longitude, latitude] (GeoJSON 标准)\n * 注意:GeoJSON 标准允许数组包含更多元素(如海拔),但本组件只使用前两个\n */\nexport type LatLngPoint = LatLng | [number, number] | number[];\n\n/**\n * 地图标注点(POI)\n */\nexport interface MapPoi {\n /**\n * 标注点 ID\n */\n id: string;\n\n /**\n * 标注点名称\n */\n name: string;\n\n /**\n * 标注点坐标\n */\n position: LatLng;\n}\n\n/**\n * 矩形坐标边界\n */\nexport interface LatLngBounds {\n /**\n * 西南坐标\n */\n southwest: LatLng;\n\n /**\n * 东北坐标\n */\n northeast: LatLng;\n}\n\n/**\n * 地图相机位置\n */\nexport interface CameraPosition {\n /**\n * 中心坐标\n */\n target?: LatLng;\n\n /**\n * 缩放级别(3-20)\n */\n zoom?: number;\n\n /**\n * 朝向、旋转角度(0-360度)\n */\n bearing?: number;\n\n /**\n * 倾斜角度(0-60度)\n */\n tilt?: number;\n}\n\n/**\n * 地图相机更新参数\n *\n * 与 `CameraPosition` 不同,这个类型用于描述“局部更新”:\n * 你可以只传 `zoom`、只传 `target`,或组合传入需要变更的字段。\n */\nexport interface CameraUpdate {\n /**\n * 中心坐标\n */\n target?: LatLng;\n\n /**\n * 缩放级别(3-20)\n */\n zoom?: number;\n\n /**\n * 朝向、旋转角度(0-360度)\n */\n bearing?: number;\n\n /**\n * 倾斜角度(0-60度)\n */\n tilt?: number;\n}\n\n/**\n * 地图类型\n */\nexport enum MapType {\n /**\n * 标准地图\n */\n Standard = 0,\n\n /**\n * 卫星地图\n */\n Satellite = 1,\n\n /**\n * 夜间地图\n */\n Night = 2,\n\n /**\n * 导航地图\n */\n Navi = 3,\n\n /**\n * 公交地图\n * @platform android\n */\n Bus = 4,\n}\n\n/**\n * 颜色值类型\n * 支持:\n * - 十六进制字符串: '#AARRGGBB' 或 '#RRGGBB'\n * - 数字格式: 0xAARRGGBB (用于 Android)\n */\nexport type ColorValue = string | number;\n"]}
1
+ {"version":3,"file":"common.types.js","sourceRoot":"","sources":["../../src/types/common.types.ts"],"names":[],"mappings":"AA8LA;;GAEG;AACH,MAAM,CAAN,IAAY,OA0BX;AA1BD,WAAY,OAAO;IACjB;;OAEG;IACH,6CAAY,CAAA;IAEZ;;OAEG;IACH,+CAAa,CAAA;IAEb;;OAEG;IACH,uCAAS,CAAA;IAET;;OAEG;IACH,qCAAQ,CAAA;IAER;;;OAGG;IACH,mCAAO,CAAA;AACT,CAAC,EA1BW,OAAO,KAAP,OAAO,QA0BlB","sourcesContent":["/**\n * 高德地图通用类型定义\n * 基于 Expo Modules API\n */\nimport { PermissionResponse } from 'expo-modules-core';\n\n/**\n * SDK 配置参数\n */\nexport interface SDKConfig {\n /** Android 平台的高德地图 API Key */\n androidKey?: string;\n /** iOS 平台的高德地图 API Key */\n iosKey?: string;\n /** web api key 如果要使用expo-gaode-map-web-api相关的功能,需要配置web api key*/\n webKey?: string;\n}\n\n/**\n * 隐私同意配置\n * 推荐优先使用该配置对象一次性完成隐私状态同步\n */\nexport interface PrivacyConfig {\n /** 是否已经向用户展示隐私弹窗或隐私说明 */\n hasShow: boolean;\n /** 展示内容中是否包含隐私政策条款 */\n hasContainsPrivacy: boolean;\n /** 用户是否已经同意隐私政策 */\n hasAgree: boolean;\n /** 可选的隐私协议版本号;变更后会要求重新同意 */\n privacyVersion?: string;\n}\n/**\n * 隐私政策状态\n */\nexport interface PrivacyStatus {\n hasShow: boolean;\n hasContainsPrivacy: boolean;\n hasAgree: boolean;\n isReady: boolean;\n privacyVersion?: string | null;\n agreedPrivacyVersion?: string | null;\n restoredFromStorage?: boolean;\n}\n\n/**\n * 权限状态(增强版,支持 Android 14+ 和 iOS 17+)\n */\nexport interface PermissionStatus extends PermissionResponse{\n /** 是否已授权(前台位置权限) */\n granted: boolean;\n \n // Android 专用字段\n /** Android 精确位置权限 */\n fineLocation?: boolean;\n /** Android 粗略位置权限 */\n coarseLocation?: boolean;\n /** Android 后台位置权限(Android 10+) */\n backgroundLocation?: boolean;\n /** 是否应显示权限说明(Android) */\n shouldShowRationale?: boolean;\n /** 权限是否被永久拒绝(Android) */\n isPermanentlyDenied?: boolean;\n /** 是否为 Android 14+(Android) */\n isAndroid14Plus?: boolean;\n \n // 其他字段\n /** 额外的消息说明 */\n message?: string;\n}\n\n/**\n * 点坐标(屏幕坐标)\n */\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * 地理坐标\n */\nexport interface LatLng {\n /**\n * 纬度\n */\n latitude: number;\n\n /**\n * 经度\n */\n longitude: number;\n}\n\n/**\n * 坐标点类型\n * 支持对象格式 { latitude, longitude }\n * 或数组格式 [longitude, latitude] (GeoJSON 标准)\n * 注意:GeoJSON 标准允许数组包含更多元素(如海拔),但本组件只使用前两个\n */\nexport type LatLngPoint = LatLng | [number, number] | number[];\n\n/**\n * 地图标注点(POI)\n */\nexport interface MapPoi {\n /**\n * 标注点 ID\n */\n id: string;\n\n /**\n * 标注点名称\n */\n name: string;\n\n /**\n * 标注点坐标\n */\n position: LatLng;\n}\n\n/**\n * 矩形坐标边界\n */\nexport interface LatLngBounds {\n /**\n * 西南坐标\n */\n southwest: LatLng;\n\n /**\n * 东北坐标\n */\n northeast: LatLng;\n}\n\n/**\n * 地图相机位置\n */\nexport interface CameraPosition {\n /**\n * 中心坐标\n */\n target?: LatLng;\n\n /**\n * 缩放级别(3-20)\n */\n zoom?: number;\n\n /**\n * 朝向、旋转角度(0-360度)\n */\n bearing?: number;\n\n /**\n * 倾斜角度(0-60度)\n */\n tilt?: number;\n}\n\n/**\n * 地图相机更新参数\n *\n * 与 `CameraPosition` 不同,这个类型用于描述“局部更新”:\n * 你可以只传 `zoom`、只传 `target`,或组合传入需要变更的字段。\n */\nexport interface CameraUpdate {\n /**\n * 中心坐标\n */\n target?: LatLng;\n\n /**\n * 缩放级别(3-20)\n */\n zoom?: number;\n\n /**\n * 朝向、旋转角度(0-360度)\n */\n bearing?: number;\n\n /**\n * 倾斜角度(0-60度)\n */\n tilt?: number;\n}\n\n/**\n * 地图类型\n */\nexport enum MapType {\n /**\n * 标准地图\n */\n Standard = 1,\n\n /**\n * 卫星地图\n */\n Satellite = 2,\n\n /**\n * 夜间地图\n */\n Night = 3,\n\n /**\n * 导航地图\n */\n Navi = 4,\n\n /**\n * 公交地图\n * @platform android\n */\n Bus = 5,\n}\n\n/**\n * 颜色值类型\n * 支持:\n * - 十六进制字符串: '#AARRGGBB' 或 '#RRGGBB'\n * - 数字格式: 0xAARRGGBB (用于 Android)\n */\nexport type ColorValue = string | number;\n"]}
@@ -179,7 +179,7 @@ public class ExpoGaodeMapModule: Module {
179
179
 
180
180
  if status == .authorizedAlways || status == .authorizedWhenInUse {
181
181
  let manager = self.getLocationManager()
182
- manager.locationManager?.requestLocation(withReGeocode: manager.locationManager?.locatingWithReGeocode ?? true, completionBlock: { location, regeocode, error in
182
+ manager.requestSingleLocation { location, regeocode, error in
183
183
  if let error = error {
184
184
  promise.reject("LOCATION_ERROR", error.localizedDescription)
185
185
  return
@@ -214,7 +214,7 @@ public class ExpoGaodeMapModule: Module {
214
214
  }
215
215
 
216
216
  promise.resolve(locationData)
217
- })
217
+ }
218
218
  } else {
219
219
  promise.reject("LOCATION_ERROR", "location unauthorized")
220
220
  }
@@ -699,8 +699,13 @@ public class ExpoGaodeMapModule: Module {
699
699
  if self.permissionManager == nil {
700
700
  self.permissionManager = PermissionManager()
701
701
  }
702
-
703
- self.permissionManager?.requestPermission { granted, status in
702
+
703
+ guard let permissionManager = self.permissionManager else {
704
+ promise.reject("PERMISSION_MANAGER_INIT_FAILED", "权限管理器初始化失败")
705
+ return
706
+ }
707
+
708
+ permissionManager.requestPermission { _, _ in
704
709
  // 无论结果如何,都延迟后再次检查最终状态
705
710
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
706
711
  let finalStatus = self.currentAuthorizationStatus()
@@ -16,8 +16,8 @@ import QuartzCore
16
16
  class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate {
17
17
  // MARK: - 属性
18
18
 
19
- /// 地图类型 (0:标准 1:卫星 2:夜间 3:导航)
20
- var mapType: Int = 0
19
+ /// 地图类型 (1:标准 2:卫星 3:夜间 4:导航 5:公交)
20
+ var mapType: Int = 1
21
21
  /// 初始相机位置
22
22
  var initialCameraPosition: [String: Any]?
23
23
  /// 是否显示缩放控件
@@ -96,6 +96,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
96
96
  private var uiManager: UIManager!
97
97
  /// 地图是否已加载完成
98
98
  private var isMapLoaded = false
99
+ /// 初始相机是否已应用(仅应用一次,避免与运行时相机控制冲突)
100
+ private var hasAppliedInitialCameraPosition = false
99
101
  /// 是否正在处理 annotation 选择事件
100
102
  private var isHandlingAnnotationSelect = false
101
103
  /// MarkerView 的隐藏容器(用于渲染 children)
@@ -356,7 +358,7 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
356
358
  return
357
359
  }
358
360
 
359
- uiManager.setMapType(0)
361
+ uiManager.setMapType(1)
360
362
  uiManager.setShowsScale(showsScale)
361
363
  uiManager.setShowsCompass(showsCompass)
362
364
  uiManager.setZoomEnabled(isZoomEnabled)
@@ -380,9 +382,10 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
380
382
 
381
383
  uiManager.setMapType(mapType)
382
384
 
383
- // 如果有初始位置,设置相机位置
384
- if let position = initialCameraPosition {
385
+ // initialCameraPosition 只应用一次,避免每次 props 更新重置相机导致操作延迟感
386
+ if !hasAppliedInitialCameraPosition, let position = initialCameraPosition {
385
387
  cameraManager.setInitialCameraPosition(position)
388
+ hasAppliedInitialCameraPosition = true
386
389
  }
387
390
 
388
391
  uiManager.setShowsScale(showsScale)
@@ -413,17 +416,20 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
413
416
  */
414
417
  private func updateAppleMapStyle() {
415
418
  switch mapType {
416
- case 1: // 卫星
419
+ case 2: // 卫星
417
420
  appleMapView.mapType = .satellite
418
421
  appleMapView.overrideUserInterfaceStyle = .unspecified
419
- case 2: // 夜间
422
+ case 3: // 夜间
420
423
  // 苹果地图没有专门的夜间模式枚举,通过强制 Dark Mode 实现
421
424
  appleMapView.mapType = .standard
422
425
  appleMapView.overrideUserInterfaceStyle = .dark
423
- case 3: // 导航
426
+ case 4: // 导航
424
427
  appleMapView.mapType = .standard
425
428
  appleMapView.overrideUserInterfaceStyle = .unspecified
426
- default: // 标准 (0)
429
+ case 5: // 公交
430
+ appleMapView.mapType = .standard
431
+ appleMapView.overrideUserInterfaceStyle = .unspecified
432
+ default: // 标准 (1,兼容旧值 0)
427
433
  appleMapView.mapType = .standard
428
434
  // 标准模式下跟随系统,如果系统是深色则显示深色,否则浅色
429
435
  appleMapView.overrideUserInterfaceStyle = .unspecified
@@ -853,6 +859,8 @@ class ExpoGaodeMapView: ExpoView, MAMapViewDelegate, UIGestureRecognizerDelegate
853
859
 
854
860
  mapView = resolvedMapView
855
861
  super.addSubview(resolvedMapView)
862
+ isMapLoaded = false
863
+ hasAppliedInitialCameraPosition = false
856
864
 
857
865
  cameraManager = CameraManager(mapView: resolvedMapView)
858
866
  uiManager = UIManager(mapView: resolvedMapView)
@@ -918,6 +926,12 @@ extension ExpoGaodeMapView {
918
926
  public func mapViewDidFinishLoadingMap(_ mapView: MAMapView) {
919
927
  guard !isMapLoaded else { return }
920
928
  isMapLoaded = true
929
+
930
+ // 兜底:若初始化阶段尚未生效,在加载完成后应用一次初始相机
931
+ if !hasAppliedInitialCameraPosition, let position = initialCameraPosition {
932
+ cameraManager?.setInitialCameraPosition(position)
933
+ hasAppliedInitialCameraPosition = true
934
+ }
921
935
 
922
936
  // 地图加载完成后,应用自定义样式
923
937
  if let styleData = customMapStyleData {
@@ -2,14 +2,6 @@ import ExpoModulesCore
2
2
  import MAMapKit
3
3
  import CoreLocation
4
4
 
5
- enum MapType: Int, Enumerable {
6
- case standard = 0
7
- case satellite = 1
8
- case night = 2
9
- case navi = 3
10
- case bus = 4
11
- }
12
-
13
5
  /**
14
6
  * 高德地图视图 Module
15
7
  */
@@ -20,8 +12,8 @@ public class ExpoGaodeMapViewModule: Module {
20
12
  View(ExpoGaodeMapView.self) {
21
13
  Events("onMapPress", "onPressPoi", "onMapLongPress", "onLoad", "onLocation", "onCameraMove", "onCameraIdle")
22
14
 
23
- Prop("mapType") { (view: ExpoGaodeMapView, type: MapType) in
24
- view.mapType = type.rawValue
15
+ Prop("mapType") { (view: ExpoGaodeMapView, type: Int) in
16
+ view.mapType = type
25
17
  }
26
18
 
27
19
  Prop("initialCameraPosition") { (view: ExpoGaodeMapView, position: [String: Any]?) in
@@ -29,14 +29,15 @@ class UIManager: NSObject, MAMapViewDelegate {
29
29
 
30
30
  /**
31
31
  * 设置地图类型
32
- * @param type 0:标准 1:卫星 2:夜间 3:导航
32
+ * @param type 1:标准 2:卫星 3:夜间 4:导航 5:公交
33
33
  */
34
34
  func setMapType(_ type: Int) {
35
35
  guard let mapView = mapView else { return }
36
36
  switch type {
37
- case 1: mapView.mapType = .satellite
38
- case 2: mapView.mapType = .standardNight
39
- case 3: mapView.mapType = .navi
37
+ case 2: mapView.mapType = .satellite
38
+ case 3: mapView.mapType = .standardNight
39
+ case 4: mapView.mapType = .navi
40
+ case 5: mapView.mapType = .bus
40
41
  default: mapView.mapType = .standard
41
42
  }
42
43
 
@@ -122,6 +122,23 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
122
122
  ensureLocationManager()?.stopUpdatingHeading()
123
123
  }
124
124
 
125
+ func requestSingleLocation(completion: @escaping (_ location: CLLocation?, _ reGeocode: AMapLocationReGeocode?, _ error: Error?) -> Void) {
126
+ guard let manager = ensureLocationManager() else {
127
+ let error = NSError(
128
+ domain: "ExpoGaodeMap",
129
+ code: -1,
130
+ userInfo: [NSLocalizedDescriptionKey: "定位管理器未初始化,请先完成隐私协议确认"]
131
+ )
132
+ completion(nil, nil, error)
133
+ return
134
+ }
135
+
136
+ manager.requestLocation(
137
+ withReGeocode: manager.locatingWithReGeocode,
138
+ completionBlock: completion
139
+ )
140
+ }
141
+
125
142
  // MARK: - 初始化
126
143
 
127
144
  @discardableResult
@@ -622,7 +622,9 @@ class MarkerView: ExpoView {
622
622
  }
623
623
 
624
624
  private func childrenCacheKey(for size: CGSize) -> String {
625
- let baseKey = cacheKey ?? "children_\(ObjectIdentifier(self).hashValue)"
625
+ let signature = childrenRenderSignature()
626
+ let baseKey = cacheKey.map { "\($0)|\(signature)" }
627
+ ?? "children_\(ObjectIdentifier(self).hashValue)|\(signature)"
626
628
  let roundedWidth = Int(ceil(size.width))
627
629
  let roundedHeight = Int(ceil(size.height))
628
630
  return "\(baseKey)|\(roundedWidth)x\(roundedHeight)"
@@ -68,7 +68,7 @@ public class MarkerViewModule: Module {
68
68
  Prop("growAnimation") { (view: MarkerView, enabled: Bool) in
69
69
  view.setGrowAnimation(enabled)
70
70
  }
71
- Prop("cacheKey") { (view: MarkerView, key: String) in
71
+ Prop("cacheKey") { (view: MarkerView, key: String?) in
72
72
  view.setCacheKey(key)
73
73
  }
74
74