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