reactjsgoogle-mapsreact-google-maps

google-map-react marker clustering issue


Hi I am making a react site which ideally should involve clustering of markers.

I have used two different techniques for this both effectively display onto the map and one even clusters but I am hoping that I can combine these techniques.

The reason for this is the two techniques are slightly different and using the technique that effectively clusters will not let me map out components but instead have to return a google marker.

This is the first technique I used.

import React, { Component, useState } from "react";
import GoogleMapReact from "google-map-react";

import TravelAlert from "./TravelAlerts/TravelAlerts";
import IntelAlert from "./IntelAlerts/IntelAlerts";

import "./GoogleMap.css";

//class component
export class GoogleMap extends Component {
  constructor(props) {
    super(props);
  }
  componentDidMount() {}
  static defaultProps = {
    center: {
      lat: 59.955413,
      lng: 30.337844,
    },
    zoom: 11,
  };

  render() {
    return (
      <>
        <GoogleMapReact
          style={{ height: "100vh", width: "100%", zIndex: -1 }}
          bootstrapURLKeys={{
            key: process.env.REACT_APP_MAP_API_KEY,
          }}
          defaultCenter={this.props.center}
          defaultZoom={this.props.zoom}
        >
//Simply .mapping a bunch of props (if done this way component just needs a lat and a lng prop for each component )
          {this.props.travelAlerts.map((ta) => (
            <TravelAlert
              lat={ta.latitude}
              lng={ta.longitude}
              title={ta.title}
              description={ta.description}
              notes={ta.notes}
              riskId={ta.riskId}
              startDate={ta.startDate}
              endDate={ta.endDate}
            />
          ))}

          {this.props.intelAlerts.map((ta) => (
            <IntelAlert
              lat={ta.latitude}
              lng={ta.longitude}
              title={ta.title}
              description={ta.description}
              notes={ta.notes}
              riskId={ta.riskId}
              startDate={ta.startDate}
              endDate={ta.endDate}
            />
          ))}
        </GoogleMapReact>
      </>
    );
  }
}

export default GoogleMap;

I really like this technique as it simply allows you to map any component to a lat and lng of the screen. However using this technique I could not get the clustering of these markers working.

This is the second technique but this will not allow me to map out any custom marker and means I have to map out the markers of the map before the map has loaded. This is not preferred as I cannot map my own custom markers.

import React, { Component } from "react";
import GoogleMapReact from "google-map-react";
import intelAlert from "../../../components/Map/MapContainer/GoogleMap/IntelAlerts/IntelAlerts";
// import MarkerClusterer from "@google/markerclusterer";

export default class GoogleMapContainer extends Component {
  componentDidMount() {
    const script = document.createElement("script");
    script.src =
      "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js";
    script.async = true;
    document.body.appendChild(script);
  }

  setGoogleMapRef(map, maps) {
    this.googleMapRef = map;
    this.googleRef = maps;
    
    let markers =
      this.props.intelAlerts &&
      this.props.intelAlerts.map((location) => {
        var loc = { lat: location.latitude, lng: location.longitude };
        return new this.googleRef.Marker({ position: loc });
      });
    let markerCluster = new MarkerClusterer(map, markers, {
      imagePath:
        "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m",
      gridSize: 10,
      minimumClusterSize: 2,
    });
  }

  static defaultProps = {
    center: {
      lat: 59.95,
      lng: 30.33,
    },
    zoom: 11,
  };

  render() {
    if (this.props.intelAlerts.length) {
      return (
        <GoogleMapReact
          style={{ height: "100vh", width: "100%", zIndex: -1 }}
          bootstrapURLKeys={{ key: process.env.REACT_APP_MAP_API_KEY }}
          yesIWantToUseGoogleMapApiInternals
          onGoogleApiLoaded={({ map, maps }) => this.setGoogleMapRef(map, maps)}
          defaultCenter={{ lat: -31.56391, lng: 147.154312 }}
          defaultZoom={15}
          options={{ streetViewControl: true }}
        />
      );
    } else {
      return <></>;
    }
  }
}

Is there a way to still be able to map out my own custom markers and include marker clustering.

Any help or direction means allot as there is little documentation on this issue.

I am using google-map-react npm package.

EDIT I have done something similar to this in angular. In Angular you can simply do this.

<div ng-if="showTravellers" >
      <agm-marker-cluster    [minimumClusterSize]= "minClusterSize"> 

          <agm-marker *ngFor="let m of intelligenceAlerts;let i = index;" [visible]="showIntelligence"  [latitude]="m.latitude" [longitude]="m.longitude" [iconUrl]= 'getTheIcon(m.riskId)'  >
            <agm-snazzy-info-window [maxWidth]="600" maxHeight="200px" [closeWhenOthersOpen]="true" backgroundColor="getTheColor(m.colour);">
                <ng-template>
                    <mat-form-field appearance="standard">
                        <mat-card> <h3><b>{{m.colour}}</b> </h3></mat-card>
                  <mat-card> <h3><b>{{m.title}}</b> </h3></mat-card>
                  <mat-card> <p>{{m.notes}} </p></mat-card>
                   <button>Description</button> <button>Start Date</button><button>End Date</button><button>Notes</button>
                 </mat-form-field>
                </ng-template>
               </agm-snazzy-info-window> 
         </agm-marker>
    </agm-marker-cluster>
    </div>

Is there an equivalent in reactjs?


Solution

  • You can just load the Maps JS API dynamically instead of relying on 3rd party libraries/packages. That way, you can simply follow their official documentations. For this example, I followed these two to cluster map markers as well as add custom markers:

    Here is a Stackblitz sample I made for your reference: https://stackblitz.com/edit/react-map-cluster-64766101

    App.js

    import React, { Component } from "react";
    import { render } from "react-dom";
    import Map from "./components/map";
    import "./style.css";
    
    class App extends Component {
      render() {
        return (
          <Map
            id="myMap"
            options={{
              center: { lat: -28.024, lng: 140.887 },
              zoom: 3
            }}
          />
        );
      }
    }
    
    export default App;
    

    map.js

    import React, { Component } from "react";
    import { render } from "react-dom";
    
    const locations = [
      { lat: -31.56391, lng: 147.154312 },
      { lat: -33.718234, lng: 150.363181 },
      { lat: -33.727111, lng: 150.371124 },
      { lat: -33.848588, lng: 151.209834 },
      { lat: -33.851702, lng: 151.216968 },
      { lat: -34.671264, lng: 150.863657 },
      { lat: -35.304724, lng: 148.662905 },
      { lat: -36.817685, lng: 175.699196 },
      { lat: -36.828611, lng: 175.790222 },
      { lat: -37.75, lng: 145.116667 },
      { lat: -37.759859, lng: 145.128708 },
      { lat: -37.765015, lng: 145.133858 },
      { lat: -37.770104, lng: 145.143299 },
      { lat: -37.7737, lng: 145.145187 },
      { lat: -37.774785, lng: 145.137978 },
      { lat: -37.819616, lng: 144.968119 },
      { lat: -38.330766, lng: 144.695692 },
      { lat: -39.927193, lng: 175.053218 },
      { lat: -41.330162, lng: 174.865694 },
      { lat: -42.734358, lng: 147.439506 },
      { lat: -42.734358, lng: 147.501315 },
      { lat: -42.735258, lng: 147.438 },
      { lat: -43.999792, lng: 170.463352 }
    ];
    
    class Map extends Component {
      constructor(props) {
        super(props);
        this.state = {
          map: ""
        };
      }
    
      onScriptLoad() {
        this.state.map = new window.google.maps.Map(
          document.getElementById(this.props.id),
          this.props.options
        );
        this.addMarker();
      }
    
      componentDidMount() {
        if (!window.google) {
          var s = document.createElement("script");
          s.type = "text/javascript";
          s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
          var x = document.getElementsByTagName("script")[0];
          x.parentNode.insertBefore(s, x);
    
          var s2 = document.createElement("script");
          s2.type = "text/javascript";
          s2.src = `https://unpkg.com/@google/markerclustererplus@4.0.1/dist/markerclustererplus.min.js`;
          var x2 = document.getElementsByTagName("script")[0];
          x2.parentNode.insertBefore(s2, x2);
    
          s.addEventListener("load", e => {
            this.onScriptLoad();
          });
        } else {
          this.onScriptLoad();
        }
      }
      // Add some markers to the map.
      addMarker() {
        const markers = locations.map((location, i) => {
          return new google.maps.Marker({
            position: location,
            icon:
              "https://developers.google.com/maps/documentation/javascript/examples/full/images/info-i_maps.png",
            map: this.state.map
          });
        });
        // Add a marker clusterer to manage the markers.
        new MarkerClusterer(this.state.map, markers, {
          imagePath:
            "https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m"
        });
      }
    
      render() {
        return <div className="map" id={this.props.id} />;
      }
    }
    
    export default Map;
    

    Note: Replace the API Key placeholder with your actual API Key