From 74c49498df6c2bcefe841b06d37fd011a9170276 Mon Sep 17 00:00:00 2001
From: Esko Ikkala <esko.ikkala@aalto.fi>
Date: Mon, 6 Aug 2018 11:22:41 +0300
Subject: [PATCH] Add react-google-maps for creating a result heatmap

---
 package-lock.json                 | 96 +++++++++++++++++++++++++++----
 package.json                      |  1 +
 src/client/components/map/GMap.js | 21 +++++++
 src/client/containers/MapApp.js   | 40 +++++++++----
 src/client/reducers/options.js    |  2 +
 5 files changed, 138 insertions(+), 22 deletions(-)
 create mode 100644 src/client/components/map/GMap.js

diff --git a/package-lock.json b/package-lock.json
index 76138ce0..c2f2eaf0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2676,6 +2676,11 @@
         }
       }
     },
+    "can-use-dom": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz",
+      "integrity": "sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo="
+    },
     "caniuse-api": {
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz",
@@ -5753,7 +5758,8 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -5774,12 +5780,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -5794,17 +5802,20 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5921,7 +5932,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5933,6 +5945,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5947,6 +5960,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -5954,12 +5968,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -5978,6 +5994,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6071,6 +6088,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -6156,7 +6174,8 @@
         "safe-buffer": {
           "version": "5.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -6192,6 +6211,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -6211,6 +6231,7 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -6254,12 +6275,14 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -6550,6 +6573,11 @@
       "resolved": "https://registry.npmjs.org/google-maps/-/google-maps-3.3.0.tgz",
       "integrity": "sha512-pj4En0cWKG+lcBvC7qrzu5ItiMsYNTgjG2capsPzAbAM/O8ftugGpUUftTTwdGL8KlNvB4CEZ6IBWwpWYzUEpw=="
     },
+    "google-maps-infobox": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/google-maps-infobox/-/google-maps-infobox-2.0.0.tgz",
+      "integrity": "sha512-hTuWmWZZSOxf5D/z7l3/hTF1grgRvLG53BEKMdjiKOG+FcK/kH7vqseUeyIU9Zj2ZIqKTOaro0nknxpAuRq4Vw=="
+    },
     "got": {
       "version": "8.3.1",
       "resolved": "https://registry.npmjs.org/got/-/got-8.3.1.tgz",
@@ -9707,6 +9735,16 @@
         "object-visit": "^1.0.0"
       }
     },
+    "marker-clusterer-plus": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/marker-clusterer-plus/-/marker-clusterer-plus-2.1.4.tgz",
+      "integrity": "sha1-+O/3TVmdqzt9Dj/tUmTqDnBPXWc="
+    },
+    "markerwithlabel": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/markerwithlabel/-/markerwithlabel-2.0.1.tgz",
+      "integrity": "sha512-UnfHImP2rVpUHDa18/08DvOaMB1NjSkeE/wkiI7DgvflzcK3rKwb2DHU4u9tSEHfjf2piR/E7I9zGvUZRHivqg=="
+    },
     "math-expression-evaluator": {
       "version": "1.2.17",
       "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
@@ -11934,6 +11972,37 @@
       "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-1.0.0.tgz",
       "integrity": "sha512-dcQpdWr62flXQJuM8/bVEY5/10ad2SYBUafp8H4q4WHR3fTA/MMlp8mpzX12I0CCoEJc1P6QdiMg7U+7lFS6Rw=="
     },
+    "react-google-maps": {
+      "version": "9.4.5",
+      "resolved": "https://registry.npmjs.org/react-google-maps/-/react-google-maps-9.4.5.tgz",
+      "integrity": "sha512-8z5nX9DxIcBCXuEiurmRT1VXVwnzx0C6+3Es6lxB2/OyY2SLax2/LcDu6Aldxnl3HegefTL7NJzGeaKAJ61pOA==",
+      "requires": {
+        "babel-runtime": "^6.11.6",
+        "can-use-dom": "^0.1.0",
+        "google-maps-infobox": "^2.0.0",
+        "invariant": "^2.2.1",
+        "lodash": "^4.16.2",
+        "marker-clusterer-plus": "^2.1.4",
+        "markerwithlabel": "^2.0.1",
+        "prop-types": "^15.5.8",
+        "recompose": "^0.26.0",
+        "scriptjs": "^2.5.8",
+        "warning": "^3.0.0"
+      },
+      "dependencies": {
+        "recompose": {
+          "version": "0.26.0",
+          "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.26.0.tgz",
+          "integrity": "sha512-KwOu6ztO0mN5vy3+zDcc45lgnaUoaQse/a5yLVqtzTK13czSWnFGmXbQVmnoMgDkI5POd1EwIKSbjU1V7xdZog==",
+          "requires": {
+            "change-emitter": "^0.1.2",
+            "fbjs": "^0.8.1",
+            "hoist-non-react-statics": "^2.3.1",
+            "symbol-observable": "^1.0.4"
+          }
+        }
+      }
+    },
     "react-is": {
       "version": "16.4.2",
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.4.2.tgz",
@@ -12793,7 +12862,6 @@
               "version": "1.1.11",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -12962,7 +13030,6 @@
               "version": "3.0.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "brace-expansion": "^1.1.7"
               }
@@ -13308,6 +13375,11 @@
       "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=",
       "dev": true
     },
+    "scriptjs": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/scriptjs/-/scriptjs-2.5.8.tgz",
+      "integrity": "sha1-0MQ5VcLmutM7bk7fe1O4llqnyl8="
+    },
     "section-iterator": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
diff --git a/package.json b/package.json
index 0d979cc8..d4c14f5d 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
     "react-data-grid": "^4.0.8",
     "react-data-grid-addons": "^4.0.8",
     "react-dom": "^16.4.2",
+    "react-google-maps": "^9.4.5",
     "react-leaflet": "^1.9.1",
     "react-leaflet-control": "^1.4.1",
     "react-leaflet-fullscreen": "0.0.6",
diff --git a/src/client/components/map/GMap.js b/src/client/components/map/GMap.js
new file mode 100644
index 00000000..69490079
--- /dev/null
+++ b/src/client/components/map/GMap.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { withScriptjs, withGoogleMap, GoogleMap } from 'react-google-maps';
+import HeatmapLayer from 'react-google-maps/lib/components/visualization/HeatmapLayer';
+
+let GMap = withScriptjs(withGoogleMap((props) =>
+  <GoogleMap
+    defaultZoom={4}
+    defaultCenter={{ lat: 65.184809, lng: 27.31405 }}
+  >
+    <HeatmapLayer data={
+      props.results.reduce((data, obj) => {
+        if (obj.hasOwnProperty('lat') && obj.hasOwnProperty('long')) {
+          data.push(new google.maps.LatLng(obj.lat, obj.long));
+        }
+        return data;
+      },[])
+    } />
+  </GoogleMap>
+));
+
+export default GMap;
diff --git a/src/client/containers/MapApp.js b/src/client/containers/MapApp.js
index e5074354..b506ab57 100644
--- a/src/client/containers/MapApp.js
+++ b/src/client/containers/MapApp.js
@@ -12,8 +12,10 @@ import Paper from '@material-ui/core/Paper';
 import Immutable from 'immutable';
 import VirtualizedTable from '../components/VirtualizedTable';
 import LeafletMap from '../components/map/LeafletMap';
+import GMap from '../components/map/GMap';
 import Pie from '../components/Pie';
 
+
 import {
   getVisibleResults,
   getVisibleValues
@@ -65,8 +67,31 @@ const styles = theme => ({
 });
 
 let MapApp = (props) => {
-  const { classes, error, analysisView } = props;
-  // console.log(props.results);
+  const { classes, error, analysisView, heatMap } = props;
+
+  let map = '';
+  if (heatMap) {
+    map = (
+      <GMap
+        results={props.results}
+        googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyCKWw5FjhwLsfp_l2gjVAifPkT3cxGXhA4&v=3.exp&libraries=geometry,drawing,places,visualization"
+        loadingElement={<div style={{ height: `100%` }} />}
+        containerElement={<div style={{ height: `100%` }} />}
+        mapElement={<div style={{ height: `100%` }} />}
+      />
+    );
+  } else {
+    map = (
+      <LeafletMap
+        sliderValue={100}
+        results={props.results}
+        geoJSON={props.geoJSON}
+        geoJSONKey={props.geoJSONKey}
+        getGeoJSON={props.getGeoJSON}
+        analysisView={props.analysisView}
+      />
+    );
+  }
 
   return (
     <div className={classes.root}>
@@ -101,14 +126,7 @@ let MapApp = (props) => {
           </Grid>
           <Grid item xs={12} sm={analysisView ? 4 : 8}>
             <Paper className={classes.map}>
-              <LeafletMap
-                sliderValue={100}
-                results={props.results}
-                geoJSON={props.geoJSON}
-                geoJSONKey={props.geoJSONKey}
-                getGeoJSON={props.getGeoJSON}
-                analysisView={props.analysisView}
-              />
+              {map}
             </Paper>
             <div className={classes.map}>
               <Pie data={props.results} groupBy={props.search.groupBy} query={props.search.query} />
@@ -126,6 +144,7 @@ const mapStateToProps = (state) => {
     results: getVisibleResults(state.search),
     resultValues: getVisibleValues(state.search),
     analysisView: state.options.analysisView,
+    heatMap: state.options.heatMap,
     error: state.error,
     geoJSON: state.map.geoJSON,
     geoJSONKey: state.map.geoJSONKey,
@@ -154,6 +173,7 @@ MapApp.propTypes = {
   search: PropTypes.object.isRequired,
   error: PropTypes.object.isRequired,
   analysisView: PropTypes.bool.isRequired,
+  heatMap: PropTypes.bool.isRequired,
   openAnalysisView: PropTypes.func.isRequired,
   closeAnalysisView: PropTypes.func.isRequired,
   updateQuery: PropTypes.func.isRequired,
diff --git a/src/client/reducers/options.js b/src/client/reducers/options.js
index 83bd2412..151982a9 100644
--- a/src/client/reducers/options.js
+++ b/src/client/reducers/options.js
@@ -7,12 +7,14 @@ import {
 
 const DEFAULT_LANGUAGE = 'en';
 const DEFAULT_ANALYSIS_VIEW = true;
+const DEFAULT_HEATMAP = true;
 const DEFAULT_RESULT_FORMAT = 'table';
 
 export const INITIAL_STATE = {
   language: DEFAULT_LANGUAGE,
   resultFormat: DEFAULT_RESULT_FORMAT,
   analysisView: DEFAULT_ANALYSIS_VIEW,
+  heatMap: DEFAULT_HEATMAP
 };
 
 const options = (state = INITIAL_STATE, action) => {
-- 
GitLab