1 /**
  2  * Mapstraction implementation for Geo Mashup maps.
  3  * @fileOverview
  4  */
  5 
  6 /**
  7  * @name AjaxRequestOptions
  8  * @class This type represents options used for an AJAX request.
  9  * It has no constructor, but is instantiated as an object literal.
 10  *
 11  * @property {String} url The AJAX request URL.
 12  */
 13 
 14 /**
 15  * @name ContentFilter
 16  * @class This type represents objects used to filter content.
 17  * It has no constructor, but is instantiated as an object literal.
 18  * 
 19  * @name ContentFilter#content
 20  * @property {String} content HTML content to filter.
 21  */
 22 
 23 /*global GeoMashup */
 24 /*global customizeGeoMashup, customizeGeoMashupMap, customGeoMashupColorIcon, customGeoMashupCategoryIcon */
 25 /*glboal customGeoMashupSinglePostIcon, customGeoMashupMultiplePostImage */
 26 /*global mxn */
 27 
 28 GeoMashup.loadFullPost = function( point ) {
 29 	var i, request, objects, object_ids;
 30 
 31 	this.getShowPostElement().innerHTML = '<div align="center"><img src="' +
 32 		this.opts.url_path + '/images/busy_icon.gif" alt="Loading..." /></div>';
 33 	object_ids = [];
 34 	objects = this.getObjectsAtLocation( point );
 35 	for( i = 0; i < objects.length; i += 1 ) {
 36 		object_ids.push( objects[i].object_id );
 37 	}
 38 	request = {
 39 		url: this.geo_query_url + '&object_name=' + this.opts.object_name +
 40 			'&object_ids=' + object_ids.join( ',' ) + '&template=full-post'
 41 	};
 42 	/**
 43 	 * Requesting full post content.
 44 	 * @name GeoMashup#fullPostRequest
 45 	 * @event
 46 	 * @param {Array} objects Objects included in the request
 47 	 * @param {AjaxRequestOptions} options
 48 	 */
 49 	this.doAction( 'fullPostRequest', objects, request );
 50 	jQuery.get( request.url, function( content ) {
 51 		var filter = {content: content};
 52 		/**
 53 		 * Loading full post content.
 54 		 * @name GeoMashup#fullPostLoad
 55 		 * @event
 56 		 * @param {Array} objects Objects included in the request
 57 		 * @param {ContentFilter} filter 
 58 		 */
 59 		GeoMashup.doAction( 'fullPostLoad', objects, filter );
 60 		jQuery( GeoMashup.getShowPostElement() ).html( filter.content );
 61 		/**
 62 		 * The full post display has changed.
 63 		 * @name GeoMashup#fullPostChanged
 64 		 * @event
 65 		 */
 66 		GeoMashup.doAction( 'fullPostChanged' );
 67 	} );
 68 };
 69 
 70 GeoMashup.createCategoryLine = function ( category ) {
 71 	// Polylines are close, but the openlayers implementation at least cannot hide or remove a polyline
 72 	var options = {color: category.color, width: 5, opacity: 0.5};
 73 	category.line = new mxn.Polyline(category.points);
 74 	/**
 75 	 * A category line was created.
 76 	 * @name GeoMashup#categoryLine
 77 	 * @event
 78 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
 79 	 * @param {Polyline} line
 80 	 */
 81 	this.doAction( 'categoryLine', this.opts, category.line );
 82 	/**
 83 	 * A category will be added with the given options.
 84 	 * @name GeoMashup#categoryLineOptions
 85 	 * @event
 86 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
 87 	 * @param {Object} options Modifiable <a href="http://mapstraction.github.com/mxn/build/latest/docs/symbols/mxn.Polyline.html#addData">Mapstraction</a>
 88 	 *   or <a href="http://code.google.com/apis/maps/documentation/javascript/v2/reference.html#GPolylineOptions">Google</a> Polyline options
 89 	 */
 90 	this.doAction( 'categoryLineOptions', this.opts, options );
 91 	this.map.addPolylineWithData( category.line, options );
 92 	if (this.map.getZoom() > category.max_line_zoom) {
 93 		try {
 94 			category.line.hide();
 95 		} catch( e ) {
 96 			// Not implemented?
 97 			this.map.removePolyline( category.line );
 98 		}
 99 	}
100 };
101 
102 GeoMashup.openInfoWindow = function( marker ) {
103 	var objects, request, object_ids = [], i, object_element, point = marker.location;
104 
105 	if ( this.open_window_marker && !this.opts.multiple_info_windows ) {
106 		this.open_window_marker.closeBubble();
107 	}
108 	if ( this.locations[point].loaded ) {
109 		marker.openBubble();
110 	} else {
111 		marker.setInfoBubble( '<div align="center"><img src="' + this.opts.url_path + 
112 			'/images/busy_icon.gif" alt="Loading..." /></div>' );
113 		marker.openBubble();
114 		this.open_window_marker = marker;
115 		// Collect object ids
116 		objects = this.getObjectsAtLocation( point );
117 		for( i = 0; i < objects.length; i += 1 ) {
118 			object_ids.push( objects[i].object_id );
119 		}
120 		// Do an AJAX query to get content for these objects
121 		request = {
122 			url: this.geo_query_url + '&object_name=' + this.opts.object_name +
123 				'&object_ids=' + object_ids.join( ',' ) 
124 		};
125 		/** 
126 		 * A marker's info window content is being requested.
127 		 * @name GeoMashup#markerInfoWindowRequest
128 		 * @event
129 		 * @param {Marker} marker
130 		 * @param {AjaxRequestOptions} request Modifiable property: url
131 		 */
132 		this.doAction( 'markerInfoWindowRequest', marker, request );
133 		jQuery.get( 
134 			request.url,
135 			function( content ) {
136 				var filter = {content: content};
137 				marker.closeBubble();
138 				/**
139 				 * A marker info window content is being loaded.
140 				 * @name GeoMashup#markerInfoWindowLoad
141 				 * @event
142 				 * @param {Marker} marker
143 				 * @param {ContentFilter} filter Modifiable property: content
144 				 */
145 				GeoMashup.doAction( 'markerInfoWindowLoad', marker, filter );
146 				marker.setInfoBubble( GeoMashup.parentizeLinksMarkup( filter.content ) );
147 				marker.openBubble();
148 			}
149 		); 
150 	}
151 };
152 
153 GeoMashup.closeInfoWindow = function( marker ) {
154 	marker.closeBubble();
155 };
156 
157 GeoMashup.addGlowMarker = function( marker ) {
158 	var point = marker.location, 
159 		glow_options = {
160 			clickable : true,
161 			icon : this.opts.url_path + '/images/mm_20_glow.png',
162 			iconSize : [ 22, 30 ],
163 			iconAnchor : [ 11, 27 ] 
164 		};
165 
166 	if ( this.glow_marker ) {
167 		this.removeGlowMarker();
168 	} 
169 	/** 
170 	 * A highlight "glow" marker is being created.
171 	 * @name GeoMashup#glowMarkerIcon
172 	 * @event
173 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
174 	 * @param {Object} glow_options Modifiable <a href="http://mapstraction.github.com/mxn/build/latest/docs/symbols/mxn.Marker.html#addData">Mapstraction</a> 
175 	 *   or <a href="http://code.google.com/apis/maps/documentation/javascript/v2/reference.html#GMarkerOptions">Google</a> marker options
176 	 */
177 	this.doAction( 'glowMarkerIcon', this.opts, glow_options );
178 	this.glow_marker = new mxn.Marker( point );
179 	this.glow_marker.addData( glow_options );
180 	this.glow_marker.click.addHandler( function() {
181 		GeoMashup.deselectMarker();
182 	} );
183 	this.map.addMarker( this.glow_marker );
184 };
185 
186 GeoMashup.removeGlowMarker = function() {
187 	if ( this.glow_marker ) {
188 		this.glow_marker.hide();
189 		this.map.removeMarker( this.glow_marker );
190 		this.glow_marker = null;
191 	}
192 };
193 
194 GeoMashup.hideAttachments = function() {
195 	var i, j, obj;
196 
197 	/* No removeOverlay (yet)
198 	for ( i = 0; i < this.open_attachments.length; i += 1 ) {
199 		this.map.removeOverlay( this.open_attachments[i] );
200 	} 
201 	this.open_attachments = [];
202 	*/
203 };
204 
205 GeoMashup.showMarkerAttachments = function( marker ) {
206 	var i, j, objects, object_ids=[], ajax_params = {action: 'geo_mashup_kml_attachments'};
207 
208 	this.hideAttachments(); // check support
209 	objects = this.getObjectsAtLocation( marker.location );
210 	for ( i = 0; i < objects.length; i += 1 ) {
211 		object_ids.push( objects[i].object_id );
212 	}
213 	ajax_params.post_ids = object_ids.join( ',' );
214 	jQuery.getJSON( this.opts.ajaxurl + '?callback=?', ajax_params, function( data ) {
215 		jQuery.each( data, function( i, url ) {
216 			GeoMashup.open_attachments.push( url );
217 			GeoMashup.map.addOverlay( url );
218 		} );
219 	} );
220 };
221 
222 GeoMashup.addObjectIcon = function( obj ) {
223 	if (typeof customGeoMashupCategoryIcon === 'function') {
224 		obj.icon = customGeoMashupCategoryIcon(this.opts, obj.categories);
225 	} 
226 	if (!obj.icon) {
227 		if (obj.categories.length > 1) {
228 			obj.icon = this.clone(this.multiple_category_icon);
229 		} else if (obj.categories.length === 1) {
230 			obj.icon = this.clone(this.categories[obj.categories[0]].icon);
231 		} else {
232 			obj.icon = this.colorIcon( 'red' );
233 		} 
234 		/**
235 		 * An icon is being assigned to an object.
236 		 * @name GeoMashup#objectIcon
237 		 * @event
238 		 * @param {GeoMashupOptions} properties Geo Mashup configuration data
239 		 * @param {GeoMashupObject} object 
240 		 */
241 		this.doAction( 'objectIcon', this.opts, obj );
242 	}
243 };
244 
245 GeoMashup.createMarker = function(point,obj) {
246 	var marker, marker_opts;
247 
248 	if ( !obj.icon ) {
249 		this.addObjectIcon( obj );
250 	}
251 	marker_opts = {
252 		label: obj.title, 
253 		icon: obj.icon.image,
254 		iconSize: obj.icon.iconSize,
255 		iconShadow: obj.icon.iconShadow,
256 		iconAnchor: obj.icon.iconAnchor,
257 		iconShadowSize: obj.icon.shadowSize,
258 		visible: true
259 	};
260 	/**
261 	 * A marker is being created for an object.
262 	 * @name GeoMashup#objectMarkerOptions
263 	 * @event
264 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
265 	 * @param {Object} glow_options Modifiable <a href="http://mapstraction.github.com/mxn/build/latest/docs/symbols/mxn.Marker.html#addData">Mapstraction</a> 
266 	 *   or <a href="http://code.google.com/apis/maps/documentation/javascript/v2/reference.html#GMarkerOptions">Google</a> marker options
267 	 * @param {GeoMashupObject} object
268 	 */
269 	this.doAction( 'objectMarkerOptions', this.opts, marker_opts, obj );
270 	marker = new mxn.Marker( point );
271 	marker.addData( marker_opts );
272 
273 	marker.click.addHandler( function() {
274 		// Toggle marker selection
275 		if ( marker == GeoMashup.selected_marker ) {
276 			GeoMashup.deselectMarker();
277 		} else {
278 			GeoMashup.selectMarker( marker );
279 		}
280 	} ); 
281 
282 	/**
283 	 * A marker was created.
284 	 * @name GeoMashup#marker
285 	 * @event
286 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
287 	 * @param {Marker} marker
288 	 */
289 	this.doAction( 'marker', this.opts, marker );
290 
291 	return marker;
292 };
293 
294 GeoMashup.clickObjectMarker = function( object_id, try_count ) {
295 	var obj = this.objects[object_id];
296 	if (typeof try_count === 'undefined') {
297 		try_count = 1;
298 	}
299 	if ( obj && obj.marker && try_count < 4 ) {
300 		// openlayers/mxn seems to have trouble displaying an infobubble right away
301 		if ( try_count < 2 ) {
302 			try_count += 1;
303 			setTimeout(function () {GeoMashup.clickObjectMarker(object_id, try_count);}, 1000);
304 		} else {
305 			obj.marker.click.fire();
306 		}
307 	}
308 };
309 
310 GeoMashup.colorIcon = function( color_name ) {
311 	var icon = this.clone( this.base_color_icon );
312 	icon.image = this.opts.url_path + '/images/mm_20_' + color_name + '.png';
313 	return icon;
314 };
315 
316 GeoMashup.getMarkerLatLng = function( marker ) {
317 	return marker.location;
318 };
319 
320 GeoMashup.hideMarker = function( marker ) {
321 	if ( marker == this.selected_marker ) {
322 		this.deselectMarker();
323 	}
324 	marker.hide();
325 };
326 
327 GeoMashup.showMarker = function( marker ) {
328 	marker.show();
329 };
330 
331 GeoMashup.hideLine = function( line ) {
332 	try {
333 		line.hide();
334 	} catch( e ) {
335 		this.map.removePolyline( line );
336 	}
337 };
338 
339 GeoMashup.showLine = function( line ) {
340 	try {
341 		line.show();
342 	} catch( e ) {
343 		this.map.addPolyline( line );
344 	}
345 };
346 
347 GeoMashup.newLatLng = function( lat, lng ) {
348 	return new mxn.LatLonPoint( lat, lng );
349 };
350 
351 GeoMashup.extendLocationBounds = function( latlng ) {
352 	if ( this.location_bounds ) {
353 		this.location_bounds.extend( latlng );
354 	} else {
355 		this.location_bounds = new mxn.BoundingBox( latlng, latlng );
356 	}
357 };
358 
359 GeoMashup.addMarkers = function( markers ) {
360 	this.forEach( markers, function( i, marker ) {
361 		this.map.addMarker( marker );
362 	} );
363 };
364 
365 GeoMashup.makeMarkerMultiple = function( marker ) {
366 	var plus_image;
367 	if (typeof customGeoMashupMultiplePostImage === 'function') {
368 		plus_image = customGeoMashupMultiplePostImage(this.opts, marker);
369 	}
370 	if (!plus_image) {
371 		plus_image = this.opts.url_path + '/images/mm_20_plus.png';
372 	}
373 	marker.setIcon( plus_image );
374 	/** 
375 	 * A marker representing multiple objects was created.
376 	 * @name GeoMashup#multiObjectMarker
377 	 * @event
378 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
379 	 * @param {Marker} marker
380 	 */
381 	this.doAction( 'multiObjectMarker', this.opts, marker );
382 	/** 
383 	 * A marker representing multiple objects was created with this icon.
384 	 * @name GeoMashup#multiObjectIcon
385 	 * @event
386 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
387 	 * @param {String} plus_image Icon URL
388 	 */
389 	this.doAction( 'multiObjectIcon', this.opts, plus_image );
390 };
391 
392 GeoMashup.autoZoom = function() {
393 	var max_zoom;
394 	this.map.autoCenterAndZoom();
395 	max_zoom = parseInt( this.opts.auto_zoom_max, 10 );
396 	if ( this.map.getZoom() > max_zoom ) {
397 		this.map.setZoom( max_zoom );
398 	} 
399 };
400 
401 GeoMashup.isMarkerVisible = function( marker ) {
402 	var map_bounds;
403 	try {
404 		map_bounds = this.map.getBounds();
405 	} catch( e ) {
406 		// No bounds available yet, no markers are visible
407 		return false;
408 	}
409 	return ( marker.getAttribute( 'visible' ) && map_bounds && map_bounds.contains( marker.location ) ); 
410 };
411 
412 GeoMashup.centerMarker = function( marker, zoom ) {
413 	if ( typeof zoom === 'number' ) {
414 		this.map.setCenterAndZoom( marker.location, zoom );
415 	} else {
416 		this.map.setCenter( marker.location, {}, true );
417 	}
418 };
419 
420 GeoMashup.createMap = function(container, opts) {
421 	var i, type_num, center_latlng, map_opts, map_types, request, url, objects, point, marker_opts, 
422 		clusterer_opts, single_marker, ov, credit_div, initial_zoom = 1, controls = {}, filter = {};
423 
424 	this.container = container;
425 	this.base_color_icon = {};
426 	this.base_color_icon.image = opts.url_path + '/images/mm_20_black.png';
427 	this.base_color_icon.iconShadow = opts.url_path + '/images/mm_20_shadow.png';
428 	this.base_color_icon.iconSize = [12, 20];
429 	this.base_color_icon.shadowSize =  [22, 20];
430 	this.base_color_icon.iconAnchor = [6, 20];
431 	this.base_color_icon.infoWindowAnchor = [5, 1];
432 	this.multiple_category_icon = this.clone( this.base_color_icon );
433 	this.multiple_category_icon.image = opts.url_path + '/images/mm_20_mixed.png';
434 
435 	// Falsify options to make tests simpler
436 	this.forEach( opts, function( key, value ) {
437 		if ( 'false' === value || 'FALSE' === value ) {
438 			opts[key] = false;
439 		}
440 	} );
441 
442 	// See if we have access to a parent frame
443 	this.have_parent_access = false;
444 	try {
445 		if ( typeof parent === 'object' ) {
446 			// Try access, throws an exception if prohibited
447 			parent.document.getElementById( 'bogus-test' );
448 			// Access worked
449 			this.have_parent_access = true;
450 		}
451 	} catch ( parent_exception ) { }
452 
453 	// For now, siteurl is the home url
454 	opts.home_url = opts.siteurl;
455 
456 	map_types = {
457 		'G_NORMAL_MAP' : mxn.Mapstraction.ROAD,
458 		'G_SATELLITE_MAP' : mxn.Mapstraction.SATELLITE,
459 		'G_HYBRID_MAP' : mxn.Mapstraction.HYBRID,
460 		'G_PHYSICAL_MAP' : mxn.Mapstraction.PHYSICAL
461 	};
462 
463 	if (typeof opts.map_type === 'string') {
464 		if ( map_types[opts.map_type] ) {
465 			opts.map_type = map_types[opts.map_type] ;
466 		} else {
467 			type_num = parseInt(opts.map_type, 10);
468 			if ( isNaN(type_num) || type_num > 2 ) {
469 				opts.map_type = map_types.G_NORMAL_MAP;
470 			} else {
471 				opts.map_type = type_num;
472 			}
473 		}
474 	} else if (typeof opts.map_type === 'undefined') {
475 		opts.map_type = map_types.G_NORMAL_MAP;
476 	}
477 	this.map = new mxn.Mapstraction( this.container, opts.map_api );
478 	map_opts = {enableScrollWheelZoom: true, enableDragging: true};
479 	if ( 'enableGeoMashupExtras' in this.map ) {
480 		this.map.enableGeoMashupExtras();
481 	}
482 	/**
483 	 * The map options are being set.
484 	 * @name GeoMashup#mapOptions
485 	 * @event
486 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
487 	 * @param {Object} map_opts Modifiable <a href="http://mapstraction.github.com/mxn/build/latest/docs/symbols/mxn.Mapstraction.html#options">Mapstraction</a> 
488 	 *   or <a href="http://code.google.com/apis/maps/documentation/javascript/v2/reference.html#GMapOptions">Google</a> map options
489 	 */
490 	this.doAction( 'mapOptions', opts, map_opts );
491 	this.map.setOptions( map_opts );
492 	this.map.setCenterAndZoom(new mxn.LatLonPoint(0,0), 0);
493 
494 	/**
495 	 * The map was created.
496 	 * @name GeoMashup#newMap
497 	 * @event
498 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
499 	 * @param {Map} map
500 	 */
501 	this.doAction( 'newMap', opts, this.map );
502 
503 	// Create the loading spinner icon and show it
504 	this.spinner_div = document.createElement( 'div' );
505 	this.spinner_div.innerHTML = '<div id="gm-loading-icon" style="-moz-user-select: none; z-index: 100; position: absolute; left: ' +
506 		( jQuery(this.container).width() / 2 ) + 'px; top: ' + ( jQuery(this.container).height() / 2 ) + 'px;">' +
507 		'<img style="border: 0px none ; margin: 0px; padding: 0px; width: 16px; height: 16px; -moz-user-select: none;" src="' +
508 		opts.url_path + '/images/busy_icon.gif"/></a></div>';
509 	this.showLoadingIcon();
510 	this.map.load.addHandler( function() {GeoMashup.hideLoadingIcon();} );
511 
512 	if (!opts.object_name) {
513 		opts.object_name = 'post';
514 	}
515 	this.opts = opts;
516 	filter.url = opts.siteurl + '/?geo_mashup_content=geo-query&map_name=' + encodeURIComponent( opts.name );
517 	if ( 'lang' in opts ) {
518 		filter.url += '&lang=' + encodeURIComponent( opts.lang );
519 	}
520 	/**
521 	 * The base URL used for geo queries is being set.
522 	 * @name GeoMashup#geoQueryUrl
523 	 * @event
524 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
525 	 * @param {Object} filter Mofiable property: url
526 	 */
527 	this.doAction( 'geoQueryUrl', this.opts, filter );
528 	this.geo_query_url = filter.url;
529 
530 	// TODO: Try to deleselect markers with clicks? Need to make sure we don't get other object's clicks.
531 	this.map.changeZoom.addHandler( function( old_zoom, new_zoom ) {
532 		GeoMashup.adjustZoom( old_zoom, new_zoom );
533 		GeoMashup.adjustViewport();
534 	} );
535 	this.map.endPan.addHandler( function() {GeoMashup.adjustViewport();} );
536 
537 	// No clustering available
538 
539 	if ( opts.zoom !== 'auto' && typeof opts.zoom === 'string' ) {
540 		initial_zoom = parseInt(opts.zoom, 10);
541 	}else {
542 		initial_zoom = opts.zoom;
543 	}
544 
545 	if (opts.load_kml) {
546 		try {
547 			if ( initial_zoom === 'auto' ) {
548 				this.map.addOverlay( opts.load_kml, true );
549 			} else {
550 				this.map.addOverlay( opts.load_kml );
551 			}
552 		} catch (e) {
553 			// Probably not implemented
554 		}
555 	}
556 
557 	this.buildCategoryHierarchy();
558 
559 	try {
560 		this.map.setMapType( opts.map_type );
561 	} catch ( map_type_ex) {
562 		// Probably not implemented
563 	}
564 	if ( initial_zoom === 'auto' ) {
565 		// Wait to center and zoom after loading
566 	} else if (opts.center_lat && opts.center_lng) {
567 		// Use the center from options
568 		this.map.setCenterAndZoom(new mxn.LatLonPoint( parseFloat( opts.center_lat ), parseFloat( opts.center_lng ) ), initial_zoom );
569 	} else if (opts.object_data && opts.object_data.objects[0]) {
570 		center_latlng = new mxn.LatLonPoint( parseFloat( opts.object_data.objects[0].lat ), parseFloat( opts.object_data.objects[0].lng ) );
571 		this.map.setCenterAndZoom( center_latlng, initial_zoom );
572 	} else {
573 		// Center on the most recent located object
574 		url = this.geo_query_url + '&limit=1';
575 		if (opts.map_cat) {
576 			url += '&map_cat='+opts.map_cat;
577 		}
578 		jQuery.getJSON( url, function( objects ) {
579 			if (objects.length>0) {
580 				center_latlng = new mxn.LatLonPoint( parseFloat( objects[0].lat ), parseFloat( objects[0].lng ) );
581 				this.map.setCenterAndZoom( center_latlng, initial_zoom );
582 			} 
583 		} );
584 	}
585 
586 	this.location_bounds = null;
587 
588 	if (opts.map_content === 'single')
589 	{
590 		if (opts.center_lat && opts.center_lng && !opts.load_kml)
591 		{
592 			marker_opts = {visible: true};
593 			if (typeof customGeoMashupSinglePostIcon === 'function') {
594 				marker_opts = customGeoMashupSinglePostIcon(this.opts);
595 			}
596 			if ( !marker_opts.image ) {
597 				marker_opts = this.colorIcon( 'red' );
598 				marker_opts.icon = marker_opts.image;
599 			}
600 			/**
601 			 * A single map marker is being created with these options
602 			 * @name GeoMashup#singleMarkerOptions
603 			 * @event
604 			 * @param {GeoMashupOptions} properties Geo Mashup configuration data
605 			 * @param {Object} marker_opts Mofifiable Mapstraction or Google marker options
606 			 */
607 			this.doAction( 'singleMarkerOptions', this.opts, marker_opts );
608 			single_marker = new mxn.Marker(
609 				new mxn.LatLonPoint( parseFloat( this.opts.center_lat ), parseFloat( this.opts.center_lng ) )
610 			);
611 			this.map.addMarkerWithData( single_marker, marker_opts );
612 			/**
613 			 * A single map marker was added to the map.
614 			 * @name GeoMashup#singleMarker
615 			 * @event
616 			 * @param {GeoMashupOptions} properties Geo Mashup configuration data
617 			 * @param {Marker} single_marker
618 			 */
619 			this.doAction( 'singleMarker', this.opts, single_marker );
620 		}
621 	} else if (opts.object_data) {
622 		this.addObjects(opts.object_data.objects,true);
623 	} else {
624 		// Request objects near visible range first
625 		this.requestObjects(true);
626 
627 		// Request all objects
628 		this.requestObjects(false);
629 	}
630 
631 	if ('GSmallZoomControl' === opts.map_control || 'GSmallZoomControl3D' === opts.map_control) {
632 		controls.zoom = 'small';
633 	} else if ('GSmallMapControl' === opts.map_control) {
634 		controls.zoom = 'small';
635 		controls.pan = true;
636 	} else if ('GLargeMapControl' === opts.map_control || 'GLargeMapControl3D' === opts.map_control) {
637 		controls.zoom = 'large';
638 		controls.pan = true;
639 	}
640 
641 	if (opts.add_map_type_control ) {
642 		controls.map_type = true;
643 	}
644 
645 	if (opts.add_overview_control) {
646 		controls.overview = true;
647 	}
648 	this.map.addControls( controls );
649 
650 	this.map.load.addHandler( function() {GeoMashup.updateVisibleList();} );
651 	if (typeof customizeGeoMashupMap === 'function') {
652 		customizeGeoMashupMap(this.opts, this.map);
653 	}
654 	if (typeof customizeGeoMashup === 'function') {
655 		customizeGeoMashup(this);
656 	}
657 	/**
658 	 * The map has loaded.
659 	 * @name GeoMashup#loadedMap
660 	 * @event
661 	 * @param {GeoMashupOptions} properties Geo Mashup configuration data
662 	 * @param {Map} map
663 	 */
664 	this.doAction( 'loadedMap', this.opts, this.map );
665 
666 };
667