How to calculate a distance between two points along a path? - javascript

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>

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.

Problem with drawing arrows in OpenLayers

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}

MarkerClusters in OpenLayer 6.3.1 with WMS as source

I am using OpenLayer version 6.3.1 and would like to know how to do a MarkerClusters using WMS data source.
My problem is that I don't know how to link the Cluster with WMS. the WMS source is a point.shp (geometrically)
I tried to replace the cluster sources by the WMS one, but it doesn't work.
Here is my code
var ImageWMS = new ImageWMS({
url: 'http://localhost:8080/geoserver/ACCESSOINSMADA/wms',
params: {'LAYERS': 'ACCESSOINSMADA:csb2-pub', 'TILED': true},
serverType: 'geoserver',
transition: 0,
});
var ImageLayers = new ImageLayer({
source: ImageWMS,
})
var distance = document.getElementById('distance');
var count = 20000;
var features = new Array(count);
var e = 3500000;
for (var i = 0; i < count; ++i) {
var coordinates = [2 * e * Math.random() - e, 2 * e * Math.random() - e];
features[i] = new Feature(new Point(coordinates));
}
var source = new VectorSource({
features: features,
});
var clusterSource = new Cluster({
distance: parseInt(distance.value, 10),
source: source,
});
var styleCache = {};
var clusters = new VectorLayer({
source: clusterSource,
style: function (feature) {
var size = feature.get('features').length;
var style = styleCache[size];
if (!style) {
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: '#fff',
}),
fill: new Fill({
color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
}
return style;
},
});
A WMS output is (generally) an image, so you won't be able to change the data and cluster it in a client such as OpenLayers; to do that you'll need to fetch the data through say a WFS.
As an alternative to clustering points in a client, you can configure the WMS to serve out clustered points.
For an example see:
WMS providing clustered points, shown in OpenLayers

Dynamically change location of a input svg icon

I'm using openlayers and javascript.
I'm trying to dynamically change the position of a svg file, based on openlayers example
https://openlayers.org/en/latest/examples/dynamic-data.html
but I'd like the sgv file to be the head of the animation instead of the black circle drawn.
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import Draw from 'ol/interaction/Draw';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import {OSM, Vector as VectorSource} from 'ol/source';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import {fromLonLat} from 'ol/proj';
import TileJSON from 'ol/source/TileJSON';
import {Icon, Style} from 'ol/style';
import {getVectorContext} from 'ol/render';
import {Circle as CircleStyle, Fill, Stroke} from 'ol/style';
import {MultiPoint} from 'ol/geom';
var home = new Point(fromLonLat([-46.68, -23.59]));
var Plane = new Feature({
geometry: home
});
Plane.setStyle(new Style({
image: new Icon({
color: '#F5AC7C',
crossOrigin: 'anonymous',
rotation: 180 / 180 * 3.14,
src: 'data/plane.svg'
})
}));
var imageStyle = new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({color: 'yellow'}),
stroke: new Stroke({color: 'red', width: 1})
})
});
var headInnerImageStyle = new Style({
image: new CircleStyle({
radius: 2,
fill: new Fill({color: 'blue'})
})
});
var headOuterImageStyle = new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({color: 'black'})
})
});
var rasterLayer = new TileLayer({
source: new OSM()
});
var source = new VectorSource({
wrapX: false,
features: [Rectangle, Alvo_Secundario]
});
var vectorLayer = new VectorLayer({
source: source
});
var map = new Map({
layers: [rasterLayer, vectorLayer],
target: 'map',
view: new View({
center:fromLonLat([-46.68, -23.59]),
zoom: 4
})
});
var n = 100;
var omegaTheta = 20000; // Rotation period in ms
var R = 3e6;
var r = 1e6;
var p = 1e6;
rasterLayer.on('postrender', function(event) {
var vectorContext = getVectorContext(event);
var frameState = event.frameState;
var theta = 2 * Math.PI * frameState.time / omegaTheta;
var coordinates = [];
var i;
for (i = 0; i < n; ++i) {
var t = theta + 2 * Math.PI * i / n;
var x = (R + r) * Math.cos(t) + p * Math.cos((R + r) * t / r);
var y = (R + r) * Math.sin(t) + p * Math.sin((R + r) * t / r);
coordinates.push([x, y]);
}
vectorContext.setStyle(imageStyle);
vectorContext.drawGeometry(new MultiPoint(coordinates));
var headPoint = new Point(coordinates[coordinates.length - 1]);
vectorContext.setStyle(headOuterImageStyle);
vectorContext.drawGeometry(headPoint);
vectorContext.setStyle(headInnerImageStyle);
vectorContext.drawGeometry(headPoint);
map.render();
});
map.render();
It draws 100 circles making an animation, the head point is a black circle, I'd like it to be the sgv file that is initially drawn at "home" point
If is possible, how can I change the 100 circle points to became 100 sgv files?
Icon styles seem to work in vectorContext only if the icon is already loaded. If it is not already being used you need to force it to load when the style is constructed. The most reliable method where you can ensure it is loaded is to load src to an img and use the img in the icon constructor https://codesandbox.io/s/dynamic-data-3x8ux Don't attempt to use an incomplete style in vectorContext as it stops the rendering and locks the map.
To fix I wrote a function that receives a new point and calls source.addFeature(newPoint);

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/

Categories