I am trying to get the area measurements of polygons so I can list them in a table to the side of the map, next to the name of the polygon. This is what I have tried with no success:
$("#polygon").on("click", function (){
createPolygon = new L.Draw.Polygon(map, drawControl.options.polygon);
createPolygon.enable();
}
var polygon = new L.featureGroup();
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
}
var seeArea = createPolygon._getMeasurementString();
console.log(seeArea); //Returns null
}
Any help on this would be appreciated!
You can access the geometry utility library provided with Leaflet.
var area = L.GeometryUtil.geodesicArea(layer.getLatLngs());
In your example, you are trying to access a control itself, which is what the variable createPolygon is assigned to. Instead, you want to take the area of the layer that got drawn.
map.on('draw:created', function (e) {
var type = e.layerType,
layer = e.layer;
if (type === 'polygon') {
polygons.addLayer(layer);
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs());
console.log(seeArea);
}
}
Once you verify you are getting the area, you can just assign it to the variables that populate the table next to the map.
Note: area will be in squareMeters by default
I found that none of the above answers worked for calculating the area of non-contiguous polygons. Here's an example polygon where the above functions returned an area of 0:
For anyone who needs to do that, here is the code that worked for me (using the L.GeometryUtil function from Leaflet.draw):
var poly = // Your polygon layer here; may or may not be contiguous
var area = 0;
for (island of poly.getLatLngs()) {
// If the polygon is non-contiguous, access the island
if (island.length < 2) {
island = island[0]
}
// Sum the area within each "island"
area += L.GeometryUtil.geodesicArea(island);
}
L.GeometryUtil.geodesicArea(layer.getLatLngs())[0] should get you the area.
But I ended up using leaflet-geoman-free to do the drawing and use turf.js to get the area.
map.pm.enableDraw('Polygon', {
snappable: true,
snapDistance: 20,
});
map.on('pm:create', e => {
const layer = e.layer
alert(turf.area(layer.toGeoJSON()))
});
add corrections:
var seeArea = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
console.log(seeArea);
Related
I'm trying to get a map that when you click at it generates a filter of a shapefile and then adds a layer only with the feature that matches the place where you click it.
I find this example http://plnkr.co/edit/o5Q0p3?p=preview&preview but in my case I need that the layer isn't added to map at fist. So, with only the map of leaflet you click and generates the filter of the shapefile and then you can see the feature.
This is what I have:
'''
var shpfileM = new L.Shapefile('assets/Muncipios.zip', {
onEachFeature: function (feature, layer) {
if (feature.properties) {
layer.bindPopup(Object.keys(feature.properties).map(function (k) {
return k + ": " + feature.properties[k];
}).join("<br />"), {
maxHeight: 200
});
}
},
style: {
color: 'green',
fillColor: 'green',
fillOpacity: 0.1
}
});
function onMapClick(e) {
var longlat = map.getLatLng();
eachMun = L.Shapefile(shpfileM, {
filter: filter(feature),
if (feature = longlat) { return
L.layer(feature).addTo(map)
}
})};
map.on('click', onMapClick);
Thank you a lot! and I hope that many other people that might have the same question finds the answers useful. (:
it is little more convenient to use shapefile-js https://github.com/calvinmetcalf/shapefile-js instead of L.Shapefile. Because it allows to load geojson data once and reuse it on each click.
also include turf.js
<script src="https://cdn.jsdelivr.net/npm/#turf/turf#5/turf.min.js"></script>
// create map
var mymap = L.map(...)
// create variable, where json from shp, will be once loaded and stored
let jsonData = null
// create variable for polygons group
let group = null
// read shp data and put it to variable
shp("filepath.zip").then(function (geojson) {
console.log(geojson)
jsonData = geojson
});
function onMapClick(e) {
// get point from click event and create turf point
let point = turf.point([e.latlng.lng, e.latlng.lat])
// clear polygons, if they already exist
if (group) {
group.clearLayers()
group.removeFrom(mymap)
}
group = L.geoJSON(jsonData, {
style: {...},
filter: (el) => {
// try catch, to avoid invalid geometry errors
try {
// check point in polygon
return turf.booleanPointInPolygon(point, el.geometry)
} catch(error) {
return false
}
},
onEachFeature: function(){...}
})
// add filtered layer group to map
group.addTo(mymap)
}
mymap.on('click', onMapClick);
OR using L.Shapefile
function onMapClick(e) {
let point = turf.point([e.latlng.lng, e.latlng.lat])
if (group) {
group.clearLayers()
group.removeFrom(mymap)
}
group = new L.Shapefile('filepath.zip', {
// same options as in previous example
})
group.addTo(mymap)
};
mymap.on('click', onMapClick);
I'm a french student in computering and I have to use Mapbox but since I create a class I'm stuck by this error.When I wasn't in a class it worked perfectly but now it's fully broken.And I saw on some topics it could come from safari but I already tested it on Mozilla and it still broken.
This is my class.
constructor() {
//Temporary array of currentMarkers
let currentMarkers=[];
let type ="";
//Create the map
mapboxgl.accessToken = 'private data';
this.mapbox = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40], // starting position
zoom: 9 // starting zoom
});
//Add search bar from a plugin
let geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
placeholder: 'Saissisez une adresse', // Placeholder text for the search bar
marker: {
color: 'orange'
},
mapboxgl: mapboxgl
});
this.mapbox.addControl(geocoder);
const mbox = this;
this.mapbox.on("click",function(){
this.getcoordonates();
});
//Allow us to create marker just with a research
geocoder.on('result', function(e) {
//Deleting all current markers
if (currentMarkers!==null) {
for (let i = currentMarkers.length - 1; i >= 0; i--) {
currentMarkers[i].remove();
}
}
//Add the markers who come with the research
this.addMarker(e.result.center[0],e.result.center[1]);
// Delete the last marker who got placed
//currentMarkers[currentMarkers.length - 1].remove();
});
this.addPoint(2.333333 ,48.866667 ,"supervisor");
}
//split the JSON.stringify(e.lngLat.wrap()) to get the coordinates
async mysplit(m){
let z = m.split(',');
let x = z[0].replace("{\"lng\":","");
let g = z[1].replace("\"lat\":","");
let y = g.replace("}","");
await addMarker(x,y);
}
//Add a marker on click after the excution of mysplit() function
async addMarker(x,y) {
// tmp marker
let oneMarker= new mapboxgl.Marker()
.setLngLat([x,y])
.addTo(this.mbox);
currentMarkers.push(oneMarker);
}
// Get the coordinates and send it to split function
getcoordonates(){
mbox.on('click',function(e){
let m = JSON.stringify(e.lngLat.wrap());
this.mbox.mysplit(m);
});
}
addPoint(y,x,type)
{
let color ="";
let t = type;
if(t == "supervisor")
{
color = "grey";
}else if (t =="fieldworker") {
color = "red";
}else if (t == "intervention") {
color = "blue";
}else alert("Nous ne pouvons pas ajouter votre marker\nLatitude : "+x+"\nLongitude :"+y+"\n car "+t+" n'est pas un type reconnu" );
let myMarker = new mapboxgl.Marker({"color": color})
.setLngLat([y,x])
.addTo(this.mbox);
}
}
Thanks for help and have a good day :) ! Sorry if my English isn't that good.
As the first step, the
this.mapbox.on("click", function(e){
this.getcoordonates();
});
was called outside of any method of the Map class. It would most probably belong to the constructor (as discussed above in the question's comments section).
Next, the callback changes the scope of this, so it is not anymore pointing to a Map instance. A common solution to this issue is to store/backup this before, something like:
constructor() {
...
const thisObject = this;
this.mapbox.on("click", function(e) {
thisObject.getcoordonates();
});
}
Update:
The logic of code in its current form tries to add a new click event listener every time the map is clicked. Which is not desired. This getcoordonates() function is not really needed. Instead this should work (never tested it, it is based on your code):
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
let m = JSON.stringify(e.lngLat.wrap());
mbox.mysplit(m);
});
}
Remarks:
There is no real logic behind JSON-encoding the lngLat object before calling mysplit just to decode it there.
You won't need it here, but the reverse of the JSON.stringify() is JSON.parse(). There is no need to work with it on a string level, like the current mysplit() method does.
Instead, the mysplit() method should be called directly with the e.lngLat object as its argument.
Going further, since there is no "splitting" (decoding) really needed, the mysplit() method isn't really needed either.
In the end, something like this should work:
constructor() {
...
const mbox = this;
this.mapbox.on("click", function(e) {
await mbox.addMarker(e.lngLat.lng, e.lngLat.lat);
});
}
Objective
I wish to remove the duplication code brought by the anonymous function calls.
Background
I am doing a very simple project where I use Google Maps API to show a map with two searchboxes. The user puts a start address and an end address in those boxes and I show markers in the map.
To achieve this I have two anonymous functions for the listeners, which are exactly equal except for one point - one uses the startSearchBox and the other one the endSearchBox.
What I tried
This duplication of code is unnecessary, and so I tried to pass the searchboxes as a parameter to the anonymous function, however that didn't work.
I also considered creating the searchboxes as global variables, but that is a bad practice I wish to avoid.
How can I eliminate the duplication in this code?
Code
function initSearchBoxes() {
// Create the search box and link it to the UI element.
let startInput = document.getElementById('start-input');
let startSearchBox = new google.maps.places.SearchBox(startInput);
let endInput = document.getElementById('end-input');
let endSearchBox = new google.maps.places.SearchBox(endInput);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
startSearchBox.setBounds(map.getBounds());
endSearchBox.setBounds(map.getBounds());
});
startSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = startSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
endSearchBox.addListener('places_changed', function() {
deleteAllMarkers();
let places = endSearchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
}
You can wrap your callback function in another "factory" function. The factory will take a parameter (the search box reference) and then it will return the actual handler:
function makeSearchHandler(searchBox) {
return function() {
deleteAllMarkers();
let places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
let bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
// // Create a marker for each place.
let newMarker = createMarker(place.geometry.location, place.name, markerLabels.nextSymbol(), true);
markerLib.trackMarker(newMarker);
newMarker.setMap(map);
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
}
else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
};
}
That function contains the code from your original, but instead of directly referring to either startSearchBox or endSearchBox, it uses the parameter passed to the factory. The returned function will therefore work like yours, but the code is only present once.
You can then use that function to create the callbacks:
startSearchBox.addListener('places_changed', makeSearchHandler(startSearchBox));
endSearchBox.addListener('places_changed', makeSearchHandler(endSearchBox));
I am writing a simple widget that simulates a simple 8-bit CPU. For that I am abusing the Ace Editor, as you can see at the center of the image, as my "RAM"-view.
I want to highlight the line that corresponds to the value of the program counter and I am using addMarker() to do so.
However, I can't seem to get rid of that marker once I have set it. _marker is a private member that holds the value of the last marker set. But for some reason removeMarker(_marker) has no effect:
/**
*
*/
setMarker: function(position) {
//if(_marker != null) {
window.cpuRamView.session.removeMarker(_marker);
//}
_marker = new window.Range(position, 0, position, _content[position].length);
window.cpuRamView.session.addMarker(
_marker, "programCounterLocation", "fullLine"
);
}
What am I doing wrong here? :/
add marker returns an id, and removeMarker requires that id, so you can do something like
var Range = require("ace/range").Range // not the window Range!!
var _range
setMarker = function(position) {
if(_range != null) {
window.cpuRamView.session.removeMarker(_range.id);
}
_range = new Range(position, 0, position, _content[position].length);
_range.id = window.cpuRamView.session.addMarker(
_range, "programCounterLocation", "fullLine"
);
}
if(this.marker) {
this.editor.getSession().removeMarker(this.marker);
}
this.marker = this.editor.getSession().addMarker(
new Range(prop('errorLine')(formulaError), prop('errorPosition')(formulaError), prop('errorLine')(formulaError), prop('errorPosition')(formulaError) + 5), style.errorMarker, 'text');
}
set a variable marker to receive the return value, just like this:
marker=editor.session.addMarker(range, "myMarker", "fullLine");
and then remove this marker, like this:
editor.session.removeMarker(marker);
I basically want to create a new path with the canvas globalCompositeOperation set to 'destination-out'. Is this possible? If so, how?
I noticed that Item has a blendMode property: http://paperjs.org/reference/item#blendmode, but trying the different values does not provide the desired effect.
http://jsfiddle.net/D8vMG/12/
In light of your recent comment, you would need to create layers, as described here:
http://paperjs.org/tutorials/project-items/project-hierarchy/#removing-items-and-children
and then you can add your paths to a layer and do something like this:
//add image to project as background
// ... your code here ...
var secondLayer = new Layer();
Whenever you create a new Layer, it becomes the active layer of the project, and then you can draw on top of the second layer all you want.
if you want a simple 'undo' button you can do:
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "undo") {
myPath.opacity = '0'; //this makes the last path used completely see through
// or myPath.remove(); // this removes the path.
//if you're intent on something that writes and erases, you could do
}
}
But what you are looking for is something like this:
function onMouseDrag(event) {
if (window.mode == "drawing") {
myPath.add(event.point);
}
else if (window.mode == "erasing") {
myPath.removeSegment(0);
}
}
this erases the segments of the path from beginning to end. For the full functionality, you need something that identifies a path on click, (layer.getChildren() ? then select child). Then using the point on mouse move you need to identify the segment index and remove it from the path using .removeSegment(index).
http://paperjs.org/reference/path#removesegment-index
Well, the simple solution would be to just create a path which is white. http://jsfiddle.net/D8vMG/11/
function onMouseDown(event) {
if (window.mode == "drawing") {
myPath = new Path();
myPath.strokeColor = 'black';
}
else if (window.mode == "erasing") {
myPath = new Path();
myPath.strokeColor = 'white';
myPath.strokeWidth = 10;
}
}