Issues with Pen Points and Groupings

After receiving json data, my goal is to populate a map with markers that will cluster together if they are located too closely. To achieve this, I am utilizing specific extensions for Markers and Clusters available at this link.

I have written code that successfully accomplishes the task at hand, but there are a couple of issues that I'm facing:

1 - It appears that markers and clusters are stacking on top of each other. When using Chrome's inspector tool to remove one marker or cluster, another one seems to appear right beneath it. Additionally, as I zoom in or drag the map, more stacking occurs.

2 - While zooming out, some markers remain outside the cluster, yet the cluster count includes these outliers. This issue seems connected to the stacking problem mentioned in the previous point.

Below are the main sections of my code:

function initialize(lat, lng) {
    window.lat = lat;
    window.lng =lng;
   var myLatlng = new google.maps.LatLng(lat,lng);
   var mapOptions = {
      mapTypeControl: false,
      center: myLatlng,
      zoom: 13,
      maxZoom:18,
      zoomControl: true,
      mapTypeControl: true
   };

    map = new google.maps.Map(document.getElementById('map-full'), mapOptions);
    google.maps.event.addListener(map, 'idle', function() {
        $mapBounds = map.getBounds();
        getJSONData($mapBounds);
    });

}

google.maps.event.addDomListener(window, 'load', initialize(<?=$location;?>));

function getJSONData(map_bounds){
    var bounds  = {
        'swlat':map_bounds.getSouthWest().lat(),
        'swlng':map_bounds.getSouthWest().lng(),
        'nelat':map_bounds.getNorthEast().lat(),
        'nelng':map_bounds.getNorthEast().lng()
    };

    data = {
        'bounds': bounds,
    }

 $.ajax({
    type: "POST",
    dataType: 'json',
    url: "<?=$data_URL;?>",
    data: data,
    success: function (json) {
        populateMap(json, bounds);
    }
    });
}    

function populateMap(data, bounds){

//// ADD Markers
    var markerCluster = null;
    var markers = [];
    var infowindow = new google.maps.InfoWindow();

    leftList(data); // adds properties to left list
    for (var i = 0; i < data.length; i++) {


        var latLng = new google.maps.LatLng(data[i].lat, data[i].lng);  
        // drop the marker
        var marker = new MarkerWithLabel({
            position: latLng,
            map: map,
            labelContent: data[i].price,
            labelAnchor: new google.maps.Point(27, 35),
            title: data[i].title,
            labelClass: "map-markers",
            infoData: data[i],
            zIndex: i

        });

        google.maps.event.addListener(map, 'dragstart', function() {
            infowindow.close();
        });

        google.maps.event.addListener(marker, 'click', function() {
            infowindow.close();
            var info_content = makeMarkerInfo(this.infoData, this.index);
            infowindow.setContent(info_content);
            infowindow.open(map,this);

        });

        markers.push(marker);

    }

///// ADD CLUSTERS
    var clusterOptions = {
        zoomOnClick: false
    }

    markerCluster = new MarkerClusterer(map, markers, clusterOptions);

    // Zoom in or show infowindow when click on Cluster
    google.maps.event.addListener(markerCluster, 'clusterclick', function(cluster) {
        if (map.getZoom() < map.maxZoom ){        
            map.setCenter(cluster.center_);
            map.setZoom(map.getZoom() + 4);
        } else {

            var content = '';
            // Convert lat/long from cluster object to a usable MVCObject
            var info = new google.maps.MVCObject;
            info.set('position', cluster.center_);
            //Get markers
            var marks_in_cluster = cluster.getMarkers();

            console.log(marks_in_cluster);

            for (var z = 0; z < marks_in_cluster.length; z++) {
                content = makeClusterInfo(marks_in_cluster,z) ;
            }
            infowindow.close();
            infowindow.setContent(content); 
            infowindow.open(map, info);
            google.maps.event.addListener(map, 'zoom_changed', function() {
                infowindow.close()
            });
        } 
    });

}

Answer №1

Whenever the map triggers the idle event (this occurs when the map becomes idle after panning or zooming, you can refer to the documentation here), it's important to duplicate the markers. Prior to adding new markers on the map, it is recommended to CLEAR those existing markers by iterating through the markers array and setting the map value to NULL.

var i = 0;
var total = markers.length;
for (; i<total; i++) {
 markers[i].setMap(null);
}

Following this step, a complete REBUILD of everything should be performed. The Clusterer functionality changes at this point. Therefore, encapsulate the call to the clusterer in a function and execute it each time the markers array undergoes modifications. This approach should effectively handle the task!

I hope this explanation proves helpful!

EDIT: Provided below is a revised version of the code. Although not optimized, take some time to analyze it and understand the modifications made. Comments have been included to aid comprehension. PLEASE NOTE THAT THIS CODE HAS NOT BEEN TESTED.

Here is the revision:

// Presumably, this segment exists within your code
var map;

/**
 * Initialization function
 * @param  {float} lat Latitude
 * @param  {float} lng Longitude
 */
function initialize(lat, lng) {
    window.lat = lat;
    window.lng =lng;
    var myLatlng = new google.maps.LatLng(lat,lng);
    var mapOptions = {
      mapTypeControl: false,
      center: myLatlng,
      zoom: 13,
      maxZoom:18,
      zoomControl: true,
      mapTypeControl: true
    };

    map = new google.maps.Map(document.getElementById('map-full'), mapOptions);
    google.maps.event.addListener(map, 'idle', function() {
        $mapBounds = map.getBounds();
        getJSONData($mapBounds);
    });
}

google.maps.event.addDomListener(window, 'load', initialize(<?=$location;?>));

/**
 * Retrieves JSON data for the map
 * @param  {array} map_bounds Boundaries of the Google map post-idle
 */
function getJSONData(map_bounds){
    var bounds  = {
        'swlat':map_bounds.getSouthWest().lat(),
        'swlng':map_bounds.getSouthWest().lng(),
        'nelat':map_bounds.getNorthEast().lat(),
        'nelng':map_bounds.getNorthEast().lng()
    };

    data = {
        'bounds': bounds,
    }

    $.ajax({
        type: "POST",
        dataType: 'json',
        url: "<?=$data_URL;?>",
        data: data,
        success: function (json) {
            populateMap(json, bounds);
        }
    });
}

// Tracking markers and clusterer
var markers;
var markerCluster;

/**
 * Generates the map with the provided data
 * @param  {json} data    Map data
 * @param  {array} bounds Map boundaries
 */
function populateMap(data, bounds){

    // Assuming the capability to clear markers and clusterer
    // If present at this stage
    if (markers.length) {
        var i = 0;
        var total = markers.length;
        for (; i<total; i++) {
            // Eliminate click listeners
            google.maps.event.clearListeners(markers[i], 'click');
            markers[i].setMap(null);
        }
    }

    // Restart the process
    markers = [];

    if (markerCluster) {
        // Remove click listeners
        google.maps.event.clearListeners(markerCluster, 'clusterclick');
        markerCluster.setMap(null);
    }

    // Restart the process
    markerCluster = null;

    // UNTESTED PORTION BEGINS FROM HERE
    var infowindow = new google.maps.InfoWindow();

    leftList(data); // appends properties to the left list
    for (var i = 0; i < data.length; i++) {


        var latLng = new google.maps.LatLng(data[i].lat, data[i].lng);  
        // place the marker
        var marker = new MarkerWithLabel({
            position: latLng,
            map: map,
            labelContent: data[i].price,
            labelAnchor: new google.maps.Point(27, 35),
            title: data[i].title,
            labelClass: "map-markers",
            infoData: data[i],
            zIndex: i

        });

        google.maps.event.addListener(map, 'dragstart', function() {
            infowindow.close();
        });

        google.maps.event.addListener(marker, 'click', function() {
            infowindow.close();
            var info_content = makeMarkerInfo(this.infoData, this.index);
            infowindow.setContent(info_content);
            infowindow.open(map,this);

        });

        markers.push(marker);

    }

    ///// ADD CLUSTERS
    var clusterOptions = {
        zoomOnClick: false
    }

    markerCluster = new MarkerClusterer(map, markers, clusterOptions);

    // Zoom in or display infowindow upon clicking Cluster
    google.maps.event.addListener(markerCluster, 'clusterclick', function(cluster) {
        if (map.getZoom() < map.maxZoom ){
            map.setCenter(cluster.center_);
            map.setZoom(map.getZoom() + 4);
        } else {

            var content = '';
            // Converting lat/long from cluster object to a useful MVCObject
            var info = new google.maps.MVCObject;
            info.set('position', cluster.center_);
            // Obtain markers
            var marks_in_cluster = cluster.getMarkers();

            console.log(marks_in_cluster);

            for (var z = 0; z < marks_in_cluster.length; z++) {
                content = makeClusterInfo(marks_in_cluster,z) ;
            }
            infowindow.close();
            infowindow.setContent(content);
            infowindow.open(map, info);
            google.maps.event.addListener(map, 'zoom_changed', function() {
                infowindow.close()
            });
        }
    });

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing the setInterval function within the componentDidMount lifecycle method in React

I am facing a challenge in updating the state of a React component every 1000 ms. I attempted to use setInterval in the componentDidMount method, but encountered issues. Currently, when I log the results, I see an empty state object in the constructor and ...

What makes 'Parsing JSON with jQuery' unnecessary?

Just performed an ajax request with a query and noticed that my response is already in the form of a JavaScript object. When I try to parse the JSON using: var obj = jQuery.parseJSON(response); 'obj' turns out to be null, yet I can directly ac ...

Considering a Servlet for managing AJAX requests?

Looking for advice on best practices for implementing a yes or no question with AJAX requests. Open to feedback if I make any missteps along the way. I have a specific Servlet (AjaxServlet?) designated to handle all AJAX requests AjaxServlet is mapped t ...

A possible invocation of a function using a JavaScript "class"

Presented here is a node module I've been working on: var _ = require('lodash'); function Config(configType, configDir) { this.configType = configType; this.configDir = configDir; }; Config.prototype.read = function() { // read file t ...

Struggling to update a Knockout observable array

I'm attempting to send some data to a Knockout observable array. Despite receiving data from my AJAX call without any errors during script execution, I find that the userNames array remains empty when accessed. What could be causing this issue? UserH ...

Dealing with an empty request.FILES using FileUploadParser in Django Rest Framework and Angular file upload technique

Uploading files in an angular view can sometimes be tricky, especially when using templates. The code snippet below shows how to upload multiple and single files using Angular File Upload library. Multiple <input type="file" name="file" nv-file-select= ...

Event listener is failing to execute the functions

Even though the inline onmouseover="verdadero()" function is properly set up Upon further inspection, it appears that while the event listener is added to the box element, the function is not being triggered when hovering over it, and console.lo ...

Preserving the name binding in Ractive's radio button functionality

Is there a way to bind a ractive variable to a radio button without showing the curly brackets in the HTML? The simplest method is to use <input type="radio" name="{{myVar}}" value="1" checked> but I want it to output as <input type="radio" nam ...

Struggling with displaying Firebase data in React

I am facing an issue with rendering data fetched from Firebase onto the screen. The problem arises when I attempt to call the function that retrieves data from the database inside the componentDidMount() lifecycle method. Surprisingly, the function does no ...

When I try to hover my mouse over the element for the first time, the style.cursor of 'hand' is not functioning as expected

Just delving into the world of programming, I recently attempted to change the cursor style to hand during the onmouseover event. Oddly enough, upon the initial page load, the border style changed as intended but the cursor style remained unaffected. It wa ...

I have noticed that the baseline of a Span element has shifted after updating my Chrome browser to a version that begins with

Once I updated to chrome Version 108.0.5359.94 (Official Build) (64-bit) from 107.0.5304.87 (Official Build) (64-bit), the behavior of the span element changed drastically. It shifted its baseline when multiple spans were stacked on top of each other. Exp ...

Deleting Firestore ancestor documents and sub-collections that do not exist

My goal is to tidy up my collection data. The collection I'm working with is named "teams". Within this collection, there is a sub-collection called "players". I used a basic delete query in Firestore to remove the document under ...

What's the best way to handle the output of an HTTP request in my specific situation?

Struggling to pass http request results from parent controller to child controller... This is what I have: <div ng-controller = "parentCtrl"> <button ng-click="callApi()">click me</button> <div ng-controller = "childCtrl"& ...

What is the best way to apply dynamic wrapping of a substring with a component in Vue.js?

My Objective My goal is to take a user input string and render it with specific substrings wrapped in a component. Specifically, I am looking to identify dates within the string using a regex pattern and then wrap these dates in a Vuetify chip. Progress ...

Using jQuery with multiple selectors can become tricky when dealing with elements that may or may not be present

I decided to be more efficient by using multiple selectors instead of rewriting the same code repeatedly. Typically, if one element exists then the others do not. $('form#post, form#edit, form#quickpostform').submit( function() { ...

Using jQuery to highlight the navigation menu when a specific div scrolls into view

I have implemented a side navigation consisting of circular divs. Clicking on one scrolls you to the corresponding .block div, and everything functions correctly. However, I am now curious if it is feasible to highlight the relevant .nav-item div based on ...

How to customize the text color of the notchedOutline span in Material UI

I'm currently working on customizing the textfield in materialUI. This is what I am trying to modify. I am facing challenges in changing the color of the span tag that contains the text, and also in maintaining the border color when the textfield is ...

Contrasting actions observed when employing drag functionality with arrays of numbers versus arrays of objects

Being a newcomer to D3 and JavaScript, I'm hoping someone can help me clarify this simple point. I am creating a scatter graph with draggable points using code that closely resembles the solution provided in this Stack Overflow question. When I const ...

How to properly align TableHeader and TableBody contents in a Material-UI table

I am experiencing an issue with a file that is supposed to display a table of data pulled from a database. Although the table does appear, all the data seems to be displayed under the ISSUE NUMBER column, instead of being aligned with their respective col ...

Unlocking the Magic of JSONP: A Comprehensive Guide

Currently attempting to utilize JSONP in order to work around Cross Domain challenges. I referenced this solution: Basic example of using .ajax() with JSONP? $.getJSON("http://example.com/something.json?callback=?", function(result){ //response data a ...