
import Feature from "ol/Feature.js";
import { fromLonLat } from "ol/proj";
import Map from "ol/Map.js";
import VectorSource from "ol/source/Vector.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { getVectorContext } from "ol/render.js";
import LineString from "ol/geom/LineString.js";
import MultiLineString from "ol/geom/MultiLineString";
import { transform2D } from "ol/geom/flat/transform";
import { Style } from "ol/style";
import Stroke from "ol/style/Stroke.js";
import { isEmpty } from 'ol/extent';
export function lineDistance (from, to) {
const [x1, y1] = from;
const [x2, y2] = to;
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
export function getDestination (from, bearing, distance) {
const [x, y] = from;
const endX = Math.cos((bearing * Math.PI) / 180) * distance + x;
const endY = Math.sin((bearing * Math.PI) / 180) * distance + y;
return [endX, endY];
}
export const pathAngle = (points, toDegrees = false) => {
const [
[pointFromX = 0, pointFromY = 0],
[pointToX = 0, pointToY = 0]
] = points;
return toDegrees
? (Math.atan2(pointToY - pointFromY, pointToX - pointFromX) * 180) / Math.PI
: Math.atan2(pointToY - pointFromY, pointToX - pointFromX);
};
const DEFAULT_LINE_WIDTH = 5 * window.devicePixelRatio;
const DEFAULT_LINE_OUT_WIDTH = DEFAULT_LINE_WIDTH * 1.2;
const DASH_OFFSET = 50 * window.devicePixelRatio;
const DURATION = 30;
let offset = DASH_OFFSET;
let layer = null;
let timer = null;
let map = null;
const initLayer = (data, cMap) => {
map = cMap;
delLayer()
let newData = [];
data.forEach((item) => {
newData.push([item.longitude, item.latitude]);
});
const lineString = new LineString(newData);
const routeFeature = new Feature({
geometry: lineString
});
routeFeature.setStyle(
new Style({
stroke: new Stroke({
color: [0, 0, 0, 0],
})
})
);
const source = new VectorSource({
features: [routeFeature]
});
layer = new VectorLayer({
source,
});
function splitLinePoint (from, to, res, marginDistance) {
let _distnace = lineDistance(from, to);
let _bearing = pathAngle([from, to], true);
const offsetDistance = DASH_OFFSET - marginDistance;
if (_distnace > offsetDistance) {
const [endX, endY] = getDestination(from, _bearing, offsetDistance);
res.push([endX, endY, _bearing, true]);
return splitLinePoint([endX, endY], to, res, 0);
} else {
return _distnace + marginDistance;
}
}
function getLineOffsetDashPixelCoords (coordinates, offset) {
const destinationArray = [];
let marginDistance = 0;
marginDistance += offset;
for (let d = 0, len = coordinates.length - 1; d < len; d++) {
const [from, to] = [coordinates[d], coordinates[d + 1]];
destinationArray.push([...from, 0, false]);
marginDistance = splitLinePoint(from, to, destinationArray, marginDistance);
destinationArray.push([...to, 0, false]);
}
return destinationArray;
}
function rendererArrows (event) {
const vectorContext = getVectorContext(event);
const layer = event.target;
const transform = vectorContext["transform_"];
const ctx = vectorContext["context_"];
const source = layer.getSource();
const viewExtent = event.frameState.extent;
source.forEachFeatureInExtent(viewExtent, (feature) => {
const geometry = feature.getGeometry();
let lineStrings;
if (geometry instanceof MultiLineString)
lineStrings = geometry.getLineStrings();
else if (geometry instanceof LineString) lineStrings = [geometry];
if (!lineStrings) return;
lineStrings.forEach((lineString, index) => {
const coords = lineString.getFlatCoordinates();
const pixelCoordinates = transform2D(
coords,
0,
coords.length,
2,
transform,
[]
);
let pixelCoords = [];
for (let j = 0; j < pixelCoordinates.length; j += 2) {
const x = pixelCoordinates[j];
const y = pixelCoordinates[j + 1];
pixelCoords.push([x, y]);
}
const dashPixelCoords = getLineOffsetDashPixelCoords(pixelCoords, offset);
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = DEFAULT_LINE_OUT_WIDTH;
ctx.strokeStyle = "rgb(156, 0, 34)";
ctx.beginPath();
dashPixelCoords
.filter((dash) => !dash[3])
.forEach((dash, index) => {
const [x, y] = dash;
if (index === 0) ctx.moveTo(dash[0], dash[1]);
ctx.lineTo(x, y);
});
ctx.stroke();
ctx.lineWidth = DEFAULT_LINE_WIDTH;
ctx.strokeStyle = "rgba(1,194,125,1)";
ctx.beginPath();
dashPixelCoords
.filter((dash) => !dash[3])
.forEach((dash, index) => {
const [x, y] = dash;
if (index === 0) ctx.moveTo(dash[0], dash[1]);
ctx.lineTo(x, y);
});
ctx.stroke();
ctx.lineWidth = 1.5 * window.devicePixelRatio;
ctx.strokeStyle = "white";
dashPixelCoords
.filter((dash) => dash[3])
.forEach((dash, index) => {
const [x, y, angle] = dash;
const arrowCoord = getDestination([x, y], angle - 180, 5);
var l = DEFAULT_LINE_WIDTH * 0.65;
var a = Math.atan2(y - arrowCoord[1], x - arrowCoord[0]);
var x3 = x - l * Math.cos(a + (45 * Math.PI) / 180);
var y3 = y - l * Math.sin(a + (45 * Math.PI) / 180);
var x4 = x - l * Math.cos(a - (45 * Math.PI) / 180);
var y4 = y - l * Math.sin(a - (45 * Math.PI) / 180);
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x, y);
ctx.lineTo(x4, y4);
ctx.stroke();
});
});
});
}
function flash () {
map.render();
}
layer.on("prerender", rendererArrows);
map.addLayer(layer);
const extent = source.getExtent();
if (!isEmpty(extent)) {
map.getView().fit(extent, {
padding: [100, 100, 100, 100],
duration: 1000
});
}
timer = setInterval(() => {
if (!(offset % DASH_OFFSET)) {
offset = DASH_OFFSET;
}
offset -= 1;
flash();
}, DURATION);
}
const delLayer = () => {
if (layer) map.removeLayer(layer);
if (timer) clearInterval(timer);
}
export { initLayer, delLayer };
import { initLayer, delLayer } from './main.js';
initLayer(data, map);