Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding map #15

Merged
merged 36 commits into from
Feb 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d28cc5c
Added the needed libraries
ajakubo1 Dec 23, 2016
d27bb06
Created components which will be displayed in the main page (map with…
ajakubo1 Dec 23, 2016
72fc2ba
Adjusting packages and webpack config for new css package (plus icons)
ajakubo1 Dec 29, 2016
5ae58d3
Adding all of the map components for testsing react-leaflet
ajakubo1 Dec 29, 2016
8854cd9
Adding a possibility of using different icon markers
ajakubo1 Dec 29, 2016
cba6e99
Adding map markers for different levels of air pollution
ajakubo1 Dec 29, 2016
a900f40
Dealing with test error (adding jsdom for window/document references,…
ajakubo1 Dec 29, 2016
61c8d31
Moving .nycrc to package (for some reason a couple of settings doesnt…
ajakubo1 Dec 29, 2016
afa95bb
Changing data component to DataHolder
ajakubo1 Dec 29, 2016
4ca5b9d
Adding tests for DataHolder and SmogMap
ajakubo1 Dec 29, 2016
03dbb12
removed too much last time
ajakubo1 Dec 29, 2016
d76b38c
More cleanup for coverage reports
ajakubo1 Dec 30, 2016
2e7f994
Integrating redux to current application + adding initial states and …
ajakubo1 Jan 1, 2017
2a3d5f2
Changing SmogMap to PollutionMap
ajakubo1 Jan 10, 2017
b1c57b5
Making sure tests are passing now
ajakubo1 Jan 10, 2017
79c28d7
Moved the data as an initial redux state, moved center of the map
ajakubo1 Jan 10, 2017
04aae90
Changing the structure, so that each of components can be more testable
ajakubo1 Jan 22, 2017
bdd38d6
Splitting the tests for testing Component itself via mount, as well a…
ajakubo1 Jan 22, 2017
fd53024
Rewritting everything so that a fetch polyfill is used instead of jus…
ajakubo1 Jan 22, 2017
20f13d2
Correcting a couple of errors + adding geolocation extraction
ajakubo1 Jan 25, 2017
f126531
adding tests for actions
ajakubo1 Jan 27, 2017
2cdbcbe
restore fetch mock
ajakubo1 Jan 27, 2017
911a2ff
Adding tests for geolocation
ajakubo1 Jan 27, 2017
2dcfa2b
Adding location listeners
ajakubo1 Jan 28, 2017
7b3c507
Unfortunatelly, I have to decrese coverage % - nyc is not handling pr…
ajakubo1 Jan 28, 2017
4e71a11
Some adjustments to data structure for map
ajakubo1 Feb 4, 2017
b375672
Move function is triggered too many times
ajakubo1 Feb 4, 2017
622a7c3
One more refactoring done
ajakubo1 Feb 4, 2017
0bff80c
Adding some tests to check if handleMoveEnd works properly
ajakubo1 Feb 4, 2017
f520865
Adding more tests for coverage
ajakubo1 Feb 4, 2017
3c4d344
Changing the url name
ajakubo1 Feb 4, 2017
f2cdadf
swapped value to last_metering
ajakubo1 Feb 6, 2017
35c028a
More cleanup
ajakubo1 Feb 6, 2017
2a41583
Last line spaces :)
ajakubo1 Feb 6, 2017
9b0b5f4
Adjusted actions to send filter request for location bound box (min l…
ajakubo1 Feb 6, 2017
caf4815
Forgotten to swap placement in tests
ajakubo1 Feb 6, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions .nycrc

This file was deleted.

73 changes: 73 additions & 0 deletions app/actions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { getAPI } from '../request';
import { API_GET_STATION_DATA } from '../urls'

export const FETCH_DATA_FOR_LOCATION = 'FETCH_DATA_FOR_LOCATION';
export const RECEIVE_DATA_FOR_LOCATION = 'RECEIVE_DATA_FOR_LOCATION';
export const INVALIDATE_LOCATION_DATA = 'INVALIDATE_LOCATION_DATA';

export const fetchDataForLocation = (southWest, northEast) => ({
type: FETCH_DATA_FOR_LOCATION,
northEast,
southWest
});

export const receiveDataForLocation = (data) => ({
type: RECEIVE_DATA_FOR_LOCATION,
data
});

export const invalidateLocationData = () => ({
type: INVALIDATE_LOCATION_DATA
});


export const invalidateAndFetchData = (southWest, northEast) => {
return (dispatch) => {
dispatch(invalidateLocationData());
dispatch(fetchDataForLocation(southWest, northEast));
return getAPI(API_GET_STATION_DATA, {
in_bbox: southWest.lat + "," + southWest.lng + "," + northEast.lat + "," + northEast.lng
}).then((response) => {
dispatch(receiveDataForLocation(
[
// TODO it should have data from repsonse
{
position: [52.229, 21.032],
description: "This marker should be blue",
last_metering: {
pm10: null,
pm25: null
}
},
{
position: [52.217, 20.987],
description: "This marker should be green",
last_metering: {
pm10: 25,
pm25: 25
}
},
{
position: [52.236, 20.976],
description: "This marker should be orange",
last_metering: {
pm10: 75,
pm25: 75
}
},
{
position: [52.259, 21.045],
description: "This marker should be red",
last_metering: {
pm10: 200,
pm25: 200
}
},
]
))
}


);
}
};
9 changes: 9 additions & 0 deletions app/components/DataHolder.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export default class Data extends React.Component {
render () {
return (<div className="data">
I'm data
</div>)
}
}
127 changes: 127 additions & 0 deletions app/components/PollutionMap.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { Map, Marker, Popup, TileLayer } from 'react-leaflet';
import {noDataIcon, okIcon, warningIcon, errorIcon} from '../helpers/mapMarkers';
import { invalidateAndFetchData } from '../actions';



export class PollutionMap extends React.Component {

constructor (props) {
super(props);

this.state = {
mapCenter: [52.229, 21.011],
zoom: 13,
lastUpdated: Date.now()
};

this.handleMoveEnd = this.handleMoveEnd.bind(this);
}

componentDidMount () {
this.getGeolocation();
}

getGeolocation () {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
this.updatePosition(position.coords.latitude, position.coords.longitude);
});
}
// TODO - some geolocation warning? Or a fallback of some kind?
}

updatePosition (lat, lng) {
const bounds = this.refs.map.leafletElement.getBounds();
const zoom = this.refs.map.leafletElement.getZoom();
this.setState({
mapCenter: [lat, lng],
zoom,
lastUpdated: Date.now()
});
this.props.invalidateAndFetchData(bounds.getSouthWest(), bounds.getNorthEast());
}

handleMoveEnd () {
if (Date.now() - this.state.lastUpdated > 20) {
const center = this.refs.map.leafletElement.getCenter();
this.updatePosition(center.lat, center.lng);
}
}

getMarkerIcon(pollutionLevel) {
if (pollutionLevel === null) {
return noDataIcon;
} else if (pollutionLevel < 50) {
return okIcon;
} else if (pollutionLevel < 150) {
return warningIcon;
} else {
return errorIcon;
}
}

extractMarkers(stationData) {
return stationData.map((markerData, iterator) => {
const icon = this.getMarkerIcon(markerData.last_metering.pm10);
return (
<Marker key={iterator} position={markerData.position} icon={icon}>
<Popup>
<span>{markerData.description}</span>
</Popup>
</Marker>
);
});
}

render () {
const { mapCenter, zoom } = this.state;
const data = this.props.data.toJS();

const markers = this.extractMarkers(data);

return (
<Map center={mapCenter}
ref='map'
onMoveend={this.handleMoveEnd}
zoom={zoom}>
{this.props.mapSpec}
{markers}
</Map>)
}
}
PollutionMap.propTypes = {
isFetching: React.PropTypes.bool.isRequired,
data: React.PropTypes.object.isRequired,
invalidateAndFetchData: React.PropTypes.func.isRequired,
mapSpec: React.PropTypes.node,
};

const mapStateToProps = (state) => ({
isFetching: state.get('mapData').get('isFetching'),
data: state.get('mapData').get('data')
});

const mapDispatchToProps = (dispatch) => ({
invalidateAndFetchData: bindActionCreators(invalidateAndFetchData, dispatch)
});

export const ConnectedPollutionMap = connect(mapStateToProps, mapDispatchToProps)(PollutionMap);

const PollutionMapWrapper = () => {

const mapSpec = (
<TileLayer
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
);

return (<ConnectedPollutionMap mapSpec={mapSpec}/>)
};

export default PollutionMapWrapper
36 changes: 36 additions & 0 deletions app/helpers/mapMarkers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import noDataImageUrl from '../../images/marker-no-data.png';
import okImageUrl from '../../images/marker-ok.png';
import warningImageUrl from '../../images/marker-warning.png';
import errorImageUrl from '../../images/marker-error.png';
import Leaflet from 'leaflet';
import assign from 'lodash.assign';

const defaultProperties = {
iconSize: [25, 41],
iconAnchor: [13, 40],
popupAnchor: [0, -30]
};

export const noDataIcon = Leaflet.icon(assign(
{
iconUrl: noDataImageUrl,
}, defaultProperties
));

export const okIcon = Leaflet.icon(assign(
{
iconUrl: okImageUrl,
}, defaultProperties
));

export const warningIcon = Leaflet.icon(assign(
{
iconUrl: warningImageUrl,
}, defaultProperties
));

export const errorIcon = Leaflet.icon(assign(
{
iconUrl: errorImageUrl,
}, defaultProperties
));
7 changes: 6 additions & 1 deletion app/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import Root from './root';
import store from './store';


ReactDOM.render(
<Root/>
<Provider store={store}>
<Root />
</Provider>
, document.getElementById('root')
);
28 changes: 28 additions & 0 deletions app/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Immutable from 'immutable';

import {
INVALIDATE_LOCATION_DATA,
FETCH_DATA_FOR_LOCATION,
RECEIVE_DATA_FOR_LOCATION
} from '../actions';

const initialState = {
isFetching: false,
data: Immutable.fromJS([])
};

const mapData = (state = Immutable.fromJS(initialState), action) => {
switch (action.type) {
case INVALIDATE_LOCATION_DATA:
return state.set('data', Immutable.fromJS([]));
case FETCH_DATA_FOR_LOCATION:
return state.set('isFetching', true);
case RECEIVE_DATA_FOR_LOCATION:
return state.set('isFetching', false)
.set('data', Immutable.fromJS(action.data));
default:
return state;
}
};

export default mapData
15 changes: 15 additions & 0 deletions app/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import fetch from 'isomorphic-fetch';
import queryString from 'query-string';

export const FAKE_DOMAIN = 'http://fakedomain.com/';

export const getAPI = (location, data) => {
return fetch(createUrl(location) + "?" + queryString.stringify(data))
};

const createUrl = (location) => {
if (process.env.NODE_ENV === 'test') {
location = FAKE_DOMAIN + location
}
return location
};
7 changes: 6 additions & 1 deletion app/root.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React from 'react';
import PollutionMap from './components/PollutionMap';
import DataHolder from './components/DataHolder';

class Root extends React.Component {
render () {
return (
<h1>I've been rendered with React</h1>
<div className="root">
<PollutionMap/>
<DataHolder/>
</div>
);
}
}
Expand Down
12 changes: 12 additions & 0 deletions app/store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { combineReducers } from 'redux-immutable';
import thunkMiddleware from 'redux-thunk';
import Immutable from 'immutable';
import mapData from './reducers';

import { createStore, applyMiddleware } from 'redux';

const initialState = Immutable.Map();
const rootReducer = combineReducers({ mapData });
const store = createStore(rootReducer, initialState, applyMiddleware(thunkMiddleware));

export default store;
1 change: 1 addition & 0 deletions app/urls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const API_GET_STATION_DATA = '/station/';
Binary file added images/marker-error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/marker-no-data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/marker-ok.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/marker-warning.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading