Google 3.32 causes MapOverlay error - javascript

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

Related

How to have fixed drag step in mxgraph

Hi i'm looking for drag step for each drag item, let say 200px
below image is demonstrating it:
i have searched a lot but did not find any solution related to that.
Question: The imaginary drag box step should be set to 200px, in other words it should jump to 200px
Full view codepen: https://codepen.io/eabangalore/pen/GaPzJw?editors=1000
Below snippet is not working please see codepen above
<!--
Copyright (c) 2006-2013, JGraph Ltd
Dynamic toolbar example for mxGraph. This example demonstrates changing the
state of the toolbar at runtime.
-->
<html>
<head>
<title>Toolbar example for mxGraph</title>
<!-- Sets the basepath for the library if not in same directory -->
<script type="text/javascript">
mxBasePath = 'https://jgraph.github.io/mxgraph/javascript/src';
function setGraphData(){
var graphState = {"tagName":"mxGraphModel","children":[{"tagName":"root","children":[{"tagName":"mxCell","attributes":{"id":"0"}},{"tagName":"mxCell","attributes":{"id":"1","parent":"0"}},{"tagName":"mxCell","attributes":{"id":"2","style":"","vertex":"1","parent":"1"},"children":[{"tagName":"mxGeometry","attributes":{"x":"400","y":"130","width":"100","height":"40","as":"geometry"}}]},{"tagName":"mxCell","attributes":{"id":"3","style":"","vertex":"1","parent":"1"},"children":[{"tagName":"mxGeometry","attributes":{"x":"661","y":"101","width":"100","height":"40","as":"geometry"}}]}]}]};
localStorage.setItem('graphState',JSON.stringify(graphState));
}
function html2json(html){
if(html.nodeType==3){
return {
"tagName":"#text",
"content":html.textContent
}
}
var element = {
"tagName":html.tagName
};
if(html.getAttributeNames().length>0){
element.attributes = html.getAttributeNames().reduce(
function(acc,at){acc[at]=html.getAttribute(at); return acc;},
{}
);
}
if(html.childNodes.length>0){
element.children = Array.from(html.childNodes)
.filter(
function(el){
return el.nodeType!=3
||el.textContent.trim().length>0
})
.map(function(el){return html2json(el);});
}
return element;
}
function json2html(json){
var xmlDoc = document.implementation.createDocument(null, json.tagName);
var addAttributes = function(jsonNode, node){
if(jsonNode.attributes){
Object.keys(jsonNode.attributes).map(
function(name){
node.setAttribute(name,jsonNode.attributes[name]);
}
);
}
}
var addChildren = function(jsonNode,node){
if(jsonNode.children){
jsonNode.children.map(
function(jsonChildNode){
json2htmlNode(jsonChildNode,node);
}
);
}
}
var json2htmlNode = function(jsonNode,parent){
if(jsonNode.tagName=="#text"){
return xmlDoc.createTextNode(jsonNode.content);
}
var node = xmlDoc.createElement(jsonNode.tagName);
addAttributes(jsonNode,node);
addChildren(jsonNode,node);
parent.appendChild(node);
}
addAttributes(json,xmlDoc.firstElementChild);
addChildren(json,xmlDoc.firstElementChild);
return xmlDoc;
}
</script>
<!-- Loads and initializes the library -->
<script type="text/javascript" src="https://jgraph.github.io/mxgraph/javascript/src/js/mxClient.js"></script>
<!-- Example code -->
<script type="text/javascript">
// Program starts here. Creates a sample graph in the
// DOM node with the specified ID. This function is invoked
// from the onLoad event handler of the document (see below).
function main()
{
setGraphData();
// Checks if browser is supported
if (!mxClient.isBrowserSupported())
{
// Displays an error message if the browser is
// not supported.
mxUtils.error('Browser is not supported!', 200, false);
}
else
{
// Defines an icon for creating new connections in the connection handler.
// This will automatically disable the highlighting of the source vertex.
mxConnectionHandler.prototype.connectImage = new mxImage('images/connector.gif', 16, 16);
// Creates the div for the toolbar
var tbContainer = document.createElement('div');
tbContainer.style.position = 'absolute';
tbContainer.style.overflow = 'hidden';
tbContainer.style.padding = '2px';
tbContainer.style.left = '0px';
tbContainer.style.top = '0px';
tbContainer.style.width = '24px';
tbContainer.style.bottom = '0px';
document.body.appendChild(tbContainer);
// Creates new toolbar without event processing
var toolbar = new mxToolbar(tbContainer);
toolbar.enabled = false
// Creates the div for the graph
var container = document.createElement('div');
container.style.position = 'absolute';
container.style.overflow = 'hidden';
container.style.left = '24px';
container.style.top = '0px';
container.style.right = '0px';
container.style.bottom = '0px';
container.style.background = 'url("editors/images/grid.gif")';
document.body.appendChild(container);
// Workaround for Internet Explorer ignoring certain styles
if (mxClient.IS_QUIRKS)
{
document.body.style.overflow = 'hidden';
new mxDivResizer(tbContainer);
new mxDivResizer(container);
}
// Creates the model and the graph inside the container
// using the fastest rendering available on the browser
var model = new mxGraphModel();
var graph = new mxGraph(container, model);
// Enables new connections in the graph
graph.setConnectable(true);
graph.setMultigraph(false);
// Stops editing on enter or escape keypress
var keyHandler = new mxKeyHandler(graph);
var rubberband = new mxRubberband(graph);
var addVertex = function(icon, w, h, style)
{
var vertex = new mxCell(null, new mxGeometry(0, 0, w, h), style);
vertex.setVertex(true);
var img = addToolbarItem(graph, toolbar, vertex, icon);
img.enabled = true;
graph.getSelectionModel().addListener(mxEvent.CHANGE, function()
{
var tmp = graph.isSelectionEmpty();
mxUtils.setOpacity(img, (tmp) ? 100 : 20);
img.enabled = tmp;
});
};
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/rectangle.gif', 100, 40, '');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/rounded.gif', 100, 40, 'shape=rounded');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/ellipse.gif', 40, 40, 'shape=ellipse');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/rhombus.gif', 40, 40, 'shape=rhombus');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/triangle.gif', 40, 40, 'shape=triangle');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/cylinder.gif', 40, 40, 'shape=cylinder');
addVertex('https://jgraph.github.io/mxgraph/javascript/examples/editors/images/actor.gif', 30, 40, 'shape=actor');
// read state on load
if(window.localStorage.graphState){
var doc = json2html(JSON.parse(localStorage.graphState));
var dec = new mxCodec(doc);
dec.decode(doc.documentElement, graph.getModel());
}
// save state on change
graph.getModel().addListener('change',function(){
var codec = new mxCodec();
window.localStorage.graphState = JSON.stringify(html2json(codec.encode(
graph.getModel()
)));
});
}
}
function addToolbarItem(graph, toolbar, prototype, image)
{
// Function that is executed when the image is dropped on
// the graph. The cell argument points to the cell under
// the mousepointer if there is one.
var funct = function(graph, evt, cell, x, y)
{
graph.stopEditing(false);
var vertex = graph.getModel().cloneCell(prototype);
vertex.geometry.x = x;
vertex.geometry.y = y;
graph.addCell(vertex);
graph.setSelectionCell(vertex);
}
// Creates the image which is used as the drag icon (preview)
var img = toolbar.addMode(null, image, function(evt, cell)
{
var pt = this.graph.getPointForEvent(evt);
funct(graph, evt, cell, pt.x, pt.y);
});
// Disables dragging if element is disabled. This is a workaround
// for wrong event order in IE. Following is a dummy listener that
// is invoked as the last listener in IE.
mxEvent.addListener(img, 'mousedown', function(evt)
{
// do nothing
});
// This listener is always called first before any other listener
// in all browsers.
mxEvent.addListener(img, 'mousedown', function(evt)
{
if (img.enabled == false)
{
mxEvent.consume(evt);
}
});
mxUtils.makeDraggable(img, graph, funct);
return img;
}
</script>
</head>
<!-- Calls the main function after the page has loaded. Container is dynamically created. -->
<body onload="main();" >
</body>
</html>
Changed how value is set to the graph state:
function generateData(x1, y1, x2, y2){
var graphState = {"tagName":"mxGraphModel","children":[{"tagName":"root","children":[{"tagName":"mxCell","attributes":{"id":"0"}},{"tagName":"mxCell","attributes":{"id":"1","parent":"0"}},{"tagName":"mxCell","attributes":{"id":"2","style":"","vertex":"1","parent":"1"},"children":[{"tagName":"mxGeometry","attributes":{"x": x1,"y": y1 ,"width":"100","height":"40","as":"geometry"}}]},{"tagName":"mxCell","attributes":{"id":"3","style":"","vertex":"1","parent":"1"},"children":[{"tagName":"mxGeometry","attributes":{"x": x2,"y": y2,"width":"100","height":"40","as":"geometry"}}]}]}]};
return graphState;
}
function setGraphData(){
var graphState = generateData("400", "130", "661", "101");
localStorage.setItem('graphState',JSON.stringify(graphState));
}
Refresh the data after page load:
if(window.localStorage.graphState){
var doc = json2html(JSON.parse(localStorage.graphState));
var dec = new mxCodec(doc);
dec.decode(doc.documentElement, graph.getModel());
setTimeout(function() {
var doc = json2html(generateData("400", "30", "661", "101"));
var dec = new mxCodec(doc);
dec.decode(doc.documentElement, graph.getModel());
}, 5000);
}
This will move the box up after a gap of 5 seconds.
CodePen

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.

How to target element on a Google Maps custom InfoBox?

I have created a custom close button for the InfoBox, but I cannot get it to close. I have tried to create an eventlistener for when the box is clicked, but no luck so far. The code is as follows:
InfoPopupbox.prototype = new google.maps.OverlayView();
InfoPopupbox.prototype.onAdd = function() {
//add is after the map has been initiated
var div = document.createElement('div');
var classname = "infoWrapper ";
if (this.customclass){
classname += this.customclass;
}
div.className = classname ;
div.style.border = "none";
div.style.borderWidth = "0px";
div.style.position = "absolute";
div.appendChild(this.content_);
this.div_ = div;
var panes = this.getPanes();
//panes.overlayLayer.appendChild(div);
//float the Pane above the map
panes.floatPane.appendChild(div);
google.maps.event.addListener( this , 'click' , function(){
console.log('click close');
that.infotoggle();
});
};
You want to use addDomListener instead of addListener for click, and you'll need to bind it to the DOM Element:
google.maps.event.addDomListener(this.div, 'click', function() { ... });

Using Google Maps custom overlays to create custom icons (with Raphael JS)

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.

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);
}

Categories

Resources