GT2/GT2-iOS/node_modules/react-native-maps/lib/components/MapView.js

752 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import PropTypes from 'prop-types';
import React from 'react';
import {
EdgeInsetsPropType,
Platform,
Animated,
requireNativeComponent,
NativeModules,
ColorPropType,
findNodeHandle,
ViewPropTypes,
View,
} from 'react-native';
import MapMarker from './MapMarker';
import MapPolyline from './MapPolyline';
import MapPolygon from './MapPolygon';
import MapCircle from './MapCircle';
import MapCallout from './MapCallout';
import MapUrlTile from './MapUrlTile';
import MapLocalTile from './MapLocalTile';
import AnimatedRegion from './AnimatedRegion';
import {
contextTypes as childContextTypes,
getAirMapName,
googleMapIsInstalled,
createNotSupportedComponent,
} from './decorateMapComponent';
import * as ProviderConstants from './ProviderConstants';
const MAP_TYPES = {
STANDARD: 'standard',
SATELLITE: 'satellite',
HYBRID: 'hybrid',
TERRAIN: 'terrain',
NONE: 'none',
MUTEDSTANDARD: 'mutedStandard',
};
const GOOGLE_MAPS_ONLY_TYPES = [
MAP_TYPES.TERRAIN,
MAP_TYPES.NONE,
];
const viewConfig = {
uiViewClassName: 'AIR<provider>Map',
validAttributes: {
region: true,
},
};
// if ViewPropTypes is not defined fall back to View.propType (to support RN < 0.44)
const viewPropTypes = ViewPropTypes || View.propTypes;
const propTypes = {
...viewPropTypes,
/**
* When provider is "google", we will use GoogleMaps.
* Any value other than "google" will default to using
* MapKit in iOS or GoogleMaps in android as the map provider.
*/
provider: PropTypes.oneOf([
'google',
]),
/**
* Used to style and layout the `MapView`. See `StyleSheet.js` and
* `ViewStylePropTypes.js` for more info.
*/
style: viewPropTypes.style,
/**
* A json object that describes the style of the map. This is transformed to a string
* and saved in mayStyleString to be sent to android and ios
* https://developers.google.com/maps/documentation/ios-sdk/styling#use_a_string_resource
* https://developers.google.com/maps/documentation/android-api/styling
*/
customMapStyle: PropTypes.array,
/**
* A json string that describes the style of the map
* https://developers.google.com/maps/documentation/ios-sdk/styling#use_a_string_resource
* https://developers.google.com/maps/documentation/android-api/styling
*/
customMapStyleString: PropTypes.string,
/**
* If `true` the app will ask for the user's location.
* Default value is `false`.
*
* **NOTE**: You need to add NSLocationWhenInUseUsageDescription key in
* Info.plist to enable geolocation, otherwise it is going
* to *fail silently*!
*/
showsUserLocation: PropTypes.bool,
/**
* The title of the annotation for current user location. This only works if
* `showsUserLocation` is true.
* There is a default value `My Location` set by MapView.
*
* @platform ios
*/
userLocationAnnotationTitle: PropTypes.string,
/**
* If `false` hide the button to move map to the current user's location.
* Default value is `true`.
*
* @platform android
*/
showsMyLocationButton: PropTypes.bool,
/**
* If `true` the map will focus on the user's location. This only works if
* `showsUserLocation` is true and the user has shared their location.
* Default value is `false`.
*
* @platform ios
*/
followsUserLocation: PropTypes.bool,
/**
* If `false` points of interest won't be displayed on the map.
* Default value is `true`.
*
*/
showsPointsOfInterest: PropTypes.bool,
/**
* If `false` compass won't be displayed on the map.
* Default value is `true`.
*
* @platform ios
*/
showsCompass: PropTypes.bool,
/**
* If `false` the user won't be able to pinch/zoom the map.
* Default value is `true`.
*
*/
zoomEnabled: PropTypes.bool,
/**
* If `false` the user won't be able to pinch/rotate the map.
* Default value is `true`.
*
*/
rotateEnabled: PropTypes.bool,
/**
* If `true` the map will be cached to an Image for performance
* Default value is `false`.
*
*/
cacheEnabled: PropTypes.bool,
/**
* If `true` the map will be showing a loading indicator
* Default value is `false`.
*
*/
loadingEnabled: PropTypes.bool,
/**
* Loading background color while generating map cache image or loading the map
* Default color is light gray.
*
*/
loadingBackgroundColor: ColorPropType,
/**
* Loading indicator color while generating map cache image or loading the map
* Default color is gray color for iOS, theme color for Android.
*
*/
loadingIndicatorColor: ColorPropType,
/**
* If `false` the user won't be able to change the map region being displayed.
* Default value is `true`.
*
*/
scrollEnabled: PropTypes.bool,
/**
* If `false` the user won't be able to adjust the cameras pitch angle.
* Default value is `true`.
*
*/
pitchEnabled: PropTypes.bool,
/**
* If `false` will hide 'Navigate' and 'Open in Maps' buttons on marker press
* Default value is `true`.
*
* @platform android
*/
toolbarEnabled: PropTypes.bool,
/**
* A Boolean indicating whether on marker press the map will move to the pressed marker
* Default value is `true`
*
* @platform android
*/
moveOnMarkerPress: PropTypes.bool,
/**
* A Boolean indicating whether the map shows scale information.
* Default value is `false`
*
*/
showsScale: PropTypes.bool,
/**
* A Boolean indicating whether the map displays extruded building information.
* Default value is `true`.
*/
showsBuildings: PropTypes.bool,
/**
* A Boolean value indicating whether the map displays traffic information.
* Default value is `false`.
*/
showsTraffic: PropTypes.bool,
/**
* A Boolean indicating whether indoor maps should be enabled.
* Default value is `false`
*
* @platform android
*/
showsIndoors: PropTypes.bool,
/**
* A Boolean indicating whether indoor level picker should be enabled.
* Default value is `false`
*
* @platform android
*/
showsIndoorLevelPicker: PropTypes.bool,
/**
* The map type to be displayed.
*
* - standard: standard road map (default)
* - satellite: satellite view
* - hybrid: satellite view with roads and points of interest overlayed
* - terrain: topographic view
* - none: no base map
*/
mapType: PropTypes.oneOf(Object.values(MAP_TYPES)),
/**
* The region to be displayed by the map.
*
* The region is defined by the center coordinates and the span of
* coordinates to display.
*/
region: PropTypes.shape({
/**
* Coordinates for the center of the map.
*/
latitude: PropTypes.number.isRequired,
longitude: PropTypes.number.isRequired,
/**
* Difference between the minimun and the maximum latitude/longitude
* to be displayed.
*/
latitudeDelta: PropTypes.number.isRequired,
longitudeDelta: PropTypes.number.isRequired,
}),
/**
* The initial region to be displayed by the map. Use this prop instead of `region`
* only if you don't want to control the viewport of the map besides the initial region.
*
* Changing this prop after the component has mounted will not result in a region change.
*
* This is similar to the `initialValue` prop of a text input.
*/
initialRegion: PropTypes.shape({
/**
* Coordinates for the center of the map.
*/
latitude: PropTypes.number.isRequired,
longitude: PropTypes.number.isRequired,
/**
* Difference between the minimun and the maximum latitude/longitude
* to be displayed.
*/
latitudeDelta: PropTypes.number.isRequired,
longitudeDelta: PropTypes.number.isRequired,
}),
/**
* A Boolean indicating whether to use liteMode for android
* Default value is `false`
*
* @platform android
*/
liteMode: PropTypes.bool,
/**
* (Google Maps only)
*
* Padding that is used by the Google Map View to position
* the camera, legal labels and buttons
*
*/
mapPadding: EdgeInsetsPropType,
/**
* Maximum size of area that can be displayed.
*
* @platform ios
*/
maxDelta: PropTypes.number,
/**
* Minimum size of area that can be displayed.
*
* @platform ios
*/
minDelta: PropTypes.number,
/**
* Insets for the map's legal label, originally at bottom left of the map.
* See `EdgeInsetsPropType.js` for more information.
*/
legalLabelInsets: EdgeInsetsPropType,
/**
* Callback that is called once the map is fully loaded.
*/
onMapReady: PropTypes.func,
/**
* Callback that is called continuously when the user is dragging the map.
*/
onRegionChange: PropTypes.func,
/**
* Callback that is called once, when the user is done moving the map.
*/
onRegionChangeComplete: PropTypes.func,
/**
* Callback that is called when user taps on the map.
*/
onPress: PropTypes.func,
/**
* Callback that is called when user makes a "long press" somewhere on the map.
*/
onLongPress: PropTypes.func,
/**
* Callback that is called when user makes a "drag" somewhere on the map
*/
onPanDrag: PropTypes.func,
/**
* Callback that is called when a marker on the map is tapped by the user.
*/
onMarkerPress: PropTypes.func,
/**
* Callback that is called when a marker on the map becomes selected. This will be called when
* the callout for that marker is about to be shown.
*
* @platform ios
*/
onMarkerSelect: PropTypes.func,
/**
* Callback that is called when a marker on the map becomes deselected. This will be called when
* the callout for that marker is about to be hidden.
*
* @platform ios
*/
onMarkerDeselect: PropTypes.func,
/**
* Callback that is called when a callout is tapped by the user.
*/
onCalloutPress: PropTypes.func,
/**
* Callback that is called when the user initiates a drag on a marker (if it is draggable)
*/
onMarkerDragStart: PropTypes.func,
/**
* Callback called continuously as a marker is dragged
*/
onMarkerDrag: PropTypes.func,
/**
* Callback that is called when a drag on a marker finishes. This is usually the point you
* will want to setState on the marker's coordinate again
*/
onMarkerDragEnd: PropTypes.func,
/**
* Minimum zoom value for the map, must be between 0 and 20
*/
minZoomLevel: PropTypes.number,
/**
* Maximum zoom value for the map, must be between 0 and 20
*/
maxZoomLevel: PropTypes.number,
};
class MapView extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: Platform.OS === 'ios',
};
this._onMapReady = this._onMapReady.bind(this);
this._onMarkerPress = this._onMarkerPress.bind(this);
this._onChange = this._onChange.bind(this);
this._onLayout = this._onLayout.bind(this);
}
getChildContext() {
return { provider: this.props.provider };
}
componentWillUpdate(nextProps) {
const a = this.__lastRegion;
const b = nextProps.region;
if (!a || !b) return;
if (
a.latitude !== b.latitude ||
a.longitude !== b.longitude ||
a.latitudeDelta !== b.latitudeDelta ||
a.longitudeDelta !== b.longitudeDelta
) {
this.map.setNativeProps({ region: b });
}
}
componentDidMount() {
const { isReady } = this.state;
if (isReady) {
this._updateStyle();
}
}
_updateStyle() {
const { customMapStyle } = this.props;
this.map.setNativeProps({ customMapStyleString: JSON.stringify(customMapStyle) });
}
_onMapReady() {
const { region, initialRegion, onMapReady } = this.props;
if (region) {
this.map.setNativeProps({ region });
} else if (initialRegion) {
this.map.setNativeProps({ initialRegion });
}
this._updateStyle();
this.setState({ isReady: true }, () => {
if (onMapReady) onMapReady();
});
}
_onLayout(e) {
const { layout } = e.nativeEvent;
if (!layout.width || !layout.height) return;
if (this.state.isReady && !this.__layoutCalled) {
const { region, initialRegion } = this.props;
if (region) {
this.__layoutCalled = true;
this.map.setNativeProps({ region });
} else if (initialRegion) {
this.__layoutCalled = true;
this.map.setNativeProps({ initialRegion });
}
}
if (this.props.onLayout) {
this.props.onLayout(e);
}
}
_onMarkerPress(event) {
if (this.props.onMarkerPress) {
this.props.onMarkerPress(event.nativeEvent);
}
}
_onChange(event) {
this.__lastRegion = event.nativeEvent.region;
if (event.nativeEvent.continuous) {
if (this.props.onRegionChange) {
this.props.onRegionChange(event.nativeEvent.region);
}
} else if (this.props.onRegionChangeComplete) {
this.props.onRegionChangeComplete(event.nativeEvent.region);
}
}
animateToRegion(region, duration) {
this._runCommand('animateToRegion', [region, duration || 500]);
}
animateToCoordinate(latLng, duration) {
this._runCommand('animateToCoordinate', [latLng, duration || 500]);
}
animateToBearing(bearing, duration) {
this._runCommand('animateToBearing', [bearing, duration || 500]);
}
animateToViewingAngle(angle, duration) {
this._runCommand('animateToViewingAngle', [angle, duration || 500]);
}
fitToElements(animated) {
this._runCommand('fitToElements', [animated]);
}
fitToSuppliedMarkers(markers, animated) {
this._runCommand('fitToSuppliedMarkers', [markers, animated]);
}
fitToCoordinates(coordinates = [], options = {}) {
const {
edgePadding = { top: 0, right: 0, bottom: 0, left: 0 },
animated = true,
} = options;
this._runCommand('fitToCoordinates', [coordinates, edgePadding, animated]);
}
setMapBoundaries(northEast, southWest) {
this._runCommand('setMapBoundaries', [northEast, southWest]);
}
/**
* Takes a snapshot of the map and saves it to a picture
* file or returns the image as a base64 encoded string.
*
* @param config Configuration options
* @param [config.width] Width of the rendered map-view (when omitted actual view width is used).
* @param [config.height] Height of the rendered map-view (when omitted actual height is used).
* @param [config.region] Region to render (Only supported on iOS).
* @param [config.format] Encoding format ('png', 'jpg') (default: 'png').
* @param [config.quality] Compression quality (only used for jpg) (default: 1.0).
* @param [config.result] Result format ('file', 'base64') (default: 'file').
*
* @return Promise Promise with either the file-uri or base64 encoded string
*/
takeSnapshot(args) {
// For the time being we support the legacy API on iOS.
// This will be removed in a future release and only the
// new Promise style API shall be supported.
if (Platform.OS === 'ios' && (arguments.length === 4)) {
console.warn('Old takeSnapshot API has been deprecated; will be removed in the near future'); //eslint-disable-line
const width = arguments[0]; // eslint-disable-line
const height = arguments[1]; // eslint-disable-line
const region = arguments[2]; // eslint-disable-line
const callback = arguments[3]; // eslint-disable-line
this._runCommand('takeSnapshot', [
width || 0,
height || 0,
region || {},
'png',
1,
'legacy',
callback,
]);
return undefined;
}
// Sanitize inputs
const config = {
width: args.width || 0,
height: args.height || 0,
region: args.region || {},
format: args.format || 'png',
quality: args.quality || 1.0,
result: args.result || 'file',
};
if ((config.format !== 'png') &&
(config.format !== 'jpg')) throw new Error('Invalid format specified');
if ((config.result !== 'file') &&
(config.result !== 'base64')) throw new Error('Invalid result specified');
// Call native function
if (Platform.OS === 'android') {
return NativeModules.AirMapModule.takeSnapshot(this._getHandle(), config);
} else if (Platform.OS === 'ios') {
return new Promise((resolve, reject) => {
this._runCommand('takeSnapshot', [
config.width,
config.height,
config.region,
config.format,
config.quality,
config.result,
(err, snapshot) => {
if (err) {
reject(err);
} else {
resolve(snapshot);
}
},
]);
});
}
return Promise.reject('takeSnapshot not supported on this platform');
}
_uiManagerCommand(name) {
return NativeModules.UIManager[getAirMapName(this.props.provider)].Commands[name];
}
_mapManagerCommand(name) {
return NativeModules[`${getAirMapName(this.props.provider)}Manager`][name];
}
_getHandle() {
return findNodeHandle(this.map);
}
_runCommand(name, args) {
switch (Platform.OS) {
case 'android':
NativeModules.UIManager.dispatchViewManagerCommand(
this._getHandle(),
this._uiManagerCommand(name),
args
);
break;
case 'ios':
this._mapManagerCommand(name)(this._getHandle(), ...args);
break;
default:
break;
}
}
render() {
let props;
if (this.state.isReady) {
props = {
region: null,
initialRegion: null,
onMarkerPress: this._onMarkerPress,
onChange: this._onChange,
onMapReady: this._onMapReady,
onLayout: this._onLayout,
...this.props,
};
if (Platform.OS === 'ios' && props.provider === ProviderConstants.PROVIDER_DEFAULT
&& GOOGLE_MAPS_ONLY_TYPES.includes(props.mapType)) {
props.mapType = MAP_TYPES.standard;
}
props.handlePanDrag = !!props.onPanDrag;
} else {
props = {
style: this.props.style,
region: null,
initialRegion: null,
onMarkerPress: this._onMarkerPress,
onChange: this._onChange,
onMapReady: this._onMapReady,
onLayout: this._onLayout,
};
}
if (Platform.OS === 'android' && this.props.liteMode) {
return (
<AIRMapLite
ref={ref => { this.map = ref; }}
{...props}
/>
);
}
const AIRMap = getAirMapComponent(this.props.provider);
return (
<AIRMap
ref={ref => { this.map = ref; }}
{...props}
/>
);
}
}
MapView.propTypes = propTypes;
MapView.viewConfig = viewConfig;
MapView.childContextTypes = childContextTypes;
MapView.MAP_TYPES = MAP_TYPES;
const nativeComponent = Component => requireNativeComponent(Component, MapView, {
nativeOnly: {
onChange: true,
onMapReady: true,
handlePanDrag: true,
},
});
const airMaps = {
default: nativeComponent('AIRMap'),
};
if (Platform.OS === 'android') {
airMaps.google = airMaps.default;
} else {
airMaps.google = googleMapIsInstalled ? nativeComponent('AIRGoogleMap') :
createNotSupportedComponent('react-native-maps: AirGoogleMaps dir must be added to your xCode project to support GoogleMaps on iOS.'); // eslint-disable-line max-len
}
const getAirMapComponent = provider => airMaps[provider || 'default'];
const AIRMapLite = NativeModules.UIManager.AIRMapLite &&
requireNativeComponent('AIRMapLite', MapView, {
nativeOnly: {
onChange: true,
onMapReady: true,
handlePanDrag: true,
},
});
MapView.Marker = MapMarker;
MapView.Polyline = MapPolyline;
MapView.Polygon = MapPolygon;
MapView.Circle = MapCircle;
MapView.UrlTile = MapUrlTile;
MapView.LocalTile = MapLocalTile;
MapView.Callout = MapCallout;
Object.assign(MapView, ProviderConstants);
MapView.ProviderPropType = PropTypes.oneOf(Object.values(ProviderConstants));
MapView.Animated = Animated.createAnimatedComponent(MapView);
MapView.AnimatedRegion = AnimatedRegion;
module.exports = MapView;