MASSIVE GOOGLE MAPS. 5000 places, 5MB. How to make it faster? - javascript

I'm trying to load a Google Maps v3 that shows 5000 markers clickable. My JS contains 10 lines per place:
var myLatlng_4821 = new google.maps.LatLng(43.0754329,-71.4699752);
var contentString_4821 = "<b>Place 1</b>";
var infowindow_4821 = new google.maps.InfoWindow({content: contentString_4821});
var marker_4821 = new google.maps.Marker({
position: myLatlng_4821,
map: map,
icon: image
});
google.maps.event.addListener(marker_4821, 'click', function() {
infowindow_4821.open(map,marker_4821);
});
So it gives me a 50 000 lines js (5MB). I would love to cache it but heroku doesn't allow a page to take more than 30s to load so I can't even cache it once.
Would you have any tricks to load this page faster? I only managed to do it locally (takes 45s~). I have nothing else to load. I use RoR.
Thanks a lot!
SOLUTION (it's pretty cool).
var myLatlng = [];
var infowindow = [];
var marker = [];
function MakeInfoWindowEvent(map, infowindow, marker) {
return function() {
infowindow.open(map, marker);
};
}
for (var i=0;i < places.length;i++)
{
myLatlng[i] = new google.maps.LatLng(places[i][0],places[i][1]);
infowindow[i] = new google.maps.InfoWindow({
content: places[i][2]
});
marker[i] = new google.maps.Marker({
position: myLatlng[i],
map: map
});
google.maps.event.addListener(marker[i], 'click', MakeInfoWindowEvent(map, infowindow[i], marker[i]))
}
}

If I understand you correctly, you have the example code repeated 5000 times, with '4821', the lat&lng and the content string differing for each of the 5000 markers?
If so, then you need to change your approach so that its the data that is 'repeated' and not the code...
var markers = [
[43.0754329,-71.4699752,"Info for first marker"],
[45.0473490,-56.47688356,"Info for second marker"],
.
.
.
[20.1234384,12.23462566,"Info for last marker"]
];
for(var i=0; i < markers.length; i++){
var latLng = new google.maps.LatLng(markers[i][0], markers[i][1]);
var contentString = markers[i][2];
var infowindow = new google.maps.InfoWindow({content: contentString});
var marker = new google.maps.Marker({
position: latlng,
map: map,
icon: image
});
google.maps.event.addListener(marker, 'click', function()
{
infowindow.open(map,marker);
});
}
This would reduce the size of your page by a significant amount, and will probably solve the caching issue.
A word of warning though - the above code merely illustrates how to remove the repeated code and replace it with data lookup, but there may be issues with creating so many InfoWindow instances at once (You would probably be better off creating the InfoWindow inside the listener.
Also, I'm not completely sure about the visiblity of variables in the closures, so you may end up with 5000 markers having the attributes of the last item in the list (slight changes may be required)

As all information you need for a single marker is this:
[lat,lng,contentString]
...you could store this data in an array and loop the array.
Depending on the contentString you should now need something from 150KB upwards, what shouldn't be a big problem.
If the data related to the markes doesn't change you can use an external js-file, so the browser can use them from it's cache on future visits.

I'm not sure what the overhead on the constructors for the Google InfoWindow and Marker are, I can imagine they're hefty, but I can tell you for sure that by adding a new function using the closure variables, you're preventing the disposal of EVERY object in the routine.
This may account for the majority of your problem and is a classic scenario for a memory leak. See the accepted answer from this post.
I would agree that you'd be better off storing what you need in an array and only instantiating when necessary in your click handler.

Related

GoogleMaps InfoWindow appear at a corner

here is my code in html to generate marker and infowindow(with ruby on rails)
var marker=[]
function initMap() {
var latLng1 = new google.maps.LatLng(1.352083, 103.819836);
var myOptions = {
zoom: 12,
center: latLng1,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById('map_canvas'), myOptions);
for(i=0;i<gon.astatic.length;i++){
var latLng = new google.maps.LatLng(gon.astatic[i][1], gon.astatic[i][2]);
if(i<2){
marker[i] = new MarkerWithLabel({position: latLng, map: map,icon:"/assets/green_MarkerV.png" ,labelClass: "labels",labelContent: gon.astatic[i][3]});}
else
{
marker[i] = new MarkerWithLabel({position: latLng, map: map,icon:"/assets/green_MarkerN.png" ,labelClass: "labels",labelContent: gon.astatic[i][3]});
}
var iw =new google.maps.InfoWindow({content: 'HI' });
google.maps.event.addListener(marker[i],"mouseover",function(e){iw.open(map,marker[i]);})
}
this gon is just some 'stupid' method I use to pass data from ruby on rails controller to javascript.
for all marker,the infowindow all appear at corner.
But for my another map(which have only one marker with infowindow)it works fine.
What might be my problem?why this infowindow appear in wrong position?Instead of just above the marker?
EDIT:
After half day's trouble shoot,I feel the problem is at
google.maps.event.addListener(marker[i],"mouseover",function(e){iw.open(map,marker[i]);})
when the listener calls back,the value inside marker is i ,which is not a actual number,so the marker display at a corner.I feel the problem is can't pass variable into addListener,can only put in actual number.How to solve this?
Each instance of the function declared inside the for loop shares the same closure containing the value i, and so all of your addListener calls are essentially calling iw.open(map, undefined) since i will be off the end of the array at the end of the iteration.
See JavaScript closure inside loops – simple practical example for sample solutions to this problem, and How do JavaScript closures work for more information about closures in JavaScript in general.
The problem is with your MarkerWithLabel library. Infowindow take position from marker. Try use this link http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.8/docs/examples.html . It has all the things that you want to implement. It it's not work then you can also set position for infowindow with setPosition() function just pass latlng that you used to create marker and you are done.
i dont recommend using new gem just to pass data from ruby to js...you can do this simply by many ways...your code seems good but i cannot say how gon is handling your js script.Please take a look at this similar question where i have implemented the same dynamic map with dynamic markers and infowindows.This code is working great
see here

Javascript argument parameters and google maps

I'm introducing myself to Google Maps API, and to JavaScript as well.
I already have set my map on my website, and now I'm trying to dynamically set multiple markers on the same map.
For that, I wrote this function:
function teste(lat,long){
alert (lat);
alert (long);
//var companyPos3 = new google.maps.LatLng(41.545308,-8.421782);
var companyPos3 = new google.maps.LatLng(lat,long);
var companyMarker3 = new google.maps.Marker({
position: companyPos3,
map: map,
// icon: companyLogo,
title:"Mar!!!" });
//}
...
}
And my problem resides right here. I can't set the google.maps.LatLng with 'lat' and 'long' function parameters. However, the alert dialog messages pop up, showing the values that are from PHP.
I tried also to remove the alert function but the marker didn't show as well.
I'm not sure if it there is a Google Maps API trick, or JavaScript trick.
Oh, and if I set the values as the commented line, it works, but I don't want this solution :).
long is the reserved word in JS. Please check here. So, please try lng instead of long also for setting icon please see here. Try something like
function teste(lat,lng){
alert (lat);
alert (lng);
//var companyPos3 = new google.maps.LatLng(41.545308,-8.421782);
var companyPos3 = new google.maps.LatLng(lat,lng);
var companyMarker3 = new google.maps.Marker({
position: companyPos3,
map: map,
// icon: companyLogo,
title:"Mar!!!" });
//}
...
}

want to get the contents of infobubble of a marker?

I am using gmap v3. I am facing a problem.
The problem is that I am creating dynamically marker and at similar time I am creating the infowindow of that marker.
Now I want to add some contents in any infowindow of a marker.
But don't know how i can get the content of a infowindow.
I have stored my markers objects in a array and also infowindow's objects.
But not found any solution.
I want to get infowindow's content on the basis of marker.
EDIT:
var markerArray = new Array();
var infoArray = new Array();
function placemarker(point,id, contents){
var marker = new google.maps.Marker({
icon: image,
position: point,
map: map,
title: name
});
markerArray[id] = marker;
var infoBubble = new InfoBubble();
var content = contents;
infoBubble.setContent(content);
google.maps.event.addListener(marker,"mouseover",function(event){
for(var i=0; i < infoArray.length ; i++ )
infoArray[i].close();
infoBubble.open(map,marker);
});
infoArray.push(infoBubble);
}
This function is called within a function many time that create marker on map.
now on a condition two markers are at same lat long and I want to show single marker with infowindow of both markers content. I have able to create single marker but not able to append the content in a info window.
If you are already keeping all the InfoWindow's in your infoArray, why don't you simply store them with the same id as you do with your markers?
var markers = {};
var infoWindows = {};
function placemarker(point, id, contents) {
var marker = ...
...
markers[id] = marker;
var infoWindow = ...
...
infoWindows[id] = infoWindow;
}
(Note that I have replaced your arrays with hashes.)
Now you can access every marker's InfoWindow (and its content via getContent()) the same way you access the marker itself.
This is pretty old but figured I would give this a shot
I'm not sure what InfoBubble is for my example I'm using InfoWindow.
function createMarker(objPoint)
{
objPoint = new google.maps.LatLng(strLat, strLng);
strFromHtml = 'Your HTML';
//creates the window with the options
objInfoWindow = new google.maps.InfoWindow(
{
content: strToHtml,
maxWidth: 250,
zIndex: -1
});
var markerOptions =
{
position: objPoint,
map: objMap
}
objMarker = new google.maps.Marker(markerOptions);
//if the marker is clicked open the window
google.maps.event.addListener(objMarker, "click", function()
{
objInfoWindow.open(objMap, objMarker);
});
}
I first create the window, then the marker, I then attach a click listener for the marker to display the infowindow on it when the marker is clicked. You could take the click out and just display it but for my purposes I wanted them to have to click it.
EDIT
Ok I've reread your question about 50 times and I hope this solution is good. As for the multiple markers in one location I don't have an answer for that. I think I have an answer for the multiple markers with its own info. If this doesn't work then you are gonna have to create an example in jsfiddle or something otherwise I don't understand what you want. Check it out here jsfiddle
When creating the infoWindow you only create it once holding default content. So put it in the initialization of the map itself. Then when creating the markers you can use the marker itself to contain the html for the infowindow to show.
var markerOptions =
{
position: objPoint,
map: objMap,
html: strHtml//added to make the marker object store the content of the infowindow
}
Then in your mouseover listener you use this.html to get the content for the infowindow
google.maps.event.addListener(objMarker, "mouseover", function()
{
objInfoWindow.setContent(this.html);
objInfoWindow.open(objMap, this);
});
As for the multiple markers in one spot. I would have to say programmatically you are gonna have to catch this and only create one marker with the content of both in one marker.
I don't think it is possible to search for a marker in a google maps (which I think is what you are trying to do).
Do you need to create two markers when they anyway are on the same point? If no I would just keep track of the infoBubbles in an array where the point coordinates is the key. So before you create a new infoBubble you check if you already have one and if that is the case you just replace it with a new combined infoBubble.
Here is a code draft.
if(infoArray[point.lat+'_'+point.lng] != undefined){
tmpBubble = infoArray[point.lat+'_'+point.lng];
infoBubble = createCombinedBubble(tmpBubble, infoBubble);
}
infoArray[point.lat+'_'+point.lng] = infoBubble;

Google Maps V3: Only show markers in viewport - Clear markers issue

I like to create a map with Google Maps that can handle large amounts of markers (over 10.000). To not slow down the map I've created a XML-file that only outputs the markers that are inside the current viewport.
First, I use initialize() to setup the map options:
function initialize() {
var myLatlng = new google.maps.LatLng(51.25503952021694,3.27392578125);
var myOptions = {
zoom: 8,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
google.maps.event.addListener(map, 'tilesloaded', function () {
loadMapFromCurrentBounds(map);
});
}
When the event 'tilesloaded' is finished, I use loadMapFromCurrentBounds(), this functions will get the current bounds and sends a request to the XML-file to show the markers that are inside the current viewport:
function loadMapFromCurrentBounds(map) {
// First, determine the map bounds
var bounds = map.getBounds();
// Then the points
var swPoint = bounds.getSouthWest();
var nePoint = bounds.getNorthEast();
// Now, each individual coordinate
var swLat = swPoint.lat();
var swLng = swPoint.lng();
var neLat = nePoint.lat();
var neLng = nePoint.lng();
downloadUrl("mapsxml.php?swLat="+swLat+"&swLng="+swLng+"&neLat="+neLat+"&neLng="+neLng+"", function(data) {
var xml = parseXml(data);
var markers = xml.documentElement.getElementsByTagName("marker");
var infoWindow = new google.maps.InfoWindow;
for (var i = 0; i < markers.length; i++) {
var address = markers[i].getAttribute("address");
var type = markers[i].getAttribute("type");
var name = markers[i].getAttribute("name");
var point = new google.maps.LatLng(
parseFloat(markers[i].getAttribute("lat")),
parseFloat(markers[i].getAttribute("lng"))
);
var html = "<b>" + name + "</b> <br/>" + address;
var icon = customIcons[type] || {};
var marker = new google.maps.Marker({
map: map,
position: point,
icon: icon.icon,
shadow: icon.shadow});
bindInfoWindow(marker, map, infoWindow, html);
}
})
}
This is working great, however, the current code doesn't offload markers that aren't in de viewport anymore. Besides that, it loads markers again who are already loaded, that slows down the map really fast when moving the map a view times in the same area.
So when the viewport changes, I like to clear the whole map first before loading new markers. What is the best way to do this?
You need to add another Event Listener to the map:
google.maps.event.addListener(map,'bounds_changed', removeMarkers);
See here for more on removing all markers from a google map - unfortunately I dont think it can be done with one call. So you will have to write the removeMarkers or something similar which will have to iterate through all the markers on the map removing them individually like so:
markersArray[i].setMap(null);
I don't know whether it's quicker to check if the marker is in the viewport before removing by using:
map.getBounds();
Read more about Google Map API v3 events
Due to the following explanation using 'tilesloaded' or 'bounds_changed' would be very wrong and cause unwilling continuous firings. Instead you would want to use 'idle' event which will fire once the user has stopped panning/zooming.
google.maps.event.addListener(map, 'idle', loadMapFromCurrentBounds);
https://developers.google.com/maps/articles/toomanymarkers#viewportmarkermanagement
You may want to check out this thread. Daniel answered this quite nicely.
What's the most efficient way to create routes on google maps from gps files?
Also, bounds_changed is the first opportunity to call your function. tilesloaded, will be called constantly. The viewport may contain more than one tile to fill the viewport.
Alternatively, you can also do a setVisible(false).
In order to remove the marker, you may need to remove the listeners.
google.maps.event.clearInstanceListeners(marker);
marker.setMap(null);
markers.remove(marker);
delete marker;
This article goes through it pretty nicely:
Dynamically loading thousands of markers in Google Maps
dynamically load markers until we reach a threshold
keep a hashtable of markers that have already been added
after the threshold has been reached, remove markers that aren’t currently within the viewport
remove all markers from the map when the user has zoomed out, and don’t load any markers until the user zooms back to a reasonable level
Your original function seems like a lot of code. I'd do something like this:
if( map.getBounds().contains(markers[i].getPosition()) ) {
myMarkerDisplayFunction(markers[i]);
}
You might want to check out this documentation from Google. It explains what you need:
With the new list of markers you can remove the current markers
(marker.setMap(null)) that are on the map and
add the new ones (marker.setMap(map)).

Google Maps - Add two markers (directions)

Playing around with Google Maps these days, with some directions.
I have a map that gets the directions and address (reverse-geocoding) when dragging and dropping the markers.
If there is two nodes on the map (http://dev.korebogen.dk/gmap/) the script is working fine (click set directions) - but I need to add a click event so I can place those two markers instead of hard-code the location by hand, but still be able to drag them around - or place new with new click. But I only need A to B markers.
Ive been playing around with some click events, but I can not seem to accomplish what I am looking for - hope to see some help here. Thank you very much.
This code will allow you to click and place two markers, which you can then use to load GDirections, and remove the original markers. Note that you must use this format for the query string: "from: marker#35,-25 to: marker#-20,15".
var markerArray = [];
var listener = GEvent.addListener(map, "click", function(overlay, latlng) {
var marker = new GMarker(latlng, { draggable: true });
map.addOverlay(marker);
markerArray.push(marker);
if (markerArray.length > 1) {
GEvent.removeListener(listener);
var marker1 = markerArray[0];
var marker2 = markerArray[1];
gdir.load("from: marker1#" + marker1.getLatLng() + " to: marker2#" + marker2.getLatLng());
map.removeOverlay(marker1);
map.removeOverlay(marker2);
}
});

Categories