Using Google Maps custom overlays to create custom icons (with Raphael JS) - javascript
I'm using Google Maps OverlayView class to create custom markers (with Raphael JS) and am having issues accessing certain properties of my new subclass when calling a public method.
I followed Google's fairly straightforward example here ~ https://developers.google.com/maps/documentation/javascript/overlays#CustomOverlays ~ to create a custom marker class, including its 'hide' and 'show' methods.
function MapCustomMarker(opts){
this.pos_ = opts.position;
this.map_ = opts.map;
this.div_ = null;
this.color_ = (!opts.color ? '#e32636' : opts.color);
this.height_ = 32;
this.width_ = 32;
this.scale_ = 1.2;
this.icons_ = {
pinpoint:'M16,3.5c-4.142,0-7.5,3.358-7.5,7.5c0,4.143,7.5,18.121,7.5,18.121S23.5,15.143,23.5,11C23.5,6.858,20.143,3.5,16,3.5z M16,14.584c-1.979,0-3.584-1.604-3.584-3.584S14.021,7.416,16,7.416S19.584,9.021,19.584,11S17.979,14.584,16,14.584z',
help: 'M12.558,15.254c2.362,0,4.277-1.916,4.277-4.279s-1.916-4.279-4.277-4.279c-2.363,0-4.28,1.916-4.28,4.279S10.194,15.254,12.558,15.254zM15.662,15.224c-0.875,0.641-1.941,1.031-3.103,1.031c-1.164,0-2.231-0.391-3.105-1.031c-0.75,0.625-1.498,1.519-2.111,2.623c-1.422,2.563-1.578,5.192-0.35,5.874c0.55,0.312,1.127,0.078,1.723-0.496c-0.105,0.582-0.166,1.213-0.166,1.873c0,2.938,1.139,5.312,2.543,5.312c0.846,0,1.265-0.865,1.466-2.188c0.201,1.311,0.62,2.188,1.462,2.188c1.396,0,2.544-2.375,2.544-5.312c0-0.66-0.062-1.291-0.167-1.873c0.598,0.574,1.174,0.812,1.725,0.496c1.228-0.682,1.069-3.311-0.353-5.874C17.159,16.742,16.412,15.849,15.662,15.224zM19.821,3.711l-1.414,1.414c1.499,1.499,2.428,3.569,2.428,5.851c0,2.283-0.929,4.353-2.428,5.853l1.413,1.412c1.861-1.86,3.015-4.43,3.015-7.265C22.835,8.142,21.683,5.572,19.821,3.711zM16.288,14.707l1.413,1.414c1.318-1.318,2.135-3.138,2.135-5.145c0-2.007-0.816-3.827-2.134-5.145l-1.414,1.414c0.956,0.956,1.547,2.275,1.547,3.731S17.243,13.751,16.288,14.707zM21.941,1.59l-1.413,1.414c2.042,2.042,3.307,4.862,3.307,7.971c0,3.11-1.265,5.93-3.308,7.972l1.413,1.414c2.405-2.404,3.895-5.725,3.895-9.386C25.835,7.315,24.346,3.995,21.941,1.59z'
}
this.popup_ = 'M16,5.333c-7.732,0-14,4.701-14,10.5c0,1.982,0.741,3.833,2.016,5.414L2,25.667l5.613-1.441c2.339,1.317,5.237,2.107,8.387,2.107c7.732,0,14-4.701,14-10.5C30,10.034,23.732,5.333,16,5.333z';
this.icon_ = this.icons_[opts.icon];
this.setMap(opts.map);
}
MapCustomMarker.prototype = new google.maps.OverlayView();
MapCustomMarker.prototype.onAdd = function() {
// Create the DIV and set some basic attributes.
var div = document.createElement('div');
div.style.border = "none";
div.style.borderWidth = "0px";
div.style.position = "absolute";
div.style.cursor = "pointer";
div.style.width = this.width_+"px";
div.style.height = this.height_+"px";
var paper = Raphael(div,this.height_, this.width_);
var el = paper.path(Raphael.transformPath(this.icon_, 's'+this.scale_)).attr({fill: this.color_, stroke: "#333333"});
// Set the overlay's div_ property to this DIV
this.div_ = div;
// We add an overlay to a map via one of the map's panes.
// We'll add this overlay to the overlayImage pane.
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(div);
}
MapCustomMarker.prototype.draw = function() {
// Size and position the overlay.
var overlayProjection = this.getProjection();
// We'll use these coordinates to position the DIV.
var o = overlayProjection.fromLatLngToDivPixel(this.pos_);
var l = o.x - Math.round(this.width_ / 2);
var t = o.y - this.height_;
this.div_.style.left = l + 'px';
this.div_.style.top = t + 'px';
}
MapCustomMarker.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
MapCustomMarker.prototype.hide = function() {
console.log(this.div_);
console.log(this.color_);
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
MapCustomMarker.prototype.show = function() {
if (this.div_) {
this.div_.style.visibility = "visible";
}
}
MapCustomMarker.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
} else {
this.hide();
}
}
}
This class creates markers on my map with the Raphael icons very nicely.
The problem comes when I want to hide or show any specific marker.
var marker = new MapCustomMarker({position: pos, map: self.map, icon:'help', color:'#e32636'});
marker.hide();
marker.hide() is not hiding the markers.
You'll notice in the "hide" method, I have two console.log commands testing the values of this.color_ and this.div_. console.log(this.color_) returns the color set when the object is initiated. console.log(this.div_) returns null even though it was obviously altered in the 'onAdd' and 'draw' methods when the marker was created.
I'm not sure if this is a misunderstanding of public and private properties in javascript or something else. I used the google maps custom overlay example almost exactly.
If anyone has any ideas, please pass them along. (And aside from this one issue, I hope this code will assist others who want to merge Raphael JS capabilities with Google Maps.)
Thanks!
So it appears that marker.hide() is being called before the methods 'onAdd' and 'draw' have a chance to alter the this.div_ variable when the object is instantiated. Since I want all markers to be hidden when they are created, I'm just going to add
div.style.visibility = "hidden";
to the onAdd method, and will call maker.show() later, well after all of the markers have been loaded.
Related
DomMarkers and DomIcons not working inside a ShadowRoot
I'm trying to use DomMakers and DomIcons inside a ShadowRoot. When the markers load, I get this error: Uncaught TypeError: Cannot read properties of undefined (reading 'getPropertyValue') at rm.Ga (mapsjs-core.js:350:435) at kn (mapsjs-core.js:376:338) at S.Ga (mapsjs-core.js:376:52) at T.de (mapsjs-core.js:408:437) at $o (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:5:219) at Zo (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:4:425) at fp.g (eval at <anonymous> (mapsjs-core.js:73:36), <anonymous>:16:301) This happens because mapsjs-core can't find the canvas element, since it's inside a ShadowRoot. Here is the code snippet where the error occurs: var r = g.style; f.push({ Aj: r.getPropertyValue(tm), bo: r.getPropertyPriority(tm), style: r }); g is supposed to be the map canvas, but inside a ShadowRoot it's the document element I'm using Here Maps API for Javascript v3.1.30.7 on a React app. If I change to Markers and Icons the problem is gone, but I lose the interactivity I need.
It works only in this way: https://jsfiddle.net/ba2oL057/2/ function drawCanvas() { var canvas = document.createElement('canvas'); if (canvas.getContext) { var ctx = canvas.getContext('2d'); ctx.fillRect(25, 25, 100, 100); ctx.clearRect(45, 45, 60, 60); ctx.strokeRect(50, 50, 50, 50); } return canvas; } /** * Create a marker that is capable of receiving DOM events and add it * to the map. * * #param {H.Map} map A HERE Map instance within the application */ function addDomMarker(map) { function setToShadow(outerElement){ var innerElement = document.createElement('div'); outerElement.attachShadow({mode: 'open'}); outerElement.shadowRoot.innerHTML = ` <div></div> `; let outerShElem = outerElement.shadowRoot.querySelectorAll('div')[0]; outerShElem.style.userSelect = 'none'; outerShElem.style.webkitUserSelect = 'none'; outerShElem.style.msUserSelect = 'none'; outerShElem.style.mozUserSelect = 'none'; outerShElem.style.cursor = 'default'; innerElement.style.color = 'red'; innerElement.style.backgroundColor = 'blue'; innerElement.style.border = '2px solid black'; innerElement.style.font = 'normal 12px arial'; innerElement.style.lineHeight = '12px' innerElement.style.paddingTop = '2px'; innerElement.style.paddingLeft = '4px'; innerElement.style.width = '20px'; innerElement.style.height = '20px'; // add negative margin to inner element // to move the anchor to center of the div innerElement.style.marginTop = '-10px'; innerElement.style.marginLeft = '-10px'; outerShElem.appendChild(innerElement); outerShElem.appendChild(drawCanvas()); // Add text to the DOM element innerElement.innerHTML = 'C'; outerShElem.addEventListener('mouseover', changeOpacity); outerShElem.addEventListener('mouseout', changeOpacityToOne); } var outerElement = document.createElement('div'); //document.body.appendChild(outerElement); function changeOpacity(evt) { evt.target.style.opacity = 0.6; }; function changeOpacityToOne(evt) { evt.target.style.opacity = 1; }; //create dom icon and add/remove opacity listeners var domIcon = new H.map.DomIcon(outerElement, { // the function is called every time marker enters the viewport onAttach: function(clonedElement, domIcon, domMarker) { setToShadow(clonedElement); }, // the function is called every time marker leaves the viewport onDetach: function(clonedElement, domIcon, domMarker) { let outerShElem = clonedElement.shadowRoot.querySelectorAll('div')[0]; outerShElem.removeEventListener('mouseover', changeOpacity); outerShElem.removeEventListener('mouseout', changeOpacityToOne); } }); // Marker for Chicago Bears home var bearsMarker = new H.map.DomMarker({lat: 41.8625, lng: -87.6166}, { icon: domIcon }); map.addObject(bearsMarker); //setTimeout(()=>{map.removeObject(bearsMarker);}, 0); } /** * Boilerplate map initialization code starts below: */ //Step 1: initialize communication with the platform // In your own code, replace variable window.apikey with your own apikey var platform = new H.service.Platform({ apikey: window.apikey }); var defaultLayers = platform.createDefaultLayers(); //Step 2: initialize a map - this map is centered over Chicago. var map = new H.Map(document.getElementById('map'), defaultLayers.vector.normal.map,{ center: {lat:41.881944, lng:-87.627778}, zoom: 11, pixelRatio: window.devicePixelRatio || 1 }); // add a resize listener to make sure that the map occupies the whole container window.addEventListener('resize', () => map.getViewPort().resize()); //Step 3: make the map interactive // MapEvents enables the event system // Behavior implements default interactions for pan/zoom (also on mobile touch environments) var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map)); // Now use the map as required... addDomMarker(map);
Google 3.32 causes MapOverlay error
Everything works fine using 3.31, but when I use the 3.32 or 3.33 script my overlay fails to load. Here is my custom overlay class: function MapOverlay(bounds, image, map) { // Now initialize all properties. var sw = new google.maps.LatLng(bounds.southWest.latitude, bounds.southWest.longitude); var ne = new google.maps.LatLng(bounds.northEast.latitude, bounds.northEast.longitude); //if (westernCorner.longitude > easternCorner.longitude) this.bounds = new google.maps.LatLngBounds(sw, ne); this.image = image; this.map = map; // We define a property to hold the image's // div. We'll actually create this div // upon receipt of the add() method so we'll // leave it null for now. this.div = null; // Explicitly call setMap() on this overlay this.setMap(map); } MapOverlay.prototype = new google.maps.OverlayView(); MapOverlay.prototype.onAdd = function() { // Note: an overlay's receipt of onAdd() indicates that // the map's panes are now available for attaching // the overlay to the map via the DOM. // Create the DIV and set some basic attributes. var div = document.createElement('DIV'); div.style.border = "none"; div.style.borderWidth = "0px"; div.style.position = "absolute"; // Create an IMG element and attach it to the DIV. var img = document.createElement("img"); img.src = this.image; img.style.width = "100%"; img.style.height = "100%"; div.appendChild(img); // Set the overlay's div_ property to this DIV this.div = div; // We add an overlay to a map via one of the map's panes. // We'll add this overlay to the overlayImage pane. var panes = this.getPanes(); panes.overlayLayer.appendChild(div); }; MapOverlay.prototype.draw = function() { // Size and position the overlay. We use a southwest and northeast // position of the overlay to peg it to the correct position and size. // We need to retrieve the projection from this overlay to do this. var overlayProjection = this.getProjection(); // Retrieve the southwest and northeast coordinates of this overlay // in latlngs and convert them to pixels coordinates. // We'll use these coordinates to resize the DIV. var sw = overlayProjection.fromLatLngToDivPixel(this.bounds.getSouthWest()); var ne = overlayProjection.fromLatLngToDivPixel(this.bounds.getNorthEast()); // Resize the image's DIV to fit the indicated dimensions. var div = this.div; div.style.left = sw.x + 'px'; div.style.top = ne.y + 'px'; console.log("ne.x: " + ne.x + " sw.x " + sw.x + " width: " + (ne.x - sw.x)); div.style.width = (ne.x - sw.x) + 'px'; div.style.height = (sw.y - ne.y) + 'px'; }; MapOverlay.prototype.onRemove = function() { this.div.parentNode.removeChild(this.div); this.div = null; }; // Note that the visibility property must be a string enclosed in quotes MapOverlay.prototype.hide = function() { if (this.div) { this.div.style.visibility = "hidden"; } }; MapOverlay.prototype.show = function() { if (this.div) { this.div.style.visibility = "visible"; } }; And here is where the overlay is set up: addImageOverlay: function(bounds, url, hidden) { var ne = new google.maps.LatLng(bounds.northEast.latitude, bounds.northEast.longitude); var sw = new google.maps.LatLng(bounds.southWest.latitude, bounds.southWest.longitude); var b = new google.maps.LatLngBounds(sw, ne); if(this.imageOverlay){ this.imageOverlay.setMap(null); this.imageOverlay = null; } this.imageOverlay = new MapOverlay(bounds, url, this.map); this.imageOverlay.setMap(this.map); if (hidden === true) { this.imageOverlay.setMap(null); } else { this.imageOverlay.setMap(this.map); } return this.imageOverlay; } The error I am getting is: Cannot read property 'parentNode' of null at MapOverlay.onRemove (MapOverlay.js:90) From what I can tell so far, the onAdd() method does not appear to be getting called when I call setMap(map). Therefore, this.div is null when onRemove() is called (thus the NPE). Map is populated and valid as far as I can tell. Again, this is only happening in 3.32 and 3.33 of Google Maps API. I can't find any change in the documentation that would be causing this and have been stuck for a day now. I can stick with 3.31 for now, but it is supposedly being sunset in August.
MapOverlay.prototype.onRemove = function() { if (this.div && this.div.parentNode) { this.div.parentNode.removeChild(this.div); } this.div = null; }; Added IF block and this will solves problem. Please find the reference link below:- 1. https://github.com/googlemaps/v3-utility-library/issues/393 2. https://github.com/enyo/dropzone/issues/1083
Disable drag on Google Map Javascript custom controls
I want to create a Google map custom control using the Javascript API. The control is an 'add waypoint' button. The user is supposed to be able to drag from the control, causing a marker to appear at the mouse pointer, and drop this marker on to the map. The intended behavior is nearly identical to the existing pegman feature. The problem is that the Google map controls seem to interact with drag, even when the drag has been explicitly disabled, as seen below. In the example code, The first time I click and drag, it works as intended. The second time I click and drag, I get a 'no drag' icon and the control spawns a ghost of itself. The third time I drag, it's working again. On the next drag, there's a 'no drag'. The sequence continues, with odd drags working, and even drags failing. Here's what I believe to be the relevant section: <div id="map"></div> <script> var spawning = false; var inProgress = false; var waypointSpawner; var targetLat; var targetLng; function initMap() { map = new google.maps.Map(document.getElementById('map'), { center: {lat: 0.0000, lng: 0.0000}, zoom: 2 }); // Create the DIV to hold the control and call the CenterControl() // constructor passing in this DIV. var centerControlDiv = document.createElement('div'); var centerControl = new CenterControl(centerControlDiv, map); centerControlDiv.index = 1; map.controls[google.maps.ControlPosition.LEFT_TOP].push(centerControlDiv); } function CenterControl(controlDiv, map) { // Set CSS for the control border. var controlUI = document.createElement('div'); controlUI.style.backgroundColor = '#fff'; controlUI.style.border = '2px solid #fff'; controlUI.style.borderRadius = '3px'; controlUI.style.boxShadow = '0 2px 6px rgba(0,0,0,.3)'; controlUI.style.cursor = 'pointer'; controlUI.style.marginBottom = '22px'; controlUI.style.width = '40px'; controlUI.style.height = '50px'; controlUI.style.opacity = "0.7"; controlUI.style.backgroundImage = "url('create_waypoint_icon.png')"; controlUI.title = 'Add Waypoint'; controlUI.draggable = "false"; //controlDiv.draggable = "false"; controlDiv.appendChild(controlUI); } I was unable to get the above code to work in jsfiddle, but there are a couple of other examples that illustrate the problem. http://jsfiddle.net/maunovaha/jptLfhc8/ Attempting to drag and drop the blue and green squares in the top left of the map results in some unintended behavior. Depending on your zoom level, and somewhat randomly, you will get a variety of different outcomes - 1 - Nothing happens. The pointer remains as a finger and moves normally. 2 - The finger pointer changes to a 'no drag' but otherwise moves normally. 3 - The finger pointer changes to a 'no drag' and a blue-and green semi-transparent 'to-paste' widget. 4 - The finger pointer changes to a 'no drag', and either the blue or green half of the widget (depending on your selection) goes with it. This behavior is also somewhat evident in the Google control example code: https://developers.google.com/maps/documentation/javascript/examples/control-custom In this instance, when the text is highlighted, you get the 'no drag' icon. The parent control doesn't appear to be selectable, which is the desired goal. I believe that the custom control is acquiring some kind of 'focus' status, and that clicking it again clears the focus. This behavior is evident in the green/blue example. Perhaps the control is being 'highlighted' somehow? I attempted to implement the solutions given in How can I make a div unselectable? to no effect. I also tried a variety of ways to make the div undraggable in the code, but this did not appear to have any impact. I also tried simulating mouse clicks on other parts of the program, and double clicking on the control, to change the focus/highlight status, but it appears the API doesn't actually carry out the clicks, only generates events from them. I couldn't find a way to cause a 'real' click. Manually clicking, then clicking and dragging worked. I also tried making the control draggable but it still has the 'no drag' mouse pointer even when dragging it about.
If I understand correctly what you are saying, you want to be able to drag a marker onto the map, from outside, and place a point on that position. I have the following solution which does that. First we need to build the map: function buildMap() { var g = google.maps; var mapOptions = { center: new g.LatLng(52.052491, 9.84375), zoom: 4, mapTypeId: g.MapTypeId.ROADMAP, streetViewControl: false, panControl: false }; map = new g.Map(document.getElementById("map"), mapOptions); iw = new g.InfoWindow(); g.event.addListener(map, "click", function() { if (iw) iw.close() }); drag_area = document.getElementById("markers"); var b = drag_area.getElementsByTagName("div"); b[0].onmousedown = initDrag dummy = new DummyOView() } We create a standard map and add features such as infoWindows to display the lat lng when the marker is added. In addition we get where the marker is held and onMouseDown call a function (soon to come). We use b[0] as we are getting the first set of <div> tags in the mark-up below, this is where the draggable icon is held: <div id="markers"> <div id="m1" class="drag" style="left:0; background-image: url('https://maps.gstatic.com/mapfiles/ms/icons/ltblue-dot.png')"> </div> </div> The DummyOView is a map OverlayView which allows us to drag onto the map and grab the coords and position. More information on SO + credit to : More info function DummyOView() { this.setMap(map); this.draw = function() {} } DummyOView.prototype = new google.maps.OverlayView(); The initDrag function is where most of the work is done, this is quite a lengthy function so I did comment the code, any questions on clarification just add a comment. //function that allows us to drag the marker from the div to the map function initDrag(e) { //allows us to drag the marker and keep record of the clientX client Y coordinates var j = function(e) { var a = {}; if (!e) var e = window.event; a.x = e.clientX; a.y = e.clientY return a }; //function called whenever the mouse moves. this will keep track of the marker as we move around var k = function(e) { //check to ensure that the object is of class drag - otherwise we could drag everything if (obj && obj.className == "drag") { var i = j(e), deltaX = i.x - l.x, deltaY = i.y - l.y; obj.style.left = (obj.x + deltaX) + "px"; obj.style.top = (obj.y + deltaY) + "px"; obj.onmouseup = function() { //get the information to check to see if the dragObj is on the map on mouse up var a = map.getDiv(), mLeft = a.offsetLeft, mTop = a.offsetTop, mWidth = a.offsetWidth, mHeight = a.offsetHeight; var b = drag_area.offsetLeft, areaTop = drag_area.offsetTop, oWidth = obj.offsetWidth, oHeight = obj.offsetHeight; //check to see if obj is in bounds of map div X and Y var x = obj.offsetLeft + b + oWidth / 2; var y = obj.offsetTop + areaTop + oHeight / 2; if (x > mLeft && x < (mLeft + mWidth) && y > mTop && y < (mTop + mHeight)) { var c = 1; var mapTemp = google.maps; var point = new mapTemp.Point(x - mLeft - c, y - mTop + (oHeight / 2)); var proj = dummy.getProjection(); var latlng = proj.fromContainerPixelToLatLng(point); var backImage = obj.style.backgroundImage.slice(4, -1).replace(/"/g, ""); createDraggedMarker(latlng, backImage); fillMarker(backImage) } } } return false }; //assign the event to a windows event obj = e.target ? e.target : e.srcElement; //if the object where the event took place is not called drag cancel the event if (obj.className != "drag") { if (e.cancelable) e.preventDefault(); obj = null; return } else { z_index += 1; obj.style.zIndex = z_index.toString(); obj.x = obj.offsetLeft; obj.y = obj.offsetTop; var l = j(e); //get the initial position of the marker relative to the client document.onmousemove = k; //if we lift the mouse up outside the map div set to null and leave where it is document.onmouseup = function() { document.onmousemove = null; document.onmouseup = null; if (obj) obj = null } } return false } Summarised this function is called whenever the mouse has been clicked and dragged. It registed the starting position and the positions of the map div and checks when the onmouseup to see if the current mouse position is over the map div. If so we create a marker on the map and re-add the marker icon to the div to allow redragging. //when the marker is dragged onto the map //we call this function to create a marker on the map function createDraggedMarker(position, iconImage) { var mapLocal = google.maps; var icon = { url: iconImage, size: new mapLocal.Size(32, 32), anchor: new mapLocal.Point(15, 32) }; var marker = new mapLocal.Marker({ position: position, map: map, clickable: true, draggable: true, crossOnDrag: false, optimized: false, icon: icon, zIndex: highestOrder() }); mapLocal.event.addListener(marker, "click", function() { actual = marker; var lat = actual.getPosition().lat(); var lng = actual.getPosition().lng(); var innerHtml = "<div class='infowindow'>" + lat.toFixed(6) + ", " + lng.toFixed(6) + "<\/div>"; iw.setContent(innerHtml); iw.open(map, this) }); mapLocal.event.addListener(marker, "dragstart", function() { if (actual == marker) iw.close(); z_index += 1; marker.setZIndex(highestOrder()) }) } Re-adding the marker to the div //Used to replace the marker //once we have moved it from its div function fillMarker(a) { var div = document.createElement("div"); div.style.backgroundImage = "url(" + a + ")"; var padding; if (obj.id == "m1") { padding = "0px" } div.style.left = padding; div.id = obj.id; div.className = "drag"; div.onmousedown = initDrag; drag_area.replaceChild(div, obj); obj = null } JSfiddle you can use : https://jsfiddle.net/q3wjrpdw/7/ Any questions feel free to ask.
Google maps v3: clustering with custom markers
I'm trying to use MarkerClusterer to clusterize the markers on my map. The problem is that I'm not using default markers (google.maps.Marker), but instead a custom class which hinerits from google.maps.OverlayView. Unfortunately it seems that the library has been developed assuming the use of basic markers, in fact I get errors because my class does not implement methods defined in google.maps.Marker. Is it possible to use the MarkerClusterer by keeping my custom markers? EDIT: it was a lot easier than I expected, I solved by implementing 2 methods in my custom class: setVisible() and getPosition() in order to help others the following is my complete interface (without full implementation): BFPushpin = function(config) { this.setMap(config.map); this.set("position", config.position); // other settings... }; // my class extends google.maps.OverlayView BFPushpin.prototype = new google.maps.OverlayView(); BFPushpin.prototype.getBounds = function() { return new google.maps.LatLngBounds(this.position, this.position); }; BFPushpin.prototype.getPoint = function() { var bounds = this.getBounds(); var projection = this.getProjection(); var sw = projection.fromLatLngToDivPixel(bounds.getSouthWest()); var ne = projection.fromLatLngToDivPixel(bounds.getNorthEast()); return new google.maps.Point(sw.x, ne.y); }; BFPushpin.prototype.getSuperContainer = function() { var panes = this.getPanes(); return jQuery(panes ? panes.overlayImage : ""); }; BFPushpin.prototype.getContainer = function() { // return inner container }; BFPushpin.prototype._generatePopupContent = function() { // return markup for the popupwindow }; BFPushpin.prototype._addListeners = function() { // add handlers for the pushpin }; BFPushpin.prototype.onAdd = function() { // customize content here }; BFPushpin.prototype.onRemove = function() { // remove pin container here }; BFPushpin.prototype.draw = function() { // set display style here }; BFPushpin.prototype.setVisible = function(visible) { // set display block or hidden }; BFPushpin.prototype.getPosition = function() { return this.position; };
Or just define the functions that the MarkerClusterer expects on the marker. setMap and getPosition() and some other ones.
You should probably define your new marker class in such a way that it also inherits from google.maps.Marker (i.e. that it implements its interface). It is logical that MarkerClusterer uses this interface - it has to suppose the markers are markers in order to work with them :-)
Hopefully this will also help people trying to get this solution to work. Thanks #daveoncode for the example. I was able to modify it to get it working for me: renderMap() { const mapProperties = { center: new google.maps.LatLng(34.0234, -84.6155), zoom: 10, mapTypeId: google.maps.MapTypeId.ROADMAP }; this.map = new google.maps.Map(this.mapElement.nativeElement, mapProperties); let markers = []; for (let i=0; i<this._sitesList.length; i++) { let bounds = new google.maps.LatLngBounds( new google.maps.LatLng(this._sitesList[i].lat, this._sitesList[i].lon) ); let position = new google.maps.LatLng(this._sitesList[i].lat, this._sitesList[i].lon); let html = this.makeHtmlForSitePanel(i, this._sitesList[i]); // I have a function to return html for my OverlayView panels here. markers.push( this.generateSitePanel(bounds, html, this.map, position) ); } var markerCluster = new MarkerClusterer(this.map, markers, { imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m' }); } generateSitePanel(bounds, html, map, position) { SitePanel.prototype = new google.maps.OverlayView(); function SitePanel (bounds, html, map, position) { this.bounds_ = bounds; this.set('position', position); this.html_ = html; this.map_ = map; this.div_ = null; this.setMap(map); } SitePanel.prototype.getBounds = function() { return new google.maps.LatLngBounds(this.position, this.position); }; SitePanel.prototype.getPoint = function() { var bounds = this.getBounds(); var projection = this.getProjection(); var sw = projection.fromLatLngToDivPixel(bounds.getSouthWest()); var ne = projection.fromLatLngToDivPixel(bounds.getNorthEast()); return new google.maps.Point(sw.x, ne.y); }; SitePanel.prototype.getSuperContainer = function(){ var panes = this.getPanes(); return $(panes ? panes.overlayImage : ''); }; SitePanel.prototype.getContainer = function() { // return inner container // I don't have anything for this one }; SitePanel.prototype.getPosition = function() { return this.position; }; SitePanel.prototype.onAdd = function() { var div = document.createElement('div'); div.className = 'tooltip-container-'; div.innerHTML = this.html_; div.style.position = 'absolute'; this.div_ = div; var panes = this.getPanes(); panes.overlayImage.appendChild(div); }; SitePanel.prototype.draw = function() { var overlayProjection = this.getProjection(); var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest()); var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast()); var div = this.div_; div.style.left = sw.x + 'px'; div.style.top = ne.y + 20 + 'px'; }; SitePanel.prototype.onRemove = function() { this.div_.parentNode.removeChild(this.div_); this.div_ = null; }; return new SitePanel(bounds, html, map, position); }
Google maps v3 polyline tooltip
A google maps marker object (google.maps.Marker) has a title property, so when a user moves their mouse over the marker a simple tooltip is displayed. There isn't a title property on a polyline (google.maps.Polyline). Is there a way I can do this / simulate this in V3? I could do this in V2, and I can't find an example for V3.
I combined #samshull's answer above (duly upvoted!) with info from here to make the InfoWindow appear where the user's cursor mouses over the line: // Open the InfoWindow on mouseover: google.maps.event.addListener(line, 'mouseover', function(e) { infoWindow.setPosition(e.latLng); infoWindow.setContent("You are at " + e.latLng); infoWindow.open(map); }); // Close the InfoWindow on mouseout: google.maps.event.addListener(line, 'mouseout', function() { infoWindow.close(); }); Here, line is your PolyLine object; map is your Map object; and infoWindow is your InfoWindow object, which I just create with: var infoWindow = new google.maps.InfoWindow(); I also follow this advice by re-using the same InfoWindow object for all my polylines rather than creating a new one for each line: Best practices: For the best user experience, only one info window should be open on the map at any one time. Multiple info windows make the map appear cluttered. If you only need one info window at a time, you can create just one InfoWindow object and open it at different locations or markers upon map events, such as user clicks. If you do need more than one info window, you can display multiple InfoWindow objects at the same time. Note that infoWindow.setContent() takes a string. So call toString() on a number variable if you want to display a number in the InfoWindow. I view all of this as an imperfect workaround until Google Maps hopefully one day add a title property to PolylineOptions, just like they've already done for MarkerOptions.
I am not 100% this is the only way, or the best way, but it is a way to create a window over your Polyline In Google maps V3, you should to create an InfoWindow then set the content using myInfoWindow.setContent("Hello World!") In order to make it show on mouseover, you will need to do something like: google.maps.event.addListener(myPolyline, 'mouseover', function() { myInfoWindow.open(mymap); // mymap represents the map you created using google.maps.Map }); // assuming you want the InfoWindow to close on mouseout google.maps.event.addListener(myPolyline, 'mouseout', function() { myInfoWindow.close(); });
I found this online that helped me do tooltips on polygons, from http://philmap.000space.com/gmap-api/poly-hov.html: var tooltip=function(){ var id = 'tt'; var top = 3; var left = 3; var maxw = 200; var speed = 10; var timer = 20; var endalpha = 95; var alpha = 0; var tt,t,c,b,h; var ie = document.all ? true : false; return{ show:function(v,w){ if(tt == null){ tt = document.createElement('div'); tt.setAttribute('id',id); t = document.createElement('div'); t.setAttribute('id',id + 'top'); c = document.createElement('div'); c.setAttribute('id',id + 'cont'); b = document.createElement('div'); b.setAttribute('id',id + 'bot'); tt.appendChild(t); tt.appendChild(c); tt.appendChild(b); document.body.appendChild(tt); tt.style.opacity = 0; tt.style.filter = 'alpha(opacity=0)'; document.onmousemove = this.pos; } tt.style.visibility = 'visible'; tt.style.display = 'block'; c.innerHTML = v; tt.style.width = w ? w + 'px' : 'auto'; if(!w && ie){ t.style.display = 'none'; b.style.display = 'none'; tt.style.width = tt.offsetWidth; t.style.display = 'block'; b.style.display = 'block'; } if(tt.offsetWidth > maxw){tt.style.width = maxw + 'px'} h = parseInt(tt.offsetHeight) + top; clearInterval(tt.timer); tt.timer = setInterval(function(){tooltip.fade(1)},timer); }, pos:function(e){ var u = ie ? event.clientY + document.documentElement.scrollTop : e.pageY; var l = ie ? event.clientX + document.documentElement.scrollLeft : e.pageX; tt.style.top = (u - h) + 'px'; tt.style.left = (l + left) + 'px'; }, fade:function(d){ var a = alpha; if((a != endalpha && d == 1) || (a != 0 && d == -1)){ var i = speed; if(endalpha - a < speed && d == 1){ i = endalpha - a; }else if(alpha < speed && d == -1){ i = a; } alpha = a + (i * d); tt.style.opacity = alpha * .01; tt.style.filter = 'alpha(opacity=' + alpha + ')'; }else{ clearInterval(tt.timer); if(d == -1){tt.style.display = 'none'} } }, hide:function(){ clearInterval(tt.timer); tt.timer = setInterval(function(){tooltip.fade(-1)},timer); } }; }(); Also, Please see this SO discussion about the same topic: Tooltip over a Polygon in Google Maps And, Google maps rectangle/polygon with title
If i'm not mistaken i don't think it is possible to set the tooltip since as you mentioned there is not a title property in PolygonOptions object.But you can make a div that looks exactly the same as the tooltip and place it let's say in the tip of your mouse during the mousemove event.I tried also to find a solution to place this tooltip somewhere in the center of the polygon but i think it is too much of a trouble that's why i also think the google guys didn't implement it also. Cheers