I would like to draw the simple arrow in OpenLayers.
I found some examples here:
OpenLayer 4 draw arrows on map
https://openlayers.org/en/latest/examples/line-arrows.html
Draw arrow without using any image in openlayers3
and made some code:
var arrowInteraction = new ol.interaction.Draw({
type: 'LineString',
source: vectorLayer.getSource(),
geometryFunction: function(coordinates, geometry) {
var geometry = coordinates.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);
var lineStr1 = new ol.geom.LineString([end, [end[0] - 200000, end[1]
+ 200000]]);
lineStr1.rotate(rotation, end);
var lineStr2 = new ol.geom.LineString([end, [end[0] - 200000, end[1]
- 200000]]);
lineStr2.rotate(rotation, end);
var stroke = new ol.style.Stroke({
color: 'green',
width: 1
});
styles.push(new ol.style.Style({
geometry: lineStr1,
stroke: stroke
}));
styles.push(new ol.style.Style({
geometry: lineStr2,
stroke: stroke
}));
});
return styles;
}
I am getting an error:
coordinates.getGeometry is not a function
What can be wrong here?
My full code is here:
https://mktest.opx.pl/
This might help: TypeError: Cannot read properties of undefined (reading 'id')
If I understand it right, the functions aren't called synchronized.
In that tutorial, he catch it with:
itemToForm = () => {
if(this.item === undefined) {return}
// The rest of the code
}
But you can also catch it with
if(yourObjectOrValue === undefined) {// The rest of the code}
Related
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.
I want to add moving arrows or overlay animation in the Flights Animation example in OpenLayers 6.
I tried doing the overlay moving animation with JavaScript setInterval(), but so far I have only succeeded in animating a single LineString, that too after the line is finished drawing. I wanted to add the moving animation as the line is being drawn, kind of like tracing the LineString's path.
Can someone please help me with this?
Following is the code snippet where I have tried to add the moving animation:
var markerEl = document.getElementById('geo-marker');
var marker = new Overlay({
positioning: 'center-center',
offset: [0, 0],
element: markerEl,
stopEvent: false
});
map.addOverlay(marker);
function animateFlights(event) {
var coords;
var vectorContext = getVectorContext(event);
var frameState = event.frameState;
var features = flightSource.getFeatures();
for (var i = 0; i < features.length; i++) {
var feature = features[i];
if (!feature.get('finished')) {
coords = feature.getGeometry().getCoordinates();
var elapsedTime = frameState.time - feature.get('start');
var elapsedPoints = elapsedTime * pointsPerMs;
if (elapsedPoints >= coords.length) {
feature.set('finished', true);
}
var maxIndex = Math.min(elapsedPoints, coords.length);
var currentLine = new LineString(coords.slice(0, maxIndex));
vectorContext.setStyle(strokeStyle1);
vectorContext.drawGeometry(currentLine);
if (feature.get('finished')) {
var interval = setInterval(
function () { return animatePath(coords, interval) }, 10);
}
}
}
map.render();
}
function animatePath(path, clearInterval) {
if (i == path.length) {
stopAnimatePath(clearInterval);
}
marker.setPosition(path[i]);
i = i + 1;
}
function stopAnimatePath(clearInterval) {
clearInterval(clearInterval);
}
Here is a link to a snapshot of how my app looks right now
Trace your LineString
It should be enough to set your map center to the last point of your LineString if you update often enough
map.getView().setCenter(lastPoint)
If it gets laggy use
var pan = ol.animation.pan({
source: map.getView().getCenter()
});
map.beforeRender(pan);
map.getView().setCenter(lastPoint);
Draw arrows
To draw arrows on your LineString you can use the following style
var styleFunction = function (feature) {
var geometry = feature.getGeometry();
var styles = [
// linestring
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#000',
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);
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(end),
image: new ol.style.RegularShape({
fill: new ol.style.Fill({color: '#000'}),
points: 3,
radius: 8,
rotation: -rotation,
angle: Math.PI / 2 // rotate 90°
})
}));
});
return styles;
};
more details: https://stackoverflow.com/a/58237497/546526
I'm using openlayers3 and I have the encoded geometry. I can get the coordinates (lat,lng) for all points in the path (around 500 points per path). Given a random point inside the path, how do I calculate the distance between the beginning of the path up to that point?
I've taken a look at turfjs and it looks very promising, but the solution I pictured using it wouldn't be very nice. Taking a random point (p1), I could discover the point (p2) of the path that is closest to p1, then generate a new polygon and calculate its total distance. It may have performance issues, although the search would be O(log n) and the new polygon O(n).
EDIT: the random point is not necessarily inside the path, it's a GPS coordinate and there's a margin for error.
EDIT 2: estimation on the number of points was off, each path has about 500 points, not 5k
Does anyone know of a better approach? I'm not very experienced with either openlayers3 nor turfjs.
As you mentioned you're using OpenLayers 3, I've done an example using OpenLayers 3, the idea is:
Get Closest Point across the LineString given a coordinate
Iterate over LineString points calculating the distance of each individual paths and see if our closest point intersects the individual path.
/* Let's Generate a Random LineString */
var length = 5000;
var minLongitude = Math.random()*-180 + 180;
var minLatitude = Math.random()*-90 + 90;
var wgs84Sphere = new ol.Sphere(6378137);
var lastPoint = [minLongitude, minLatitude]
var points = Array.from({ length })
.map( _ =>{
var newPoint = [
Math.random() * (Math.random() > 0.8 ? -.005 : .005) + lastPoint[0]
, Math.random() * (Math.random() > 0.2 ? -.005 : .005) + lastPoint[1]
]
lastPoint = newPoint;
return newPoint;
})
var distanceTotal = points
.reduce((dis, p, i)=>{
if(points[i + 1])
dis += wgs84Sphere.haversineDistance(p, points[i + 1] )
return dis;
}, 0);
console.log(distanceTotal)
var extent = new ol.extent.boundingExtent(points)
//console.log(points)
var lineString = new ol.Feature({
geometry : new ol.geom.LineString(points)
});
var source = new ol.source.Vector();
var layer = new ol.layer.Vector({ source });
source.addFeature(lineString);
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'map',
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: new ol.View({
projection : 'EPSG:4326',
center: [0, 0],
zoom: 2
})
});
map.addLayer(layer)
map.getView().fit(extent, map.getSize())
var auxLayer = new ol.layer.Vector({ source : new ol.source.Vector() })
var styleAux = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'green',
width: 2
})
});
var styleAuxLine = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'green',
width: 0.5
})
});
var styleAuxPoint = new ol.style.Style({
image : new ol.style.Circle({
radius: 5,
fill: null,
stroke: new ol.style.Stroke({color: 'black', width: 2})
})
});
var styleAuxSourcePoint = new ol.style.Style({
image : new ol.style.Circle({
radius: 3,
fill: null,
stroke: new ol.style.Stroke({color: '#00bbff', width: 0.5})
})
});
auxLayer.setStyle(function(f, r){
var type = f.getGeometry().getType();
if(type === 'LineString') return styleAux;
return styleAuxPoint;
})
map.addLayer(auxLayer);
map.on('pointermove', function(e){
if(e.dragging) return;
var coord = e.coordinate;
var distance = 0;
var pointsGeometry = [];
var sourcePointFeature = new ol.Feature({
geometry : new ol.geom.Point(coord)
});
var closestPoint = lineString.getGeometry().getClosestPoint(coord);
var lineDiffFeature = new ol.Feature({
geometry : new ol.geom.LineString([
coord, closestPoint
])
});
for(let i = 0; i< points.length - 1; i++){
var p = points[i]
var next = points[i + 1];
var subLineStringGeom = new ol.geom.LineString([ p, next ]);
pointsGeometry.push(p);
var e = 1e-10;
var extent = [ closestPoint[0] - e, closestPoint[1] - e
, closestPoint[0] + e, closestPoint[1] + e
]
if(subLineStringGeom.intersectsExtent(extent)){
//console.log(i);
pointsGeometry.push(closestPoint);
distance += wgs84Sphere.haversineDistance(p, closestPoint);
break;
}
distance += wgs84Sphere.haversineDistance(p, next);
}
console.log(closestPoint)
var cpGeometry = new ol.geom.Point(closestPoint);
var cpFeature = new ol.Feature({ geometry : cpGeometry });
var geometry = new ol.geom.LineString(pointsGeometry);
var newFeature = new ol.Feature({ geometry });
auxLayer.getSource().clear();
auxLayer.getSource().refresh();
auxLayer.getSource().addFeature(lineDiffFeature);
auxLayer.getSource().addFeature(newFeature);
auxLayer.getSource().addFeature(sourcePointFeature);
auxLayer.getSource().addFeature(cpFeature);
sourcePointFeature.setStyle(styleAuxSourcePoint);
lineDiffFeature.setStyle(styleAuxLine);
//console.log(geometry.getLength())
console.log(distance);
})
html, body, #map {
width : 100%;
height : 100%;
padding : 0px;
margin : 0px;
}
<script src="https://openlayers.org/en/v3.20.1/build/ol.js"></script>
<link href="https://openlayers.org/en/v3.20.1/css/ol.css" rel="stylesheet"/>
<div id="map" class="map" tabindex="0"></div>
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/
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.