EGMap 2.0 Google Maps Extension

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); 	

        };

};



Greetings!

Thank you for sharing! If this goes this way, I will have to write a pdf wiki for this extension only :)

Thanks to all

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.

nereia,

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:




thingMarkers[markerId] = marker;

cluster.addMarker(marker);



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.

Glad I helped. We will wait for your final update.

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');




Example of using Panoramio





$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

);

$gMap->setCenter(34.04924594193164, -118.24104309082031);


$gMap->setLayer(new EGMapLayer(EGMapLayer::PANORAMIO));


$gMap->renderMap(array(),'en','ES');



Example of using Circle overlay




$gMap = new EGMap();

$gMap->setWidth(588);

$gMap->setHeight(345);

$gMap->zoom = 6;

$gMap->mapTypeControlOptions = array(

 'position'=> EGMapControlPosition::RIGHT_TOP,

 'style'=>EGMap::MAPTYPECONTROL_STYLE_DROPDOWN_MENU

);

$gMap->setCenter(34.04924594193164, -118.24104309082031);


$circle = new EGMapCircle(new EGMapCoord(34.04924594193164, -118.24104309082031));

$circle->radius = 300000;

$circle->addHtmlInfoWindow(new EGMapInfoWindow('Hey! I am a circlel!'));

$gMap->addCircle($circle);


$gMap->renderMap(array(),'en','ES');



Support for Rectangle Overlay has been included. Following an example:




$gMap = new EGMap();

$gMap->setWidth(588);

$gMap->setHeight(345);

$gMap->zoom = 3;

$gMap->mapTypeControlOptions = array(

 'position'=> EGMapControlPosition::RIGHT_TOP,

 'style'=>EGMap::MAPTYPECONTROL_STYLE_DROPDOWN_MENU

);

$gMap->setCenter(25.774252, -80.190262);


$bounds = new EGMapBounds(new EGMapCoord(25.774252, -80.190262),new EGMapCoord(32.321384, -64.75737) );

$rec = new EGMapRectangle($bounds);

$rec->addHtmlInfoWindow(new EGMapInfoWindow('Hey! I am a rectangle!'));

$gMap->addRectangle($rec);




$gMap->renderMap(array(),'en','ES');



All updates not yet included on the extension repository. To access these features, please download from SVN.

@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.

I am :)

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.

Thanks for sharing nereia…

Hi, Antonio

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?

Best Regards

Peng

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.

Check my article here: http://www.ramirezcobos.com/2011/02/05/a-reverse-geolocator-tool-with-egmap-2-0-extension/

Maybe you get some inspiration…

About your Polyline… would be nice to see.

PS: I highly recommend you to download the SVN from the google code project. The reverse geolocator has been updated by Say_Ten.

[/code]

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

################################################################

$polylines = array();

foreach($gpsData as $gpsLocation){

// Create marker


&#036;marker = new EGMapMarker(&#036;gpsLocation-&gt;lat, &#036;gpsLocation-&gt;lon, array('title' =&gt; 'Marker'));


&#036;gMap-&gt;addMarker(&#036;marker);





&#036;coords[] = new EGMapCoord(&#036;gpsLocation-&gt;lat, &#036;gpsLocation-&gt;lon);

}

$polylines = new EGMapPolyline($coords);

//adding the polylines

$gMap->addPolyline($polylines);

$gMap->centerOnPolylines();

$gMap->zoomOnPolylines(0.1);

$gMap->renderMap();

##################################################################

I wasn’t allowed to add a link as a newbie??.. *^$%@! funny… ;)

thanks

Peng

I found the problem, the infoWindow cannot be initialized when we loading the google map.

so it could be defiend as a null object, and then assgin the new google.maps.InfoWindow({content:"content"}) to it with CCleintScript.

thanks again Antonio!

Regards

Peng

Thanks for sharing :)

I’m trying to display the map in a fancybox, but it doesn’t render fully. Does anyone else have this problem too?

Hello!

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