-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from Expensify/hayata-implement-MapView
Implement basic map view
- Loading branch information
Showing
13 changed files
with
1,036 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import {MapViewProps} from './MapViewTypes'; | ||
|
||
const DEFAULT_ZOOM = 10; | ||
const DEFAULT_COORDINATE: [number, number] = [-122.4021, 37.7911]; | ||
|
||
export const PADDING = 50; | ||
export const DEFAULT_INITIAL_STATE: MapViewProps['initialState'] = { | ||
location: DEFAULT_COORDINATE, | ||
zoom: DEFAULT_ZOOM, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import Mapbox from '@rnmapbox/maps'; | ||
|
||
function Direction({coordinates}: {coordinates: Array<[number, number]>}) { | ||
if (coordinates.length < 1) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Mapbox.ShapeSource | ||
id="routeSource" | ||
shape={{ | ||
type: 'Feature', | ||
properties: {}, | ||
geometry: { | ||
type: 'LineString', | ||
coordinates, | ||
}, | ||
}} | ||
> | ||
<Mapbox.LineLayer | ||
id="routeFill" | ||
style={{ | ||
lineColor: 'blue', | ||
lineWidth: 3, | ||
}} | ||
/> | ||
</Mapbox.ShapeSource> | ||
); | ||
} | ||
|
||
export default Direction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import {Layer, Source} from 'react-map-gl'; | ||
import {View} from 'react-native'; | ||
|
||
function Direction({coordinates}: {coordinates: Array<[number, number]>}) { | ||
if (coordinates.length < 1) { | ||
return null; | ||
} | ||
return ( | ||
<View> | ||
{coordinates && ( | ||
<Source | ||
id="route" | ||
type="geojson" | ||
data={{ | ||
type: 'Feature', | ||
properties: {}, | ||
geometry: { | ||
type: 'LineString', | ||
coordinates, | ||
}, | ||
}} | ||
> | ||
<Layer | ||
id="route" | ||
type="line" | ||
source="route" | ||
layout={{'line-join': 'round', 'line-cap': 'round'}} | ||
paint={{'line-color': '#888', 'line-width': 4}} | ||
/> | ||
</Source> | ||
)} | ||
</View> | ||
); | ||
} | ||
|
||
export default Direction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import {forwardRef, useEffect, useImperativeHandle, useMemo, useRef} from 'react'; | ||
import Mapbox, {MarkerView} from '@rnmapbox/maps'; | ||
import {View} from 'react-native'; | ||
import {MapViewProps, MapViewHandle} from './MapViewTypes'; | ||
import Direction from './Direction'; | ||
import Utils from './utils'; | ||
|
||
const MapView = forwardRef<MapViewHandle, MapViewProps>(function MapView( | ||
{accessToken, style, styleURL, pitchEnabled, mapPadding, initialState, waypoints, markerComponent: MarkerComponent, directionCoordinates}, | ||
ref, | ||
) { | ||
const cameraRef = useRef<Mapbox.Camera>(null); | ||
|
||
const bounds = useMemo(() => { | ||
if (!waypoints || waypoints.length === 0) { | ||
return undefined; | ||
} | ||
|
||
if (waypoints.length === 1) { | ||
cameraRef.current?.flyTo(waypoints[0]); | ||
cameraRef.current?.zoomTo(15); | ||
return undefined; | ||
} | ||
|
||
const {southWest, northEast} = Utils.getBounds(waypoints); | ||
return { | ||
ne: northEast, | ||
sw: southWest, | ||
paddingTop: mapPadding, | ||
paddingRight: mapPadding, | ||
paddingBottom: mapPadding, | ||
paddingLeft: mapPadding, | ||
}; | ||
}, [waypoints]); | ||
|
||
useImperativeHandle( | ||
ref, | ||
() => ({ | ||
flyTo: (location: [number, number], animationDuration?: number) => cameraRef.current?.flyTo(location, animationDuration), | ||
}), | ||
[], | ||
); | ||
|
||
// Initialize Mapbox on first mount | ||
useEffect(() => { | ||
Mapbox.setAccessToken(accessToken); | ||
}, []); | ||
|
||
return ( | ||
<View style={style}> | ||
<Mapbox.MapView | ||
styleURL={styleURL} | ||
pitchEnabled={pitchEnabled} | ||
style={{flex: 1}} | ||
> | ||
<Mapbox.Camera | ||
ref={cameraRef} | ||
defaultSettings={{ | ||
centerCoordinate: initialState?.location, | ||
zoomLevel: initialState?.zoom, | ||
}} | ||
bounds={bounds} | ||
/> | ||
{MarkerComponent && | ||
waypoints && | ||
waypoints.map((waypoint) => ( | ||
<MarkerView | ||
id={`${waypoint[0]},${waypoint[1]}`} | ||
key={`${waypoint[0]},${waypoint[1]}`} | ||
coordinate={waypoint} | ||
> | ||
<MarkerComponent /> | ||
</MarkerView> | ||
))} | ||
{directionCoordinates && <Direction coordinates={directionCoordinates} />} | ||
</Mapbox.MapView> | ||
</View> | ||
); | ||
}); | ||
|
||
export default MapView; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import Map, {MapRef, Marker} from 'react-map-gl'; | ||
import {RefObject, forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; | ||
import WebMercatorViewport from '@math.gl/web-mercator'; | ||
import {View} from 'react-native'; | ||
import {MapViewHandle, MapViewProps} from './MapViewTypes'; | ||
import Utils from './utils'; | ||
import 'mapbox-gl/dist/mapbox-gl.css'; | ||
import Direction from './Direction'; | ||
import {DEFAULT_INITIAL_STATE} from './CONST'; | ||
|
||
const getMapDimension = (mapRef: RefObject<MapRef>): {width: number; height: number} | undefined => { | ||
if (!mapRef.current?.getMap()) { | ||
return undefined; | ||
} | ||
|
||
const {clientWidth, clientHeight} = mapRef.current.getCanvas(); | ||
return {width: clientWidth, height: clientHeight}; | ||
}; | ||
|
||
const MapView = forwardRef<MapViewHandle, MapViewProps>(function MapView( | ||
{accessToken, waypoints, style, mapPadding, markerComponent: MarkerComponent, directionCoordinates, initialState = DEFAULT_INITIAL_STATE}, | ||
ref, | ||
) { | ||
const mapRef = useRef<MapRef>(null); | ||
const [bounds, setBounds] = useState<{ | ||
longitude: number; | ||
latitude: number; | ||
zoom: number; | ||
}>(); | ||
|
||
useEffect(() => { | ||
if (!waypoints || waypoints.length === 0) { | ||
return; | ||
} | ||
|
||
if (waypoints.length === 1) { | ||
mapRef.current?.flyTo({ | ||
center: waypoints[0], | ||
zoom: 15, | ||
}); | ||
return; | ||
} | ||
|
||
const {northEast, southWest} = Utils.getBounds(waypoints); | ||
const {width, height} = getMapDimension(mapRef) || { | ||
width: 0, | ||
height: 0, | ||
}; | ||
const viewport = new WebMercatorViewport({height, width}); | ||
|
||
const {latitude, longitude, zoom} = viewport.fitBounds([southWest, northEast], { | ||
padding: mapPadding, | ||
}); | ||
|
||
setBounds({latitude, longitude, zoom}); | ||
}, [waypoints]); | ||
|
||
useImperativeHandle( | ||
ref, | ||
() => ({ | ||
flyTo: (location: [number, number], animationDuration?: number) => | ||
mapRef.current?.flyTo({ | ||
center: location, | ||
duration: animationDuration, | ||
}), | ||
}), | ||
[], | ||
); | ||
|
||
return ( | ||
<View style={style}> | ||
<Map | ||
ref={mapRef} | ||
mapboxAccessToken={accessToken} | ||
initialViewState={{ | ||
longitude: initialState?.location[0], | ||
latitude: initialState?.location[1], | ||
zoom: initialState?.zoom, | ||
}} | ||
mapStyle="mapbox://styles/mapbox/streets-v9" | ||
{...bounds} | ||
> | ||
{MarkerComponent && | ||
waypoints && | ||
waypoints.map((waypoint) => ( | ||
<Marker | ||
key={`${waypoint[0]},${waypoint[1]}`} | ||
longitude={waypoint[0]} | ||
latitude={waypoint[1]} | ||
> | ||
<MarkerComponent /> | ||
</Marker> | ||
))} | ||
{directionCoordinates && <Direction coordinates={directionCoordinates} />} | ||
</Map> | ||
</View> | ||
); | ||
}); | ||
|
||
export default MapView; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import {ComponentType} from 'react'; | ||
import type {StyleProp, ViewStyle} from 'react-native'; | ||
|
||
export type MapViewProps = { | ||
// Public access token to be used to fetch map data from Mapbox. | ||
accessToken: string; | ||
// Style applied to MapView component. Note some of the View Style props are not available on ViewMap | ||
style: StyleProp<ViewStyle>; | ||
// Link to the style JSON document. | ||
styleURL?: string; | ||
// Whether map can tilt in the vertical direction. | ||
pitchEnabled?: boolean; | ||
// Padding to apply when the map is adjusted to fit waypoints and directions | ||
mapPadding?: number; | ||
// Initial coordinate and zoom level | ||
initialState?: { | ||
location: [number, number]; | ||
zoom: number; | ||
}; | ||
// Locations on which to put markers | ||
waypoints?: Array<[number, number]>; | ||
// React component to use for the marker. If not provided, markers are not displayed for waypoints. | ||
markerComponent?: ComponentType; | ||
// List of coordinates which together forms a direction. | ||
directionCoordinates?: Array<[number, number]>; | ||
}; | ||
|
||
export type MapViewHandle = { | ||
flyTo: (location: [number, number], animationDuration?: number) => void; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import MapView from './MapView'; | ||
|
||
export type {MapViewProps, MapViewHandle} from './MapViewTypes'; | ||
export default MapView; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
function getBounds(waypoints: Array<[number, number]>): {southWest: [number, number]; northEast: [number, number]} { | ||
const lngs = waypoints.map((waypoint) => waypoint[0]); | ||
const lats = waypoints.map((waypoint) => waypoint[1]); | ||
|
||
return { | ||
southWest: [Math.min(...lngs), Math.min(...lats)], | ||
northEast: [Math.max(...lngs), Math.max(...lats)], | ||
}; | ||
} | ||
|
||
export default { | ||
getBounds, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import MapView from './MapView'; | ||
|
||
export * from './MapView'; | ||
export default MapView; |
This file was deleted.
Oops, something went wrong.