Using leaflet.FreeDraw with leaflet.path.drag - leaflet

I am wondering if it's possible to use Leaflet.FreeDraw with leaflet.path.drag to be able to drag the polygon created by FreeDraw plugin.
jsfiddle
I tried to enable dragging plugin like this, but it doesn't work.
const map = new L.Map(document.querySelector('section.map'), { doubleClickZoom: false }).setView(LAT_LNG, 14);
L.tileLayer(TILE_URL).addTo(map);
const freeDraw = new FreeDraw({ mode: FreeDraw.ALL });
map.addLayer(freeDraw);
freeDraw.dragging.enable();

You could extract the bounds from the FreeDraw by listening to the markers event to create a polygon or other map object using leaflet enabled with dragging. See working example below.
You should consider whether you would like to disable the FreeDraw after this, using the option leaveModeAfterCreate:true as the user may get additional polygons when dragging
const LAT_LNG = [51.505, -0.09];
const TILE_URL = 'https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}#2x.png';
const map = new L.Map(document.querySelector('section.map'), { doubleClickZoom: false }).setView(LAT_LNG, 14);
L.tileLayer(TILE_URL).addTo(map);
const freeDraw = new FreeDraw({
mode: FreeDraw.ALL,
leaveModeAfterCreate:true //recommended to prevent undesired creation of multiple polygons
});
map.addLayer(freeDraw);
//freeDraw.dragging.enable();
//STEP 1: Listen to markers event raised by free draw whenever edits (create/edit/deletions are made to the map)
freeDraw.on("markers",function(event){
//we are only interested in create events
//we aim to extract the bounds and remove the existing
// freedraw references. If it is that you would like your
// user to edit the polygon, then you may keep these and do the // additional work to manage and update these references
if(event.eventType=='create' && event.latLngs.length > 0){
//capture the current polygon bounds (store in 1st position)
var latLngs = event.latLngs[0];
freeDraw.clear(); //clear freedraw markers
//create polygon from lat lng bounds retrieved
var polygon = L.polygon(
latLngs.map(function(latLng){
return [latLng.lat,latLng.lng];
}), {
color: 'red',
draggable: true //make polygon draggable
}).addTo(map);
}
})
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.map {
width: 100vw;
height: 100vh;
}
.map.mode-create {
cursor: crosshair;
}
.leaflet-edge {
background-color: #95bc59;
box-shadow: 0 0 0 2px white, 0 0 10px rgba(0, 0, 0, .35);
border-radius: 50%;
cursor: move;
outline: none;
transition: background-color .25s;
}
.leaflet-polygon {
fill: #b4cd8a;
stroke: #50622b;
stroke-width: 2;
fill-opacity: .75;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.2/leaflet.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.2/leaflet-src.js"></script>
<script src="https://rawgit.com/Wildhoney/Leaflet.FreeDraw/master/dist/leaflet-freedraw.iife.js"></script>
<script src="https://npmcdn.com/leaflet.path.drag/src/Path.Drag.js"></script>
<section class="map"></section>
NB. Also see working example on js-fiddle here https://jsfiddle.net/ytevLbgs/

Related

Mapbox fitbounds() - Invalid LngLat object: (NaN, NaN)

I have been banging my head against the desk for the last few hours.
I am trying to get Mapbox to zoom in on load to the bounding area of all my markers.
However, this is the error I am getting for the code below.
This error comes after the console log image below, so the lat lng coordinates are definitely there.
Uncaught Error: Invalid LngLat object: (NaN, NaN)
const onLoad = () => {
let points = [];
props.cats.forEach(cat => (
points.push([ cat.lat, cat.lng ])
));
points.length > 0 && ref.current.getMap().fitBounds(points, {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
easing(t) {
return t * (2 - t);
},
});
};
If you have more than a pair of coordinates, you should first reduce the array with coordinates.reduce, and then define the bounds through new mapboxgl.LngLatBounds. After that you can fly with map.fitBounds to the bounds, defining your favorite padding and easing function as you did in your code.
var coordinates = points;
var bounds = coordinates.reduce(function(bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
map.fitBounds(bounds, {
padding: { top: 50, bottom: 50, left: 50, right: 50 },
easing(t) {
return t * (2 - t);
}
});
I have prepared this fiddle with the solution how to fit bounds to a list of coords with an array of 3 coords, but you can apply easily to yours.
And this is the result
Then tap on Zoom to bounds
In my case, I was getting the same error when the padding value was high.
//this did not work
map.fitBounds(fitBounds, {padding: 150});
//this worked
map.fitBounds(fitBounds);
For the fitBounds() function you will need to pass your bounds as a LngLatBounds object, an array of LngLatLike objects in [South West, North East] order, or an array of numbers in [west, south, east, north] order. Mapbox has an example of this on their website here.
If you want to capture all of your markers you could calculate the most western, southern, eastern, and northern values of your coordinates and then pass them as an array. In your case: [-0.54664079, 51.38542169, -0.3735228, 51.45368209].
mapboxgl.accessToken = 'pk.eyJ1IjoicGxtYXBib3giLCJhIjoiY2s3MHkzZ3VnMDFlbDNmbzNiajN5dm9lOCJ9.nbbtDF54HIXo0mCiekVxng';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
document.getElementById('fit').addEventListener('click', function() {
map.fitBounds([-0.54664079, 51.38542169, -0.3735228, 51.45368209]);
});
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
#fit {
display: block;
position: relative;
margin: 0px auto;
width: 50%;
height: 40px;
padding: 10px;
border: none;
border-radius: 3px;
font-size: 12px;
text-align: center;
color: #fff;
background: #ee8a65;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Fit a map to a bounding box</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.11.1/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.11.1/mapbox-gl.css" rel="stylesheet" />
</head>
<body>
<div id="map"></div>
<br />
<button id="fit">Fit to Box</button>
</body>
</html>
I don't know this is relevant with your problem but when I get this error in mobile browsers on my map work, I solved problem with this code:
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|OperaMini/i.test(navigator.userAgent)) {
$('#mapContainer').css({ 'width': (($(window).width())) + 'px' });
}
#mapContainer width was 100% on default.
one option is to find the maximum lng and lat for north east and minimum lng and lat for south west like
function buildBounds(cords) {
const allLats = [];
const allLngs = [];
cords.forEach((cord) => {
allLats.push(cord.lat);
allLngs.push(cord.lng);
});
const ne = [Math.max(...allLngs), Math.max(...allLats)];
const sw = [Math.min(...allLngs), Math.min(...allLats)];
console.log(ne, sw);
return [sw, ne];
}

Leaflet.js: Use ctrl + scroll to zoom the map & Move map with two fingers on mobile

I'm using http://leafletjs.com/ ... is it possible to only:
Use ctrl + scroll to zoom the map
Move map with two fingers on mobile/tablet
... so similar what google maps does? With the comments ...
So far thats my setup:
// Leaflet Maps
var contactmap = L.map('contact-map', {
center: [41.3947688, 2.0787279],
zoom: 15,
scrollWheelZoom: false
});
There is an amazing library that does exactly that. Leaflet.GestureHandling
It is an add on to leaflet that works right of the box, it's also modular and can be installed using npm.
Here's a working example using leaflet and GestureHandling.
You can try it also on mobile.
P.S. It has multiple languages baked in:)
// Attach it as a handler to the map
const map = L.map('map', {
gestureHandling: true
}).setView([51.505, -0.09], 13);
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
#map {
height: 400px;
width: 400px;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.4.0/dist/leaflet.css"
integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
crossorigin=""/>
<link rel="stylesheet" href="//unpkg.com/leaflet-gesture-handling/dist/leaflet-gesture-handling.min.css"
type="text/css">
<script src="https://unpkg.com/leaflet#1.4.0/dist/leaflet.js"
integrity="sha512-QVftwZFqvtRNi0ZyCtsznlKSWOStnDORoefr1enyq5mVL4tmKB3S/EnC3rRJcxCPavG10IcrVGSmPh6Qw5lwrg=="
crossorigin=""></script>
<script src="//unpkg.com/leaflet-gesture-handling"></script>
<div id="map"></div>
zoom map using ctrl + zoom. I did in custom way .
html code is below
<div id="map"></div>
css
.map-scroll:before {
content: 'Use ctrl + scroll to zoom the map';
position: absolute;
top: 50%;
left: 50%;
z-index: 999;
font-size: 34px;
}
.map-scroll:after {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
content: '';
background: #00000061;
z-index: 999;
}
jQuery
//disable default scroll
map.scrollWheelZoom.disable();
$("#map").bind('mousewheel DOMMouseScroll', function (event) {
event.stopPropagation();
if (event.ctrlKey == true) {
event.preventDefault();
map.scrollWheelZoom.enable();
$('#map').removeClass('map-scroll');
setTimeout(function(){
map.scrollWheelZoom.disable();
}, 1000);
} else {
map.scrollWheelZoom.disable();
$('#map').addClass('map-scroll');
}
});
$(window).bind('mousewheel DOMMouseScroll', function (event) {
$('#map').removeClass('map-scroll');
})
In simple way when user scroll on map then detect ctrl button is pressed or not then just I add one class that will showing message on map. and prevent screen zoom-in and zoom-out outside of map.
I managed to solve your second problem.
I used css for displaying the message using a ::after pseudo selector.
#map {
&.swiping::after {
content: 'Use two fingers to move the map';
}
}
And javascript to capture the touch events.
mapEl.addEventListener("touchstart", onTwoFingerDrag);
mapEl.addEventListener("touchend", onTwoFingerDrag);
function onTwoFingerDrag (e) {
if (e.type === 'touchstart' && e.touches.length === 1) {
e.currentTarget.classList.add('swiping')
} else {
e.currentTarget.classList.remove('swiping')
}
}
It checks if the type is a touchevent and if you are using 1 finger, if so it adds the class to the map with the message. If you use more than one finger it removes the class.
Working demo I suggest you using a mobile device.
Code pen from the demo

Mapbox allow user to click on map and pin?

As the question says I want to allow users to click on the map and add a pin, I need to do that in order to display to users where is the business located and because I don't know the location. I need to allow them to insert it. However, documentation is a bit confusing for me and either I cannot find it or I just missed it.
So far I have:
<div id="right" class="map">
<div id='map' style='width: 100%; height: 100%;'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibGl2ZS1vbGRoYW0iLCJhIjoiY2ozbXk5ZDJ4MDAwYjMybzFsdnZwNXlmbyJ9.VGDuuC92nvPbJo-qvhryQg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v10'
});
</script>
</div>
You could add a marker using the mapbox gl js API.
Example is already there:
https://www.mapbox.com/blog/custom-markers-mapboxgl/
https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/
If you want to add the marker on a clicked position you could use something like this:
map.on('click', (e) => {
var coords = `lat: ${e.lngLat.lat} <br> lng: ${e.lngLat.lng}`;
// create the popup
var popup = new mapboxgl.Popup().setText(coords);
// create DOM element for the marker
var el = document.createElement('div');
el.id = 'marker';
// create the marker
new mapboxgl.Marker(el)
.setLngLat(e.lngLat)
.setPopup(popup)
.addTo(map);
});
.mapboxgl-marker {
height: 20px;
width: 20px;
z-index: 5;
border: 1px solid black;
border-radius: 50%;
background-color: #305bad;
}
You can add a click event handler to the map surface. In the handler, add a layer with your "pin". In the example below, I added a circle layer but you could just as easily add a symbol layer for a custom "pin".
self.map.on("click", function(e){
console.log("background click", e.lngLat);
var geojson = {
type: "FeatureCollection",
features: [{
type:"Feature",
geometry: { type: "Point", coordinates: [ e.lngLat.lng, e.lngLat.lat ]}
}]
};
self.map.addSource("pins", {
"type": "geojson",
"data": geojson
});
self.map.addLayer({
id: "pinsLayer",
type: "circle",
source: "pins",
paint: {
"circle-color": "red",
"circle-radius": 5
}
});
});

Mapbox-gl-js - Mouse events are off after applying CSS transform to map

I am using mapbox-gl-js to display a map inside of a responsive container.
Regardless of the size of the responsive container, I want the map to maintain the same resolution such that resizing the window does not affect the bounds or zoom level of the map, it simply scales the map as if it were a static image.
I achieve this by dynamically calculating the scale factor and applying it to the container via CSS transform:
#map {
width: 100%;
height: 100%;
}
#map-scaler {
width: 1280px;
height: 1280px;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
transform-origin: 0 0;
}
#map-container {
background-color: #fff;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
width: 100%;
height: 100%;
}
<div id="map-container">
<div id="map-scaler">
<div id="map"></div>
</div>
</div>
// Get size of map container for display
var mapContainer = $("#map-container");
var mcWidth = mapContainer.width();
var mcHeight = mapContainer.height();
// Get size of scaler container
var scaleContainer = $("#map-scaler");
var sWidth = scaleContainer.width();
var sHeight = scaleContainer.height();
var scaleWidth = mcWidth / sWidth;
var scaleHeight = mcHeight / sHeight;
$("#map-scaler").css("transform", "scale(" + scaleWidth + ", " + scaleHeight + ")");
This achieves the desired results visually. For example, I can have a 1280x1280 map displayed inside of a 500x500 container.
The problem is that all the mouse events are now "off". For example, if you attempt a map click or a scroll zoom, the map applies your mouse event to the wrong area of the map.
How can I compensate for my CSS transform so that mouse events accurately reflect where the user clicked?
Can you try the resize method? As mentionned in the doc, the method looks at your div width&height and resize your map. Event should be updated too.
https://www.mapbox.com/mapbox-gl-js/api/#Map#resize

Can you use jQuery .css() with .live()?

I have a div with class="centerMessage" . This div is inserted into the DOM at a point after the page is loaded. I would like to change the CSS on this div to center it. I tried the CSS function below, but it did not work. Does anybody know a way to do this?
function centerPopup() {
var winWidth = $(window).width();
var winHeight = $(window).height();
var positionLeft = (winWidth/2) - (($('.centerMessage').width())/2);
var positionTop = (winHeight/2) - (($('.centerMessage').height())/2);
$('.centerMessage').live( function(){
$(this).css("position","absolute");
$(this).css("top",positionTop + "px");
$(this).css("left",positionLeft + "px");
});
}
If my assumption of what you're trying to achieve is correct, you don't need any Javascript to do this. It can be achieved by some simple CSS.
.centerMessage {
position: absolute;
top: 50%;
left: 50%;
margin-top: -150px; /* half of the height */
margin-left: -300px; /* half of the width */
width: 600px;
height: 300px;
background: #ccc;
}
Demo: http://jsbin.com/awuja4
.live() does not accept JUST a function. If you want something to happen with live, it needs an event as well, like click. If you want something to happen always for every .centerMessage, you will need the plugin .livequery()
I believe that the following works in FF & Webkit.
div.centerMessage{
position: absolute;
width: /* width */;
height: /* height */;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
margin: auto;
}
I know this is already answered, but I thought I'd provide a working jsFiddle demo using JavaScript like the OP originally wanted, but instead of using live(), I use setInterval():
First, we need to declare a couple variables for use later:
var $centerMessage,
intervalId;
The OP's issue was that they didn't know when the div was going to be created, so with that in mind we create a function to do just that and call it via setTimeout() to simulate this div creation:
function createDiv() {
$('<div class="centerMessage">Center Message Div</div>').appendTo("body");
}
$(function() { setTimeout("createDiv()", 5000); });
Finally, we need to create a function that will check, using setInterval() at a rate of 100ms, to see if the div has been created and upon creation, goes about modifying the div via jQuery:
function checkForDiv() {
$centerMessage = $('.centerMessage');
if ($centerMessage.length) {
clearInterval(intervalId);
var $window = $(window),
winHeight = $window.height(),
winWidth = $window.width(),
positionTop = (winHeight / 2) - ($centerMessage.height() / 2),
positionLeft = (winWidth / 2) - ($centerMessage.width() / 2);
$centerMessage.css({
"display" : "block",
"position" : "absolute",
"top" : positionTop.toString() + "px",
"left" : positionLeft.toString() + "px"
});
}
}
$(function() { intervalId = setInterval(checkForDiv, 100); });
Try Use this
$('.centerMessage').live('click', function(){
Try this
$('#foo').on('click', function() {
alert($(this).text());
});
$('#foo').trigger('click');
OR
$('#foo').live('click', function() {
alert($(this).text());
});
$('#foo').trigger('click');