Extending the default OpenLayers edit style? - javascript

I have a vector style, with a larger image: radius. I would like to have the select interaction's style match the vector style's image: radius.
How can I do so without manually redefining the whole editing style based on this page in the documentation?
Is it possible to take the default style and override only one part? Like the image's radius? Or at least redefine only the whole image?

Fiddle.
Share a style function between ol.layer.Vector and ol.interaction.Select and when selecting, change a variable that will be read within your function:
var radius = 10;
var styleFunction = function() {
return [
new ol.style.Style({
image: new ol.style.Circle({
radius: radius,
fill: new ol.style.Fill({
color: 'green'
})
})
})
];
};
var select_interaction = new ol.interaction.Select({
style: styleFunction
});
select_interaction.on('select', function(evt) {
radius = evt.selected.length > 0 ? 20 : 10;
});
I'm proposing a ol.style.Circle#setRadius that could be used in this case.

Related

Challenge for polygon display within leaflet

We have a specific design challenge for polygon display within leaflet (latest version).
We have polygons which are rendered with a solid border as well as a semi-transparent background.
We are looking for a way to draw a solid borderline as well as a wider "inline" border and no background.
Note: the question is for polygons not rectangular. The below image
and code is just for example.
Is there any way to achieve this?
var polygon = L.polygon([
[ 51.72872938200587, -2.415618896484375 ],
[ 51.72872938200587, -2.080535888671875 ],
[ 51.901918172561714, -2.080535888671875 ],
[ 51.901918172561714, -2.415618896484375 ],
[ 51.72872938200587, -2.415618896484375 ]
],{
color:'#2F538F',
fillOpacity: 0.9,
fillColor: '#BFBFBF',
}).addTo(map);
This is achievable by utilizing leaftlet's class extension system.
To start with, leaflet's class diagram could be consulted to determine where the extension is needed. As a general rule, first try to extend classes towards the root, and prefer L.Class.extend over L.Class.include.
Working Solution:
Codesandbox
One approach is hooking into the rendering process. In the following example, L.Canvas is extended to a custom L.Canvas.WithExtraStyles class (leaflet's plugin building guidelines). The custom Renderer is then provided to map.
In this approach, note that multiple borders and fills (both inset and outset) could be provided using the extraStyles config.
extraStyle custom property accepts Array of PathOptions. With an additional inset, whose value could be positive or a negative number of pixels representing the offset form the border of the main geometry. A negative value of inset will put the border outside of the original polygon.
While implementing such customizations, special care must be taken to make sure leaflet is not considering the added customizations as separate geometric shapes. Otherwise interactive functionalities e.g. Polygon Edit or Leaflet Draw will have unexpected behaviour.
// CanvasWithExtraStyles.js
// First step is to provide a special renderer which accept configuration for extra borders.
// Here L.Canvas is extended using Leaflet's class system
const styleProperties = ['stroke', 'color', 'weight', 'opacity', 'fill', 'fillColor', 'fillOpacity'];
/*
* #class Polygon.MultiStyle
* #aka L.Polygon.MultiStyle
* #inherits L.Polygon
*/
L.Canvas.WithExtraStyles = L.Canvas.extend({
_updatePoly: function(layer, closed) {
const centerCoord = layer.getCenter();
const center = this._map.latLngToLayerPoint(centerCoord);
const originalParts = layer._parts.slice();
// Draw extra styles
if (Array.isArray(layer.options.extraStyles)) {
const originalStyleProperties = styleProperties.reduce(
(acc, cur) => ({ ...acc, [cur]: layer.options[cur] }),
{}
);
const cx = center.x;
const cy = center.y;
for (let eS of layer.options.extraStyles) {
const i = eS.inset || 0;
// For now, the algo doesn't support MultiPolygon
// To have it support MultiPolygon, find centroid
// of each MultiPolygon and perform the following
layer._parts[0] = layer._parts[0].map(p => {
return {
x: p.x < cx ? p.x + i : p.x - i,
y: p.y < cy ? p.y + i : p.y - i
};
});
//Object.keys(eS).map(k => layer.options[k] = eS[k]);
Object.keys(eS).map(k => (layer.options[k] = eS[k]));
L.Canvas.prototype._updatePoly.call(this, layer, closed);
}
// Resetting original conf
layer._parts = originalParts;
Object.assign(layer.options, originalStyleProperties);
}
L.Canvas.prototype._updatePoly.call(this, layer, closed);
}
});
// Leaflet's conventions to also provide factory methods for classes
L.Canvas.withExtraStyles = function(options) {
return new L.Canvas.WithExtraStyles(options);
};
// --------------------------------------------------------------
// map.js
const map = L.map("map", {
center: [52.5145206, 13.3499977],
zoom: 18,
renderer: new L.Canvas.WithExtraStyles()
});
new L.tileLayer(
"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png",
{
attribution: `attribution: '© OpenStreetMap, © CARTO`,
detectRetina: true
}
).addTo(map);
// Map center
const { x, y } = map.getSize();
// Left Polygon
const polyStyle1 = {
color: '#2f528f',
extraStyles: [
{
color: 'transparent',
weight: 10,
fillColor: '#d9d9d9'
}
]
};
// Sudo coordinates are generated form map container pixels
const polygonCoords1 = [
[0, 10],
[300, 10],
[300, 310],
[0, 310]
].map(point => map.containerPointToLatLng(point));
const polygon1 = new L.Polygon(polygonCoords1, polyStyle1);
polygon1.addTo(map);
// Right Polygon
const polyStyle2 = {
fillColor: "transparent",
color: '#2f528f',
extraStyles: [
{
inset: 6,
color: '#d9d9d9',
weight: 10
}
]
};
const polygonCoords2 = [
[340, 10],
[640, 10],
[640, 310],
[340, 310]
].map(point => map.containerPointToLatLng(point));
const polygon2 = new L.Polygon(polygonCoords2, polyStyle2);
polygon2.addTo(map);
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" rel="stylesheet"/>
<div id="map" style="width: 100vw; height: 100vw">0012</div>
Ideal Solution:
Implement a plugin as a separate npm module.
Try to extend or hook into Renderer itself instead of separately extending L.Canvas and L.SVG.
Hook the cusomization into base class Path instead of individual shapes: Polygon, Polyline or Circle.
Use the Recatngle/Polygon method.
// define rectangle geographical bounds
var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
// create an orange rectangle
L.rectangle(bounds, {}).addTo(map);
The Use options to get the desired effect on the lines. Options are inherited from polyline options
There you can tweak color, opacity, fill, fillColor, fillOpacity and fillRule to get the desired effect on the lines

JavaScript - How to draw black outline (or stroke) for a red line - leaflet mapbox

I try to figure out how to draw linestring with fillcolor: red and outline: black. Like the following image:
style = {
fillColor: 'rgb(255,0,0)',
outline: 'rgb(0,0,0)'
weight: 10
};
It does not work, maybe I need to use strokeStyle?
Any help will be appreciated.
Lines do not, and cannot have a fill colour.
What you really want is to draw two line symbols per line geometry, while making sure the black line is drawn first (i.e. behind) the red one.
In Leaflet, create two map panes, make sure their z-indexes are right (read the tutorial!), add layers as appropriate, reusing the line geometry but changing the symbol appearance.
#mik1971
Look at this example. My situation is similar, but it is not efficient for other possible analyzes:
var geojson = new L.geoJson(data, {
style: function (feature) {
return {color: 'black', opacity: 1, weight: 3};
}
}).addTo(map);
var geojson = new L.geoJson(data, {
style: function (feature) {
var c;
switch (feature.properties.dpv_cal) {
case 'Pavimentada':
c = '#ff0127';
break;
case 'Mejorada':
c = '#db6adb';
break;
case 'Natural':
c = '#ffffff';
break;
default:
c = 'grey';
break;
}
return {color: c, opacity: 1, weight: 2};
}
}).addTo(map);
}
});
I have declared two times my geojson and it works.

Place a PolygonGeometry in the air in Cesium

I'm trying to position a PolygonGeometry in the air in Cesium. In short, I'd like to use height to create an offset from the ground, and extrudedHeight to give the object a certain thickness. However when I set extrudedHeight, the height setting itself is ignored and the extrusion goes down all the way to the ground. So I can layer planes on top of each other, but no three-dimensional objects. What's the correct way to achieve this?
Here's what I'm doing so far:
polygonGeometry = Cesium.PolygonGeometry.fromPositions(
positions: pos,
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
extrudedHeight: #options.extrudedHeight,
height:#options.height
)
geometryInstance = new Cesium.GeometryInstance
geometry: polygonGeometry
primitive = new Cesium.Primitive
geometryInstances: [geoInstance]
It's hard to say exactly what the problem is, since your example is incomplete and also seems to be using a form of data binding that I'm not familiar with. But here's a complete example that can be copied/pasted into Sandcastle so that you can compare.
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var positions = Cesium.Cartesian3.fromDegreesArray([
-88.0, 35.0,
-80.0, 35.0,
-80.0, 40.0,
-88.0, 40.0
]);
var geometryInstance = new Cesium.GeometryInstance({
geometry : Cesium.PolygonGeometry.fromPositions({
positions : positions,
height: 1000000,
extrudedHeight: 1500000,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.ORANGE)
}
});
scene.primitives.add(new Cesium.Primitive({
geometryInstances : geometryInstance,
appearance : new Cesium.PerInstanceColorAppearance({
closed : true,
translucent : false
})
}));

Positioning A Set in Raphael Paper

Is there any way to group bunch of elements of paper in one set and only position that set in the paper?
For example At This Example I was trying to put some circles in side a rectangle and just position the rectangle in each part of the paper. Can you please let me know how to do it?
var paper = Raphael('my-canvas', 500, 300);
paper.canvas.style.backgroundColor = '#F00';
var rect = paper.rect(5, 5, 100, 100);
var st = paper.set();
st.push(
rect.circle(10, 10, 5),
rect.circle(30, 10, 5));
You can do this via a transform on the set (you could also do it with another attribute like x,y but only if the elements use that specific attribute).
Its worth noting, that although you can apply a transform to the set, it is in effect applying the transform to each element in the set. Ie there is no specific 'set' or 'group' element in Raphael (there is in Snap.svg which is its updated brother, but doesn't quite have the same backwards compatibility). So there is no true hierarchy of groups, where they could have separate transforms which cascade down.
var paper = Raphael('my-canvas', 500, 300);
paper.canvas.style.backgroundColor = '#F00';
var rect = paper.rect(5, 5, 100, 100);
var st = paper.set();
st.push(
paper.rect(5, 5, 100, 100),
paper.circle(10, 10, 5).attr({ fill: 'blue' }),
paper.circle(30, 10, 5).attr({ fill: 'green' })
);
st.transform('t20,20');
st.animate({ transform: 't100,100' }, 2000);
Its worth looking at the Raphael docs for transforms if not sure here
jsfiddle
Instead of using a set, you can create a new div, add a new paper element to this div and then change the position of the div.
If you are using jquery, it could look something like this:
$(".body").append('<div id="setContainer"></div>');
paper = new Raphael(document.getElementById("setContainer"), 500, 300);
paper.canvas.style.backgroundColor = '#F00';
var rect = paper.rect(5, 5, 100, 100);
var circle = paper.circle(10, 10, 5);
var circle2 = paper.circle(30, 10, 5);
// then you can adjust the position of the div, for example to 100,100
$("#setContainer").css("top", 100);
$("#setContainer").css("left", 100);
The advantage of using this approach rather than a set is that it gives you more flexibility for the way you want to manipulate the elements you are grouping together.
You can even wrap a function around this code in order to define the id of the container programatically if you wanted to create several instances of the same group of elements.

How can I change individual object's attributes in the Raphael SET?

Let's consider I have a circle and a rectangle in ss Raphael Set:
<script type="text/javascript">
var paper = Raphael("canvas", 500, 500),
r = paper.rect(100, 100, 140, 80),
c = paper.circle(100, 100, 80);
c.attr({fill: 'red', stroke: 'black'});
r.attr({fill: 'black', stroke: 'red'});
var ss = paper.set(r, c);
</script>
Now at some point in the program, I need to change say Circle's fill: 'white'.
I know that it is possible to change Set's attribute as follows: ss.attr({fill: 'white'}), but this applies to all of its elements. I still want to keep the color of rectangle unchanged. I have tried ss.c.attr(), but no result.
Any idea how can I achieve this. Thanks
I had the same problem a while ago. Then I found out that you can access SET individual objects just like in array.
For exp.; ss[0] returns your r rectangle object; i.e. ss[0].attr({'//here'})

Categories

Resources