xref: /expo/packages/expo-maps/src/Map.tsx (revision ee0de234)
1import { ProxyNativeModule, requireNativeModule } from 'expo-modules-core';
2import React from 'react';
3import { Platform, findNodeHandle } from 'react-native';
4
5import { CameraMove } from './Common.types';
6import { DefaultNativeExpoMapViewProps, ExpoMapState, ExpoMapViewProps } from './Map.types';
7import {
8  NativeExpoAppleMapsView,
9  NativeExpoAppleMapsModule,
10  NativeExpoGoogleMapsView,
11  NativeExpoGoogleMapsModule,
12} from './NativeExpoMapView';
13import * as Utils from './Utils';
14
15export { Marker } from './Marker';
16export { Polygon } from './Polygon';
17export { Polyline } from './Polyline';
18export { Circle } from './Circle';
19export { Cluster } from './Cluster';
20export { KML } from './KML';
21export { GeoJson } from './GeoJson';
22export { Overlay } from './Overlay';
23export { ExpoMapRef } from './Map.types';
24export { POICategoryType } from './Map.types';
25export { Heatmap } from './Heatmap';
26export * from './Events';
27
28const defaultNativeExpoMapViewProps: DefaultNativeExpoMapViewProps = {
29  mapType: 'normal',
30  showZoomControls: true,
31  showCompass: true,
32  showMapToolbar: true,
33  showMyLocationButton: true,
34  showLevelPicker: true,
35  enableRotateGestures: false,
36  enableScrollGestures: true,
37  enableTiltGestures: false,
38  enableZoomGestures: true,
39  initialCameraPosition: {
40    target: {
41      latitude: 51.51,
42      longitude: 0.13,
43    },
44    zoom: 4,
45    animate: true,
46    duration: 1000,
47  },
48  enableTraffic: false,
49  enablePOISearching: false,
50  enablePOIs: false,
51  enablePOIFilter: [],
52  createPOISearchRequest: '',
53  clickablePOIs: true,
54};
55
56/**
57 * Main map component of Expo Maps library.
58 *
59 * See {@link ExpoMapViewProps} to learn more about props.
60 */
61export class ExpoMap extends React.Component<ExpoMapViewProps> {
62  state: ExpoMapState = {
63    markers: [],
64    polygons: [],
65    polylines: [],
66    circles: [],
67    clusters: [],
68    kmls: [],
69    geojsons: [],
70    overlays: [],
71    heatmaps: [],
72  };
73  _ismounted = false;
74  mapView = React.createRef<ExpoMap>();
75
76  getSearchCompletions(queryFragment: string) {
77    const nodeHandle = findNodeHandle(this.mapView.current);
78    let module: ProxyNativeModule;
79    if (Platform.OS === 'ios' && this.props.provider === 'apple') {
80      module = NativeExpoAppleMapsModule;
81    } else {
82      module = NativeExpoGoogleMapsModule;
83    }
84
85    module
86      .getSearchCompletions(nodeHandle, queryFragment)
87      .then((response: [string]) => {
88        console.log(response);
89      })
90      .catch((error: Error) => {
91        console.log('Error with message: ' + error.message);
92      });
93  }
94
95  async moveCamera(cameraMove: CameraMove) {
96    const nodeHandle = findNodeHandle(this.mapView.current);
97    let module: ProxyNativeModule;
98    if (Platform.OS === 'ios' && this.props.provider === 'apple') {
99      module = requireNativeModule('ExpoAppleMaps');
100    } else {
101      module = requireNativeModule('ExpoGoogleMaps');
102    }
103    return module.moveCamera(nodeHandle, cameraMove);
104  }
105
106  componentDidMount() {
107    this.mapChildren();
108    this._ismounted = true;
109  }
110
111  componentWillUnmount() {
112    this._ismounted = false;
113  }
114
115  componentDidUpdate(_, prevState: ExpoMapState) {
116    if (Object.is(this.state, prevState)) {
117      this.mapChildren();
118    }
119  }
120
121  private async mapChildren() {
122    const childrenArray = React.Children.map(this.props.children, async (child) => {
123      if (!Utils.isSimpleType(child)) {
124        if (Utils.isMarker(child)) {
125          return Utils.buildMarkerObject(child);
126        } else if (Utils.isPolygon(child)) {
127          return Utils.buildPolygonObject(child);
128        } else if (Utils.isPolyline(child)) {
129          return Utils.buildPolylineObject(child);
130        } else if (Utils.isCircle(child)) {
131          return Utils.buildCircleObject(child);
132        } else if (Utils.isKML(child)) {
133          return Utils.buildKMLObject(child);
134        } else if (Utils.isGeoJson(child)) {
135          return Utils.buildGeoJsonObject(child);
136        } else if (Utils.isCluster(child)) {
137          return Utils.buildClusterObject(child);
138        } else if (Utils.isOverlay(child)) {
139          return Utils.buildOverlayObject(child);
140        } else if (Utils.isHeatmap(child)) {
141          return Utils.buildHeatmapObject(child);
142        }
143        Utils.warnIfChildIsIncompatible(child);
144        return null;
145      }
146      Utils.warnIfChildIsIncompatible(child);
147      return null;
148    });
149
150    if (childrenArray !== undefined) {
151      // TODO(@lukmccall): remove any cast
152      const propObjects = await Promise.all(childrenArray as any);
153      if (this._ismounted) {
154        this.setState({
155          markers: propObjects.filter((elem) => elem?.type === 'marker'),
156          polygons: propObjects.filter((elem) => elem?.type === 'polygon'),
157          polylines: propObjects.filter((elem) => elem?.type === 'polyline'),
158          circles: propObjects.filter((elem) => elem?.type === 'circle'),
159          clusters: propObjects.filter((elem) => elem?.type === 'cluster'),
160          kmls: propObjects.filter((elem) => elem?.type === 'kml'),
161          geojsons: propObjects.filter((elem) => elem?.type === 'geojson'),
162          overlays: propObjects.filter((elem) => elem?.type === 'overlay'),
163          heatmaps: propObjects.filter((elem) => elem?.type === 'heatmap'),
164        });
165      }
166    }
167  }
168
169  render() {
170    if (Platform.OS === 'ios' && this.props.provider === 'apple') {
171      if (parseInt(Platform.Version, 10) < 13 && this.state.geojsons.length > 0) {
172        console.warn(
173          "Versions of iOS < 13 doesn't support GeoJSON features for Apple Maps. Adding of GeoJSON for these versions will be omitted."
174        );
175      }
176      if (parseInt(Platform.Version, 10) < 13) {
177        console.warn(
178          "Versions of iOS < 13 doesn't support Points Of Interest Filters and their display modifications for Apple Maps. Adding POI filters for these versions will be omitted."
179        );
180      }
181      return (
182        <NativeExpoAppleMapsView
183          {...defaultNativeExpoMapViewProps}
184          {...this.props}
185          markers={this.state.markers}
186          polygons={this.state.polygons}
187          polylines={this.state.polylines}
188          circles={this.state.circles}
189          clusters={this.state.clusters}
190          kmls={this.state.kmls}
191          geojsons={this.state.geojsons}
192          ref={this.mapView}
193        />
194      );
195    }
196
197    let googleMapsJsonStyleString = this.props.googleMapsJsonStyleString
198      ? this.props.googleMapsJsonStyleString
199      : '';
200    if (this.props.enablePOIs === false) {
201      if (this.props.googleMapsJsonStyleString) {
202        console.warn(
203          "Expo Maps enablePOIs prop isn't effective when custom Google Maps map style is active. Please adjust your style manually to disable the POIs. https://developers.google.com/maps/documentation/ios-sdk/poi"
204        );
205      } else {
206        googleMapsJsonStyleString = JSON.stringify([
207          {
208            featureType: 'poi',
209            stylers: [{ visibility: 'off' }],
210          },
211        ]);
212      }
213    }
214
215    return (
216      <NativeExpoGoogleMapsView
217        {...defaultNativeExpoMapViewProps}
218        {...this.props}
219        googleMapsJsonStyleString={googleMapsJsonStyleString}
220        markers={this.state.markers}
221        polygons={this.state.polygons}
222        polylines={this.state.polylines}
223        circles={this.state.circles}
224        clusters={this.state.clusters}
225        kmls={this.state.kmls}
226        geojsons={this.state.geojsons}
227        ref={this.mapView}
228        overlays={this.state.overlays}
229        heatmaps={this.state.heatmaps}
230      />
231    );
232  }
233}
234