google-maps-react icon indicating copy to clipboard operation
google-maps-react copied to clipboard

Is google clustering possible with this library?

Open noushad-pp opened this issue 9 years ago • 16 comments

I want to know whether goole clustering is possible with this library

noushad-pp avatar Oct 29 '16 15:10 noushad-pp

+1

artemis-prime avatar Aug 01 '17 15:08 artemis-prime

Use this: https://www.npmjs.com/package/node-js-marker-clusterer Works for me.

import MarkerClusterer from 'node-js-marker-clusterer';

const mc = new MarkerClusterer(
        this.map, // from new google.maps.Map();
        markers, // from const markers = locations.map()
        {
          styles: [{
            width: 40,
            height: 40,
            url: '/assets/icon-markercluster.png',
            textColor: 'white',
          }],
        },
      );

ghost avatar Feb 07 '18 11:02 ghost

@joriscolours do you have a sample?

scyrizales avatar Nov 12 '18 03:11 scyrizales

@scyrizales I used it here: https://www.vanlanschot.nl/contact/kantoren

ghost avatar Nov 12 '18 11:11 ghost

@joriscolours Hi. I have a question. Could you please show us a code snippet, how did you use this Cluster with this plugin. Thank you.

wamujlb avatar Nov 21 '18 11:11 wamujlb

Something like this.

The 'parent' component:

/**
 * This component requires the 3rd party HOC to add the <script> tag dynamically and pass the google (google.maps) object to the subcomponent.
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { GoogleApiWrapper } from 'google-maps-react';

import GoogleMap from 'Feature/Locations/code/Patterns/Molecules/GoogleMap';

class GoogleMapContainer extends Component {
  render() {
    return (
      <GoogleMap
        google={this.props.google}
        {...this.props}
      />
    );
  }
}

GoogleMapContainer.propTypes = {
  google: PropTypes.objectOf(PropTypes.any),
  settings: PropTypes.objectOf(PropTypes.any),
};
GoogleMapContainer.defaultProps = {
  google: null,
  settings: null,
};

export default GoogleApiWrapper(props => ({
  apiKey: props.settings.apiKey,
}
))(GoogleMapContainer);

The child component, <GoogleMap>.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import MarkerClusterer from 'node-js-marker-clusterer';

class GoogleMap extends Component {
  componentDidMount() {
    this.loadMap();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.google !== this.props.google || prevProps.locations !== this.props.locations) {
      this.loadMap();
    }
  }

  loadMap() {
    if (this.props && this.props.google) {
      const { google } = this.props;
      const node = this.mapRef;
      const mapConfig = Object.assign({}, {
        center: { lat: this.props.settings.defaultLatitude, lng: this.props.settings.defaultLongitude },
        zoom: this.props.settings.zoom,
        mapTypeControl: false,
        streetViewControl: false,
        gestureHandling: 'cooperative',
      });

      this.map = new google.maps.Map(node, mapConfig);

      const infowindow = new google.maps.InfoWindow({
        content: this.props.labels.loading,
      });

      const markers = this.props.locations.map((location) => {
        const marker = new google.maps.Marker({
          position: { lat: location.lat, lng: location.lng },
          map: this.map,
          content: `<div class="c-maps__callout">
            ...
          </div>`,
          icon: '/static/assets/icon-mapmarker.png',
        });

        if (location.isOpen) {
          setTimeout(() => {
            infowindow.setContent(marker.content);
            infowindow.open(this.map, marker);
          }, 1000);
        }

        google.maps.event.addListener(marker, 'click', () => {
          infowindow.setContent(marker.content);
          infowindow.open(this.map, marker);
        });

        return marker;
      });

      return new MarkerClusterer(
        this.map,
        markers,
        {
          styles: [{
            width: 40,
            height: 40,
            url: '/static/assets/icon-markercluster1.png',
            textColor: 'white',
          }],
        },
      );
    }

    return {};
  }

  render() {
    return (
      <div
        className="c-maps"
        ref={(e) => {
          this.mapRef = e;
        }}
      />
    );
  }
}

ghost avatar Nov 21 '18 12:11 ghost

By the way @joriscolours, I was able to find it from your page, just a little unminifying action :D To be clear with anyone, in order to use MarkerClusterer, you need to stop using the Marker component exposed by this google-maps-react

scyrizales avatar Nov 21 '18 19:11 scyrizales

I found a simple solution to the clustering. First, I'm using the google markerclusterer.js file you can download from google here: https://developers.google.com/maps/documentation/javascript/marker-clustering

Then, I observed that the Maps component passes props to child components, including maps, and google. Both of these are needed to create the clusters. From there, I just created a "MarkerCluster" component. Full source is here

import React, {useEffect} from 'react'
import PropTypes from 'prop-types'

import MarkerClusterer from './markerclusterer'

const evtNames = [
  'click',
  'dblclick',
  'dragend',
  'mousedown',
  'mouseout',
  'mouseover',
  'mouseup',
  'recenter',
]

const markerCluster = (props) => {
  const {map, google, markers} = props
  
  const handleEvent = ({event, marker, entry}) => {
    if (props[event]) {
      props[event]({
        props: props,
        marker: marker,
        event: event,
        entry: entry
      })
    }
  }
  
  // This hook works like ComponentWillMount
  // The  hook isn't really needed, this whole thing worked without it,
  // I added the hook so that I could implement a cleanup function
  useEffect(() => {
    if (map && markers) {
      const mapMarkers = markers.map((marker) => {
        const entry = new google.maps.Marker({
          position: {
            lat: marker.position.lat,
            lng: marker.position.lng
          },
          map: map,
          name: marker.name
        })
        
        evtNames.forEach(e => {
          entry.addListener(e, () => handleEvent({
            event: e,
            marker: marker,
            entry: entry
          }))
        })
        
        return entry
      })
      
      const clusterer = new MarkerClusterer(map, mapMarkers, {imagePath: '/static/images/maps/m'})
      
      // Cleanup function. Note, this is only returned if we create the markers
      return () => {
        //console.log('Cleaning up markers')
        clusterer.clearMarkers()
      }
    }
  }, [map, google, markers])
  
  
  // Do we need to render anything??
  return (null)
  
}

markerCluster.propTypes = {
  map: PropTypes.object,
  google: PropTypes.object,
  markers: PropTypes.arrayOf(PropTypes.shape({
    position: PropTypes.shape({
      lat: PropTypes.number.isRequired,
      lng: PropTypes.number.isRequired,
    }).isRequired,
    name: PropTypes.string.isRequired,
  })),
}

export default markerCluster

Then in your map implemtation, something like this:

      <Map google={google}>
        <MarkerCluster
          markers={markers}
          click={this.onMarkerClick}
          mouseover={this.onMouseOver}
          mouseout={this.onMouseOut}
        />
      </Map>

I used the same technique found in the Marker component to map handlers to the markers that are created, and I pass enough information back so handlers can do what is needed. I probably pass too much... but, it works.

This could probably be added to the library pretty easily ;-)

kutenai avatar Mar 19 '19 01:03 kutenai

@kutenai nice. thanks for the solution.

iakashpatel avatar Mar 28 '19 06:03 iakashpatel

@kutenai i tried the same but I am getting an error

Uncaught TypeError: _markerclusterer__WEBPACK_IMPORTED_MODULE_2___default.a is not a constructor at markercluster.js:57

kartik4all avatar Oct 21 '19 11:10 kartik4all

Hi @kutenai, The link for the markerclusterer.js from https://developers.google.com/maps/documentation/javascript/marker-clustering isn't available at this point in time. I have used this file instead Archive which I assume is the same file you were talking about. However, I am getting an error that tells me that in the markerclusterer.js file that google is undefined. Is it because the file I have found is not the same as the one you have used? or is there an underlying problem elsewhere?

ChrisnNg avatar Nov 06 '19 06:11 ChrisnNg

You need to instantiate the google map. You can't do this from your compiled code, since node does not expose global variables, so I add the logic to my template. Here is my initialization code

<script type="text/javascript">
    function createMap(location) {
        var mapOptions = {
            center: location['center'],
            zoom: location.zoom_level
        };
        return new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
    }

    function initMap() {
        var location = {{ location|safe }};
        var map = createMap(location);

        if ('polymap'.indexOf(location['type']) !== -1) {
            createPolyMap(map, location);
        } else {
            createCircleMap(map, location);
        }
    }
    
    function createPolyMap(map, options) {
        var polyPath = new google.maps.Polygon({
            paths: options['paths'],
            strokeColor: '#FF0000',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#FF0000',
            fillOpacity: 0.35
        });
        polyPath.setMap(map);
        return polyPath;
    }
    
    function createCircleMap(map, options) {
        var radius = options['radius'];
        return new google.maps.Circle({
            strokeColor: '#FF0000',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#FF0000',
            fillOpacity: 0.35,
            center: options['center'],
            map: map,
            radius: radius
        });
    }

    function createMarker(map, latLong, title, desc) {
        return new google.maps.Marker({
            position: latLong,
            map: map,
            title: title,
            desc: desc
        });
    }
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAP_API_KEY }}&callback=initMap" type="text/javascript"></script>

You will need to secure an API key, which can cost you money, but this is generally a few cents, or a few dollars until you get to high traffic. Hopefully, by then you won't care.

kutenai avatar Nov 06 '19 15:11 kutenai

You need to instantiate the google map. You can't do this from your compiled code, since node does not expose global variables, so I add the logic to my template. Here is my initialization code

<script type="text/javascript">
    function createMap(location) {
        var mapOptions = {
            center: location['center'],
            zoom: location.zoom_level
        };
        return new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
    }

    function initMap() {
        var location = {{ location|safe }};
        var map = createMap(location);

        if ('polymap'.indexOf(location['type']) !== -1) {
            createPolyMap(map, location);
        } else {
            createCircleMap(map, location);
        }
    }
    
    function createPolyMap(map, options) {
        var polyPath = new google.maps.Polygon({
            paths: options['paths'],
            strokeColor: '#FF0000',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#FF0000',
            fillOpacity: 0.35
        });
        polyPath.setMap(map);
        return polyPath;
    }
    
    function createCircleMap(map, options) {
        var radius = options['radius'];
        return new google.maps.Circle({
            strokeColor: '#FF0000',
            strokeOpacity: 0.8,
            strokeWeight: 2,
            fillColor: '#FF0000',
            fillOpacity: 0.35,
            center: options['center'],
            map: map,
            radius: radius
        });
    }

    function createMarker(map, latLong, title, desc) {
        return new google.maps.Marker({
            position: latLong,
            map: map,
            title: title,
            desc: desc
        });
    }
</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAP_API_KEY }}&callback=initMap" type="text/javascript"></script>

You will need to secure an API key, which can cost you money, but this is generally a few cents, or a few dollars until you get to high traffic. Hopefully, by then you won't care.

Hi @kutenai , may I know where did you put the initialization code? Where is this "template" located? Thank you!

fjregencia avatar Feb 24 '20 03:02 fjregencia

Is it possible to cluster with Marker component?

michaelHottomali avatar Jun 04 '20 06:06 michaelHottomali

Hi @kutenai I implemented google-maps-react and its working fine but I need to implement clustering please let me know what is the best way to implement it. Thanks

loveheenavasu avatar Sep 03 '20 06:09 loveheenavasu

@kutenai Is it possible to cluster with Marker component?

loveheenavasu avatar Sep 03 '20 06:09 loveheenavasu