Skip to content

Commit 29561cb

Browse files
author
gondzo
committed
Merge branch 'moveDronesMap' into dev
# Conflicts: # config/default.js # src/routes/index.js # src/services/APIService.js
2 parents 40c8d3c + f6bca5d commit 29561cb

File tree

17 files changed

+220
-4
lines changed

17 files changed

+220
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
* node v6 (https://nodejs.org)
55

66
## Quick Start
7-
* `npm install -g nodemon`
87
* `npm install`
98
* `npm run dev`
109
* Navigate browser to `http://localhost:3000`
@@ -18,6 +17,7 @@ See Guild https://github.com/lorenwest/node-config/wiki/Configuration-Files
1817
|----|-----------|
1918
|`PORT`| The port to listen|
2019
|`GOOGLE_API_KEY`| The google api key see (https://developers.google.com/maps/documentation/javascript/get-api-key#key)|
20+
|`API_BASE_URL`| The base URL for Drone API |
2121

2222

2323
## Install dependencies

config/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
* Main config file
44
*/
55
module.exports = {
6+
// below env variables are NOT visible in frontend
67
PORT: process.env.PORT || 3000,
8+
9+
// below env variables are visible in frontend
710
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || 'AIzaSyCrL-O319wNJK8kk8J_JAYsWgu6yo5YsDI',
811
API_BASE_PATH: process.env.API_BASE_PATH || 'https://kb-dsp-server.herokuapp.com',
912
};

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"json-loader": "^0.5.4",
4545
"lodash": "^4.16.4",
4646
"moment": "^2.17.0",
47+
"node-js-marker-clusterer": "^1.0.0",
4748
"node-sass": "^3.7.0",
4849
"postcss-flexboxfixer": "0.0.5",
4950
"postcss-loader": "^0.13.0",
@@ -76,6 +77,7 @@
7677
"redux-logger": "^2.6.1",
7778
"redux-thunk": "^2.0.0",
7879
"sass-loader": "^4.0.0",
80+
"socket.io-client": "^1.7.1",
7981
"style-loader": "^0.13.0",
8082
"superagent": "^2.3.0",
8183
"superagent-promise": "^1.1.0",

src/components/Header/Header.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { PropTypes } from 'react';
22
import CSSModules from 'react-css-modules';
3+
import { Link } from 'react-router';
34
import SearchInput from '../SearchInput';
45
import Dropdown from '../Dropdown';
56
import styles from './Header.scss';
@@ -30,8 +31,8 @@ export const Header = ({location, selectedCategory, categories, user, notificati
3031
return (
3132
<li styleName="pages">
3233
<ul>
33-
<li className={currentRoute === 'Dashboard' ? 'active' : null}><a href="/dashboard">Dashboard</a></li>
34-
<li className={currentRoute === 'Requests' ? 'active' : null}><a href="/my-request">Requests</a></li>
34+
<li className={currentRoute === 'Dashboard' ? 'active' : null}><Link to="/dashboard">Dashboard</Link></li>
35+
<li className={currentRoute === 'Requests' ? 'active' : null}><Link to="/my-request">Requests</Link></li>
3536
<li className={currentRoute === 'MyDrones' ? 'active' : null}>My Drones</li>
3637
<li className={currentRoute === 'MyServices' ? 'active' : null}>My Services</li>
3738
<li className={currentRoute === 'Analytics' ? 'active' : null}>Analytics</li>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React, { PropTypes } from 'react';
2+
import CSSModules from 'react-css-modules';
3+
import MarkerClusterer from 'node-js-marker-clusterer';
4+
import styles from './DronesMapView.scss';
5+
6+
const getIcon = (status) => {
7+
switch (status) {
8+
case 'in-motion':
9+
return 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png';
10+
case 'idle-ready':
11+
return 'http://maps.google.com/mapfiles/ms/icons/green-dot.png';
12+
case 'idle-busy':
13+
return 'http://maps.google.com/mapfiles/ms/icons/orange-dot.png';
14+
default:
15+
throw new Error(`invalid drone status ${status}`);
16+
}
17+
};
18+
19+
const getLatLng = ({currentLocation}) => ({lng: currentLocation[0], lat: currentLocation[1]});
20+
21+
class DronesMapView extends React.Component {
22+
23+
componentDidMount() {
24+
const { drones, mapSettings } = this.props;
25+
this.map = new google.maps.Map(this.node, mapSettings);
26+
const id2Marker = {};
27+
28+
const markers = drones.map((drone) => {
29+
const marker = new google.maps.Marker({
30+
clickable: false,
31+
crossOnDrag: false,
32+
cursor: 'pointer',
33+
position: getLatLng(drone),
34+
icon: getIcon(drone.status),
35+
label: drone.name,
36+
});
37+
id2Marker[drone.id] = marker;
38+
return marker;
39+
});
40+
this.id2Marker = id2Marker;
41+
this.markerCluster = new MarkerClusterer(this.map, markers, { imagePath: '/img/m' });
42+
}
43+
44+
componentWillReceiveProps(nextProps) {
45+
const { drones } = nextProps;
46+
drones.forEach((drone) => {
47+
const marker = this.id2Marker[drone.id];
48+
if (marker) {
49+
marker.setPosition(getLatLng(drone));
50+
marker.setLabel(drone.name);
51+
}
52+
});
53+
this.markerCluster.repaint();
54+
}
55+
56+
shouldComponentUpdate() {
57+
// the whole logic is handled by google plugin
58+
return false;
59+
}
60+
61+
componentWillUnmount() {
62+
this.props.disconnect();
63+
}
64+
65+
render() {
66+
return <div styleName="map-view" ref={(node) => (this.node = node)} />;
67+
}
68+
}
69+
70+
DronesMapView.propTypes = {
71+
drones: PropTypes.array.isRequired,
72+
disconnect: PropTypes.func.isRequired,
73+
mapSettings: PropTypes.object.isRequired,
74+
};
75+
76+
export default CSSModules(DronesMapView, styles);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.map-view {
2+
width: 100%;
3+
height: calc(100vh - 60px - 42px - 50px); // header height - breadcrumb height - footer height
4+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { asyncConnect } from 'redux-connect';
2+
import {actions} from '../modules/DronesMap';
3+
4+
import DronesMapView from '../components/DronesMapView';
5+
6+
const resolve = [{
7+
promise: ({ store }) => store.dispatch(actions.init()),
8+
}];
9+
10+
const mapState = (state) => state.dronesMap;
11+
12+
export default asyncConnect(resolve, mapState, actions)(DronesMapView);

src/routes/DronesMap/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { injectReducer } from '../../store/reducers';
2+
3+
export default (store) => ({
4+
path: 'drones-map',
5+
name: 'DronesMap', /* Breadcrumb name */
6+
staticName: true,
7+
getComponent(nextState, cb) {
8+
require.ensure([], (require) => {
9+
const DronesMap = require('./containers/DronesMapContainer').default;
10+
const reducer = require('./modules/DronesMap').default;
11+
12+
injectReducer(store, { key: 'dronesMap', reducer });
13+
cb(null, DronesMap);
14+
}, 'DronesMap');
15+
},
16+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { handleActions } from 'redux-actions';
2+
import io from 'socket.io-client';
3+
import APIService from 'services/APIService';
4+
import config from '../../../../config/default';
5+
6+
// Drones will be updated and map will be redrawn every 3s
7+
// Otherwise if drones are updated with high frequency (e.g. 0.5s), the map will be freezing
8+
const MIN_REDRAW_DIFF = 3000;
9+
10+
// can't support more than 10k drones
11+
// map will be very slow
12+
const DRONE_LIMIT = 10000;
13+
14+
let socket;
15+
let pendingUpdates = {};
16+
let lastUpdated = null;
17+
let updateTimeoutId;
18+
19+
// ------------------------------------
20+
// Constants
21+
// ------------------------------------
22+
export const DRONES_LOADED = 'DronesMap/DRONES_LOADED';
23+
export const DRONES_UPDATED = 'DronesMap/DRONES_UPDATED';
24+
25+
// ------------------------------------
26+
// Actions
27+
// ------------------------------------
28+
29+
30+
// load drones and initialize socket
31+
export const init = () => async(dispatch) => {
32+
const { body: {items: drones} } = await APIService.searchDrones({limit: DRONE_LIMIT});
33+
lastUpdated = new Date().getTime();
34+
dispatch({ type: DRONES_LOADED, payload: {drones} });
35+
socket = io(config.API_BASE_PATH);
36+
socket.on('dronepositionupdate', (drone) => {
37+
pendingUpdates[drone.id] = drone;
38+
if (updateTimeoutId) {
39+
return;
40+
}
41+
updateTimeoutId = setTimeout(() => {
42+
dispatch({ type: DRONES_UPDATED, payload: pendingUpdates });
43+
pendingUpdates = {};
44+
updateTimeoutId = null;
45+
lastUpdated = new Date().getTime();
46+
}, Math.max(MIN_REDRAW_DIFF - (new Date().getTime() - lastUpdated)), 0);
47+
});
48+
};
49+
50+
// disconnect socket
51+
export const disconnect = () => () => {
52+
socket.disconnect();
53+
socket = null;
54+
clearTimeout(updateTimeoutId);
55+
updateTimeoutId = null;
56+
pendingUpdates = {};
57+
lastUpdated = null;
58+
};
59+
60+
export const actions = {
61+
init,
62+
disconnect,
63+
};
64+
65+
// ------------------------------------
66+
// Reducer
67+
// ------------------------------------
68+
export default handleActions({
69+
[DRONES_LOADED]: (state, { payload: {drones} }) => ({ ...state, drones }),
70+
[DRONES_UPDATED]: (state, { payload: updates }) => ({
71+
...state,
72+
drones: state.drones.map((drone) => {
73+
const updated = updates[drone.id];
74+
return updated || drone;
75+
}),
76+
}),
77+
}, {
78+
drones: null,
79+
// it will show the whole globe
80+
mapSettings: {
81+
zoom: 3,
82+
center: { lat: 0, lng: 0 },
83+
},
84+
});

src/routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import MissionPlanner from './MissionPlanner';
66
import MyRequestRoute from './MyRequest';
77
import MyRequestStatusRoute from './MyRequestStatus';
88
import StatusDetailRoute from './StatusDetail';
9+
import DronesMapRoute from './DronesMap';
910

1011
export const createRoutes = (store) => ({
1112
path: '/',
@@ -26,6 +27,7 @@ export const createRoutes = (store) => ({
2627
MyRequestRoute(store),
2728
MyRequestStatusRoute(store),
2829
StatusDetailRoute(store),
30+
DronesMapRoute(store),
2931
],
3032
});
3133

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