MapPage.tsx 8.49 KB
Newer Older
1
import * as React from 'react';
2
import { connect } from 'react-redux';
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
3
import { Link } from 'react-router-dom';
4
5
6
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { bindActionCreators } from 'redux';
7
import { loadAccessionsMapInfo } from 'accessions/actions/public';
8
import AccessionFilter from 'model/accession/AccessionFilter';
9
import Loading from 'ui/common/Loading';
10
import AccessionMapInfo from 'model/accession/AccessionMapInfo';
11
import PageLayout from 'ui/layout/PageLayout';
12
import ContentHeader from 'ui/common/heading/ContentHeader';
13
import Button from '@material-ui/core/Button';
14
import Tabs, { Tab } from 'ui/common/Tabs';
15
import PrettyFilters from 'ui/common/filter/PrettyFilters';
Oleksii Savran's avatar
Oleksii Savran committed
16
import ButtonBar from 'ui/common/buttons/ButtonBar';
17

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
18
import AccessionService from 'service/genesys/AccessionService';
19
20
21

let Map;
let TileLayer;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
22
23
24
25
let Popup;
let Marker;

const popupContentLimit = 11;
26

27
28
29
30
interface IMapPageProps {
    classes?: any;
    t?: any;

31
    apiUrl: string;
32
    mapInfo: AccessionMapInfo;
33
34
    currentTab: string;
    filterCode: string;
35
    loadAccessionsMapInfo: any;
36
37
38
39
40
41
42
43
44
45
46
}

const styles = (theme) => ({
    leafletContainer: {
        width: '100%',
        minHeight: '500px',
        height: 'calc(100vh - 186px)',
    },
});

class BrowsePage extends React.Component<IMapPageProps, any> {
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
47
48
49

    private clickTimeout;

50
    protected static needs = [
51
52
      ({ params: { filterCode } }) => {
        return loadAccessionsMapInfo(filterCode || '');
53
54
55
      },
    ];

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
56
57
58
59
60
61
    public state = {
      popupPosition: [],
      geoData: [],
      otherCount: 0,
    };

62
63
64
65
66
    constructor(props, context) {
        super(props, context);
        if (typeof window !== 'undefined') {
            Map = require('react-leaflet').Map;
            TileLayer = require('react-leaflet').TileLayer;
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
67
68
            Marker = require('react-leaflet').Marker;
            Popup = require('react-leaflet').Popup;
69
70
71
72
73
        }
    }


    public componentWillMount() {
74
75
76
77
78
79
80
      const { mapInfo, filterCode, loadAccessionsMapInfo } = this.props;
      // console.log(`Filter code for map ${filterCode} ?== ${mapInfo && mapInfo.filterCode}`, filterCode, mapInfo ? mapInfo.filterCode : 'No mapInfo');
      if (mapInfo && mapInfo.filterCode !== filterCode) {
        // console.log(`mapInfo.filterCode !== filterCode. updatingRoute`);
        loadAccessionsMapInfo(filterCode || '');
      } else if (!mapInfo) {
        loadAccessionsMapInfo(filterCode || '');
81
82
83
      }
    }

Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

    private onMapClick = (e) => {
        if (this.clickTimeout) {
            console.log('prevented');
            clearTimeout(this.clickTimeout);
            this.clickTimeout = null;
            return;
        }
        this.clickTimeout = setTimeout(() => {
            console.log('started');
            this.clickTimeout = null;
            const currentZoom = e.target._zoom;
            if (currentZoom < 4) {
                return;
            }

            const {mapInfo: {filter}} = this.props;

            const correctLng = Math.abs(e.latlng.lng) > 180 ? (360 + e.latlng.lng) % 180 : e.latlng.lng;

            console.log(e);

            const filterWithGeo = {
                ...filter,
                geo: {
                    longitude: {
                        ge: correctLng - (3 / currentZoom),
                        le: correctLng + (3 / currentZoom),
                    },
                    latitude: {
                        ge: e.latlng.lat - (3 / currentZoom),
                        le: e.latlng.lat + (3 / currentZoom),
                    },
                },
            };
            AccessionService.geoJson(filterWithGeo, popupContentLimit)
                .then((res) => this.setState({popupPosition: [ e.latlng.lat,  e.latlng.lng], geoData: res.geoJson, otherCount: res.otherCount}));
        }, 200);
    }

124
125
126
127
128
    /// Wrap loadAccessionsMapInfo dispatch and fills the current sort selection
    protected myApplyFilters = (filters: AccessionFilter) => {
      const { loadAccessionsMapInfo } = this.props;

      loadAccessionsMapInfo(filters);
129
130
131
    }

    public render() {
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
132
        const {popupPosition, geoData, otherCount} = this.state;
133
134
        const position = [30, 0];

Viacheslav Pavlov's avatar
i18n    
Viacheslav Pavlov committed
135
        const { mapInfo, currentTab, classes, filterCode, t } = this.props;
136
137
138
139

        if (! mapInfo) {
          return <Loading />;
        }
140
141

        // const color = 'f00ba0';
142
        const layerUrl = `{s}/acn/tile/{z}/{x}/{y}?f=${filterCode ? filterCode : ''}`; // `&color=${color}`;
143
144

        return (
Oleksii Savran's avatar
Oleksii Savran committed
145
            <PageLayout withFooter>
Matija Obreza's avatar
Matija Obreza committed
146
              <ContentHeader title={ t('accessions.public.p.browse.title') } subTitle={ t('accessions.public.p.browse.subTitle') } />
147
148
149
                <Tabs
                    tab={ currentTab }
                    actions={
Oleksii Savran's avatar
Oleksii Savran committed
150
                      <ButtonBar>
151
                        <span>
Matija Obreza's avatar
Matija Obreza committed
152
153
                          <form method="post" action="/proxy/api/v1/acn/downloadKml">
                            <input type="hidden" name="f" value={ filterCode } />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
154
                            <Button type="submit">{ `${t('common:action.download')} ${t('accessions.public.p.map.kml')}` }</Button>
Matija Obreza's avatar
Matija Obreza committed
155
                          </form>
156
                        </span>
Oleksii Savran's avatar
Oleksii Savran committed
157
                      </ButtonBar>
158
159
                    }
                >
Viacheslav Pavlov's avatar
i18n    
Viacheslav Pavlov committed
160
161
162
                  <Tab name="data" to={ `/a/${filterCode || ''}` }>{ t('accessions.tab.data') }</Tab>
                  <Tab name="overview" to={ `/a/overview/${filterCode || ''}` }>{ t('accessions.tab.overview') }</Tab>
                  <Tab name="map" to={ `/a/map/${filterCode || '' }` }>{ t('accessions.tab.map') }</Tab>
163
164
165
                </Tabs>
                <PrettyFilters
                    prefix="accessions"
166
                    filterObj={ mapInfo && mapInfo.filter || {} }
167
168
169
                    onSubmit={ this.myApplyFilters }
                />
                <div className={ classes.leafletContainer }>
170
                { mapInfo && typeof window !== 'undefined' &&
171
                    <Map
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
172
                        onClick={ this.onMapClick }
173
                        center={ position }
174
175
                        zoom={ 3 } minZoom={ 2 } maxZoom={ 14 }
                        bounds={ mapInfo.bounds }>
176
177
178
179
180
                        <TileLayer
                            opacity={ 0.50 }
                            attribution={ '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }
                            url={ 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png' }
                        />
Matija Obreza's avatar
Matija Obreza committed
181
182
183
184
185
186
187
                        <TileLayer
                            opacity={ 0.50 }
                            maxZoom={ 7 }
                            attribution={ '&copy; worldclim.org' }
                            url={ 'https://{s}.tile.genesys-pgr.org/worldclim1.4/bio1/{z}/{x}/{y}.png' }
                            subdomains={ [ 'a', 'b', 'c', 'd' ] }
                        />
188
189
190
191
192
                        <TileLayer
                            updateInterval={ 1000 }
                            updateWhenZooming={ false }
                            attribution="&amp;copy Accession localities from <a href=&quot;/&quot;>Genesys PGR</a>"
                            url={ layerUrl }
193
                            subdomains={ mapInfo.tileServers }
194
                        />
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
195
196
197
198
199
200
201
202
203
204
                        { geoData && geoData.length > 0 &&
                            <Marker position={ popupPosition } ref={ (marker) => marker && marker.leafletElement.openPopup() } >
                                <Popup open>
                                    <div>
                                        { geoData.map((feature) => (<div><Link to={ `/a/${feature.properties.uuid}` }>{ `${feature.properties.accessionNumber} ${feature.properties.instCode}` }</Link></div>)) }
                                        { otherCount > 0 && <div>{ t('accessions.public.p.map.andMore', {otherMore: otherCount}) }</div> }
                                    </div>
                                </Popup>
                            </Marker>
                        }
205
206
207
208
209
210
211
212
213
                    </Map>
                }
                </div>
            </PageLayout>
        );
    }
}

const mapStateToProps = (state, ownProps) => ({
214
    mapInfo: state.accessions.public.mapInfo || undefined,
215
216
217
218
219
    filterCode: ownProps.match.params.filterCode || '',
    currentTab: ownProps.match.params.tab || 'map', // current tab, or ownProps.location.pathname
});

const mapDispatchToProps = (dispatch) => bindActionCreators({
220
    loadAccessionsMapInfo,
221
222
}, dispatch);

223
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(translate()(BrowsePage)));