In the code snippet below, I have setup the leaflet.draw plugin. Works fine for adding features (lines, markers, polygons). Works fine for editing and deleting. But the cancel operation does not work (nor does the simple intersection test, but I can live without that). Any idea what I did wrong to setup the plugin?
(Chrome V44, leaflet 1.0 Beta 2, leaflet.draw (0.2.4-dev) (seems to also fail in leaflet '0.7.7').
Here is the error:
Uncaught TypeError: Cannot read property '0' of undefined
L.Polyline.L.Path.extend._projectLatlngs # leaflet-src.js:5535
L.Polyline.L.Path.extend._projectLatlngs # leaflet-src.js:5547
L.Polyline.L.Path.extend._projectLatlngs # leaflet-src.js:5547
L.Polyline.L.Path.extend._project # leaflet-src.js:5519
L.SVG.L.Renderer.extend._updatePath # leaflet-src.js:6042
L.Path.L.Layer.extend.redraw # leaflet-src.js:5130
L.Polyline.L.Path.extend.setLatLngs # leaflet-src.js:5411
L.EditToolbar.Edit.L.Handler.extend._revertLayer # leaflet.draw-src.js:2759
(anonymous function) # leaflet.draw-src.js:2716
L.LayerGroup.L.Layer.extend.eachLayer # leaflet-src.js:4865
L.EditToolbar.Edit.L.Handler.extend.revertLayers # leaflet.draw-src.js:2715
L.EditToolbar.L.Toolbar.extend.disable # leaflet.draw-src.js:2578handler # leaflet-src.js:6953
and here is the code I use to setup the leaflet.draw
var theMap;
var mapLayer;
var carLayer;
var drawLayer;
var drawControl;
var trackerButton;
....
this.setupDraw();
theMap = L.map('mapCanvas', {
center: mCityCenter,
zoom: 20,
layers: [osmLight, mapLayer, carLayer, drawLayer]
});
theMap.on("draw:created", this.addDrawing);
....
this.setupDraw = function () {
drawLayer = new L.FeatureGroup();
drawControl = new L.Control.Draw({
draw: {
polygon: {
allowIntersection: false, // Restricts shapes to simple polygons
showArea: true,
drawError: {
color: '#e1e100', // Color the shape will turn when intersects
message: '<strong>Oh snap!<strong> you can\'t draw that!' // Message that will show when intersect
}
}
},
edit: {
featureGroup: drawLayer
}
});
}
this.addDrawing = function (e) {
var type = e.layerType;
var layer = e.layer;
if (type === 'marker') { }
drawLayer.addLayer(layer);
}
That version of Leaflet.draw plugin is not compatible with the version of Leaflet you are using.
Be sure to read docs for the plugin, it states you should be using Leaflet.js 0.7.
Leaflet.draw: https://github.com/Leaflet/Leaflet.draw
Leaflet.JS: http://leafletjs.com/reference.html
From the Leaflet.draw github page: "Leaflet.draw 0.2.3+ requires Leaflet 0.7.x."
As of today there does appear to be a fork of Leaflet.draw that is being developed against Leaflet 1.0 RC: https://github.com/Leaflet/Leaflet.draw/tree/leaflet-master
Related
I've been working on creating a dynamically updated Mapbox GL integration inside Webflow's CMS. I've succeeded in creating an array of features that can be read by Mapbox's API, but these features won't show on the map because the coordinates are not being read by the function that creates the map markers.
I receive the following error Cannot read property 'coordinates' of undefined at map-test-page:139 which is where a longitude and latitude is assigned to the current marker via this line: .setLngLat(marker.geometry.coordinates)
Partial solution was found here, but the code I've integrated into my site doesn't have featuresIn or featuresAt functions which seem to be the only way to include a includeGeometry: true parameter.
I'm curious if I need to rethink how I've created markers and do something with a function like map.on('click', ...) reference here.
Here is a minimal version that reproduces my issue.
If you're familiar with the Webflow interface you can view a read-only version of the site.
Any help would be greatly appreciated!
Here is the Mapbox script I'm using on the page:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/strawpari/ckp2nu3dw6cwu17nt4kqwf1vv',
center: [-13.723969, 48.360542],
zoom: 2,
pitch: 0,
bearing: 0,
antialias: true,
interactive: true
});
var geojson = {
type: 'FeatureCollection',
features: farmerArray,
};
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.setPopup(new mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML('<img src=\'' + marker.properties.image + '\' width=\'50\' height=\'50\' border-radius=\'50%\'>' + '<h3>' + marker.properties.title + '</h3><p>' + marker.properties.description + '</p>'))
.addTo(map);
});
And here is the code embedded in each CMS item that adds a farmer's information to the farmerArray which is being read by Mapbox. Text in double-quotations "" is a placeholder for the dynamic information populated by the CMS.
var farmerArrayItem =
JSON.stringify({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: ["longitude", "latitude"]
},
properties: {
title: "name",
description: "text",
image: "imagepath"
}
});
farmerArray.push(farmerArrayItem);
It doesn't appear that you have tried using the Developer Tools to debug your code. That's definitely a skill you should pick up.
With the dev tools, you will quickly see that the value of marker at the critical point is a string:
So you just need to use JSON.parse:
geojson.features.forEach(function(markerString) {
const marker = JSON.parse(markerString);
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
I'm trying to enable snapping (using Leaflet Snap)in a Leaflet application I'm creating. I'm using Leaflet Draw. Existing layers are read in from a database in geojson. I add these to the guideLayers and I add newly created features there too. It's not working. Has anyone successfully been able to create something like this? That is, been able to create a new polygon and snap to existing polygons in leaflet (geojson layers)? Thanks Dan.
Add geojson to guideLayers code:
function renderLta(_ltas,ltaLayerName) {
L.geoJSON([_ltas.geoJson], {
name:_ltas.id,
areaName:_ltas.localThreatAreaName,
areaDescription:_ltas.localThreatAreaDescription,
style: function (feature) {
return _getLtaStyle(1);
},
onEachFeature: function onEachFeature(feature, layer) {
ltaLayerName.addLayer(layer);
guideLayers.push(layer);
layer.on('click', function(e) {
if(selectedFeature) {
selectedFeature.editing.disable();
// Has there been a change? Does the user need to save?
// get layer again and redraw
drawnItems.removeLayer(selectedFeature);
selectedFeature.addTo(map_lta);
}
selectedFeature = e.target;
e.target.editing.enable();
drawnItems.addLayer(e.target);
});
}
});
ltaLayerName.addTo(map);
Add new layer/data to guideLayers code:
map.on(L.Draw.Event.CREATED, function(event) {
var layer = event.layer;
var content = getPopupContent(layer);
if (content !== null) {
layer.bindPopup(content);
}
drawnItems.addLayer(layer);
guideLayers.push(layer);
});
DrawControl Code:
var drawControl = new L.Control.Draw({
edit: {
featureGroup: drawnItems,
poly : {
allowIntersection : false
}
},
draw: {
polyline: false,
polygon : { showArea: true, allowIntersection : false, guideLayers: guideLayers, snapDistance: 500 },
circle: false,
rectangle: false,
marker: false,
circlemarker: false
}
});
map_lta.addControl(drawControl);
drawControl.setDrawingOptions({
polygon: { guideLayers: guideLayers, snapDistance: 50 },
});
You can use the following library to draw and snap https://www.npmjs.com/package/#geoman-io/leaflet-geoman-free packages
and for more functionalities which are not present in above mentioned package you can use this one especially for polylines
https://www.npmjs.com/package/leaflet-linestring-editor
I gave up on the leaflet draw library and snap, and used leaflet-geoman library instead. Snapping working perfectly.
I'm using the mapbox-gl-js API and I'm using it with react to create some custom markers as follows:
let div = document.createElement('div');
let marker = new mapboxgl.Marker(div, {
offset: [ -20, 80 ]
});
marker.setLngLat(person.geometry.coordinates);
render(
<MapPersonIcon />,
div,
() => {
marker.addTo(map);
}
);
This worked great. However I would now like to cluster these markers, producing the same affect as the functionality found with layers i.e.
https://www.mapbox.com/mapbox-gl-js/example/cluster/
Does anyone know whether this is possible (hopefully with custom clusters too) or whether it will be available in an upcoming release?
This feature is now in Mapbox GL js - https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/
Key takeaways:
When setting your data source using map.addSource, make sure you define cluster: true and clusterRadius: int, like so:
map.addSource( 'sourceName', {
type: "geojson",
data: {
type: 'FeatureCollection',
features: [JSON]
},
cluster: true,
clusterRadius: 80,
});
That will push mapbox to cluster your icons, but you need to tell mapbox what to do when it clusters those icons:
map.on( 'moveend', updateMarkers ); // moveend also considers zoomend
The business (trimmed down for relevance):
function updateMarkers(){
var features = map.querySourceFeatures( 'sourceName' );
for ( var i = 0; i < features.length; i++ ) {
var coords = features[ i ].geometry.coordinates;
var props = features[ i ].properties;
if ( props.cluster ){ // this property is only present when the feature is clustered
// generate your clustered icon using props.point_count
var el = document.createElement( 'div' );
el.classList.add( 'mapCluster' );
el.innerText = props.point_count;
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
} else { // feature is not clustered, create an icon for it
var el = new Image();
el.src = 'icon.png';
el.classList.add( 'mapMarker' );
el.dataset.type = props.type; // you can use custom data if you have assigned it in the GeoJSON data
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
}
marker.addTo( map );
}
NOTE: Don't copy paste this code, rather use it in conjunction with https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/ to get the whole picture. Hope this helps!
Answering own question:
At current it seems that this isn't possible as per mapbox's github:
If you would like to cluster your markers you will need to use mapbox's native maki icons (please see above example picture & URL) until a plugin is available for your custom HTML markers.
I'm still learning and I'm a bit stuck. I may be trying to do to much at once. I have a MapBox map working great with a clickable layer menu taken from examples on the MapBox site. I also have a MarkerClusterGroup which also works and is always visible on the map. Is there a way I could somehow have the MarkerClusterGroup clickable on/off just like layers identified in var overlays = { ...
Below is the code that I think needs the help:
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
Markers: L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
var clusterGroup = new L.MarkerClusterGroup();
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup);
});
Could be something as simple as misuse of brackets. Thanks in advance.
You have to include your Marker Cluster Group in your overlays object. For example you could instantiate it just before defining overlays, even if your Cluster Group is empty for now.
Then you fill it once it has downloaded its data.
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var clusterGroup = L.markerClusterGroup();
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
Markers: clusterGroup
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup); // use that line if you want to automatically add the cluster group to the map once it has downloaded its data.
});
When the application starts, I want to center the map view on the users current position. I have tried two different approaches and can't get them work. The first one worked properly in leaflet, but in the development process I have decided to use OL3 instead.
First approach (worked in leaflet):
var myProjectionName = "EPSG:25832";
proj4.defs(myProjectionName,
"+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs");
var centerPosition;
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function (pos) {
centerPosition =
ol.proj.transform(
[position.coords.longitude,
position.coords.latitude],
'EPSG:4326',
myProjectionName);
},
function (err) {centerPosition = [724844,6178000];},
{
enableHighAccuracy: false,
timeout: 5000,
maximumAge: 1000
});
}
My second approach was using the ol.Geolocation class:
var proj1 = ol.proj.get(myProjectionName);
var geolocation = new ol.Geolocation({
projection: proj1
});
var centerPosition= geolocation.getPosition();
The center position is used in creating the view/map object:
var map = new ol.Map({
target: 'map',
logo : false,
layers: [ GSTGroup, OVLGroup, SheatLayer],
view: new ol.View({
projection: myProjectionName,
center: centerPosition,
resolutions : AVLresolutions,
resolution : 2
})
});
I have some suspecions that the cause of the problem is the projection, but on the other hand the projection works properly in transforming layers (WMTS, Vector), source from Geojson in different coordinatesystem and in ol.control.MousePosition.
I am using Firefox 32.0.3 and the geolocator plugin to development/test
Working example in http://jsfiddle.net/AndersFinn/ak4zotn8/
Add after the map declaration the following (tested):
var proj1 = ol.proj.get(myProjectionName);
var geolocation = new ol.Geolocation({
projection: myProjectionName,
tracking: true
});
geolocation.on('change', function(evt) {
console.log(geolocation.getPosition());
map.getView().setCenter(geolocation.getPosition());
});
The most important part is tracking: true in the code: it means you check regularly the position to center.
The second important part is to bind event on geolocation object (an instance of ol.Geolocation)
See in the official examples the geolocation samples and the API docs to make some changes depending of your requirements