Custom vector shape markers in OpenLayers 3 - javascript

I have a OpenLayers 3 map on which I'm showing all kinds of data. One of them is showing boats that are detected by a nearby radar. Currently I'm displaying boats as a simple vector Circle. I'd like to display it as a vector shaped as a boat.
As far as I'm informed, my best bet is using a *.png icon, and doing something like this:
style: new ol.style.Icon({
image: new ol.style.Icon(({
anchor: [0.5, 0.5],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
opacity: 1,
scale: 1,
src: '/Content/images/boat.png',
rotation: 0
}))
});
This works but I'd like to have a vector that doesn't scale when i zoom in/out. My current solution for some different data is displaying a rectangle, but it scales when zooming:
var style = (function () {
function (feature, resolution) {
// font size
if (resolution > 0.4) var fontSize = '12px';
else var fontSize = '14px';
var temperature = feature.get('temperature') || '-';
temperature = temperature.replace(/,/g, '.');
return [new ol.style.Style({
fill: fill,
stroke: stroke,
text: new ol.style.Text({
font: 'bold ' + fontSize + ' helvetica,sans-serif',
text: Math.round(temperature * 100) / 100 + '°C',
fill: new ol.style.Fill({ color: '#000' })
}),
geometry: function (feature) {
var startingCoordinates = feature.getGeometry().getCoordinates();
var coordinates = [[
[startingCoordinates[0] + 0, startingCoordinates[1] + 0],
[startingCoordinates[0] + 33, startingCoordinates[1] + 0],
[startingCoordinates[0] + 33, startingCoordinates[1] + (-11.35)],
[startingCoordinates[0] + 0, startingCoordinates[1] + (-11.35)],
[startingCoordinates[0] + 0, startingCoordinates[1] + 0]
]];
return new ol.geom.Polygon(coordinates);
}
})]
}
})()
Is there a better solution for this than using startingCoordinates + (constant * resolution)? Are there any significant performance differences in using vector vs. png? Thanks
EDIT: After consulting with few colleagues of mine, I'm basically trying to have this http://openlayers.org/en/v3.5.0/examples/regularshape.html but with a custom shape.
EDIT2: Actually more like this http://dev.openlayers.org/examples/graphic-name.html 'The named symbols "lightning", "rectangle" and "church" are user defined.'

I know this is an old question but answering for others as this was the top result while I was looking into how to do this.
My solution is to use a data:image/svg+xml with an svg to provide an icon you can programmatically generate
new ol.style.Style({
image: new ol.style.Icon({
// svg of an equilateral triangle 25px wide at the base
src: `data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="21.65" width="25" id="canvas"><polygon points="0,0 25,0 12.5,21.65" style="fill:rgba(0,0,0,1);"></polygon></svg>')}`,
}),
})
Note: you don't need to base64 encode svg in a data url but you do need to encodeURIComponent() it for non webkit browsers.

This is how I've done it, its similar to the original but without me having to work out what the coordinates should be, I just provide how big by pixels it is and let the map calculate the rest.
geometry: function (feature) {
var startingCoordinates =feature.getGeometry().getCoordinates();
var pixel = map.getPixelFromCoordinate(startingCoordinates);
var p1,p2,p3,p4,polyCoords=[],sizex=90, sizey=30;
p1 = pixel;
p2 = [pixel[0]+sizex,p1[1]];
p3 = [pixel[0]+sizex,pixel[1]+sizey];
p4 = [pixel[0],pixel[1]+sizey];
var p = [p1,p2,p3,p4,p1];
for (var c = 0; c < 5;c++){
polyCoords.push(map.getCoordinateFromPixel(p[c]));
}
return new ol.geom.Polygon([polyCoords]);
}
this gives me a rectangle, which is what I wanted.

Related

OpenLayers creating a complex style (polygon with a hole and a stroke on one side)

There was a difficulty in creating a complex polygon style.
The wording is as follows:
the polygon should be drawn as a polygon with a hole and a stroke on the outside.
In a difficult (as it seems to me) way, I made drawing a polygon with a hole:
convert to turf
using turf.buffer and a negative buffer value, I get an internal buffer
using turf.difference (source polygon and buffer) I get a polygon with a hole
But I don't understand how to draw the border only from the outside%)
If in the same function I try to return 2 styles (line + polygon), then I get an error (Uncaught TypeError: s.simplifyTransformed is not a function).
In general, is it possible to return 2 different geometries in the style?
In the picture the red polygon is what I need to get in the end.
Also I made a minimal example on codepen
I would be grateful for your help!
upd.
loops
and zoom out
To adapt the OpenLayers 3: Offset stroke style example for a polygon you would need to extend the ring by one segment at each end so you can correctly calculate the new coordinates at the original start/end point, then remove the excess when creating the resulting polygon.
var style = function(feature, resolution) {
var poly = feature.getGeometry();
if (poly.getType() == 'Polygon') {
var coordinates = poly.getCoordinates()[0];
coordinates = coordinates.slice(-2, -1).concat(coordinates).concat(coordinates.slice(1, 2));
var geom = new ol.geom.LineString(coordinates);
var colors = ['green', 'yellow', 'red'];
var width = 4;
var styles = [];
for (var line = 0; line < colors.length; line++) {
var dist = width * resolution * (line - (colors.length-1)/2);
var coords = [];
var counter = 0;
geom.forEachSegment(function(from, to) {
var angle = Math.atan2(to[1] - from[1], to[0] - from[0]);
var newFrom = [
Math.sin(angle) * dist + from[0],
-Math.cos(angle) * dist + from[1]
];
var newTo = [
Math.sin(angle) * dist + to[0],
-Math.cos(angle) * dist + to[1]
];
coords.push(newFrom);
coords.push(newTo);
if (coords.length > 2) {
var intersection = math.intersect(coords[counter], coords[counter+1], coords[counter+2], coords[counter+3]);
coords[counter+1] = (intersection) ? intersection : coords[counter+1];
coords[counter+2] = (intersection) ? intersection : coords[counter+2];
counter += 2;
}
});
styles.push(
new ol.style.Style({
geometry: new ol.geom.Polygon([coords.slice(2, -1)]),
stroke: new ol.style.Stroke({
color: colors[line],
width: width
})
})
);
}
return styles;
}
};
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector();
var vector = new ol.layer.Vector({
source: source,
style: style
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-11000000, 4600000],
zoom: 4
})
});
map.addInteraction(new ol.interaction.Draw({
source: source,
type: 'Polygon',
style: style
}));
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.4.1/math.min.js"></script>
<div id="map" class="map"></div>
There is a problem with the original algorithm for LineStrings at corners with multiple vertices
When zoomed out the two vertices on the inner line should merge to a single point, but that is not happening, instead they cross and cause a kink in the line.

Can we use html tags and css to style the features in openlayers?

I have a polygon feature in OpenLayers 5. It has some random id which I need to show at the center of the polygon slightly right aligned.
I have used the ol.style.Text() style to display the id on polygon. I can manage the alignment using the offsetX and offsetY options of the class but how can I display the text in html elements or imitate it, because ol.style.Text() only accepts text data.
Overlays in openlayers will definitely solve the problem, I was able get middle point of polygon using getInteriorPoint() on geometry but I don't want to use overlays because there could be large number of polygons on the map and adding overlay for each would deteriorate performance and memory utilization.
Here is the expected output / I am trying to achieve :
Here is my code :
Current code
Also check what I have done to toggle the Id ON and OFF and mention if that can be bettered. The ID could be turned ON and OFF based on zoom level.
Instead of using CSS you could draw the background in a canvas element and use it in an icon style. And use a style function to style the interior point of a polygon without needing to create more features:
var canvas = document.createElement("canvas");
canvas.width = ??;
canvas.height = ??;
var ctx = canvas.getContext("2d");
// draw an arraw and rounded box
.....
.....
var iconUrl = canvas.toDataURL();
var offStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255,255,255,0)'
}),
stroke: new ol.style.Stroke({
color: 'green',
width: 1.5
})
});
var onStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255,255,255,0)'
}),
stroke: new ol.style.Stroke({
color: 'black',
width: 1.5
})
});
var styleFunction = function (feature, resolution) {
if (off) { // or use (resolution > ????)
return offStyle;
else {
var styles = [onStyle];
if (feature.getGeometry().getType == 'Polygon') {
styles.push( new ol.style.Style({
geometry: feature.getGeometry().getInteriorPoint(),
image: new ol.style.Icon({
src: iconUrl,
// options to anchor the icon
....
}),
text: new ol.style.Text({
scale: 1,
text: feature.get('.....'),
font: 'normal 10px FontAwesome',
fill: new ol.style.Fill({
color: 'black'
}),
}),
zIndex: 100
}));
}
return styles;
}
}

minimal size for a rectangle in style

I'm writing an application where users can mark regions on a world map. Now these regions can be very small, so that it's hard to click on them when not zoomed in.
Is there a way how I can define (e.g. in the style function) that a (rectangle) feature should always be rendered with at least e.g. 10px × 10px?
Update: some code I currently use:
on the drawing side:
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString',
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;
},
maxPoints: 2
});
draw.on('drawend', function(e) {
var extent = e.feature.getGeometry().getExtent();
extent = app.map.rlonlate(extent); // own function to convert it from map coordinates into lat/lon
// some code to save the extent to the database
});
and on the displaying side:
vectorSource.addFeature(
new ol.Feature({
geometry: ol.geom.Polygon.fromExtent(app.map.lonlate(extent)),
// … some more custom properties like a display name …
})
);
the style function:
function(feature) {
return [new ol.style.Style({
stroke: new ol.style.Stroke({
color: feature.get('mine') ? '#204a87' : '#729fcf',
width: 2
}),
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, ' + (feature.get('mine') ? '0.5' : '0.2') + ')'
})
})];
}
Using a style function is a good idea. The second argument of the style function is resolution which you can use to check if your feature geometry would be smaller than e.g. 10 px at the current resolution:
var styleFn = function(feature, resolution) {
var minSizePx = 30;
var minSize = minSizePx * resolution;
var extent = feature.getGeometry().getExtent();
if (ol.extent.getWidth(extent) < minSize || ol.extent.getHeight(extent) < minSize) {
// special style for polygons that are too small
var center = new ol.geom.Point(ol.extent.getCenter(extent));
return new ol.style.Style({
geometry: center,
image: ...
} else {
// normal style
return defaultStyle;
}
};
http://jsfiddle.net/ukc0nmy2/1/

DragBox change color while drawing

In order to help users to draw a boxarea with minimum of 100 px at width and height, I've thought to start drawing in red color (fill and border of box) and then change automatically to green when it reaches the 100 px mentioned while user is drawing the feature.
Any idea how to do this?
I got it something like that when user has finished drawing, but in my opinion, that behavior is not enough comfortable.
Thanks in advance
UPDATE:
http://jsfiddle.net/jonataswalker/41j800kv/
Found a better solution. Put these conditions inside a ol.interaction.Draw#StyleFunction:
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString',
maxPoints: 2,
style: function(feature){
var style;
var geometry = feature.getGeometry();
var extent = geometry.getExtent();
var topLeft =
map.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
var bottomLeft =
map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent));
var topRight =
map.getPixelFromCoordinate(ol.extent.getTopRight(extent));
var width = topRight[0] - topLeft[0];
var height = bottomLeft[1] - topLeft[1];
coords_element.innerHTML =
'width: ' + width + '<br>height: ' + height;
if (width > 100 && height > 100) {
style = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: 'red',
width: 2
})
});
} else {
style = new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
})
});
}
return [style];
},
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;
}
});
Take this piece of code and put some conditions on it:
Using the latest version of ol3. 3.13.1 you may do the following to achieve your goal.
Create a map with a layer and add a dragbox interaction
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var map = new ol.Map({
layers: [raster],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
var selectOnBoxInt = new ol.interaction.DragBox({
condition : ol.events.condition.always
});
map.addInteraction(selectOnBoxInt);
//set it active on start up
selectOnBoxInt.setActive(true);
Create two css classes holding styles for your drawbox
//this is the deafult
.ol-dragbox {
background-color: rgba(255,0,0,0.4);
border-color: rgba(2500,0,0,1);
border-width:2;
}
//this is when width,height>100
.ol-mydragbox {
background-color: rgba(0,255,0,0.4);
border-color: rgba(0,255,0,1);
border-width:2;
}
asign a boxdrag event to your drawbox interaction so you can truck down its width, height and make the style changes. For this action, and for the sake of time, I use jquery. You may use your imagination to do it without jquery.
selectOnBoxInt.on('boxdrag',function(e){
var width = Math.abs(e.target.box_.endPixel_[0] - e.target.box_.startPixel_[0]);
var height = Math.abs(e.target.box_.endPixel_[1] - e.target.box_.startPixel_[1]);
if (width>100 && height>100){
$('.ol-dragbox').removeClass('ol-dragbox').addClass('ol-mydragbox');
$('.ol-box').removeClass('ol-box').addClass('ol-mydragbox');
} else {
$('.ol-mydragbox').removeClass('ol-mydragbox').addClass('ol-dragbox');
}
});
And a fiddle to see it in action.

Openlayers3 - How to anchor a text label to a point?

I am working on an application using Openlayers3. On the map there are a couple of line segments and text labels. Each line has a color and an associated label.
For the map, I would like to have the text label anchored at a point of a line segment that is drawn on the map. So if I move the map or zoom in or out, that the label sticks to the point. However, if I zoom in or out the labels move (a lot). Dragging the map does not have this effect. Somehow I would like them to stick at a point on the line rather than move around.
Does anyone has some clever advice or links where to look? Googling for terms like 'anchor' or 'fixed point' and trying some of the recommendations did not solve the issue for me. Any help would be very much appreciated!
maybe you can be inspered by the "arrow" example wich use a style function that use geometry segments:
http://jsfiddle.net/davidhequet/7asg74Lc/
var styleFunction = function(feature, resolution) {
var geometry = feature.getGeometry();
var styles = [
// linestring
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
})
})
];
geometry.forEachSegment(function(start, end) {
var dx = end[0] - start[0];
var dy = end[1] - start[1];
var rotation = Math.atan2(dy, dx);
// arrows
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(end),
text: new ol.style.Text({
textAlign: 'left',
textBaseline: 'bottom',
font: 'Arial',
text: 'test text',
fill: new ol.style.Fill({color: 'red'}),
stroke: new ol.style.Stroke({color: 'white', width: '2'}),
offsetX: 0,
offsetY: 0,
rotation: 0
})
}));
});
return styles;
};

Categories