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