I have a textarea that I copy a GeoJson into it and the map must show its shape. This is not a problem until I wanted this shape to be editable.
So I used below code to convert it to layer and add to featuregroup so I can edit it using leaflet-draw. But this code works for POINT and LINE but not for POLYGONS. In case of polygon, move handlers that should appears at each side of polygon, not appears.
What could be problem ?
var drawnItems = L.featureGroup().addTo(mymap);
mymap.addControl(new L.Control.Draw({
edit: {
featureGroup: drawnItems,
poly: {
allowIntersection: false
}
},
draw: {
polygon: {
allowIntersection: false,
showArea: true
}
}
}));
var str = document.getElementById("ingeojson").value;
var shapeJson = JSON.parse(str);
var shape = L.geoJSON(shapeJson);
var shapeLayer = L.GeoJSON.geometryToLayer(shapeJson);
drawnItems.addLayer(shapeLayer);
shapeLayer.addTo(mymap);
mymap.fitBounds(shapeLayer.getBounds());
I finally solved it. The problem was related to version of leaflet and leaflet-draw I used.
At the time of writing this post, I used leaflet-draw 0.4.7 and leaflet 1.0.2 and the problem is solved .
Related
quick (and I believe for some of you an easy) question regarding cursor styling while hovering above geojson layer/s.
So, I have one clip layer that I'm using to create a mask around wms layers, and another one that represents some administrative areas.
As you can see in picture below
What I would like is to change style of cursor when I'm hovering above administrative areas but it seems that I'm missing something.
I'm trying to isolate to layer only administrative borders layer using this block of code:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
return vectorJLS.get('layer_name') === 'jls';
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
UPDATE
While JGH tweak code a bit it still doesn't work. I've detected that problem lies in layer that I'm using for mask clipping, when removed, code that JGH provided, works.
Here is code that I'm using for mask clipping
var clipLayer = new ol.layer.Image({
source: new ol.source.ImageVector({
source: new ol.source.Vector({
url: 'geojson/clip_wgs.geojson',
format: new ol.format.GeoJSON()
}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'black'
})
})
})
});
clipLayer.on('postcompose', function(e) {
e.context.globalCompositeOperation = 'source-over';
});
clipLayer.on('precompose', function(e) {
e.context.globalCompositeOperation = 'destination-in';
});
clipLayer.setMap(map);
Is it possible to somehow ignore clip layer when changing cursor style or should I take another approach?
UPDATE - 2
I've tweaked code a bit, but still without any success while clipedLayer is on.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return vectorJLS.get('layer_name') === 'jls';
}
});
console.log(hit)
});
Interesting problem if I might add
Finally, with help from fellow JGH I've found appropriate solution for my problem.
Searching release pages and google machine I've stumbled upon some interesting information regarding layer filters and its usage in method hasFeatureAtPixel. This block of code is valid for versions below 3.20.1 but more about that on OpenLayers Git
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
For newer versions you should use layer filter like this (I'm using 4.6.5)
map.hasFeatureAtPixel(pixel, {
layerFilter: layerFilterFn.bind(layerFilterThis)
});
Or for my particular problem like this
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Hope it helps :)
In your function, you are basically looping through all the layers at the mouse location. In that loop, if the layer has the proper name you set the pointer, else if it has a different name, you remove the pointer (or set it to something else).
As it is, it is dependent on the layer order:
ex: layer 1 = target -> set custom pointer. Layer 2 = other layer -> remove pointer. ==> final pointer: removed
ex: Layer 1 = other layer -> remove pointer. Layer 2 = target -> set custom pointer. ==> final pointer: custom pointer
The looping occurs when you set the hit variable, i.e. it corresponds to the last layer only as you are overriding the value for each layer.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = false;
e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if ( vectorJLS.get('layer_name') === 'jls') {
//IF we have found the layer, flag it (but don't return anything!)
hit = true;
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
I am using Leaflet and Leaflet.Draw, and I am letting the user from my code to draw polygon (NOT using the Leaflet Draw Controls).
While the user is drawing the polygon I need to change the color of its first vertex, for example: green, so that user knows that he needs to click on the first point in order to close the polygon and finish drawing.
How can I change color of first vertex while drawing polygon using Leaflet.Draw?
The following image for elaboration, meaning it's fixed with a Paint Software.
P.S. Here is my code
var map = L.map('mapid',
{
minZoom: -1,
maxZoom: 4,
center: [0, 0],
zoom: 1,
crs: L.CRS.Simple
});
var polygonDrawer = new L.Draw.Polygon(map);
map.on('draw:created', function (e) {
var type = e.layerType, layer = e.layer;
layer.editing.enable();
layer.addTo(map);
});
$(document)ready(function(){
polygonDrawer.enable();
});
While I was hacking with the Leaflet.Draw and on the creation of polygon I have come up with the following code:
map.on('draw:drawvertex',
function (e) {
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-editing-icon.leaflet-touch-icon.leaflet-zoom-animated.leaflet-interactive:first").css({ 'background-color': 'green' });
});
So, there is a listener you can insert it in your code, draw:drawvertex which means whenever a vertex created I need to do something.
Then, using jQuery you're selecting the first element from this long selector, and set its background color to green or any other color.
This is a way to do it with CSS only:
#root
> main
> div
> div.col-sm-8.m-auto.p-0.flex-column.float-right
> div.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom
> div.leaflet-pane.leaflet-map-pane
> div.leaflet-pane.leaflet-marker-pane
> div:nth-child(2) {
background: green;
}
For me, worked this way (classes are a little bit different. leaflet 1.3.1 and draw 0.4.3)
map.on('draw:drawvertex', function (e) {
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-editing-icon.leaflet-zoom-animated.leaflet-interactive:first").css({ 'background-color': 'green' });
});
This is worked for me:
map.on("editable:vertex:dragend", function (e) {
// Set GREEN color for Vertex START (First) Point
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-vertex-icon.leaflet-zoom-animated.leaflet-interactive.leaflet-marker-draggable:nth-child(1)").css({ 'background-color': 'green' });
// Set RED color for Vertex END (Last) Point
$(".leaflet-marker-icon.leaflet-div-icon.leaflet-vertex-icon.leaflet-zoom-animated.leaflet-interactive.leaflet-marker-draggable:nth-child(2)").css({ 'background-color': 'red' });
});
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;
}
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;
}
I'm trying to produce a mapping application with Bing Maps with a button that will retrieve a JSON string and places pins on the map based on the center of the map.
That is working fine, but I'm running into two issues that I'm having trouble diagnosing.
The first is that when I move the map after placing the pins, the majority of them disappear from the map except for 1-3. I've figured out that the pins are still being held in map.entities, but just aren't all displaying.
The second issue is that I have a click event on the pins, and sometimes when I click on a pin it will disappear (and sometimes reappear elsewhere on the map).
Here is my code:
function addPin() {
map.entities.clear();
var pinImg = "images/MapPin.jpg";
var latLong = {};
var name;
for (var i = 0; i < factualJson.response.data.length; ++i) {
latLong['latitude'] = factualJson.response.data[i].latitude;
latLong['longitude'] = factualJson.response.data[i].longitude;
name = factualJson.response.data[i].name;
var pin = new Microsoft.Maps.Pushpin(latLong, {
icon: pinImg,
anchor: new Microsoft.Maps.Point(latLong['latitude'], latLong['longitude']),
draggable: true,
width: 48,
height: 48
});
Microsoft.Maps.Events.addHandler(pin, 'click', displayName);
pin.title = name;
pin.id = 'pin' + i;
map.entities.push(pin);
}
document.getElementById("arrayLength").innerHTML = "Number of locations: " + map.entities.getLength();
}
function displayName(e) {
document.getElementById("name").innerHTML = "";
if (this.target.id != -1) {
document.getElementById("name").innerHTML = this.target.title;
}
}
function boot() {
Microsoft.Maps.loadModule('Microsoft.Maps.Overlays.Style', { callback: getMap });
}
function getMap() {
map = new Microsoft.Maps.Map($gel("bingMap"), {
credentials: getKey(),
customizeOverlays: true,
enableClickableLogo: true,
enableSearchLogo: true,
showDashboard: true,
showBreadcrumb: true,
showCopyright: true,
zoom: 10,
labelOverlay: Microsoft.Maps.LabelOverlay.hidden
});
setGeoLocation();
//setTimeout(optimizeMap, 100);
window.onresize = resizeWin;
resizeWin();
}
Currently I make an ajax call from the button, and the callback function calls 'AddPin' which adds the pins to the map. I thought I'd add in the map initialization code in case it was relevant. Currently boot() is called on body load.
For me the solution was similar to yours #canadian coder
Microsoft.Maps.Location() only accepts float values, no strings and Int.
I use MVC architecture and passed a string using a model. Later i converted that string to float and passed to Location.
Problem solved.
var pushpin = new Microsoft.Maps.Pushpin(
center, { icon: '/Content/BingPushpin.png', width: 50, height: 50, draggable: false });
pushpin.setLocation(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
dataLayer.push(pushpin);
locations.push(new Microsoft.Maps.Location
(parseFloat(#Model.Latitude) , parseFloat(#Model.Longitude)));
EDIT :
Later found out that problem still exist. Another reason can be that you are calling that Map to load twice. So check for any other instance of the map which is being loaded. In my case see below.
$(document).ready(function () {
loadSearchModule(); //calling Map to Load at a div without pushpins
//code to do something
getMapOnLocation();
}
function getMapOnLocation()
{//code to display map with pushpin
}
In the Above example I was telling the control to load my map with PushPins and when the page is fully Loaded load the map without pushpins.
Hope this helps :)
As always I need to ask the question before I figure it out myself.
The issue was that I needed to push a Microsoft location object into the pin and not an object. Like so:
var loc = new Microsoft.Maps.Location(47.592, -122.332);
And NOT my latLong object.
This also seemed to fix the issue of disappearing pins on click event.