123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- import React, { useEffect, useRef, useState } from 'react';
- import {Image, StyleSheet, View, TouchableOpacity, Text, Alert, Animated,} from 'react-native';
- import MapboxGL from '@react-native-mapbox-gl/maps';
- import 'react-native-get-random-values'
- import Modal from 'react-native-modal';
- import SlidingUpPanel from 'rn-sliding-up-panel';
- import LandmarkDetails from '../components/LandmarkDetails';
- import {Icons} from '../globals.js';
- import Icon from 'react-native-vector-icons/FontAwesome';
- import VoiceView from '../components/VoiceView';
- import RNLocation from 'react-native-location';
- import { polygon, point, lineString, booleanPointInPolygon, booleanIntersects, buffer } from '@turf/turf';
- import polyline from '@mapbox/polyline';
- import PlaceDetails from '../components/PlaceDetails';
- import axios from 'axios';
- import { useMapState } from '../contexts/MapContext';
- import { useAuthState } from '../contexts/AuthContext';
- import { API_URL } from '../globals';
- import { adaptIcon } from '../globals';
- MapboxGL.setAccessToken('pk.eyJ1IjoiY2Rtb3NzIiwiYSI6ImNrbmhuOXJzcDIyd20ycW1pYm8xaGI0aGUifQ.j04Sp636N9Wg4N9j9t2tXw');
- const directionsUrl = "https://maps.googleapis.com/maps/api/directions/json?mode=walking&alternatives=true&key=AIzaSyD06YUMazlb4Fu0Q81y_YNyEBz8PmtZyeY";
- RNLocation.configure({
- distanceFilter: 1
- });
- const Map = ({navigation}) => {
- // state
- const [tempPoint, setTempPoint] = useState(null);
- const [placesIsVisible, togglePlacesModal] = useState(false);
- const [addIsVisible, toggleAddModal] = useState(false);
- const [lmDetailsIsVisible, toggleLMDetailsModal] = useState(false);
- const [routePoints, setRoutePoints] = useState({first: null, second: null});
- const [routes, setRoutes] = useState([]);
- const [routingActive, toggleRouting] = useState(false);
- // refs
- const voiceModal = useRef();
- const map = useRef();
- const panel = useRef();
- // contexts
- const { mapState, mapDispatch } = useMapState();
- const { authState, authDispatch } = useAuthState();
- let panelPosition = new Animated.Value(0);
-
- useEffect(() => {
- async function handleLocation() {
- if (mapState.location == null && !mapState.locationPermission) {
- mapDispatch({type: 'UPDATE_LOCATION_PERMISSION', payload: await RNLocation.requestPermission({
- ios: "whenInUse",
- android: {
- detail: "fine",
- rationale: {
- title: "We need to access your location",
- message: "We use your location to provide convenient features such as geo-activated notifications and adding landmarks via voice",
- buttonPositive: "Grant permission",
- buttonNegative: "No thanks"
- }
- }
- })})
- }
- }
- handleLocation();
- const panelPositionListener = panelPosition.addListener(({value}) => {
- if (value == 0) {
- toggleRouting(false);
- }
- if (value > 0) {
- toggleRouting(true);
- }
- })
- return() => {
- panelPosition.removeListener(panelPositionListener)
- // unsubscribePermission();
- // unsubscribeLocation();
- }
- });
- const changeSelectedLandmark = (landmark) => {
- mapDispatch({ type:"UPDATE_SELECTED_LANDMARK", payload: landmark});
- }
- const showLandmarkDetails = (landmark) => {
- console.log(landmark)
- // changeSelectedLandmark(landmark);
- // toggleLMDetailsModal(true);
- }
- const openVoiceModal = () => {
- if (!mapState.locationPermission) {
- Alert.alert('You need to provide location permission to use this feature.')
- }
- else {
- RNLocation.getLatestLocation({timeout: 100}).then(location => {
- mapDispatch({ type:"UPDATE_LOCATION", payload: location});
- voiceModal.current.open();
- })
-
- }
- }
- const handleMapTouch = (e) => {
- if (routingActive) {
- setRoutePoint(e.geometry.coordinates);
- }
- else {
- setTempPoint(e.geometry.coordinates);
- // sets selected landmark with barebones object to prime for new landmark
- mapDispatch({ type:"UPDATE_SELECTED_LANDMARK", payload: {
- longitude: e.geometry.coordinates[0],
- latitude: e.geometry.coordinates[1],
- icon: 'barrier',
- title: '',
- desc: ''
- }});
- toggleAddModal(true);
- }
- }
- const handlePlaceTouch = (e) => {
- if (routingActive) {
- setRoutePoint([e.coordinates.longitude, e.coordinates.latitude]);
- }
- else {
- setTempPoint([e.coordinates.longitude, e.coordinates.latitude]);
- // gathers all places overlapping point that was touched
- const touchedPlaces = e.features.filter(f => {
- return booleanPointInPolygon(point([e.coordinates.longitude, e.coordinates.latitude]), polygon(f.geometry.coordinates));
- });
- // set selected landmark to barebones to prime for adding a landmark
- mapDispatch({ type:"UPDATE_SELECTED_LANDMARK", payload: {
- longitude: e.coordinates.longitude,
- latitude: e.coordinates.latitude,
- icon: 'barrier',
- title: 'New Landmark',
- desc: ''
- }});
- if (touchedPlaces.length == 0) {
- toggleAddModal(true);
- }
- // otherwise set state with gathered places and show places modal
- else {
- mapDispatch({ type:"UPDATE_SELECTED_PLACES", payload: touchedPlaces});
- mapDispatch({ type:"UPDATE_SELECTED_PLACE", payload: {
- id: touchedPlaces[0].id,
- name: touchedPlaces[0].properties.name,
- desc: touchedPlaces[0].properties.desc,
- tips: touchedPlaces[0].properties.tips,
- dateAdded: touchedPlaces[0].properties.dateAdded,
- postedBy: touchedPlaces[0].properties.postedBy,
- rating: touchedPlaces[0].properties.rating,
- }});
- togglePlacesModal(true);
- }
- }
- }
- const setRoutePoint = (point) => {
- if (routePoints.first) {
- setRoutePoints({...routePoints, second: point});
- }
- else {
- setRoutePoints({first: point});
- }
- }
- const buildRoutingUrl = async () => {
- const routingUrl = directionsUrl + "&origin=" + routePoints.first[1] + ',' + routePoints.first[0] + '&destination=' + routePoints.second[1] + ',' + routePoints.second[0];
- const response = await axios.get(routingUrl);
- let routes = response.data.routes.map((route, i) => {
- return {
- type: 'Feature',
- properties: {
- color: i == 0 ? '#007FFF' : '#888888',
- opacity: i == 0 ? 1 : 0.6
- },
- geometry: {
- coordinates: polyline.decode(route.overview_polyline.points).map(coords => [coords[1], coords[0]]),
- type: 'LineString'
- }
- }
- });
- mapState.landmarks.forEach(landmark => {
- const lmBuffer = buffer(point([landmark.longitude, landmark.latitude]), 10, {units: 'meters'}).geometry.coordinates
- routes = routes.map(route => {
- if (booleanIntersects(lineString(route.geometry.coordinates), polygon(lmBuffer))) {
- console.log(route);
- return {...route, properties: {color: 'red', opacity: .5,}};
- }
- return route;
- });
- })
- setRoutes(routes);
- }
- const clearRoutes = () => {
- setRoutePoints({first: null, second: null});
- setRoutes([]);
- }
- const handleDrag = (value) => {
- console.log(value);
- }
- const addLandmark = () => {
- clearModals();
- console.log(tempPoint);
- navigation.navigate("LandmarkForm");
- }
- const removeTempPoint = () => {
- setTempPoint(null);
- }
- const editLandmark = () => {
- navigation.navigate("LandmarkForm");
- }
- const closeModal = (message) => {
- toggleLMDetailsModal(false);
- if (message != null) {
- Alert.alert(message);
- }
- };
- const clearModals = () => {
- toggleLMDetailsModal(false);
- toggleAddModal(false);
- togglePlacesModal(false);
- };
- const updateDisplayedLandmarks = async (e) => {
- const neCorner = e.properties.visibleBounds[0];
- const swCorner = e.properties.visibleBounds[1];
- try {
- const response = await axios.get(`${API_URL}/api/landmarkinregion/${neCorner[0]}/${neCorner[1]}/${swCorner[0]}/${swCorner[1]}`, {
- headers: {
- "Authorization": "Bearer " + authState.accessToken
- }
- });
- const landmarks = JSON.parse(response.data.landmarks);
- mapDispatch({type: "UPDATE_LANDMARKS", payload: landmarks.data.features})
- //console.log(landmarks.data.features[0])
- } catch (error) {
- console.log(error.response.data);
- }
-
- }
- return(
- <View style={styles.container}>
- <View style={styles.mapHeader}>
- <TouchableOpacity style={{width: '25%', height: '100%', justifyContent: 'center'}} onPress={() => {clearModals(); navigation.jumpTo('Account');}}>
- <Image style={{width: 40, height: 40, borderRadius: 100, marginLeft: 20}} source={require('../assets/default-pfp.png')}/>
- </TouchableOpacity>
- <Text style={{color: 'white', fontSize: 20, marginRight: 20}}>atlas</Text>
- </View>
- <View style={styles.mapContainer}>
- <MapboxGL.MapView
- ref={map}
- style={styles.mapbox}
- onRegionIsChanging={updateDisplayedLandmarks}
- onPress={e => handleMapTouch(e)}>
- {/* <MapboxGL.UserLocation/> */}
- {/* <MapboxGL.Camera followUserLocation followUserMode={'normal'} zoomLevel={9} /> */}
- <MapboxGL.Camera zoomLevel={9} centerCoordinate={[-113.52511882781982,53.52385492230552]} />
- {tempPoint &&
- <MapboxGL.PointAnnotation id="temp" coordinate={tempPoint} />
- }
- {routePoints.first &&
- <MapboxGL.PointAnnotation id="first" coordinate={routePoints.first} />
- }
- {routePoints.second &&
- <MapboxGL.PointAnnotation id="second" coordinate={routePoints.second} />
- }
- {mapState.landmarks.map(landmark => {
- return (
- <MapboxGL.PointAnnotation onSelected={() => console.log(landmark)} key={landmark.properties.id} id={landmark.properties.id} coordinate={[landmark.geometry.coordinates[0], landmark.geometry.coordinates[1]]} >
- <Image source={Icons[adaptIcon(landmark.properties.icon)]} style={{width: 22, height: 30}} />
- </MapboxGL.PointAnnotation>
- )})}
- <MapboxGL.ShapeSource
- onPress={e => handlePlaceTouch(e)}
- fill
- id="test"
- shape={{type: "FeatureCollection", features: mapState.places.map(place => {
- return {
- type: 'Feature',
- id: place.id,
- properties: {
- name: place.name,
- tips: place.tips,
- rating: place.rating,
- postedBy: place.postedBy,
- dateAdded: place.dateAdded,
- color: place.color,
- },
- geometry: {
- "type": "Polygon",
- "coordinates": place.coordinates
- }
- }
- })}}>
- <MapboxGL.FillLayer
- id="places"
- style={{
- fillColor: ["get", "color"],
- fillOpacity: .3}}/>
- </MapboxGL.ShapeSource>
- <MapboxGL.ShapeSource
- onPress={e => handlePlaceTouch(e)}
- fill
- id="buffers"
- shape={{type: "FeatureCollection", features: mapState.landmarks.map(landmark => {
- return {
- type: 'Feature',
- id: landmark.properties.id,
- properties: {
- },
- geometry: buffer(point([landmark.geometry.coordinates[0], landmark.geometry.coordinates[1]]), 10, {units: "meters"}).geometry
- }
- })}}>
- <MapboxGL.FillLayer
- id="buffers"
- style={{
- fillColor: 'black',
- fillOpacity: .3}}/>
- </MapboxGL.ShapeSource>
- <MapboxGL.ShapeSource
- id="line"
- shape={{type: "FeatureCollection", features: routes}}>
- <MapboxGL.LineLayer
- id="lines"
- style={{lineWidth: 7, lineColor: ["get", "color"], lineOpacity: ['get', 'opacity'], lineCap: 'round', lineJoin: 'round'}} />
- </MapboxGL.ShapeSource>
- </MapboxGL.MapView>
- {!routingActive ?
- <TouchableOpacity style={styles.routeBtn} onPress={() => panel.current.show(100)}>
- <Icon name='route' size={20} name="map" color='white' />
- </TouchableOpacity> : null}
- <TouchableOpacity style={styles.micBtn} name="microphone" onPress={openVoiceModal}>
- <Icon name='microphone' size={20} color='white' />
- </TouchableOpacity>
- </View>
- {mapState.locationPermission ? <VoiceView ref={voiceModal} changeSelectedLandmark={changeSelectedLandmark} editLandmark={editLandmark} style={styles.voiceModal} onPress={openVoiceModal}/> : null}
- <Modal
- isVisible={lmDetailsIsVisible}
- style={{height: 520, margin: 0, justifyContent: 'flex-end'}}
- onBackButtonPress={() => toggleLMDetailsModal(false)}
- onBackdropPress={() => toggleLMDetailsModal(false)}
- onSwipeComplete={() => toggleLMDetailsModal(false)}
- swipeDirection={['up', 'down']}>
- <LandmarkDetails closeModal={clearModals} editLandmark={editLandmark}/>
- </Modal>
- <Modal
- style={{height: 520, margin: 0, justifyContent: 'flex-end',}}
- isVisible={placesIsVisible}
- onBackButtonPress={() => togglePlacesModal(false)}
- onBackdropPress={() => togglePlacesModal(false)}
- onSwipeComplete={() => togglePlacesModal(false)}
- swipeDirection={['up', 'down']}
- onModalWillHide={removeTempPoint}>
- <PlaceDetails closeModal={clearModals} addLandmark={addLandmark}/>
- </Modal>
- <Modal
- style={{justifyContent: 'center',}}
- isVisible={addIsVisible}
- onBackButtonPress={() => toggleAddModal(false)}
- onBackdropPress={() => toggleAddModal(false)}
- onSwipeComplete={() => toggleAddModal(false)}
- swipeDirection={['up','down']}
- onModalWillHide={removeTempPoint}>
- <View style={{maxHeight: 100, backgroundColor: '#df3f3f'}}>
- <Text style={styles.addTitle}>Add landmark here?</Text>
- <View style={{flexDirection: 'row', justifyContent: 'flex-end', alignItems: 'center', }}>
- <TouchableOpacity style={{marginRight: 40, height: 50}} onPress={() => toggleAddModal(false)}>
- <Text style={{color: 'white'}}>Cancel</Text>
- </TouchableOpacity>
- <TouchableOpacity style={{marginRight: 40, height: 50}} onPress={addLandmark}>
- <Text style={{color: 'white'}}>Add</Text>
- </TouchableOpacity>
- </View>
- </View>
- </Modal>
- <SlidingUpPanel
- animatedValue={panelPosition}
- ref={panel}
- showBackdrop={false}
- allowDragging={false}
- onDragEnd={() => console.log('test')}
- onMomentumDragEnd={() => console.log('test')}
- draggableRange={{top: 100, bottom: 0}}>
- <View style={{padding: 20, paddingRight: 10, height: 100,backgroundColor: '#df3f3f'}}>
- <Text style={{color: 'white'}}>Select points</Text>
- {routePoints.first || routePoints.second ?
- <View style={{flexDirection: 'row', marginTop: 20, justifyContent: 'flex-end'}}>
- {routePoints.first && routePoints.second ?
- <TouchableOpacity onPress={buildRoutingUrl} style={{backgroundColor: 'white', alignSelf: 'flex-end', alignItems: 'center', justifyContent: 'center', width: "40%", height: 30, marginRight: 10}}>
- <Text style={{color: 'black'}}>Show directions</Text>
- </TouchableOpacity> : null }
- <TouchableOpacity onPress={clearRoutes} style={{backgroundColor: 'white', alignSelf: 'flex-end', alignItems: 'center', justifyContent: 'center', width: "40%", height: 30,}}>
- <Text style={{color: 'black'}}>Clear</Text>
- </TouchableOpacity>
- </View> : null }
- </View>
- </SlidingUpPanel>
- </View>
- )
- }
- const styles = StyleSheet.create({
- container: {
- flex: 1
- },
- mapHeader: {
- justifyContent: 'space-between',
- flexDirection: 'row',
- alignItems: 'center',
- height: '9%',
- zIndex: 5,
- backgroundColor: '#df3f3f',
- },
- mapContainer: {
- height: '100%',
- width: '100%',
- backgroundColor: 'white',
- },
- markerContainer: {
- height: 50,
- width: 50,
- backgroundColor: 'white',
- },
- markerImg: {
- height: 25,
- width: 25
- },
- mapbox: {
- flex: 1,
- },
- routeBtn: {
- position: 'absolute',
- backgroundColor: '#df3f3f',
- justifyContent: 'center',
- alignItems: 'center',
- borderRadius: 40,
- bottom: 150,
- right: 20,
- zIndex: 5,
- width: 60,
- height: 60,
- },
- micBtn: {
- position: 'absolute',
- backgroundColor: '#df3f3f',
- justifyContent: 'center',
- alignItems: 'center',
- borderRadius: 40,
- bottom: 70,
- right: 20,
- zIndex: 5,
- width: 60,
- height: 60,
- },
- addModal: {
- backgroundColor: '#df3f3f',
- height: 100,
- },
- voiceModal: {
- backgroundColor: '#df3f3f',
- height: 200,
- },
- addTitle: {
- color: 'white',
- fontSize: 13,
- marginTop: 20,
- marginLeft: 20
- },
- placesTitle: {
- fontSize: 13,
- color: 'white',
- margin: 4,
- alignSelf: 'center'
- },
- commentContainer: {
- padding: 10
- },
- commentHeader: {
- flexDirection: 'row',
- justifyContent: 'space-between'
- },
- commentBody: {
- padding: 10
- },
- commentInputContainer: {
- paddingHorizontal: 10,
- backgroundColor: 'white',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- },
- addComment: {
- width: '100%',
- borderTopWidth: 1,
- borderColor: 'grey',
- backgroundColor: 'white',
- },
- })
-
- export default Map;
-
|