Finding Leaflet Layer Dasharray - leaflet

I am creating a document from a leaflet map. The legend from the map features will not be part of the map but a separate area on the document. I am trying to get the Layer information such as color and dasharray(solid, dashed....) information from each layer.
I have used feature.option.style, but I get function style(feature) {return....}. I want to get the actual values.
var lyrs = map._layers;
for (var f in map._layers) {
var feature = map._layers[f];
alert(feature.options.style);
return false;
}
I get this:
function style(feature) {
return {
weight: 1,
opacity: 1,
color: 'black',
dashArray: '3',
fillOpacity: 0.7,
fillColor: getColor(feature.properties.Rights, "geojson", "parcel")
};
}
I want to be able to get:
fillColor:black;
dashArray: '3'

Instead of using the style call the code should read as follows
var lyrs = map._layers;
for (var f in lyrs) {
var feature = map._layers[f];
var properties = feature.options.dashArray;
alert(properties);
return false;
}
This returns the value of 3. Exactly what was desired. The same call could be used to find the weight, opacity, color, fillOpacity or fillColor

Related

How does leaflet determine teh position of bound markers, and can I change this w/o using anchor for each feature?

PLease see this jsfiddle (scroll down in the JS window to below the geoJSON)
https://jsfiddle.net/n3zerj2q/1/
var prevLayer = null;
var map = L.map("map").setView([30, 0], 3);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var noStyle = {
fillColor: "#fff",
weight: 1,
opacity: 1,
color: 'red',
dashArray: '3',
fillOpacity: 0
};
var myGeojSonLayerGroup = L.geoJson(world, {
onEachFeature: onEachFeature2
}).addTo(map);
function onEachFeature2(feature, layer) {
if (feature.properties && feature.properties.name) {
layer.bindPopup('<span class="pcA">' + feature.properties.name + '</span>')
}
layer.setStyle(noStyle);
layer.on({
mouseover: function (e) {
e.target.openPopup();
if (prevLayer !== null) {
// Reset style
prevLayer.setStyle(styleA());
}
var thisLayer = e.target;
thisLayer.setStyle(styleX())
prevLayer = thisLayer;
}
});
}
function styleA() {
return {
fillColor: "#fff", fillOpacity: 0.0
};
}
function styleX() {
return {
fillColor: "pink", fillOpacity: 0.4
};
}
It simply displays a world map and as the user rolls over country a popup appears with the country name (as well as some color styling) - mostly this is positioned "sensibly" more or less in the middle of the country, but for some (esp bigger ones) it's way off - eg the USA, China, Russia, Argentina...
I'm not setting the position of these - leaflet is, somehow... Other than editing the (large) geoJSON to include anchor points for each country, I wonder if there's some way of tweaking whatever leaflet is doing? Why, for example, does it place Antarctica's where it does? Or China's? etc....
So when a Popup is about to be shown, it will check where, in this specific bit of code:
if (!latlng) {
if (source.getCenter) {
latlng = source.getCenter();
} else if (source.getLatLng) {
latlng = source.getLatLng();
} else if (source.getBounds) {
latlng = source.getBounds().getCenter();
} else {
throw new Error('Unable to get source layer LatLng.');
}
}
So for popups without an explicit latlng, the position will dpend on themethods available on the source layer - this means that for Rectangles and ImageOverlays that'll run getBounds().getCenter(), for Markers and Circles that'll run getLatLng(), and for Polylines and Polygons that'll run getCenter().
The getCenter() method from Polygon just delegates the work elsewhere:
return PolyUtil.polygonCenter(this._defaultShape(), this._map.options.crs);
...and, finally, the polygonCenter() function will calculate the polygon's centroid.
Note that the implementation only takes into account the first ring of the polygon:
if (!LineUtil.isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
This is due to the centroid algorithm only being able to handle "simple" polygons, with just an outer ring. Remember to read the OGC SFA specification for a refresher on (multi)polygon rings.

Using array of polyline objects as layer

I have a question about why a way that I'm trying to add polylines as a layer isn't working. To be clear, I'm not trying to assert that it should be working, just that I'm curious why it works in one case but not in another. Consider the following code:
var oMbTiles = new L.tileLayer('/mbtiles/mbtiles.php?&z={z}&x={x}&y={y}', {
tms: true,
opacity: 0.7
}),
oUpIcon = new L.Icon({
iconUrl: '/custom/css/themes/app/markers/up.png',
iconSize: [24, 26]
}),
oMapTypes = {
'Yakabox': oMbTiles
},
aFirstMarkers = [],
aFirstLines = [],
aFirstLatLng,
oFirstLine,
oFirstGroup,
oLayersControl,
oOverlayMaps,
oMap,
i;
aFirstLatLng = [
[18.319026, -66.420557],
[18.180555, -66.749961],
[18.361945, -67.175597],
[18.455183, -67.119887],
[18.158345, -66.932911],
[18.295366, -67.125135],
[18.402253, -66.711397],
[18.420412, -66.671979],
[18.445147, -66.559696],
[17.991245, -67.153993],
[18.083361, -67.153897],
[18.064919, -66.716683],
[18.412600, -66.863926],
[18.190607, -66.832041],
[18.076713, -66.947389],
[18.295913, -66.515588],
[18.263085, -66.712985],
[18.433150, -66.285875],
[17.963613, -66.947127],
[18.349416, -66.578079],
[18.448452, -66.594127],
[17.985033, -66.886536],
[18.053539, -66.792931],
[18.407226, -66.808999],
[18.134695, -67.116199],
[18.468320, -67.015781],
[18.210330, -66.591616],
[18.003422, -67.035810],
[18.277102, -66.869645],
[18.240187, -66.988776],
[18.422908, -66.489337],
[18.377637, -67.079574],
[18.332568, -67.227022],
[18.434099, -66.927384],
[18.182055, -67.132502],
[18.221464, -67.156039],
[18.107800, -67.037263],
[18.332929, -66.959689]
];
for (i = 0; i < aFirstLatLng.length; i++) {
aFirstMarkers.push(L.marker(aFirstLatLng[i]).setIcon(oUpIcon).bindPopup('lat/lng : ' + aFirstLatLng[i].join(', ')))
if (i === (aFirstLatLng.length - 1)) {
aFirstLines.push(L.polyline([aFirstLatLng[i], aFirstLatLng[0]], {color: 'red', weight: 3, opacity: 0}));
} else {
aFirstLines.push(L.polyline([aFirstLatLng[i], aFirstLatLng[i + 1]], {color: 'red', weight: 3, opacity: 0}));
}
}
oFirstLine = L.polyline(aFirstLatLng, {
weight: 5,
color: 'red'
});
oFirstLine.on('click', function () {
console.log('Clicked First line', arguments);
});
oFirstGroup = L.layerGroup(aFirstMarkers, {});
// This works
oFirstGroup.addLayer(oFirstLine);
// These next two lines do not work
// Here I'm trying to just add an array of polyline objects as a layer
//oFirstGroup.addLayer(aFirstLines);
// Here I'm trying to add the array of polyline objects as a layer group
//oFirstGroup.addLayer(L.layerGroup(aFirstLines));
oOverlayMaps = {
'First Group': oFirstGroup,
};
oMap = new L.map('map', {
minZoom: 4,
maxZoom: 10,
zoom: 9,
center: aFirstLatLng[7],
layers: [oMbTiles, oFirstGroup]
});
oLayersControl = new L.Control.Layers(oMapTypes, oOverlayMaps, {
collapsed: false
}).addTo(oMap);
So here, I'm just trying to iterate through some zip codes, create markers for each location, and connect the markers using polylines. If I instantiate the polyline object using only the array of lat/lng, that works when I add that polyline to the markers layer group (oFirstGroup). But if I pass in an array of polyline objects (which were passed in the start/end lat/lng coordinates), that doesn't work. The lines do not show up on the map. This is because I get an error saying "The provided object is not a layer". Ok, so I try to explicitly create a layer group using that array of polyline objects and while the error goes away, the lines are still not added to the map.
So I'm curious, should that be working? Or is it the case that the only way to properly create a polyline connecting markers is by passing the lat/lng coordinates as an array when instantiating a single polyline object for adding to the layer? Why is it that I can pass in an array of marker objects (when instantiating oFirstGroup) and add that layer to the map but I can't do the same thing when passing in an array of polyline objects?
thnx,
Christoph
Ok, I'm not ashamed to admit it -- I'm a moron. The problem was opacity: 0. I copied the code from elsewhere (to try to understand what was going on) and I didn't remove that. As soon as I did, voila!
I iz be dumm.
thnx,
Christoph

In Leaflet, how to make a style the default style from now on

I have geoJson map regions rendered on a map with an initial opacity. I have a slider to change that opacity on the fly.
Here is the bit where I set the opacity on the fly (typescript), which I perform on the input change event within my custom leaflet control:
this.layers.forEach((r: L.GeoJSON<any>) => {
r.eachLayer((layer: any) => {
layer.setStyle({ fillOpacity: vm.mapOptions.heatmapOpacity });
});
});
setTimeout(() => this.map.invalidateSize());
I also have the ability to hover over the regions, in which case I lower the opacity and put a border on the active region when they hover.
When they leave the region, it currently uses resetStyles on that region to reset it back to the previous style. I set this up in the options onFeature callback. The region is highlighted in the mouseover event, and reset in the mouseout event, as seen below.
options = {
style: { stroke: false,
fillColor: regionColor,
fillOpacity: mapOptions.heatmapOpacity },
onEachFeature: (feature, layer) => {
layer.on({
mouseover: (e)=> {
const lr = e.target;
lr.setStyle({
weight: 5,
color: "#666",
dashArray: "",
fillOpacity: 0.7,
stroke: true
});
if (!L.Browser.ie && !L.Browser.opera12 && !L.Browser.edge) {
lr.bringToFront();
}
},
mouseout: (e) => {
prop.featureGeoJson.resetStyle(e.target);
}
});
}
};
The problem is, if I have used setStyle to set the opacity to a different value, then I go into a region, then I leave the region again, calling resetStyle resets the style back to the original default style, before the change to the opacity was made.
Is it possible to set the default style on the layer, so that calling resetStyle will set the styles to my value with the new opacity, and not to the original opacity set when the region was created? How would I do that?
The resetStyle method of a Leaflet GeoJSON Layer Group re-applies the style that was applied at the time of creation of that group, or the default one if you did not provide style:
Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
If you later change the style of one or all of the vector layers in that group, it will be therefore overridden when you use resetStyle, and the initial style will be applied.
An easy workaround is simply to modify the GeoJSON Layer Group's style option as well. However, it will affect all its child layers:
group.options.style = newStyle;
(that is what is suggested in #GabyakaGPetrioli's answer, but you have to apply it on the group, not on individual features)
Another solution would be to record the new style of each vector layer, and use that recorded value when you want to restore the previous state, instead of using the group's resetStyle method.
var map = L.map("map").setView([48.85, 2.35], 12);
var geojson = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [2.35, 48.85]
}
};
var point;
var startStyle = {
color: "red"
};
var newStyle = {
color: 'green'
};
var group = L.geoJSON(geojson, {
style: startStyle,
pointToLayer: function(feature, latlng) {
point = L.circleMarker(latlng);
assignStyle(point, startStyle);
return point;
}
}).addTo(map);
// Record the style to the individual vector layer if necessary.
function assignStyle(leafletLayer, newStyle) {
leafletLayer.setStyle(newStyle);
leafletLayer._recordedStyle = newStyle;
}
// When desired, apply the style that has been previously recorded.
function reassignStyle(leafletLayer) {
leafletLayer.setStyle(leafletLayer._recordedStyle);
}
document.getElementById('button').addEventListener('click', function(event) {
event.preventDefault();
// Either use the wrapper function that records the new style…
//assignStyle(point, newStyle);
// Or simply modify the group's style option, if it is acceptable affecting all child layers.
point.setStyle(newStyle);
group.options.style = newStyle;
});
group.on({
mouseover: function(e) {
e.target.setStyle({
color: 'blue'
});
},
mouseout: function(e) {
//reassignStyle(e.layer);
group.resetStyle(e.layer);
}
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css">
<script src="https://unpkg.com/leaflet#1.0.3/dist/leaflet-src.js"></script>
<div id="map" style="height: 100px"></div>
<button id="button">Change color to Green…</button>
<p>…then mouseover and mouseout</p>
Use L.Util.setOptions
so instead of
layer.setStyle({ fillOpacity: vm.mapOptions.heatmapOpacity });
use
L.Util.setOptions(layer, { style: { fillOpacity: vm.mapOptions.heatmapOpacity } });

How to get the coordinates of a drawn box in OpenLayers?

I am a novice to OpenLayers, so sorry for an obvious (and perhaps dumb) question, for which I found different approaches for solutions, but none working. Tried this and that, a dozen different suggestions (here, here, here, here, here) but in vain.
Basically, I want to pass the coordinates of a drawn rectangle to another webservice. So, after having drawn the rectangle, it should spit me out the four corners of the bounding box.
What I have so far is the basic OL layers example for drawing a rectangle:
var source = new ol.source.Vector({wrapX: false});
vector = new ol.layer.Vector({
source: source,
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(0, 255, 0, 0.5)'
}),
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: '#ffcc33'
})
})
})
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vector
],
view: new ol.View({
center: ol.proj.fromLonLat([37.41, 8.82]),
zoom: 4
})
});
var draw; // global so we can remove it later
function addInteraction()
{
var value = 'Box';
if (value !== 'None')
{
var geometryFunction, maxPoints;
if (value === 'Square')
{
value = 'Circle';
geometryFunction = ol.interaction.Draw.createRegularPolygon(4);
}
else if (value === 'Box')
{
value = 'LineString';
maxPoints = 2;
geometryFunction = function(coordinates, geometry)
{
if (!geometry)
{
geometry = new ol.geom.Polygon(null);
}
var start = coordinates[0];
var end = coordinates[1];
geometry.setCoordinates([
[start, [start[0], end[1]], end, [end[0], start[1]], start]
]);
return geometry;
};
}
draw = new ol.interaction.Draw({
source: source,
type: /** #type {ol.geom.GeometryType} */ (value),
geometryFunction: geometryFunction,
maxPoints: maxPoints
});
map.addInteraction(draw);
}
}
addInteraction();
Now, what comes next? What is a good way of extracting the bounding box?
Thanks for any hints!
You need to asign a listener to the draw interaction. Like so:
draw.on('drawend',function(e){
alert(e.feature.getGeometry().getExtent());
});
Here is a fiddle

How to display Leaflet Static Labels for objects in a LayerGroup?

I'm trying to add a static label to a few CircleMarkers I've created. These markers are added to a LayerGroup and then added to the map. I've read that I need to call .showLabel() after I've added it to the object to the map. But since I am building the LayerGroup first, then adding it to the map I'm unsure of how to do this.
I thought about using L.LayerGroup.eachLayer but I'm unsure which object I would actually call the .showLayers() on. My code is below, any help is appreciated, thanks!
var jsonLayers = new L.LayerGroup();
jsonLayers.addLayer(L.geoJson(mapFeature.features[i], {
style: function (feature) {
return feature.properties && feature.properties.style;
},
onEachFeature: onEachFeature,
pointToLayer: function (feature, latlng) {
var newCircle = L.circleMarker(latlng, {
radius: 5,
fillColor: fColor,
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
newCircle.bindLabel(feature.properties.name, { noHide: true });
return newCircle;
}
}));
map.addLayer(jsonLayers);
It turns out that static labels are not supported on CircleMarkers. To solve this, I added code to Leaflet.label to allow this. I've also issued a pull request in case someone else would like to do the same thing I am doing.