MapBox GL JS marker offset - javascript

I'm using MapBox GL JS to create a map with a custom marker:
var marker = new mapboxgl.Marker(container)
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);
However, I seem to have some kind of offset problem with the marker. The thing is: when zoomed out a bit, the bottom of the marker is not really pointing to the exact location:
When I'm zooming in a bit further it reaches its destination and it's pointing to the exact spot.
I really love MapBox GL, but this particular problem is bugging me and I'd love to know how to solve it. When this is fixed my implementation is far more superior to the original mapping software I was using.

From Mapbox GL JS 0.22.0 you're able to set an offset option to the marker. https://www.mapbox.com/mapbox-gl-js/api/#Marker
For example to offset the marker so that it's anchor is the middle bottom (for your pin marker) you would use:
var marker = new mapboxgl.Marker(container, {
offset: [-width / 2, -height]
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);

New solution for mapbox-gl.js v1.0.0 - Marker objects now have an anchor option to set the position to align to the marker's Lat/Lng: https://docs.mapbox.com/mapbox-gl-js/api/#marker
var marker = new mapboxgl.Marker(container, {anchor: 'bottom');
This should cover most cases and is more reliable than a pixel offset in my experience.

I've found an solution to my problem. It might be somewhat hacky, but it solves the positioning problem of the marker: I'm using a Popup fill it with a font awesome map marker icon and remove it's "tooltip styled" borders:
Javascript:
map.on('load', function() {
var container = document.createElement('div');
var icon = document.createElement('i');
icon.dataset.city = city;
icon.addEventListener('click', function(e) {
var city = e.target.dataset.city;
var country = e.target.dataset.country
flyTo(datacenters[country][city].coordinates);
});
icon.classList.add('fa', 'fa-map-marker', 'fa-2x');
container.appendChild(icon);
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.setDOMContent(container)
.addTo(map);
});
CSS:
.map div.mapboxgl-popup-content {
background: none;
padding: 0;
}
.map .mapboxgl-popup-tip {
display: none;
}
I just hope someone comes up with a real solution, because this feels kinda dirty to me. But hey: it does the job just fine!

Mapbox Marker now has an element option see this link Mapbox Marker. So instead of appending the icon HTML to the Div element you can simply add into the options when creating a marker. I found this also gets rid of the offset problem. So using the code above you can do this....
var icon = document.createElement('i');
icon.classList.add('fas', 'fa-map-marker-alt');
icon.style.color = 'blue';
new mapboxgl.Marker(container, {anchor: 'center', offset: [0, 0], element: icon})
Also the CSS for the marker can be updated to allow a pointer
.mapboxgl-marker {
border: none;
cursor: pointer;
}

Related

Vector tiles may not be displayed in Mapbox GL JS

We have added vector tiles to the map drawn with Mapbox GL JS. We changed the tiles of addSource based on the sample below, and it shows what we expected.
https://docs.mapbox.com/mapbox-gl-js/example/third-party/
const source = "xxx"
map.addSource(source, {
type: "vector",
tiles: [`http://tiles.url/{z}/{x}/{y}.mvt`],
minzoom: 7,
maxzoom: 17,
});
const sourceLayer = "yyy";
const layerId = "zzz";
map.addLayer(
{
id: layerId,
type: "fill",
source,
"source-layer": sourceLayer,
paint: {
"fill-color": "#ff0000",
},
},
"road-label"
);
map.setPaintProperty(layerId, "fill-opacity", 0.6);
map.setPaintProperty(layerId, "fill-antialias", false);
We added a button there, and we made it so that when the button is pressed, it switches to another source and layer, which is also working as we expected.
const layerId = "zzz";
map.removeLayer(layerId);
const source = "xxx"
map.removeSource(source);
However, when we try the same thing after manipulating the zoom with NavigationControl, the tiles do not show up. How can we solve this trouble?
We have set the maxZoom and minZoom of our maps and sources appropriately. At this zoom the tiles should be visible.
After this problem is encountered, when we manipulate the zoom level, the tiles will reappear. However, if we just manipulate the zoom level from the program, the tiles will not appear.
We also tried adding all the sources and layers and changing the visibility to visible or none to switch, but the same problem occurs.
map.setLayoutProperty(
layerId,
'visibility',
isShow ? 'visible' : 'none'
)
Do you have any advice or tips for us?

Distorted display of Google Maps

What is wrong with my map?
Please go to gtob.openfile.ch/mitglieder, click on «A-Z», then on «Screenbox Multimedia Ltd.». Finally click on the button «Show map», than the same map is displayed in three div's. In the middle div, the map is distorted, and i can not find out why... (Responsive design is not finished now, please show with a screen-width of at least 1200 pixels)
Ok, here is the JavaScript/jquery for building the map:
var memberGoogleMapsData = new Object;
memberGoogleMapsData.mapContainer = $(this).closest('div').find('div.memberMap');
$(this).closest('div').find('button').on('click', function() {
memberGoogleMapsData.mapsPosLat = parseFloat(memberGoogleMapsData.mapContainer.data('mapsposlat'));
memberGoogleMapsData.mapsPosLng = parseFloat(memberGoogleMapsData.mapContainer.data('mapsposlong'));
memberGoogleMapsData.mapsZoom = parseInt(memberGoogleMapsData.mapContainer.data('mapszoom'));
memberGoogleMapsData.markerPosLat = parseFloat(memberGoogleMapsData.mapContainer.data('markerposlat'));
memberGoogleMapsData.markerPosLng = parseFloat(memberGoogleMapsData.mapContainer.data('markerposlng'));
memberGoogleMapsData.mapId = memberGoogleMapsData.mapContainer.prop('id');
memberGoogleMapsData.mapPos = new google.maps.LatLng(memberGoogleMapsData.mapsPosLat, memberGoogleMapsData.mapsPosLng);
memberGoogleMapsData.options = {
zoom: memberGoogleMapsData.mapsZoom,
center: memberGoogleMapsData.mapPos,
mapTypeId: google.maps.MapTypeId.HYBRID,
tilt: 0,
heading: 0
};
memberGoogleMapsData.myMap = new google.maps.Map(document.getElementById(memberGoogleMapsData.mapId),memberGoogleMapsData.options);
memberGoogleMapsData.myMap2 = new google.maps.Map(document.getElementById('testMap'),memberGoogleMapsData.options);
memberGoogleMapsData.myMap3 = new google.maps.Map(document.getElementById('testMap2'),memberGoogleMapsData.options);
});
In the div.memberMap I have data-Tags for providing the required data for building the google map:
<div class="memberMap" id="memberMap207" data-mapsposlat="47.50467769999999" data-mapsposlong="9.39942510000003" data-mapszoom="13" data-markerposlat="47.50467769999999" data-markerposlng="9.39942510000003">
You have the following style in the customer.css file
#members div.members img {
float: right;
max-width: 300px;
max-height: 150px;
}
Your first map is inside the div.members, so this style breaks all the tile images of the map as well.
The second map that is outside of div.members is not affected by this style.
I hope it helps!

MapBox: Markers not showing clickable cursor on mouseover

I am moving from leaflet+cloudmade to mapbox and have been doing minor rewrites to my code where necessary. I am refreshing my map and in my previous installment it was easiest to add each marker in to it's own layer and then on refresh to remove all layers and redraw the markers.
Here is my current code:
function setLeafletMarker(lat, lng, iconType, popupHTML) {
popupHTML = typeof popupHTML !== 'undefined' ? popupHTML : "";
var LamMarker = new L.Marker([lat, lng], { icon: iconType }); //.on('click', markerClick); ;
markers.push(LamMarker);
LamMarker.bindPopup(popupHTML);
map.addLayer(LamMarker);
}
I suspect this has something to do with the problem, which is that when I put my mouse cursor over a marker, it stays as a hand (draggable) instead of changing to be a pointy finger, meaning the marker is clickable. Clicking works fine, but it's not very intuitive. How do I change the hand to pointy finger?
Ran into the same problem also. Did a quick check of CSS on the mapbox site, and they seem to fix it using a css rule in their sitewide css file (not map specific). I was able to fix the problem using the same approach, by adding this to my sitewide css.
.leaflet-overlay-pane path,
.leaflet-marker-icon {
cursor: pointer;
}
I have compared the default leaflet.css with the default mapbox.css and leaflet includes this
.leaflet-clickable {
cursor: pointer;
}
while mapbox does not.
One way is you can just add the behavior to the mouseover and mouseout events:
LamMarker.on("mouseover", function(e) {
document.getElementById('map').style.cursor = "pointer";
}).on("mouseout", function(e) {
document.getElementById('map').style.cursor = "grab";
});
In the current mapbox api (2022) this works. I'm not sure if there is a smarter way to do this as the docs are terrible in this department.
map.on('mouseover', 'source-id', e => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'source-id', e => {
map.getCanvas().style.cursor = ''
})
This assumes you are adding a source layer to your map as in this example
If your not using source layers, you can target your marker icon via css
.marker svg {
cursor: pointer;
}

Make a color tinted map in Google Maps v3

I have searched around the net, and found several solutions. Including this thread here on SO. However, all of the methods creates a flicker on the screen while zooming in/out.
Is there any way of preventing this? I'm currently applying a ImageMapType as overlay. It works great, but the flickering occurs while zooming.
Are there any other alternatives to apply a color tint to the map, but not the other overlays (markers and such).
This is how I apply my ImageMapType btw:
var overlayTint = new google.maps.ImageMapType({
getTileUrl: function(tile, zoom) {return 'library/img/maptint.png';},
tileSize: new google.maps.Size(512, 512),
opacity: 0.30,
isPng: true
});
map.overlayMapTypes.insertAt(0, overlayTint);
EDIT: I'm using Satellite maps, which implies that Styled Maps are not the way to go either.
I found a solution that works pretty well!
I extended the OverlayView class to make my own custom overlay. This overlay consists of a single <div> that has a half-transparent blue:ish color. The div is screensize*2, and is placed in the middle of the screen.
The reason for being twice as big is because the zoom in/out. While you are zooming in, the overlay gets relative resized to fit the ending position. Since the ending position is smaller, this creates a big gap around the overlay. However, if we make it twice as big as the screen, we avoid this problem!
And as the tip of the iceberg, we hook the drag, dragend and center_changed events to re-positionize this div to the center of the screen. This ensures that the overlay stays in place when the user pans around as well.
Hope that this can help anybody else. Thank you for reading!
Just had to solve this myself. This thread gave me quite a few pointers on where to look, but I arrived on a slightly different solution than OP.
I instead created an overlay at the bounds of Lat/Lng, which is -90, -90 to 90 90. The overlay was again, a simple div with the background set to an rgba color.
Here is my solution in coffeescript:
gm = google.maps
center = new gm.LatLng 50, 0
class colorOverlay extends gm.OverlayView
constructor: (#color, #map) ->
#div = null
#setMap #map
#bounds = new gm.LatLngBounds(new gm.LatLng(-90, -90), new gm.LatLng(90, 90))
onAdd: () ->
col = document.createElement 'div'
col.style.width = '100%'
col.style.height = '100%'
col.style.position = 'absolute'
col.style.background = #color
#div = col
panes = #getPanes()
panes.overlayLayer.appendChild(#div)
draw: () ->
# Overlay Projection
oP = #getProjection()
sw = oP.fromLatLngToDivPixel #bounds.getSouthWest()
ne = oP.fromLatLngToDivPixel #bounds.getNorthEast()
div = #div
div.style.left = sw.x + 'px'
div.style.top = ne.y + 'px'
div.style.width = (ne.x - sw.x) + 'px'
div.style.height = (sw.y - ne.y) + 'px'
map = new gm.Map mapElem,
zoom: 10
mapTypeId: google.maps.MapTypeId.HYBRID
center: center
marker = new gm.Marker
position: center
map: map
title: "Hello World!"
overlay = new colorOverlay 'rgba(37,41,56,0.6)', map

z-Index overlay in google maps version 3

I'm trying to get an overlay in google maps api v3 to appear above all markers. But it seems that the google api always put my overlay with lowest z-index priority. Only solution i've found is to iterate up through the DOM tree and manually set z-index to a higher value. Poor solution.
I'm adding my a new div to my overlay with:
onclick : function (e) {
var index = $(e.target).index(),
lngLatXYposition = $.view.overlay.getProjection().fromLatLngToDivPixel(this.getPosition());
icon = this.getIcon(),
x = lngLatXYposition.x - icon.anchor.x,
y = lngLatXYposition.y - icon.anchor.y
$('<div>test</div>').css({ left: x, position: 'absolute', top: y + 'px', zIndex: 1000 }).appendTo('.overlay');
}
I've tried every property I could think of while creating my overlay. zIndex, zPriority etc.
I'm adding my overlay with:
$.view.overlay = new GmapOverlay( { map: view.map.gmap });
And GmapOverlay inherits from new google.maps.OverlayView.
Any ideas?
..fredrik
If anyone was having the same problem as I was, here is my problem and solution:
I needed an OverlayView which would add tooltips to markers, but my popup overlay kept showing behind the markers.
I implemented a subclass of the OverlayView as per the Google documentation:
https://developers.google.com/maps/documentation/javascript/customoverlays
When you write your custom OverlayView.prototype.onAdd function, you need to specify to which Pane to attach your overlay. I just copied the code without actually reading the surrounding explanation.
In their code, they attach the overlay to the overlayLayer pane:
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
But there are many different MapPanes you can use:
"The set of panes, of type MapPanes, specify the stacking order for different layers on
the map. The following panes are possible, and enumerated in the order in which they are stacked from bottom to top:"
MapPanes.mapPane (Level 0)
MapPanes.overlayLayer (Level 1)
MapPanes.markerLayer (Level 2)
MapPanes.overlayMouseTarget (Level 3)
MapPanes.floatPane (Level 4)
I wanted the overlay to hover over all other info on the map, so I used the floatPane pane and problem solved.
So, instead of :
this.getPanes().overlayLayer.appendChild(div)
you use this :
this.getPanes().floatPane.appendChild(div);
You can't change the zIndex of an OverlayView (it has no such property), but it holds panes that contains DOM nodes. That's where you can use the z-index property;
...
lngLatXYposition = $.view.overlay.getPanes().overlayLayer.style['zIndex'] = 1001;
...
In order to be able to play around with the paneType of the mapLabel class, I added a paneType property to the MapLabel class from google utility library (https://code.google.com/p/google-maps-utility-library-v3/source/browse/trunk/maplabel/src/maplabel.js?r=303).
This is usefull to make the label not to be hidden by a polyline.
Please find the code additions to the mapLabel.js file.
MapLabel.prototype.onAdd = function() {
var canvas = this.canvas_ = document.createElement('canvas');
var style = canvas.style;
style.position = 'absolute';
var ctx = canvas.getContext('2d');
ctx.lineJoin = 'round';
ctx.textBaseline = 'top';
this.drawCanvas_();
var panes = this.getPanes();
if (panes) {
// OLD: panes.mapPane.appendChild(canvas)
var paneType = this.get('paneType');
panes[paneType].appendChild(canvas);
}
};
MapLabel = function (opt_options) {
this.set('fontFamily', 'sans-serif');
this.set('fontSize', 12);
this.set('fontColor', '#000000');
this.set('strokeWeight', 4);
this.set('strokeColor', '#ffffff');
this.set('align', 'center');
this.set('zIndex', 1e3);
this.set('paneType', 'floatPane');
this.setValues(opt_options);
}
Sample code using the paneType:
var mapLabel = new MapLabel({
text: segDoc.curr_value.toFixed(0),
position: new google.maps.LatLng(lblLat, lblLng),
map: map.instance,
fontSize: 12,
align: 'center',
zIndex: 10000,
paneType: 'floatPane',
});
Thanks!
Setting z-index to 104 for the overLay layer seems to be the "magic" number" if you care about interacting with the markers (i.e. dragging markers). Any higher than 104 and you can not interact with the markers. Wondering if there is a less brittle solution...
Use panes.overlayMouseTarget.appendChild
If you want to allow your layer to be targetable through mouse clicks (and use events such as "click" or CSS pseudo ::hover) then you should add your overlay to the map using overlayMouseTarget
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.div_);
Also see:
https://developers.google.com/maps/documentation/javascript/reference?csw=1#MapPanes
Disclaimer: this is a dodgy solution that may stop working at any time and you definitely shouldn't use this in production.
For those looking for a quick and dirty solution, this CSS worked for me:
.gm-style > div:first-child > div:first-child > div:nth-child(4) {
z-index: 99 !important;
}
Use at your own risk!

Categories