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( {clearModals(); navigation.jumpTo('Account');}}> atlas handleMapTouch(e)}> {/* */} {/* */} {tempPoint && } {routePoints.first && } {routePoints.second && } {mapState.landmarks.map(landmark => { return ( console.log(landmark)} key={landmark.properties.id} id={landmark.properties.id} coordinate={[landmark.geometry.coordinates[0], landmark.geometry.coordinates[1]]} > )})} 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 } } })}}> 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 } })}}> {!routingActive ? panel.current.show(100)}> : null} {mapState.locationPermission ? : null} toggleLMDetailsModal(false)} onBackdropPress={() => toggleLMDetailsModal(false)} onSwipeComplete={() => toggleLMDetailsModal(false)} swipeDirection={['up', 'down']}> togglePlacesModal(false)} onBackdropPress={() => togglePlacesModal(false)} onSwipeComplete={() => togglePlacesModal(false)} swipeDirection={['up', 'down']} onModalWillHide={removeTempPoint}> toggleAddModal(false)} onBackdropPress={() => toggleAddModal(false)} onSwipeComplete={() => toggleAddModal(false)} swipeDirection={['up','down']} onModalWillHide={removeTempPoint}> Add landmark here? toggleAddModal(false)}> Cancel Add console.log('test')} onMomentumDragEnd={() => console.log('test')} draggableRange={{top: 100, bottom: 0}}> Select points {routePoints.first || routePoints.second ? {routePoints.first && routePoints.second ? Show directions : null } Clear : null } ) } 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;