@lugg/maps 0.2.0-alpha.2 → 0.2.0-alpha.20

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.
Files changed (96) hide show
  1. package/README.md +17 -4
  2. package/android/src/main/java/com/luggmaps/LuggGoogleMapView.kt +31 -37
  3. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +6 -5
  4. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +136 -14
  5. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +21 -0
  6. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +16 -0
  7. package/android/src/main/java/com/luggmaps/LuggPolylineViewManager.kt +22 -0
  8. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +157 -52
  9. package/ios/LuggAppleMapView.mm +154 -42
  10. package/ios/LuggGoogleMapView.mm +52 -22
  11. package/ios/LuggMarkerView.h +9 -0
  12. package/ios/LuggMarkerView.mm +79 -0
  13. package/ios/LuggPolylineView.h +4 -0
  14. package/ios/LuggPolylineView.mm +23 -0
  15. package/ios/core/GMSPolylineAnimator.h +3 -0
  16. package/ios/core/GMSPolylineAnimator.m +164 -41
  17. package/ios/core/MKPolylineAnimator.h +4 -0
  18. package/ios/core/MKPolylineAnimator.m +162 -43
  19. package/ios/core/PolylineAnimatorBase.h +14 -0
  20. package/ios/core/PolylineAnimatorBase.m +33 -0
  21. package/ios/extensions/MKMapView+Zoom.h +2 -0
  22. package/ios/extensions/MKMapView+Zoom.m +14 -4
  23. package/lib/module/MapProvider.js +13 -0
  24. package/lib/module/MapProvider.js.map +1 -0
  25. package/lib/module/MapProvider.types.js +4 -0
  26. package/lib/module/MapProvider.types.js.map +1 -0
  27. package/lib/module/MapProvider.web.js +20 -0
  28. package/lib/module/MapProvider.web.js.map +1 -0
  29. package/lib/module/MapView.js +2 -2
  30. package/lib/module/MapView.js.map +1 -1
  31. package/lib/module/MapView.web.js +272 -0
  32. package/lib/module/MapView.web.js.map +1 -0
  33. package/lib/module/components/Marker.js +10 -1
  34. package/lib/module/components/Marker.js.map +1 -1
  35. package/lib/module/components/Marker.web.js +33 -0
  36. package/lib/module/components/Marker.web.js.map +1 -0
  37. package/lib/module/components/Polyline.js +8 -3
  38. package/lib/module/components/Polyline.js.map +1 -1
  39. package/lib/module/components/Polyline.web.js +229 -0
  40. package/lib/module/components/Polyline.web.js.map +1 -0
  41. package/lib/module/components/index.js +2 -2
  42. package/lib/module/components/index.js.map +1 -1
  43. package/lib/module/components/index.web.js +5 -0
  44. package/lib/module/components/index.web.js.map +1 -0
  45. package/lib/module/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  46. package/lib/module/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  47. package/lib/module/index.js +3 -2
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/index.web.js +6 -0
  50. package/lib/module/index.web.js.map +1 -0
  51. package/lib/typescript/src/MapProvider.d.ts +8 -0
  52. package/lib/typescript/src/MapProvider.d.ts.map +1 -0
  53. package/lib/typescript/src/MapProvider.types.d.ts +16 -0
  54. package/lib/typescript/src/MapProvider.types.d.ts.map +1 -0
  55. package/lib/typescript/src/MapProvider.web.d.ts +11 -0
  56. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -0
  57. package/lib/typescript/src/MapView.d.ts +1 -1
  58. package/lib/typescript/src/MapView.d.ts.map +1 -1
  59. package/lib/typescript/src/MapView.types.d.ts +2 -2
  60. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  61. package/lib/typescript/src/MapView.web.d.ts +3 -0
  62. package/lib/typescript/src/MapView.web.d.ts.map +1 -0
  63. package/lib/typescript/src/components/Marker.d.ts +21 -0
  64. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  65. package/lib/typescript/src/components/Marker.web.d.ts +3 -0
  66. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Polyline.d.ts +32 -0
  68. package/lib/typescript/src/components/Polyline.d.ts.map +1 -1
  69. package/lib/typescript/src/components/Polyline.web.d.ts +3 -0
  70. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -0
  71. package/lib/typescript/src/components/index.web.d.ts +5 -0
  72. package/lib/typescript/src/components/index.web.d.ts.map +1 -0
  73. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts +4 -1
  74. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts.map +1 -1
  75. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts +7 -0
  76. package/lib/typescript/src/fabric/LuggPolylineViewNativeComponent.d.ts.map +1 -1
  77. package/lib/typescript/src/index.d.ts +3 -1
  78. package/lib/typescript/src/index.d.ts.map +1 -1
  79. package/lib/typescript/src/index.web.d.ts +7 -0
  80. package/lib/typescript/src/index.web.d.ts.map +1 -0
  81. package/package.json +15 -2
  82. package/src/MapProvider.tsx +10 -0
  83. package/src/MapProvider.types.ts +16 -0
  84. package/src/MapProvider.web.tsx +14 -0
  85. package/src/MapView.tsx +2 -2
  86. package/src/MapView.types.ts +2 -2
  87. package/src/MapView.web.tsx +337 -0
  88. package/src/components/Marker.tsx +37 -3
  89. package/src/components/Marker.web.tsx +33 -0
  90. package/src/components/Polyline.tsx +38 -1
  91. package/src/components/Polyline.web.tsx +287 -0
  92. package/src/components/index.web.ts +4 -0
  93. package/src/fabric/LuggMarkerViewNativeComponent.ts +7 -1
  94. package/src/fabric/LuggPolylineViewNativeComponent.ts +8 -0
  95. package/src/index.ts +8 -1
  96. package/src/index.web.ts +17 -0
@@ -0,0 +1,337 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useId,
6
+ useImperativeHandle,
7
+ useRef,
8
+ useState,
9
+ type CSSProperties,
10
+ } from 'react';
11
+ import type { NativeSyntheticEvent } from 'react-native';
12
+ import { View } from 'react-native';
13
+ import {
14
+ Map,
15
+ useMap,
16
+ type MapCameraChangedEvent,
17
+ type MapEvent,
18
+ } from '@vis.gl/react-google-maps';
19
+ import { Marker } from './components/Marker.web';
20
+ import { MapContext } from './MapProvider.web';
21
+
22
+ import type {
23
+ MapViewProps,
24
+ MapViewRef,
25
+ MoveCameraOptions,
26
+ FitCoordinatesOptions,
27
+ CameraEventPayload,
28
+ } from './MapView.types';
29
+ import type { Coordinate } from './types';
30
+
31
+ const createSyntheticEvent = <T,>(nativeEvent: T): NativeSyntheticEvent<T> =>
32
+ ({
33
+ nativeEvent,
34
+ currentTarget: null,
35
+ target: null,
36
+ bubbles: false,
37
+ cancelable: false,
38
+ defaultPrevented: false,
39
+ eventPhase: 0,
40
+ isTrusted: true,
41
+ preventDefault: () => {},
42
+ stopPropagation: () => {},
43
+ isDefaultPrevented: () => false,
44
+ isPropagationStopped: () => false,
45
+ persist: () => {},
46
+ timeStamp: Date.now(),
47
+ type: '',
48
+ } as unknown as NativeSyntheticEvent<T>);
49
+
50
+ const userLocationDotStyle: CSSProperties = {
51
+ width: 16,
52
+ height: 16,
53
+ backgroundColor: '#4285F4',
54
+ border: '2px solid white',
55
+ borderRadius: '50%',
56
+ boxShadow: '0 1px 4px rgba(0,0,0,0.3)',
57
+ };
58
+
59
+ function UserLocationMarker({ enabled }: { enabled?: boolean }) {
60
+ const [coordinate, setCoordinate] = useState<Coordinate | null>(null);
61
+
62
+ useEffect(() => {
63
+ if (!enabled) {
64
+ setCoordinate(null);
65
+ return;
66
+ }
67
+
68
+ let watchId: number | null = null;
69
+
70
+ const updateLocation = (position: GeolocationPosition) => {
71
+ setCoordinate({
72
+ latitude: position.coords.latitude,
73
+ longitude: position.coords.longitude,
74
+ });
75
+ };
76
+
77
+ navigator.geolocation.getCurrentPosition(updateLocation, () => {});
78
+ watchId = navigator.geolocation.watchPosition(updateLocation, () => {});
79
+
80
+ return () => {
81
+ if (watchId !== null) {
82
+ navigator.geolocation.clearWatch(watchId);
83
+ }
84
+ };
85
+ }, [enabled]);
86
+
87
+ if (!coordinate) return null;
88
+
89
+ return (
90
+ <Marker coordinate={coordinate} anchor={{ x: 0.5, y: 0.5 }}>
91
+ <div style={userLocationDotStyle} />
92
+ </Marker>
93
+ );
94
+ }
95
+
96
+ export const MapView = forwardRef<MapViewRef, MapViewProps>(function MapView(
97
+ props,
98
+ ref
99
+ ) {
100
+ const {
101
+ mapId = google.maps.Map.DEMO_MAP_ID,
102
+ initialCoordinate,
103
+ initialZoom = 10,
104
+ minZoom,
105
+ maxZoom,
106
+ zoomEnabled = true,
107
+ scrollEnabled = true,
108
+ pitchEnabled = true,
109
+ padding,
110
+ userLocationEnabled,
111
+ onCameraMove,
112
+ onCameraIdle,
113
+ onReady,
114
+ children,
115
+ style,
116
+ } = props;
117
+
118
+ const id = useId();
119
+ const map = useMap(id);
120
+ const containerRef = useRef<View>(null);
121
+ const readyFired = useRef(false);
122
+ const [isDragging, setIsDragging] = useState(false);
123
+ const wasGesture = useRef(false);
124
+ const prevPadding = useRef(padding);
125
+
126
+ const offsetCenter = useCallback(
127
+ (
128
+ coord: Coordinate,
129
+ zoom: number,
130
+ paddingOverride?: typeof padding,
131
+ reverse = false
132
+ ) => {
133
+ const p = paddingOverride ?? padding;
134
+ const div = map?.getDiv();
135
+ if (!p || !div) {
136
+ return { lat: coord.latitude, lng: coord.longitude };
137
+ }
138
+
139
+ const dir = reverse ? -1 : 1;
140
+ const scale = 256 * Math.pow(2, zoom);
141
+ const offsetX = (dir * ((p.right ?? 0) - (p.left ?? 0))) / 2;
142
+ const offsetY = (dir * ((p.bottom ?? 0) - (p.top ?? 0))) / 2;
143
+
144
+ const latRad = (coord.latitude * Math.PI) / 180;
145
+ const x = ((coord.longitude + 180) / 360) * scale + offsetX;
146
+ const y =
147
+ ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) /
148
+ 2) *
149
+ scale +
150
+ offsetY;
151
+
152
+ const lng = (x / scale) * 360 - 180;
153
+ const lat =
154
+ (Math.atan(Math.sinh(Math.PI * (1 - (2 * y) / scale))) * 180) / Math.PI;
155
+
156
+ return { lat, lng };
157
+ },
158
+ [map, padding]
159
+ );
160
+
161
+ useImperativeHandle(
162
+ ref,
163
+ () => ({
164
+ moveCamera(coordinate: Coordinate, options?: MoveCameraOptions) {
165
+ if (!map) return;
166
+
167
+ const { zoom = 0, duration = -1 } = options ?? {};
168
+ const targetZoom = zoom || map.getZoom() || initialZoom;
169
+ const center = offsetCenter(coordinate, targetZoom, undefined, false);
170
+
171
+ if (duration === 0) {
172
+ map.moveCamera({ center, zoom: targetZoom });
173
+ } else {
174
+ const currentZoom = map.getZoom();
175
+ const zoomChanged = zoom !== 0 && zoom !== currentZoom;
176
+
177
+ if (zoomChanged) {
178
+ map.setZoom(zoom);
179
+ }
180
+ map.panTo(center);
181
+ }
182
+ },
183
+
184
+ fitCoordinates(
185
+ coordinates: Coordinate[],
186
+ options?: FitCoordinatesOptions
187
+ ) {
188
+ const first = coordinates[0];
189
+ if (!map || !first) return;
190
+
191
+ const { padding: fitPadding, duration = -1 } = options ?? {};
192
+
193
+ if (coordinates.length === 1) {
194
+ this.moveCamera(first, { zoom: initialZoom, duration });
195
+ return;
196
+ }
197
+
198
+ const bounds = new google.maps.LatLngBounds();
199
+ coordinates.forEach((coord) => {
200
+ bounds.extend({ lat: coord.latitude, lng: coord.longitude });
201
+ });
202
+
203
+ map.fitBounds(bounds, {
204
+ top: (padding?.top ?? 0) + (fitPadding?.top ?? 0),
205
+ left: (padding?.left ?? 0) + (fitPadding?.left ?? 0),
206
+ bottom: (padding?.bottom ?? 0) + (fitPadding?.bottom ?? 0),
207
+ right: (padding?.right ?? 0) + (fitPadding?.right ?? 0),
208
+ });
209
+ },
210
+ }),
211
+ [map, initialZoom, padding, offsetCenter]
212
+ );
213
+
214
+ useEffect(() => {
215
+ if (map && !readyFired.current) {
216
+ readyFired.current = true;
217
+ onReady?.();
218
+ }
219
+ }, [map, onReady]);
220
+
221
+ useEffect(() => {
222
+ if (!map || !padding) return;
223
+
224
+ const prev = prevPadding.current;
225
+ const paddingChanged =
226
+ prev?.top !== padding.top ||
227
+ prev?.left !== padding.left ||
228
+ prev?.bottom !== padding.bottom ||
229
+ prev?.right !== padding.right;
230
+
231
+ if (paddingChanged) {
232
+ const center = map.getCenter();
233
+ const zoom = map.getZoom() ?? initialZoom;
234
+ if (center) {
235
+ const logicalCenter = offsetCenter(
236
+ { latitude: center.lat(), longitude: center.lng() },
237
+ zoom,
238
+ prev,
239
+ true
240
+ );
241
+ const newCenter = offsetCenter(
242
+ { latitude: logicalCenter.lat, longitude: logicalCenter.lng },
243
+ zoom,
244
+ padding,
245
+ false
246
+ );
247
+ map.panTo(newCenter);
248
+ }
249
+ prevPadding.current = padding;
250
+ }
251
+ }, [map, padding, initialZoom, offsetCenter]);
252
+
253
+ const handleDragStart = () => {
254
+ setIsDragging(true);
255
+ wasGesture.current = true;
256
+ };
257
+
258
+ const handleDragEnd = () => {
259
+ setIsDragging(false);
260
+ };
261
+
262
+ const handleCameraChanged = (event: MapCameraChangedEvent) => {
263
+ const logicalCenter = offsetCenter(
264
+ { latitude: event.detail.center.lat, longitude: event.detail.center.lng },
265
+ event.detail.zoom,
266
+ undefined,
267
+ true
268
+ );
269
+ const payload: CameraEventPayload = {
270
+ coordinate: {
271
+ latitude: logicalCenter.lat,
272
+ longitude: logicalCenter.lng,
273
+ },
274
+ zoom: event.detail.zoom,
275
+ gesture: isDragging,
276
+ };
277
+ onCameraMove?.(createSyntheticEvent(payload));
278
+ };
279
+
280
+ const handleIdle = (event: MapEvent) => {
281
+ const center = event.map.getCenter();
282
+ const zoom = event.map.getZoom() ?? 0;
283
+ const logicalCenter = offsetCenter(
284
+ { latitude: center?.lat() ?? 0, longitude: center?.lng() ?? 0 },
285
+ zoom,
286
+ undefined,
287
+ true
288
+ );
289
+ const payload: CameraEventPayload = {
290
+ coordinate: {
291
+ latitude: logicalCenter.lat,
292
+ longitude: logicalCenter.lng,
293
+ },
294
+ zoom,
295
+ gesture: wasGesture.current,
296
+ };
297
+ onCameraIdle?.(createSyntheticEvent(payload));
298
+ wasGesture.current = false;
299
+ };
300
+
301
+ const gestureHandling =
302
+ scrollEnabled === false && zoomEnabled === false
303
+ ? 'none'
304
+ : scrollEnabled === false
305
+ ? 'cooperative'
306
+ : 'auto';
307
+
308
+ const defaultCenter = initialCoordinate
309
+ ? { lat: initialCoordinate.latitude, lng: initialCoordinate.longitude }
310
+ : undefined;
311
+
312
+ return (
313
+ <MapContext.Provider value={{ map, isDragging }}>
314
+ <View ref={containerRef} style={style}>
315
+ <Map
316
+ id={id}
317
+ mapId={mapId}
318
+ defaultCenter={defaultCenter}
319
+ defaultZoom={initialZoom}
320
+ minZoom={minZoom}
321
+ maxZoom={maxZoom}
322
+ gestureHandling={gestureHandling}
323
+ disableDefaultUI
324
+ isFractionalZoomEnabled
325
+ tilt={pitchEnabled === false ? 0 : undefined}
326
+ onDragstart={handleDragStart}
327
+ onDragend={handleDragEnd}
328
+ onCameraChanged={handleCameraChanged}
329
+ onIdle={handleIdle}
330
+ >
331
+ <UserLocationMarker enabled={userLocationEnabled} />
332
+ {children}
333
+ </Map>
334
+ </View>
335
+ </MapContext.Provider>
336
+ );
337
+ });
@@ -25,6 +25,27 @@ export interface MarkerProps {
25
25
  * Anchor point for custom marker views
26
26
  */
27
27
  anchor?: Point;
28
+ /**
29
+ * Z-index for marker ordering. Higher values render on top.
30
+ */
31
+ zIndex?: number;
32
+ /**
33
+ * Rotation angle in degrees clockwise from north.
34
+ * @default 0
35
+ */
36
+ rotate?: number;
37
+ /**
38
+ * Scale factor for the marker.
39
+ * @default 1
40
+ */
41
+ scale?: number;
42
+ /**
43
+ * Rasterize custom marker view to bitmap for better performance.
44
+ * Set to false if you need live view updates (e.g., animations).
45
+ * @platform ios, android
46
+ * @default true
47
+ */
48
+ rasterize?: boolean;
28
49
  /**
29
50
  * Custom marker view
30
51
  */
@@ -33,17 +54,30 @@ export interface MarkerProps {
33
54
 
34
55
  export class Marker extends React.Component<MarkerProps> {
35
56
  render() {
36
- const { name, coordinate, title, description, anchor, children } =
37
- this.props;
57
+ const {
58
+ name,
59
+ coordinate,
60
+ title,
61
+ description,
62
+ anchor,
63
+ zIndex,
64
+ rotate = 0,
65
+ scale = 1,
66
+ rasterize = true,
67
+ children,
68
+ } = this.props;
38
69
 
39
70
  return (
40
71
  <LuggMarkerViewNativeComponent
41
- style={styles.marker}
72
+ style={[{ zIndex }, styles.marker]}
42
73
  name={name}
43
74
  coordinate={coordinate}
44
75
  title={title}
45
76
  description={description}
46
77
  anchor={anchor}
78
+ rotate={rotate}
79
+ scale={scale}
80
+ rasterize={rasterize}
47
81
  >
48
82
  {children}
49
83
  </LuggMarkerViewNativeComponent>
@@ -0,0 +1,33 @@
1
+ import { AdvancedMarker } from '@vis.gl/react-google-maps';
2
+ import type { MarkerProps } from './Marker';
3
+
4
+ const toWebAnchor = (value: number) => `-${value * 100}%`;
5
+
6
+ export function Marker({
7
+ coordinate,
8
+ title,
9
+ anchor,
10
+ zIndex,
11
+ rotate,
12
+ scale,
13
+ children,
14
+ }: MarkerProps) {
15
+ const transforms: string[] = [];
16
+ if (rotate) transforms.push(`rotate(${rotate}deg)`);
17
+ if (scale && scale !== 1) transforms.push(`scale(${scale})`);
18
+
19
+ return (
20
+ <AdvancedMarker
21
+ position={{ lat: coordinate.latitude, lng: coordinate.longitude }}
22
+ title={title}
23
+ zIndex={zIndex}
24
+ anchorLeft={anchor ? toWebAnchor(anchor.x) : undefined}
25
+ anchorTop={anchor ? toWebAnchor(anchor.y) : undefined}
26
+ style={
27
+ transforms.length > 0 ? { transform: transforms.join(' ') } : undefined
28
+ }
29
+ >
30
+ {children}
31
+ </AdvancedMarker>
32
+ );
33
+ }
@@ -4,6 +4,32 @@ import { StyleSheet } from 'react-native';
4
4
  import LuggPolylineViewNativeComponent from '../fabric/LuggPolylineViewNativeComponent';
5
5
  import type { Coordinate } from '../types';
6
6
 
7
+ export type PolylineEasing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
8
+
9
+ export interface PolylineAnimatedOptions {
10
+ /**
11
+ * Animation duration in milliseconds
12
+ * @default 2150
13
+ */
14
+ duration?: number;
15
+ /**
16
+ * Easing function for the animation
17
+ * @default 'linear'
18
+ */
19
+ easing?: PolylineEasing;
20
+ /**
21
+ * Portion of the line visible as trail (0-1)
22
+ * 1.0 = full snake effect, 0.2 = short worm
23
+ * @default 1.0
24
+ */
25
+ trailLength?: number;
26
+ /**
27
+ * Delay before animation starts in milliseconds
28
+ * @default 0
29
+ */
30
+ delay?: number;
31
+ }
32
+
7
33
  export interface PolylineProps {
8
34
  /**
9
35
  * Array of coordinates forming the polyline
@@ -21,6 +47,14 @@ export interface PolylineProps {
21
47
  * Animate the polyline with a snake effect
22
48
  */
23
49
  animated?: boolean;
50
+ /**
51
+ * Animation configuration options
52
+ */
53
+ animatedOptions?: PolylineAnimatedOptions;
54
+ /**
55
+ * Z-index for layering polylines
56
+ */
57
+ zIndex?: number;
24
58
  }
25
59
 
26
60
  export class Polyline extends React.Component<PolylineProps> {
@@ -30,15 +64,18 @@ export class Polyline extends React.Component<PolylineProps> {
30
64
  strokeColors,
31
65
  strokeWidth,
32
66
  animated = false,
67
+ animatedOptions,
68
+ zIndex,
33
69
  } = this.props;
34
70
 
35
71
  return (
36
72
  <LuggPolylineViewNativeComponent
37
- style={styles.polyline}
73
+ style={[{ zIndex }, styles.polyline]}
38
74
  coordinates={coordinates}
39
75
  strokeColors={strokeColors}
40
76
  strokeWidth={strokeWidth}
41
77
  animated={animated}
78
+ animatedOptions={animatedOptions}
42
79
  />
43
80
  );
44
81
  }