I think that here there is a mistake. To delete the markers with the "splice" function you must delete first the uppest index because in the another way the array changes the length and the index of the components and you only delete some index…
This is my code, it works to represent a lat, lon arrays and delete the old markers in an ajax function:
function clearMarkers(){
for (var j=markers.length; j>=0 ; j--){
cluster.removeMarker(markers[j]);
markers.splice(j,1);
}
};
function makeAjaxRequest(lat,lon,n){
var center = EGMap0.getCenter();
var lt=lat;
var lng=lon;
var nr=n;
// Create a cluster if it doesn't exist already
if(!cluster) {
cluster = new MarkerClusterer(EGMap0, {}); }
else clearMarkers();
for (var j=0; j<nr; j++) {
var latlng = new google.maps.LatLng(lt[j],lng[j]);
var marker = new google.maps.Marker({ position: latlng,
title: "hola"});
// Add the marker to the markers array
markers.push(marker);
// Add the marker to the cluster
cluster.addMarker(marker);
};
};
Hmm I don’t know, this is something that must be done after the initialization, but there is nothing like this on the other parts of the code, I think that it would deviate from the (very well done) class design, maybe there could be some javascript helper class, which could generate some frequently used initialization functions?
The Javascript helper class is something that is ticking in my head since long time a go, as I am not very happy with the way Javascript code is rendered. The JS code is good like that when you need to debug, but I believe that it could be much better. For example:
One js function to add markers to the map
All markers lon lat as an array, together with its info window contents *if any.
JS minified specified by coder
most used js functions -great idea
AJAX updates functions
etc
I have that in mind on next release
BTW: You know, that anybody is more than welcome to have input to the project.
I did some major re-factoring in my code over the weekend which I think has improved the performance quite a bit. I was using too much array traversal that was killing performance. I changed to using an associative array to keep track of the markers instead of the idea of using the marker’s zIndex as a unique ID. As I said before, I’ve never coded in JavaScript before so it’s really a big learning experience.
As for the listeners, Antonio is right; bounds_changed is HORRIBLE and slow. I was using it at first but realized that it triggers way too much. Now I listen for three events:
$zc = new EGMapEvent('zoom_changed', 'getListings();', true, EGMapEvent::TYPE_EVENT_DEFAULT);
$de = new EGMapEvent('dragend', 'getListings();', true, EGMapEvent::TYPE_EVENT_DEFAULT);
$tl = new EGMapEvent('tilesloaded', 'getListings();', true, EGMapEvent::TYPE_EVENT_DEFAULT);
$gMap->addEvent($de);
$gMap->addEvent($zc);
$gMap->addEvent($tl);
$gMap->renderMap();
You definitely need to listen for ‘tilesloaded’ or else you won’t be able to get the map bounds. The combination of ‘dragend’ and ‘zoom_changed’ works much much faster than ‘bounds_changed’ since they only trigger at the end of each event.
I am still cleaning up a lot of my code. I will share as much as possible shortly.
This should work fine, but it will pose some problems if you have infowindows attached to each marker. I ran into that problem with my implementation. When you click on a marker that has an infowindow, when the infowindow pops up, a lot of time the the map is dragged automatically so that the infowindow can fit on the screen. This changes the map’s boundaries and triggers the ajaxfunction again to get the items for the boundaries. At this point, you are clearing all your markers that were on the screen (including the one that was clicked). Now, the marker that you had clicked previously to open the infowindow no longer exists because it was destroyed. So now the infowindow disappears off the screen. My solution for this is to NOT clear all your markers. I have a global associative array that keeps track of the markers:
var thingMarkers = new Array();
When i trigger the ajaxRequest, I create a marker for the item I’m getting and then set the markerId like so:
var markerId = 'thing' + thingId; // thingId is the unique record id from the db. I add the word "thing" to it so that I can use it as an associative array element (actually an object property).
then i check to see if this already exists in my global thingMarkers array:
if (thingMarkers){
if(thingMarkers[markerId]){
markerExists = true;
}
Then, if either the thingMarkers array itself doesn’t exist or if the marker does not exist, I add it to the array and to the cluster:
later in the ajax request function, I check to see if all the items in my cluster and in the thingMarkers array are still within map bounds:
if(thingMarkers){
for (var j in thingMarkers){
var marker = thingMarkers[j];
var markerPosition = marker.getPosition();
var markerLat = markerPosition.lat();
var markerLng = markerPosition.lng();
if ((boundsSwLat < markerLat && markerLat < boundsNeLat) && (boundsSwLng < markerLng && markerLng < boundsNeLng)){
//Marker is within bounds. Leave it alone
}else{
// Marker is out of bounds. Remove it from the global bookMarkers array and the cluster.
delete thingMarkers[j];
cluster.removeMarker(marker);
}
}
}
With the above scenario, you are only adding new markers and deleting any that go out of bounds. Any infowindows stay in tact because you don’t destroy the objects by clearing them each time. Also, I’ve noticed that if you clear the markers each time you move around the map, the icons tend to blink which is a bit annoying.
To all of you interested. Even though new EGmap version is not yet done, we have done some cool changes to the library (you can download final updates on SVN source at google code -check repository).
Updated reverse geocoding to v3 of the Google API. (Thanks to committer Say_Ten)
Added support to polygons
Added support for Layers (Panoramio, Bicycling, and Traffic)
Added support for circles
Example to use Polygons
$gMap = new EGMap();
$gMap->setWidth(588);
$gMap->setHeight(345);
$gMap->zoom = 14;
$gMap->mapTypeControlOptions = array(
'position'=> EGMapControlPosition::RIGHT_TOP,
'style'=>EGMap::MAPTYPECONTROL_STYLE_DROPDOWN_MENU
);
$coords = array();
$coords[] = new EGMapCoord(25.774252, -80.190262);
$coords[] = new EGMapCoord(18.466465, -66.118292);
$coords[] = new EGMapCoord(32.321384, -64.75737);
$coords[] = new EGMapCoord(25.774252, -80.190262);
$polygon = new EGMapPolygon($coords);
$gMap->addPolygon($polygon);
$gMap->centerOnPolygons();
$gMap->zoomOnPolygons(0.1);
$gMap->renderMap(array(),'en','ES');
@TwinMoons: I understand what you mean but I don’t have to do the same as you. I don’t have attached windows and my markers move periodically and this is why I delete all the markers because they are continously moving when the people change their position. My example is a tracking service, a daemon writes in a database the tracking information and I showed it in the map in three different ways (show all the users, only friends or only team members). I share the code because I think that maybe can help anyone, I did it following your post (thanks for it!) and I found that the splice function was used in a wrong way.
Now, I reload the markers periodically without any user interaction, by calling a controller function that reads from the database and return the data. I’ll share this if anyone is interested.
Ok, I did it in a very simple way and maybe is not the best one but it works!
//Function javascript that calls the controller
function refresh(){
jQuery.ajax({
url: "/rwu/index.php/Trackingphp/filter/",
type: 'POST',
success: function(response,status)
{
resp=$.parseJSON(response);
var ids=resp.ids;
var lat=resp.lat;
var lon=resp.lon;
var n=lat.length;
makeAjaxRequest(lat,lon,n);
}
});
};
/*******************************************************************************************/
echo CHtml::radioButtonList('filters','',$data,
array('onchange'=>CHtml::ajax(
array(
'type'=>'POST',
'url'=>array("Trackingphp/filter"),
'success' => 'function (response,status)
{
var timer;
clearInterval(timer);
resp=$.parseJSON(response);
var ids=resp.ids;
var lat=resp.lat;
var lon=resp.lon;
var n=lat.length;
makeAjaxRequest(lat,lon,n);
timer=setInterval("refresh()", 10000);
}',)
)
)
);
And in the controller (Some times I have to pass a variable with post method sometimes only reload with the before value):
public function actionFilter()
{
if (Yii::app()->user->isGuest){
$this->actionInit(); //busca siempre todos los usuarios
}
else{
if(isset($_POST['filters'])) {
$f=$_POST['filters'];
User::model()->updateByPk((Yii::app()->user->id),array('status'=>$f));}
else $f=User::model()->findByPk(Yii::app()->user->id)->status;
$i=0;
$ids=array();
$lat=array();
$lon=array();
$time=new CDbExpression('NOW()');
$actualtime=strtotime(date('H:i:s'));
$usermodel=User::model()->findByPk(Yii::app()->user->id);
switch ($f) {
case 0:
$this->actionInit();
break;
case 1:
$myfriends=$usermodel->friends;
//lazy loading approach devuelve todos sus amigos
if (!empty($myfriends)) {
foreach ($myfriends as $friend) {
//si corrio alguna vez y si está activo en este momento
$us=User::model()->findByPk($friend->id_user2);
if ($us!=null){
$active=$us->active;
$temp=strtotime($us->temp);
if (($active=='1')and($temp>$actualtime)){
if (Tracking::model()->lastposition($friend->id_user2)->exists()){
$ids[$i]=$friend->id_user2;
$tracking=Tracking::model()->lastposition($friend->id_user2)->find();
$lat[$i]=$tracking->latitude;
$lon[$i]=$tracking->longitude;
$i++;
}
}
}
}
$data=array('id'=>$ids,'lat'=>$lat,'lon'=>$lon);
echo CJSON::encode($data);
}
break;
case 2:
$teamodel=Team::model()->findByPk($usermodel->team_idteam);
$teamfriends=$teamodel->users; //lazy loading again
if (!empty($teamfriends)) {
foreach ($teamfriends as $us) {
//$teamfriends son todos los usuarios con el mismo equipo
//hay que comprobar que estén activos
if ($us!=null){
$active=$us->active;
$temp=strtotime($us->temp);
if (($active=='1')and($temp>$actualtime)){
if (Tracking::model()->lastposition($us->idusers)->exists()){
$ids[$i]=$us->idusers;
$tracking=Tracking::model()->lastposition($us->idusers)->find();
$lat[$i]=$tracking->latitude;
$lon[$i]=$tracking->longitude;
$i++;
}
}
}
}
}
$data=array('id'=>$ids,'lat'=>$lat,'lon'=>$lon);
echo CJSON::encode($data);
break;
}
}
}
And the periodical refresh is done with "timer=setInterval("refresh()", 10000);" and in this case is every 10 seconds.
I’m new to this extension, and not sure if there’s any way to pull out the reverse geo address with latlng with an ajax call, for example, I may have 200 markers on the map, and I don’t wanna render all the infoWindow with reverse Geo address when map is initialized, because it gonna use up all my daily geocode quota. Is there any easy way to pull back the address with an ajax call into an info window when user click on the marker ?
also I added the Polyline support, it is totally based on your Polygons file, shall I uploaded it here?
The best way to do it (now and before I have a bit of time to update the extension) is to attach an even to every marker and make a call to the Info
// TOTALLY UNTESTED
// this function should be written in the doc somewhere
// The $YourEGMapObject is directly related to this function
// It is not a general function but one directly linked to
// that object name
$YourEGMapObject = new EGMap();
// Add global variable to hold the global infowindow
$YourEGMapObject->addGlobalVariable($YourEGMapObject->getJsName().'_info_window','new google.maps.InfoWindow()');
// ....
// ....
// ....
// Here is with echo but you can use CClientScript too
echo "function load_content(marker){
var latLng = marker.getPosition();
$.ajax({
'type':'POST',
'url':'".$this->createUrl('CONTROLLER/ACTION')."',
'data'<img src='http://www.yiiframework.com/forum/public/style_emoticons/default/sad.gif' class='bbc_emoticon' alt=':(' />{'lat': latLng.lat(), 'lng': latLng.lng()}),
'cache':false,
'success': function(data){
var info_window = ".$YourEGMapObject->getJsName()."_info_window; // this is the global name for shared window ((js name of map)_info_window)
if(info_window) info_window.close(); // i close it but maybe change the content by a loader.gif?
info_window.setContent(data);
info_window.open(".$YourEGMapObject->getJsName().",marker);
}
});
}";
// now that we having the general function
// we should attach an event to every marker
// *note: do not add any info_window object
$event = new EGMapEvent('click','load_content(this);');
$marker->addEvent($event);
// On the server side it is just a matter to make use of
// the EGMapGeocodedAddress and EGMapClient to
// get reverse geocoding info from a given lat and lng
That solution is if you wish to use AJAX but there are some other ways to do it, on the client side to get the Reverse Geocoding Info. It is just a matter of using geocoder object.
Thanks Antonio for your quck reply, it looks neat and clean, I try to add it into the CClientScript, and it throw a error "google.maps.InfoWindow is not a constructor", and it seems it happened to others because they uses Google map v2, which does not have google.maps.InfoWindow constructor, I see your extension uses v3, any ideas?
also those are the polylines file, and a few copy and modified code in the EGMap.php, and using like below
Nobody else has had any IE error using this extension? I have an error with main.js from google api.
This is the JS error detail:
user agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; Ant.com Toolbar 1.6; GTB7.1; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 1.1.4322; .NET CLR 3.5.21022; MEGAUPLOAD 3.0; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET4.0C; .NET4.0E)
Fecha: Wed, 29 Jun 2011 10:31:31 UTC
message: 'null' is null or not an object
line: 30
character: 1384
code: 0
URI: http://maps.gstatic.com/intl/es_es/mapfiles/api-3/5/8/main.js