Skip to content
Snippets Groups Projects
Commit c0c5b529 authored by Esko Ikkala's avatar Esko Ikkala
Browse files

Clustered map mode: support for bouncing markers and popups

parent e13b0126
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@ export const UPDATE_QUERY = 'UPDATE_QUERY';
export const TOGGLE_DATASET = 'TOGGLE_DATASET';
export const BOUNCE_MARKER = 'BOUNCE_MARKER';
export const OPEN_MARKER_POPUP = 'OPEN_MARKER_POPUP';
export const REMOVE_TEMP_MARKER = 'REMOVE_TEMP_MARKER';
export const START_SPINNER = 'START_SPINNER';
export const FETCH_SUGGESTIONS = 'FETCH_SUGGESTIONS';
export const FETCH_SUGGESTIONS_FAILED = 'FETCH_SUGGESTIONS_FAILED';
......@@ -41,6 +42,10 @@ export const openMarkerPopup = (uri) => ({
uri
});
export const removeTempMarker = () => ({
type: REMOVE_TEMP_MARKER,
});
export const startSpinner = () => ({
type: START_SPINNER,
});
......
......@@ -130,7 +130,8 @@ class VirtualizedTable extends React.PureComponent {
if (typeof rowData.lat !== 'undefined' || typeof rowData.long !== 'undefined') {
marker = (
<IconButton
onMouseOver={handleMarkerHover(rowData.s)}
onMouseOver={handleMarkerMouseOver(rowData.s)}
onMouseOut={handleMarkerMouseOut(rowData.s)}
onClick={handleMarkerClick(rowData.s)}
aria-label="Marker"
>
......@@ -149,10 +150,14 @@ class VirtualizedTable extends React.PureComponent {
this.props.openMarkerPopup(value);
};
const handleMarkerHover = value => () => {
const handleMarkerMouseOver = value => () => {
this.props.bounceMarker(value);
};
const handleMarkerMouseOut = () => () => {
this.props.removeTempMarker();
};
// always render extra columns for now
const analysisView = true;
// Some extra columns for analysis view
......@@ -330,7 +335,8 @@ VirtualizedTable.propTypes = {
fetchResults: PropTypes.func.isRequired,
clearResults: PropTypes.func.isRequired,
bounceMarker: PropTypes.func.isRequired,
openMarkerPopup: PropTypes.func.isRequired
openMarkerPopup: PropTypes.func.isRequired,
removeTempMarker: PropTypes.func.isRequired,
};
export default withStyles(styles)(VirtualizedTable);
......@@ -45,8 +45,6 @@ class LeafletMap extends React.Component {
'kotus:rajat-lansi-ita'
]);
this.bouncingMarker = this.props.bouncingMarker;
// Base layers
const OSMBaseLayer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
......@@ -92,9 +90,12 @@ class LeafletMap extends React.Component {
attribution: 'SeCo'
});
// Result marker layer
// Marker layers
this.resultMarkerLayer = L.layerGroup();
this.bouncingMarkerObj = null;
this.popupMarkerObj = null;
if (this.props.mapMode === 'cluster') {
this.updateMarkersAndCluster(this.props.results);
} else {
......@@ -102,12 +103,12 @@ class LeafletMap extends React.Component {
}
// create map
this.map = L.map('map', {
this.leafletMap = L.map('map', {
center: [65.184809, 27.314050],
zoom: 4,
layers: [
OSMBaseLayer,
this.resultMarkerLayer
this.resultMarkerLayer,
],
fullscreenControl: true,
});
......@@ -130,13 +131,13 @@ class LeafletMap extends React.Component {
this.layerControl = L.control.layers(
baseMaps,
overlayMaps,
).addTo(this.map);
).addTo(this.leafletMap);
L.control.opacity(
overlayMaps, {
collapsed: true,
position: 'bottomleft'
}).addTo(this.map);
}).addTo(this.leafletMap);
L.Marker.setBouncingOptions({ exclusive: true });
......@@ -151,21 +152,41 @@ class LeafletMap extends React.Component {
}
componentDidUpdate({ results, mapMode, geoJSONKey, bouncingMarkerKey, openPopupMarkerKey }) {
// check if results data or mapMode have changed
if (this.props.results !== results || this.props.mapMode !== mapMode) {
if (this.props.bouncingMarker === '' && this.bouncingMarkerObj !== null) {
this.leafletMap.removeLayer(this.bouncingMarkerObj);
}
if (this.props.bouncingMarkerKey !== bouncingMarkerKey) {
if (this.props.mapMode === 'cluster') {
this.updateMarkersAndCluster(this.props.results);
const m = this.markers[this.props.bouncingMarker];
const latlng = m.getLatLng();
// create a new marker so that the temporary popup can be left open
this.bouncingMarkerObj = L.marker(latlng);
this.bouncingMarkerObj.addTo(this.leafletMap).bounce(1);
} else {
this.updateMarkers(this.props.results);
this.markers[this.props.bouncingMarker].bounce(1);
}
}
if (this.props.bouncingMarkerKey !== bouncingMarkerKey) {
this.markers[this.props.bouncingMarker].bounce(3);
if (this.props.openPopupMarkerKey !== openPopupMarkerKey) {
if (this.props.mapMode === 'cluster') {
if (this.popupMarkerObj !== null) {
this.leafletMap.removeLayer(this.popupMarkerObj);
}
this.popupMarkerObj = this.markers[this.props.popupMarker];
this.popupMarkerObj.addTo(this.leafletMap).openPopup();
} else {
this.markers[this.props.popupMarker].openPopup();
}
}
if (this.props.openPopupMarkerKey !== openPopupMarkerKey) {
this.markers[this.props.openPopupMarker].openPopup();
// check if results data or mapMode have changed
if (this.props.results !== results || this.props.mapMode !== mapMode) {
if (this.props.mapMode === 'cluster') {
this.updateMarkersAndCluster(this.props.results);
} else {
this.updateMarkers(this.props.results);
}
}
// check if geoJSON has updated
......@@ -192,9 +213,11 @@ class LeafletMap extends React.Component {
updateMarkersAndCluster(results) {
this.resultMarkerLayer.clearLayers();
this.markers = {};
const clusterer = L.markerClusterGroup();
results.forEach(result => {
const marker = this.createMarker(result);
this.markers[result.s] = marker;
marker == null ? null : clusterer.addLayer(marker);
});
clusterer.addTo(this.resultMarkerLayer);
......@@ -263,7 +286,7 @@ class LeafletMap extends React.Component {
return new L.Control.OpacitySlider(opts);
};
L.control.opacitySlider({ position: 'bottomleft' }).addTo(this.map);
L.control.opacitySlider({ position: 'bottomleft' }).addTo(this.leafletMap);
}
render() {
......@@ -278,9 +301,9 @@ LeafletMap.propTypes = {
geoJSONKey: PropTypes.number.isRequired,
getGeoJSON: PropTypes.func.isRequired,
bouncingMarker: PropTypes.string.isRequired,
popupMarker: PropTypes.string.isRequired,
bouncingMarkerKey: PropTypes.number.isRequired,
openPopupMarker: PropTypes.string.isRequired,
openPopupMarkerKey: PropTypes.number.isRequired
openPopupMarkerKey: PropTypes.number.isRequired,
};
export default LeafletMap;
......@@ -31,6 +31,7 @@ import {
sortResults,
bounceMarker,
openMarkerPopup,
removeTempMarker
} from '../actions';
const styles = theme => ({
......@@ -151,6 +152,7 @@ let MapApp = (props) => {
clearSuggestions={props.clearSuggestions}
bounceMarker={props.bounceMarker}
openMarkerPopup={props.openMarkerPopup}
removeTempMarker={props.removeTempMarker}
/>
</div>
);
......@@ -177,10 +179,9 @@ let MapApp = (props) => {
geoJSONKey={map.geoJSONKey}
getGeoJSON={props.getGeoJSON}
bouncingMarker={map.bouncingMarker}
popupMarker={map.popupMarker}
bouncingMarkerKey={map.bouncingMarkerKey}
openPopupMarker={map.openPopupMarker}
openPopupMarkerKey={map.openPopupMarkerKey}
// sliderValue={100}
/>
);
}
......@@ -282,6 +283,7 @@ const mapDispatchToProps = ({
updateResultsFilter,
bounceMarker,
openMarkerPopup,
removeTempMarker
});
MapApp.propTypes = {
......@@ -306,6 +308,7 @@ MapApp.propTypes = {
getGeoJSON: PropTypes.func.isRequired,
bounceMarker: PropTypes.func.isRequired,
openMarkerPopup: PropTypes.func.isRequired,
removeTempMarker: PropTypes.func.isRequired,
updateResultFormat: PropTypes.func.isRequired,
updateMapMode: PropTypes.func.isRequired,
updateResultsFilter: PropTypes.func.isRequired,
......
......@@ -2,6 +2,7 @@ import {
UPDATE_GEOJSON,
BOUNCE_MARKER,
OPEN_MARKER_POPUP,
REMOVE_TEMP_MARKER
} from '../actions';
export const INITIAL_STATE = {
......@@ -11,8 +12,8 @@ export const INITIAL_STATE = {
}],
geoJSONKey: 0,
bouncingMarker: '',
popupMarker: '',
bouncingMarkerKey: 0,
openPopupMarker: '',
openPopupMarkerKey: 0,
};
......@@ -33,9 +34,14 @@ const map = (state = INITIAL_STATE, action) => {
case OPEN_MARKER_POPUP:
return {
...state,
openPopupMarker: action.uri,
popupMarker: action.uri,
openPopupMarkerKey: state.openPopupMarkerKey + 1
};
case REMOVE_TEMP_MARKER:
return {
...state,
bouncingMarker: '',
};
default:
return state;
}
......
......@@ -6,7 +6,7 @@ import {
const DEFAULT_LANGUAGE = 'en';
const DEFAULT_RESULT_FORMAT = 'table';
const DEFAULT_MAP_MODE = 'marker';
const DEFAULT_MAP_MODE = 'cluster';
export const INITIAL_STATE = {
language: DEFAULT_LANGUAGE,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment