Skip to content

Commit c195fde

Browse files
author
riteshsangwan
committed
center and zoom map according to mission items, rtfzs or current user location
1 parent 62f2d2c commit c195fde

File tree

7 files changed

+156
-40
lines changed

7 files changed

+156
-40
lines changed

src/layouts/CoreLayout/CoreLayout.jsx

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,86 @@
11
import React, {PropTypes} from 'react';
22
import CSSModules from 'react-css-modules';
3+
import {connect} from 'react-redux';
34
import HeaderContainer from 'containers/HeaderContainer';
45
import Breadcrumbs from 'react-breadcrumbs';
56
import Footer from 'components/Footer';
67
import styles from './CoreLayout.scss';
8+
import {userLocationUpdateAction} from '../../store/modules/global';
9+
import _ from 'lodash';
710

8-
export const CoreLayout = ({children, routes, params}) => (
9-
<div styleName="core-layout">
10-
<HeaderContainer routes={routes} />
11+
const USER_LOCATION_KEY = 'ul';
1112

12-
{ (children.props.route.path !== 'home' && children.props.route.path !== 'browse-provider') &&
13-
<div className="breadcrumb-container">
14-
<Breadcrumbs routes={routes} params={params} excludes={['CoreLayout', 'ServiceRequest']} />
15-
</div> }
13+
class CoreLayout extends React.Component {
1614

15+
constructor(props) {
16+
super(props);
17+
this.requestUserLocation = this.requestUserLocation.bind(this);
18+
}
19+
/**
20+
* React lifecycle method which is invoked after this component is mounted
21+
* This is invoked only on the page reload
22+
*/
23+
componentDidMount() {
24+
// component did mount will be called on page reload, so if already a location is cached use that
25+
// and if not than request location from user
26+
this.requestUserLocation();
27+
}
1728

18-
<div styleName="content">
19-
{children}
20-
</div>
21-
<Footer />
22-
</div>
23-
);
29+
/**
30+
* Request a user location and fire redux action handler
31+
*/
32+
requestUserLocation() {
33+
const {onUserLocationUpdate} = this.props;
34+
// don't request the permission everytime and use caching
35+
const cachedLocation = localStorage.getItem(USER_LOCATION_KEY);
36+
// just to be extra safe here as a user can manipulate the content of local storage
37+
if (cachedLocation && _.has(cachedLocation, 'lat') && _.has(cachedLocation, 'lng')) {
38+
onUserLocationUpdate(cachedLocation);
39+
} else if (_.hasIn(navigator, 'geolocation.getCurrentPosition')) {
40+
// request user location
41+
navigator.geolocation.getCurrentPosition((pos) => {
42+
onUserLocationUpdate({lat: pos.coords.latitude, lng: pos.coords.longitude});
43+
},
44+
null,
45+
{timeout: 60000}
46+
);
47+
}
48+
}
49+
50+
render() {
51+
const {children, routes, params} = this.props;
52+
return (
53+
<div styleName="core-layout">
54+
<HeaderContainer routes={routes} />
55+
56+
{ (children.props.route.path !== 'home' && children.props.route.path !== 'browse-provider') &&
57+
<div className="breadcrumb-container">
58+
<Breadcrumbs routes={routes} params={params} excludes={['CoreLayout', 'ServiceRequest']} />
59+
</div> }
60+
61+
62+
<div styleName="content">
63+
{children}
64+
</div>
65+
<Footer />
66+
</div>
67+
);
68+
}
69+
}
2470

2571
CoreLayout.propTypes = {
2672
children: PropTypes.any.isRequired,
2773
routes: PropTypes.any.isRequired,
2874
params: PropTypes.any.isRequired,
75+
onUserLocationUpdate: PropTypes.func.isRequired,
2976
};
3077

31-
export default CSSModules(CoreLayout, styles);
78+
const mapState = (state) => state.global;
79+
80+
const mapDispatchToProps = (dispatch) => ({
81+
onUserLocationUpdate: (location) => {
82+
dispatch(userLocationUpdateAction(location));
83+
},
84+
});
85+
86+
export default connect(mapState, mapDispatchToProps)(CSSModules(CoreLayout, styles));

src/routes/MissionPlanner/components/MissionMap/MissionMap.jsx

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ const polylineConfig = {
2828
},
2929
};
3030

31+
const types = {
32+
point: 'Point',
33+
polygon: 'Polygon',
34+
};
35+
3136
export const MissionGoogleMap = withGoogleMap((props) => (
3237
<GoogleMap
3338
{... mapConfig}
@@ -79,35 +84,70 @@ export class MissionMap extends Component {
7984
this.setState({
8085
lineMarkerPositions: getLineMarkerPositions(nextProps.markers),
8186
});
87+
// only required if the user location is updated
88+
// because bounds for markers and rtfzs are already set in handleMapLoad
89+
const shouldUpdateBound = !_.isEqual(this.props.userLocation, nextProps.userLocation);
90+
if (shouldUpdateBound) {
91+
const {markers, rtfzs, userLocation} = nextProps;
92+
const bounds = this.getMapBounds(markers, rtfzs, userLocation);
93+
this.map.fitBounds(bounds);
94+
}
8295
}
8396

84-
fitMapToBounds(map, markers) {
85-
if (markers.length) {
86-
const markersBounds = new google.maps.LatLngBounds();
87-
88-
for (const marker of this.props.markers) {
89-
markersBounds.extend(marker.position);
90-
}
91-
92-
map.fitBounds(markersBounds);
97+
/**
98+
* Intelligently determine the map bounds to fit the map
99+
* The order of precedence
100+
* 1. If markers are defined return the bounds for markers
101+
* 2. If markers are undefined and rtfzs are defined than return the bounds for rtfzs
102+
* 3. If mission items and rtfzs are undefined than return the bounds for current user location
103+
* if user denied location than return the default bounds
104+
* 4. If markers and rtfzs are defined return bounds for markers
105+
*
106+
* @param {Array} markers the list of markers to get the bounds
107+
* @param {Array} rtfzs the list of rtfzs to get the bounds
108+
* @param {Object} userLocation the user location to get the bounds
109+
*/
110+
getMapBounds(markers, rtfzs, userLocation) {
111+
const isMarkers = markers && markers.length > 0;
112+
const isRtfzs = rtfzs && rtfzs.length > 0;
113+
const isUserLocation = userLocation && _.has(userLocation, 'lat') && _.has(userLocation, 'lng');
114+
let bounds;
115+
if (isMarkers) {
116+
bounds = new google.maps.LatLngBounds();
117+
// bounds for markers
118+
markers.forEach((marker) => {
119+
bounds.extend(marker.position);
120+
});
121+
} else if (!isMarkers && isRtfzs) {
122+
bounds = new google.maps.LatLngBounds();
123+
// bounds for rtfzs
124+
rtfzs.forEach((rtfz) => {
125+
if (rtfz.location.type === types.point) {
126+
bounds.extend({lat: rtfz.location.coordinates[1], lng: rtfz.location.coordinates[0]});
127+
} else if (rtfz.location.type === types.polygon) {
128+
rtfz.location.coordinates.forEach((coor) => {
129+
coor.forEach((point) => {
130+
bounds.extend({lat: point[1], lng: point[0]});
131+
});
132+
});
133+
}
134+
});
135+
} else if (!isMarkers && !isRtfzs && isUserLocation) {
136+
bounds = new google.maps.LatLngBounds();
137+
// bounds for user location
138+
bounds.extend(userLocation);
93139
}
140+
return bounds;
94141
}
95142

96143
handleMapLoad(map) {
144+
const {markers, rtfzs, userLocation} = this.props;
97145
this.map = map;
98146
if (map) {
99-
if (this.props.markers.length > 0) {
100-
this.fitMapToBounds(map, this.props.markers);
101-
} else {
102-
navigator.geolocation.getCurrentPosition((pos) => {
103-
map.panTo({
104-
lat: pos.coords.latitude,
105-
lng: pos.coords.longitude,
106-
});
107-
},
108-
null,
109-
{timeout: 60000}
110-
);
147+
const bounds = this.getMapBounds(markers, rtfzs, userLocation);
148+
// if bounds are defined than only fit map to bounds, otherwise keep default bounds
149+
if (bounds) {
150+
map.fitBounds(bounds);
111151
}
112152
}
113153
}
@@ -155,6 +195,8 @@ MissionMap.propTypes = {
155195
loadNfz: PropTypes.func.isRequired,
156196
noFlyZones: PropTypes.array.isRequired,
157197
rtfzs: PropTypes.array,
198+
// the current cached user location
199+
userLocation: PropTypes.object,
158200
};
159201

160202
export default CSSModules(MissionMap, styles);

src/routes/MissionPlanner/components/MissionPlannerView.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export const getMarkerProps = (item, updateMissionItem) => {
3737
item.coordinate[2],
3838
],
3939
};
40-
4140
updateMissionItem(item.id, newMissionItem);
4241
},
4342
};
@@ -68,7 +67,7 @@ export const getMarkerProps = (item, updateMissionItem) => {
6867
return markerProps;
6968
};
7069

71-
export const MissionPlannerView = ({mission, toggleRtfzHandler, updateMissionItem, addMissionItem, deleteMissionItem, loadNfz, noFlyZones}) => {
70+
export const MissionPlannerView = ({mission, toggleRtfzHandler, userLocation, updateMissionItem, addMissionItem, deleteMissionItem, loadNfz, noFlyZones}) => {
7271
const missionItemsExt = getMissionItemsExt(mission);
7372
const markersExt = missionItemsExt.map((item) => getMarkerProps(item, updateMissionItem));
7473

@@ -83,6 +82,7 @@ export const MissionPlannerView = ({mission, toggleRtfzHandler, updateMissionIte
8382
noFlyZones={noFlyZones}
8483
markers={markersExt}
8584
rtfzs={mission.zones}
85+
userLocation={userLocation}
8686
onMapClick={(event) => addMissionItem({lat: event.latLng.lat(), lng: event.latLng.lng()})}
8787
/>
8888
<MissionSidebar missionItems={missionItemsExt} onUpdate={updateMissionItem} onDelete={deleteMissionItem} />
@@ -100,6 +100,8 @@ MissionPlannerView.propTypes = {
100100
loadNfz: PropTypes.func.isRequired,
101101
noFlyZones: PropTypes.array.isRequired,
102102
toggleRtfzHandler: PropTypes.func.isRequired,
103+
// the current cached user location
104+
userLocation: PropTypes.object,
103105
};
104106

105107
export default CSSModules(MissionPlannerView, styles);

src/routes/MissionPlanner/components/RTFZSidebar/RTFZSidebar.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
}
2020
.sidebar-item {
2121
border: 1px solid #cccccc;
22-
box-shadow: 0px 0px 2px 2px rgba(102,102,102,0.7);
22+
box-shadow: 0px 0px 2px 2px rgba(163,163,163,0.7);
2323
padding: 10px;
2424
margin: 5px 0;
2525
width: 290px;

src/routes/MissionPlanner/containers/MissionPlannerContainer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ const resolve = [{
88
promise: ({params, store}) => store.dispatch(actions.load(params.id)),
99
}];
1010

11-
const mapState = (state) => ({...state.missionPlanner, ...state.searchNFZ});
11+
const mapState = (state) => ({...state.missionPlanner, ...state.searchNFZ, userLocation: state.global.userLocation});
1212

1313
export default asyncConnect(resolve, mapState, {...actions, loadNfz})(MissionPlannerView);

src/routes/MissionPlanner/modules/MissionPlanner.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ export default handleActions({
132132
const newState = _.cloneDeep(state);
133133

134134
newState.mission = mission;
135+
// if missionItems are not defined define them to empty array
136+
newState.mission.missionItems = newState.mission.missionItems || [];
137+
// add additional show property on each zone to individually show/hide zone
138+
if (mission.zones) {
139+
newState.mission.zones = mission.zones.map((single) => {
140+
single.show = true;
141+
return single;
142+
});
143+
}
135144

136145
return newState;
137146
},

src/store/modules/global.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import config from '../../config';
55

66
import APIService from 'services/APIService';
77

8-
98
const userApi = new UserApi(config.api.basePath);
109

1110
//------------------------------------------------------------------------------
1211
// Constants
1312

1413
const LOGIN_ACTION_FAILURE = 'LOGIN_ACTION_FAILURE';
1514
const LOGIN_ACTION_SUCCESS = 'LOGIN_ACTION_SUCCESS';
15+
const USER_LOCATION_UPDATE = 'USER_LOCATION_UPDATE';
1616

1717
const LOGIN_REDIRECT = {
1818
admin: '/admin',
@@ -23,6 +23,7 @@ const LOGIN_REDIRECT = {
2323

2424
const LOGOUT_ACTION = 'LOGOUT_ACTION';
2525
const USER_INFO_KEY = 'userInfo';
26+
const USER_LOCATION_KEY = 'ul';
2627

2728
// ------------------------------------
2829
// Actions
@@ -85,6 +86,12 @@ export const logoutAction = () => (dispatch) => {
8586
});
8687
};
8788

89+
export const userLocationUpdateAction = (location) => (dispatch) => {
90+
// cache the user location in localstorage
91+
localStorage.setItem(USER_LOCATION_KEY, JSON.stringify(location));
92+
dispatch({type: USER_LOCATION_UPDATE, payload: {location}});
93+
};
94+
8895
export const signupAction = createAction('SIGNUP_ACTION');
8996

9097
export const actions = {
@@ -127,6 +134,7 @@ export default handleActions({
127134
[signupAction]: (state) => ({
128135
...state, loggedUser: isLogged, hasError, errorText, user: (loadUserInfo() ? loadUserInfo().user : {}),
129136
}),
137+
[USER_LOCATION_UPDATE]: (state, {payload: {location}}) => ({...state, userLocation: location}),
130138
}, {
131139
toggleNotif: false,
132140
loggedUser: Boolean(loadUserInfo()),

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy