I have large amount of markers I want to display based on user viewpoint. I am looking for most efficient way I can quickly update my map when user changes a viewpoint. In other words display only these markers which falls into bounds (lg, ln).
Currently my solution is following:
On page load pull all data about all places from server and store it in array.
Update map
Every time user drags map remove old markers and place new which falls into bound
Current solutions works but slow and inefficient, to complexity to refresh a map O(N*M) where N old markers which needs to be removed and M new markers which needs to be placed on the map.
I wonder if someone has idea how to make it as faster than this?
How about detecting only the markers which needs to be updated (removed/added)?
I am generally looking for any optimization suggestions - that might be algorithmic improvement, technology or architectural (processing on backend?).
Current code:
var places = function(){} // Some function that pulls a lot of places on page load
function updatePlaces(places){
google.maps.Map.prototype.clearMarkers();
if(places != null){
for(var i = 0; i < places.length; i++){
var lat = places[i].lat;
var lng = places[i].lng;
var position = new google.maps.LatLng(lat, lng);
if(map.getBounds().contains(position) && placedMarkers < markerLimitNumber) {
var marker = new MarkerWithLabel({
position: position,
draggable: false,
raiseOnDrag: true,
map: map,
labelContent: '',
labelAnchor: new google.maps.Point(-10, 15),
labelClass: "labels", // the CSS class for the label
labelStyle: {opacity: 1.0},
icon: 'images/markers/grey_circle_small.png'
});
marker.addListener('click', function(){});
markers.push(marker);
placedMarkers = placedMarkers + 1;
}
}
}
}
google.maps.Map.prototype.clearMarkers = function() {
for(var i=0; i < markers.length; i++){
markers[i].setMap(null);
}
markers = new Array();
placedMarkers = 0;
};
You could try using Google Map Marker Clusterer. It hides the markers at low zoom levels and displays them at higher zoom levels and allows addition of a large number of markers.
Here is a good article on this
https://www.mapbox.com/blog/supercluster/?utm_source=newsletter_april&utm_medium=email&utm_content=supercluster&utm_campaign=newsletter_april
Please take a look
Related
I am trying to use marker clustering because I have over 2000 markers being mapped currently, but I am not sure how to implement it with the way that I am loading the markers. Do I have to use a GeoJSON in order to use marker clustering? I would prefer to not use GeoJSON if possible, I am using a JSON file at the moment and looping through the data stored on Firebase. I would like to use the supercluster library if possible (https://github.com/mapbox/supercluster) but I am not sure what to load into index.load(points). Is clustering possible the way I have my code at the moment?
var markers = [];
allMarkers();
function allMarkers() {
for (var i = 0; i < data.length; i++) {
var marker = document.createElement("div");
marker.className = "marker";
marker.style.backgroundImage = "url(./icons/all.png)";
marker.style.backgroundSize = "100%";
marker.style.backgroundRepeat = "no-repeat";
marker.style.width = "25px";
marker.style.height = "25px";
marker.style.filter = "drop-shadow(0px 5px 6px #000000)";
new mapboxgl.Marker(marker)
.setLngLat([data[i].Long, data[i].Lat])
.setPopup(
new mapboxgl.Popup()
.setHTML(
""
).on("close", function() {
}).on("open", function() {
zoom = map.getZoom();
if (zoom < 17) {
map.flyTo({
center: [this._lngLat.lng, this._lngLat.lat],
zoom: 17
});
} else {
map.flyTo({
center: [this._lngLat.lng, this._lngLat.lat]
});
}
}).setMaxWidth("400px")
)
.addTo(map);
markers.push(marker);
}
}
The easiest way to do clustering in Mapbox-GL-JS is using a GeoJSON source with cluster: true as in this example. It doesn't matter that your data is being sent to the browser as some other format, you can convert it into GeoJSON easily and then add it.
Currently you are representing your points using Markers, rather than a layer within the map such as circle or symbol. That would be more complex to combine with clustering, as you would need to create and destroy markers on demand, as the user zooms in and out. It will be much simpler to manage with a map layer, although it will limit your ability to style the marker symbols.
I have a basic markerclusterer example which works very well.
var center = new google.maps.LatLng(37.4419, -122.1419);
var options = {
'zoom': 13,
'center': center,
'mapTypeId': google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map"), options);
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(data.photos[i].latitude,
data.photos[i].longitude);
var marker = new google.maps.Marker({'position': latLng});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers);
What I would like to do is cluster the markers by country and then once you click on it they are still clustered until on3 further click. Currently they are clustered until you are down to one result. I have thousands of markers and would like them visible after one country click and then one more click.
I looked for a solution online and found this http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/1.0/examples/google_northamerica_offices.html
which is produced using this
var officeLayer = [
{
"zoom": [0, 3],
"places": [
{ "name": "US Offices", "icon": ["us", "flag-shadow"], "posn": [40, -97] },
{ "name": "Canadian Offices", "icon": ["ca", "flag-shadow"], "posn": [58, -101] }
]
},
...
};
function setupOfficeMarkers() {
allmarkers.length = 0;
for (var i in officeLayer) {
if (officeLayer.hasOwnProperty(i)) {
var layer = officeLayer[i];
var markers = [];
for (var j in layer["places"]) {
if (layer["places"].hasOwnProperty(j)) {
var place = layer["places"][j];
var icon = getIcon(place["icon"]);
var title = place["name"];
var posn = new google.maps.LatLng(place["posn"][0], place["posn"][1]);
var marker = createMarker(posn, title, getIcon(place["icon"]));
markers.push(marker);
allmarkers.push(marker);
}
}
mgr.addMarkers(markers, layer["zoom"][0], layer["zoom"][1]);
}
}
mgr.refresh();
updateStatus(mgr.getMarkerCount(map.getZoom()));
}
I'm not sure how to implement this into what I've currently got and if i need to include any other scripts/ libraries also.
You are looking at two totally different libraries, there. Your question is about the MarkerClusterer library, but your example solution is about the MarkerManager library.
The MarkerClusterer library automatically clumps markers together based on an algorithm that tries to decide when too markers would be so close together that you can't visibly distinguish one from another. You don't really have a lot of control over when and how it decides to merge markers together this way, so this library is idea when it doesn't matter to you how they get merged, as long as merging happens. Since you want to merge markers together by political boundaries (countries) and not by proximity to each other, this is not the library for you.
The MarkerManager library does not automatically merge markers together at all. What it does do is to selectively hide and reveal markers based on the zoom level of the current map viewport. What you would need to do is do your own merging, and then add to the MarkerManager all of the merged markers, as well as the detail markers, and the zoom levels where you want each marker to be visible. Doing your own merging means you will need an alternate way of determining which country each marker point falls within. Hopefully, you already know (or can get) that information, because it's not automatically provided by any of these libraries.
tl;dr - use the MarkerManager library and not the MarkerClusterer library for grouping by countries, and it's up to you to identify the location for each country and which marker goes with which one.
Edit:
Question = "is there a way to loop through the array and check if each location (long/lat) falls within the current viewport directly" (failing that get all markers within the viewport)
Background:
I have an array of locations (lat, long, id).
I want to:
On a Google Map, use the location array to display markers.
The user can scroll/zoom the map.
Have a button underneath the map, so when the user has decided on an area, he can click the button, and the code will return the ids (from the location array) that are contained within the viewport / map bounds.
There is a .contains for Google, so I guess you could potentially use that with something like
map.getBounds().contains and somehow reference each marker.getPosition()
but I wonder if there's a way to loop through the array and check if each location (long/lat) falls within the current viewport directly
You mean something like this (not tested), map is the google.maps.Map object and needs to be in scope. markersArray is the array of markers.
for (var i=0; i< markersArray.length; i++) {
if (map.getBounds().contains(markersArray[i].getPosition())) {
// the marker is in view
} else {
// the marker is not in view
}
}
http://jsfiddle.net/UA2g2/1/
Thanks geocodezip, you gave me the idea on how to solve it via looping through the array. I don't know if this is the most efficient way, but I put together some code that seems to do what I want - if you check the jsfiddle above and view console you can see that it logs when and which points are in the viewport.
$(document).ready(function(){
var myOptions = {
center: new google.maps.LatLng(51, -2),
zoom: 9,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var storeArray = new Array(["51.38254", "-2.362804", "ID1"], ["51.235249", "-2.297804","ID2"], ["51.086126", "-2.910767","ID3"]);
google.maps.event.addListener(map, 'idle', function() {
for (i = 0; i < storeArray.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(storeArray[i][0], storeArray[i][1]),
map: map
});
}
for (var i=0; i<storeArray.length; i++) {
if (map.getBounds().contains(new google.maps.LatLng(storeArray[i][0], storeArray[i][1]))) {
console.log("marker: " + storeArray[i][2]);
}
}
});
});
i've placed around 3 hundreds of markers on google map and i also need to highlight some regions with Cirlce on it. So i've placed 3 circles on the map. I wanna show markers only inside circle, not outside the circles. Circles can be placed any where(say fixed radius) and markers shoud be inside circles. all marking and placing circles are in javascript. I'm fresh grad and new to javascript.
Pradi
The part where i draw circle.
for (var i = 0; i < ltlg.length; i++) {
var marker = new google.maps.Marker({
map: map,
position: ltlg[i],
visible: false
});
var circle = new google.maps.Circle({
map: map,
radius: 10 * 55000,
fillcolour: '#AA0000'
});
circle.bindTo('center', marker, 'position');
}
where ltlg[i] is array containg 3 lats and longs.
and part where i mark
for (var i = 0; i < ltlg1.length; i++) {
// var detail = cityDetails[i];
var markerSnowFall = new google.maps.Marker({
map: map,
icon: Image,
position: ltlg1[i]
});
Here ltlg1[i] contains some 300 hundred lats and longs.
Problem is that i dont know how to make connection between marking and placing circles, currently both(marking use ltlg1[i] and cicle use ltlg[i]) use different arrays.
Either make a loop that for each of the markers checks if the distance to the center of the circle means that the marker is within the circle and only show the markers that fits this
or if the way you are placing the markers on a map supports it, only send markers that should be drawn (for instance in a previous project we had the position of the markers in a db that we could query for all markers within a circle)
I have a webpage that receives AJAX data with values listing_id, lat, lng using $.getJSON. The callback function takes these series of values and creates a Google map marker for each set of listing_id, lat, lng. The marker is then pushed into an array markers and its listing_id into an array markersListingId.
var latLng = new google.maps.LatLng(json[i].lat, json[i].lng);
var marker = new google.maps.Marker({
position: latLng,
icon: base_url + 'images/template/markers/listing.png',
});
markers.push(marker);
markersListingId.push(json[i].listing_id);
Problem: The problem arises when I want to select a specific Marker that corresponds to a particular listing_id, especially in more complicated situations with markers being deleted and added to the markers array.
I can have for loops looping through all the marker's id in markersListingId array then using the loop's index i to get the marker in markers array, but this will make tracking very tricky.
var id_to_select = 1234;
for(var i = 0; i < markersListingId; i++) {
if(markersListingId[i] == id_to_select) {
markers[i].setMap(null);
}
}
Question: How can I store the markers so that I can easily select a specific marker by using its listing_id?
When you create the markers, instead of using an array, can you use a hash table?
http://jsfiddle.net/j9J8x/
var markers = {};
//id goes in place of 1234
n = 1234;
markers[n] = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(0,0)
});
s = '2234'
markers[s] = new google.maps.Marker({
map: map,
position: new google.maps.LatLng(20,0)
});
google.maps.event.addListener(map, 'click', function(event) {
k = 1234;
markers[k].setMap(null);
markers['2234'].setMap(null);
});
}