function gMapsHandler(
        mapCanvas,
        markersListCanvas,
        getMarkerDataUrl,
        requestedMarkerId) {

	return constructor();

	function constructor() {

		this.ONE_KILOMETER = 0.008993;
		this.ZIPCODE_ZOOMLEVEL = 12;
		this.boundsHolland = new GLatLngBounds(new GLatLng(49.724479188712984, 0.791015625), new GLatLng(54.08517342088679, 10.107421875));
		this.maxMarkerCount = 10;

		this.mapCanvas = mapCanvas;						
		this.markersGoogleMap = undefined;				
		this.markersListCanvas = markersListCanvas;		
														
		this.popupCanvas = createPopupCanvas(mapCanvas.parentNode, "popupCanvas", 105);	
		this.markerSwitch = new ElementSwitch(this.popupCanvas, 1.0, 20, 0.3);
		this.messageCanvas = createPopupCanvas(mapCanvas.parentNode, "messageCanvas", 200);		
		this.getMarkerDataUrl = getMarkerDataUrl;
		this.markersCollection = {};
		this.geocoder = new GClientGeocoder();
		this.geocoder.setBaseCountryCode("nl");

		this.searchByZipcode = searchByZipcode;
		this.searchByCity = searchByCity;
		this.searchByCriteria = searchByCriteria;

		if (GBrowserIsCompatible()) {
			markersGoogleMap = new GMap2(mapCanvas);

                  icon = new GIcon();
                  icon.image = "public/images/icons/maps_icon.png"; // De URL van de afbeelding die je wilt gebruiken
                  icon.shadow = "public/images/icons/maps_shadow.png";
                  icon.iconSize = new GSize(25, 33); // De grootte van het icoontje
                  icon.shadowSize = new GSize(60, 29); //
                  icon.iconAnchor = new GPoint(12, 33); // Anker van het icoontje, dus de plek op de afbeelding die precies op het coÃƒÂ¶rdinaat moet staan
                  icon.shadowAnchor = new GPoint(20, 29); // Anker van het icoontje, dus de plek op de afbeelding die precies op het coÃƒÂ¶rdinaat moet staan
                  icon.infoWindowAnchor = new GPoint(5, 1); // De plek tov het icoontje waar infoballonnetjes moeten verschijnen
                  this.icon = icon;

			this.markersGoogleMap = markersGoogleMap;
			this.geocoder.setViewport(boundsHolland);
			setMapArea(boundsHolland);
			markersGoogleMap.addControl(new GLargeMapControl());
			markersGoogleMap.addControl(new GMapTypeControl());
			markersGoogleMap.enableScrollWheelZoom();
			markersGoogleMap.enableDoubleClickZoom();
			GEvent.addListener(markersGoogleMap, "movestart", markerSwitch.hide);
			GEvent.addListener(markersGoogleMap, "moveend", computeMarkersVisibility);
			loadAllMarkers(getMarkerDataUrl);
		}
		return this;
	}

	function loadAllMarkers(URL) {
		showBusy("Bezig met laden");
		GDownloadUrl(URL, function(data, responseCode) {
                  var xml = GXml.parse(data);
                  var markers = xml.documentElement.getElementsByTagName("marker");

			for (var i = 0; i < markers.length; i++) {
				var marker = markers[i];
				markersCollection[marker.getAttribute("id")] = new Marker(marker,this.icon);
			}
			hideBusy();
			if (requestedZipcode != '') {
				searchByZipcode(requestedZipcode, 5);
			} else if (requestedPlace != '') {
				searchByCity(requestedPlace);
			} else if (requestedMarkerId > 0) {
				highlightStore(markersCollection[requestedMarkerId]);
			} else {
				showAreaWithAllMarkers();
			}
		});
	}


	function showAreaWithAllMarkers() {
		var bounds = new GLatLngBounds();
		for (var index in markersCollection) {
			bounds.extend(markersCollection[index].marker.getLatLng());
		}
		markersGoogleMap.zoomIn();
	}


	function Marker(xmlMarkerData,markerIcon) {
		this.zoomlevel = 3;
		this.name = xmlMarkerData.getAttribute("name");
		this.city = xmlMarkerData.getAttribute("city");
		this.image = xmlMarkerData.getAttribute("image");
		this.description = xmlMarkerData.getAttribute("description");

          

            var gMarker = new GMarker(new GLatLng(xmlMarkerData.getAttribute("lat"), xmlMarkerData.getAttribute("lng")), {
                  icon: markerIcon,
			title: this.name
		});
		this.marker = gMarker;
		this.latlng = gMarker.getLatLng();		
												
		gMarker.marker = this;
            markersGoogleMap.addOverlay(gMarker);
		GEvent.addListener(gMarker, "click", markerClickHandler);
		GEvent.addListener(gMarker, "dblclick", markerDoubleClickHandler);
	}

	// In these four event handlers below, 'this' refers to the GMarker causing the event
	function markerFocusHandler(event) {
		showMarkerPopup(this.store);
	}

	function markerClickHandler(event) {
		showMarkerPopup(this.marker);
	}

	function markerDoubleClickHandler(event) {
		highlightStore(this.marker);
	}

	function showMarkerPopup(marker) {
		var markerPixelPosition = markersGoogleMap.fromLatLngToContainerPixel(marker.latlng);
		popupCanvas.innerHTML = createPopupHtml(marker);
		var xPosition = checkPosition(markerPixelPosition.x, mapCanvas.offsetWidth, popupCanvas.offsetWidth, 18);
		var yPosition = checkPosition(markerPixelPosition.y, mapCanvas.offsetHeight, popupCanvas.offsetHeight, -100);
		popupCanvas.style.left = mapCanvas.offsetLeft + xPosition + 'px';
		popupCanvas.style.top = mapCanvas.offsetTop + yPosition + 'px';
		markerSwitch.show();
	}
      
	function createPopupHtml(store) {
		var info = '<div class="popupWrapper">';
            info += '<div class="markerLeft"><img src="'+store.image+'"></div>';
            info += '<div class="markerRight"><div class="popupCloseButton" onclick="markerSwitch.hide()"></div><div class="markerHeader">'+store.name+'</div><div id="markerDesc">'+store.description+'</div></div>';
            info += '</div>'
		return info;
	}



      function checkPosition(markerPos, mapLength, popupLength, offset){
		if (markerPos + offset + popupLength - mapLength <= (popupLength / 2)) {
			return markerPos + offset;
		} else {
			return markerPos - offset - popupLength;
		}
      }


	function highlightStore(store){
		markersGoogleMap.setCenter(store.marker.getLatLng(), ZIPCODE_ZOOMLEVEL);
		showMarkerPopup(store);
	}

	function searchByZipcode(zipcode) {
		// Only accept zipcodes of the form 1234 or 1234AB (with maybe whitespace inbetween digits and letters)
		if (/^\s?\d{4}\s?([a-zA-Z]{2})?\s?$/.test(zipcode)) {
			zipcode = zipcode.replace(/ /g, '');
			explicitSearch = true;
			searchByAddress(zipcode, 10, "Postcode");
		} else {
			alert("Postcode niet geldig");
		}
	}

	function searchByCity(city) {
		explicitSearch = true;
		var matchingmarkers = new Array();
		var bounds = new GLatLngBounds();
		var cityLowerCase = city.toLowerCase();
		for (var index in markersCollection) {
			var marker = markersCollection[index];
			if (marker.city.toLowerCase() == cityLowerCase) {
				matchingmarkers.push(marker);
				bounds.extend(marker.marker.getLatLng());
			}
		}

		if (matchingmarkers.length == 0) {
			setMapAreaToClosestByAddress(city, "Plaats");
		} else {
			setMapArea(bounds);
		}
	}
	function setMapAreaToClosestByAddress(address, locationName) {

		// find the coordinates of this city
		geocoder.getLatLng(address + ', Nederland', function(point) {
      		if ((! point) || (! boundsHolland.containsLatLng(point))) {
        		alert("Adres ongeldig");
      		} else {
				// calculate the smallest distance to a store
				var minDist = 100;
				var closestCity;
				for (var index in markersCollection) {
					var store = markersCollection[index];
					var dist = point.distanceFrom(markersCollection[index].latlng) / 1000;
					if (dist < minDist) {
						minDist = dist;
						closestCity = markersCollection[index].city;
					}
				}

				// set the map area so that the closest store is visible
				var latLngRadius = (minDist + 0.5) * ONE_KILOMETER;
				var sw = new GLatLng(point.lat() - latLngRadius, point.lng() - latLngRadius);
				var ne = new GLatLng(point.lat() + latLngRadius, point.lng() + latLngRadius);
				var bounds = new GLatLngBounds(sw, ne);

				if (minDist < 100) {
					for (var index in markersCollection) {
						var store = markersCollection[index];
						if (store.city.toLowerCase() == closestCity.toLowerCase()) {
							bounds.extend(store.marker.getLatLng());
						}
					}
				}

				setMapArea(bounds, point);
			}
		});
	}

	function searchByAddress(address, radius, locationName) {
		geocoder.getLatLng(address + ', Nederland', function(point) {
      		if ((! point) || (! boundsHolland.containsLatLng(point))) {
        		alert("Adres ongeldig");
      		} else {
				var latLngRadius = radius * ONE_KILOMETER / 2;
				var sw = new GLatLng(point.lat() - latLngRadius, point.lng() - latLngRadius);
				var ne = new GLatLng(point.lat() + latLngRadius, point.lng() + latLngRadius);
				var bounds = new GLatLngBounds(sw, ne);
				setMapArea(bounds, point);
			}
		});
	}

	function searchByCriteria() {
		computeMarkersVisibility();
	}

	function setMapArea(bounds, center) {
		markersGoogleMap.setCenter((center) ? center : (bounds.getCenter()) ,markersGoogleMap.getBoundsZoomLevel(bounds));
		markerSwitch.hide();
	}

      function computeMarkersVisibility() {
		var visibleStores = new Array();
		var currentBounds = markersGoogleMap.getBounds();
		var filteredOut = 0;
		for (var index in markersCollection) {
			var store = markersCollection[index];			
			if(checkBounds(store,currentBounds)) visibleStores.push(store);
		}
		listVisibleStores(visibleStores);
	}

      function checkBounds(marker, bounds){
          if (! bounds.containsLatLng(marker.latlng)) {
                return false;
            } else {
                return true;
            }

      }

	function createFunctionClosure(_function) {
		var args = Array.prototype.slice.call(arguments);
		args.shift();	// shift out the function reference (0th argument)
		return function() {
			_function.apply(undefined, args);
		};
	}



	function listVisibleStores(stores) {
		markersListCanvas.innerHTML = '';
		if (stores.length == 0 || stores.length > maxMarkerCount) {
			return;
		}
		markersListCanvas.appendChild(createElement('div', {id:'searchResult'}, "Gevonden verkooppunten"));
		for (var index=0; index < stores.length; index++) {
			var store = stores[index];
			var link = document.createElement("div");
			link.className = 'verkooppunt';
			link.onclick = createFunctionClosure(highlightStore, store);
                  if(index%2==0){
                      var info = '<div class="popupWrapper1">';
                  } else {
                      var info = '<div class="popupWrapper2">';
                  }
                  info += '<div class="markerLeft"><img src="'+store.image+'"></div>';
                  info += '<div class="markerRight"><div class="popupCloseButton" onclick="markerSwitch.hide()"></div><div class="markerHeader">'+store.name+'</div><div id="markerDesc">'+store.description+'</div></div>';
                  info += '</div>'
			link.innerHTML = info;
			markersListCanvas.appendChild(link);
		}
	}


	function createPopupCanvas(parent, id, zIndex) {
		var canvas = document.createElement("div");
		canvas.id = id;
		canvas.style.position = "absolute";
		canvas.style.visibility = "hidden";
		canvas.style.zIndex = zIndex + '';
		parent.appendChild(canvas);
		return canvas;
	}

	function showBusy(message) {
		messageCanvas.innerHTML = message;
		messageCanvas.style.visibility = "visible";
		messageCanvas.style.left = (mapCanvas.offsetLeft + mapCanvas.offsetWidth / 2) + 'px' ;
		messageCanvas.style.top = (mapCanvas.offsetTop + mapCanvas.offsetHeight - 60) + 'px';
	}

	function hideBusy() {
		messageCanvas.style.visibility = "hidden";
	}

	return this;
}

function createElement(elementName, attributes, bodyText){
	var element = document.createElement(elementName);
	if (attributes) {
		for (index in attributes) {
			element[index] = attributes[index];
		}
	}
	if (bodyText) {
		element.innerHTML = bodyText;
	}
	return element;
}

function ElementSwitch(element, durationInSeconds, changesPerSecond, opacityMinimum) {

	return constructor();

	function constructor() {
		this.element = element;
		this.show = show;
		this.hide = hide;
		this.change = change;
		return this;
	}
	function show() {
		element.style.visibility = "visible";
	}
	function hide() {
		element.style.visibility = "hidden";
	}
	function change() {
          if(element.style.visibility == "hidden"){
              element.style.visibility = "visible";
          } else {
              element.style.visibility = "hidden";
          }
	}
	return this;
}
