Commit b3b20c1a authored by Viacheslav Pavlov's avatar Viacheslav Pavlov

Accessions map: list selected accessions

parent 113a7e1e
......@@ -438,6 +438,7 @@
"andMore": "And {{otherMore}} more",
"kml": "KML",
"filterAccessions": "Filter accessions",
"selectArea": "Select area",
"pick": "Show climate",
"stopPick": "Cancel",
"noClimateData": "No climate data available for selected location",
......
......@@ -6941,6 +6941,11 @@
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"is-mobile": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-2.0.0.tgz",
"integrity": "sha512-k2+p7BBCzhqHMdYJwGUNNo+6zegGiMIVbM6bEPzxWXpQV6BUzV892UW0oDFgqxT6DygO7LdxRbwC0xmOhJdbew=="
},
"is-negated-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
......
......@@ -75,6 +75,7 @@
"andMore": "And {{otherMore}} more",
"kml": "KML",
"filterAccessions": "Filter accessions",
"selectArea": "Select area",
"pick": "Show climate",
"stopPick": "Cancel",
"noClimateData": "No climate data available for selected location",
......
......@@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { isMobile } from 'is-mobile';
import { bindActionCreators } from 'redux';
import {showSnackbar} from 'actions/snackbar';
import navigateTo from 'actions/navigation';
......@@ -35,6 +36,7 @@ import Paper from '@material-ui/core/Paper';
import Tooltip from '@material-ui/core/Tooltip';
import LayersIcon from '@material-ui/icons/Layers';
import ClimateIcon from '@material-ui/icons/WbSunny';
import PositionIcon from '@material-ui/icons/SettingsOverscan';
import FilterIcon from '@material-ui/icons/PermDataSetting';
import CancelIcon from '@material-ui/icons/Cancel';
import ApiCall from 'model/ApiCall';
......@@ -137,6 +139,10 @@ const styles = (theme) => ({
position: 'absolute' as 'absolute',
top: '42px',
},
pickRectangleButton: {
position: 'absolute' as 'absolute',
top: '80px',
},
controlsButton: {
width: '50px',
height: '50px',
......@@ -180,11 +186,15 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
otherCount: 0,
sidebarOpened: false,
trackClickPos: false,
trackAreaSelect: false,
dialogOpened: false,
climateData: null,
layersControlsIsOpen: false,
savedControlsIsOpen: false,
colorInputIsFocused: false,
mouseDown: false,
areaStartPos: [0, 0],
areaEndPos: [0, 0],
};
constructor(props, context) {
......@@ -247,7 +257,19 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
private setPositionPick = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({trackClickPos: !this.state.trackClickPos});
this.setState({trackClickPos: !this.state.trackClickPos, trackAreaSelect: false});
return false;
}
private setAreaTrackPick = (e) => {
e.preventDefault();
e.stopPropagation();
if (typeof window !== 'undefined' && isMobile()) {
return this.handleMobileAreaSelect();
}
this.setState({trackAreaSelect: !this.state.trackAreaSelect, trackClickPos: false, areaStartPos: [0, 0], areaEndPos: [0, 0]});
return false;
}
......@@ -272,6 +294,33 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
});
}
private handleMobileAreaSelect = () => {
const mapBounds = this.mapRef.leafletElement.getBounds();
const {mapInfo: { data: { filter } }} = this.props;
const filterWithGeo = {
...filter,
geo: {
longitude: {
ge: mapBounds._southWest.lng,
le: mapBounds._northEast.lng,
},
latitude: {
ge: mapBounds._southWest.lat,
le: mapBounds._northEast.lat,
},
climate: filter && filter.geo && filter.geo.climate,
},
};
this.myApplyFilters(filterWithGeo);
}
private handleTrackAreaStart = (e) => {
const { lat, lng } = e.latlng;
this.setState({areaStartPos: [lat, lng], areaEndPos: [lat, lng]});
}
private hideDialog = () => {
this.setState({dialogOpened: false});
}
......@@ -287,16 +336,75 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
}
}
private handleMove = (e) => {
if (this.state.trackAreaSelect && this.mapRef) {
this.setState({areaEndPos: [e.latlng.lat, e.latlng.lng]});
}
}
private handleMouseUp = (e) => {
const {areaStartPos, areaEndPos, trackAreaSelect} = this.state;
if (trackAreaSelect && (areaStartPos[0] || areaStartPos[0] || areaEndPos[1] || areaEndPos[1])) {
const currentZoom = e.target._zoom;
if (areaStartPos[0] - areaEndPos[0] < 3 / Math.pow(2, currentZoom - 2) && areaStartPos[1] - areaEndPos[1] < 3 / Math.pow(2, currentZoom - 2)) {
this.setState({trackAreaSelect: false, mouseDown: false});
return;
}
const {mapInfo: { data: { filter } }} = this.props;
const filterWithGeo = {
...filter,
geo: {
latitude: {
ge: Math.min(areaStartPos[0], areaEndPos[0]),
le: Math.max(areaStartPos[0], areaEndPos[0]),
},
longitude: {
ge: Math.min(areaStartPos[1], areaEndPos[1]),
le: Math.max(areaStartPos[1], areaEndPos[1]),
},
climate: filter && filter.geo && filter.geo.climate,
},
};
this.myApplyFilters(filterWithGeo);
}
this.setState({trackAreaSelect: false, mouseDown: false});
}
private inFilterBounds = (searchBounds) => {
const {mapInfo: { data: { filter } }} = this.props;
// no filter = always in bounds
if (!filter || !filter.geo) {
return true;
}
// longitude check
if (searchBounds[0][1] < filter.geo.longitude.ge || searchBounds[1][1] > filter.geo.longitude.le) {
return false;
}
// latitude check
return !(searchBounds[0][0] < filter.geo.latitude.ge || searchBounds[1][0] > filter.geo.latitude.le);
}
private onMapClick = (e) => {
if (e.originalEvent.target.className.indexOf('leaflet-touch') === -1) {
return;
}
this.setState({mouseDown: true});
if (this.state.trackClickPos) {
return this.handleTrackPosition(e);
}
if (this.state.trackAreaSelect) {
return this.handleTrackAreaStart(e);
}
if (this.clickTimeout) {
console.log('prevented');
clearTimeout(this.clickTimeout);
......@@ -315,14 +423,17 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
const correctLng = Math.abs(e.latlng.lng) > 180 ? (360 + e.latlng.lng) % 180 : e.latlng.lng;
console.log(e);
const width = Math.pow(2, currentZoom - 2);
// console.log(`zoom=${currentZoom} width=${width} diff=${ 3 / width}`);
const searchBounds: number[][] = [
[ e.latlng.lat - (3 / width), correctLng - (3 / width) ],
[ e.latlng.lat + (3 / width), correctLng + (3 / width) ],
];
if (!this.inFilterBounds(searchBounds)) {
return;
}
const filterWithGeo = {
...filter,
geo: {
......@@ -350,7 +461,7 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
}
public render() {
const { searchBox, geoData, otherCount, sidebarOpened, trackClickPos, dialogOpened, climateData, layersControlsIsOpen, savedControlsIsOpen } = this.state;
const { searchBox, geoData, otherCount, sidebarOpened, trackClickPos, dialogOpened, climateData, layersControlsIsOpen, savedControlsIsOpen, trackAreaSelect, mouseDown, areaStartPos, areaEndPos } = this.state;
const { mapInfo, mapLayers, currentTab, classes, filterCode, loading, suggestions, t, loadAccessionsMapInfo, initialPosition, initialZoom, myMaps, addToMyMaps, toggleMyMap } = this.props;
const position = initialPosition[0] && initialPosition[1] ? initialPosition : [5, 5];
......@@ -433,13 +544,16 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
displayName="accessions.common.modelName"
amount={ mapInfo.data.accessionCount }
/>
<div className={ `${classes.leafletContainer} ${trackClickPos && classes.crosshair}` }>
<div className={ `${classes.leafletContainer} ${(trackClickPos || trackAreaSelect) && classes.crosshair}` }>
{ loading && <Loading /> }
{ mapInfo && typeof window !== 'undefined' &&
<MapComponent
onMoveend={ this.handleMoveEnd }
onClick={ this.onMapClick }
onMouseMove={ this.handleMove }
onMouseUp={ this.handleMouseUp }
onMouseDown={ this.onMapClick }
center={ position }
dragging={ !trackAreaSelect }
zoom={ initialZoom || 3 } minZoom={ 2 } maxZoom={ 14 }
zoomDelta={ 0.5 }
bounds={ !initialZoom && initialPosition[0] && !initialPosition[1] ? mapInfo.data.bounds : initialBounds }
......@@ -472,6 +586,21 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
</Button>
</Tooltip>
</Control>
<Control position="topleft">
<Tooltip
classes={ { tooltip: classes.tooltip } }
className={ `${classes.pickRectangleButton} ${classes.mapButton}` }
title={ t(`accessions.public.p.map.${ trackClickPos ? 'stopPick' : 'selectArea'}`) }
placement="right"
>
<Button variant="outlined" onClick={ this.setAreaTrackPick }>
{ trackAreaSelect ?
<CancelIcon className={ classes.mapIcon }/> :
<PositionIcon className={ classes.mapIcon }/>
}
</Button>
</Tooltip>
</Control>
<Control position="topright">
<div
onMouseEnter={ this.openLayersControls }
......@@ -539,6 +668,11 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
</Popover>
</div>
</Control>
{ trackAreaSelect && mouseDown &&
<Rectangle bounds={ [areaStartPos, areaEndPos] }>
<div style={ {backgroundColor: 'grey', opacity: 0.4} }/>
</Rectangle>
}
<TileLayer
zIndex={ 0 }
opacity={ 0.50 }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment