control/elevationProfile/ElevationProfileView.tstypescript
import type { EventsHandler } from "../../Events";
import { View } from "../../ui/View";
import type { IViewParams, ViewEventsList } from "../../ui/View";
import { SAFE, WARNING, COLLISION, ElevationProfile } from "./ElevationProfile";

import type { ElevationProfileDrawData, GroundItem, TrackItem } from "./ElevationProfile";

import { distanceFormatExt, binarySearch } from "../../utils/shared";
import type { MouseEventExt } from "../../input/MouseHandler";

const FILL_COLOR = "#26282c";
const TRACK_COLOR = "rgb(0, 255, 50)";
const TERRAIN_COLOR = "rgb(198, 198, 198)";
const TERRAIN_FILL_COLOR = "rgb(64, 68, 82)";
const WARNING_COLOR = "rgb(255, 255, 0)";
const COLLISION_COLOR = "rgb(255, 0, 0)";
const LINE_COLORS = [TERRAIN_COLOR, WARNING_COLOR, COLLISION_COLOR];
const TERRAIN_ALPHA = 0.5;
const LINE_WIDTH = 5;

interface IElevationProfileViewParams extends IViewParams {
    fillStyle?: string;
}

type ElevationProfileViewEventsList = [
    "startdrag",
    "stopdrag",
    "pointer",
    "mouseenter",
    "mouseleave",
    "dblclick",
    "tracklength",
    "groundlength",
    "warninglength",
    "collisionlength"
];

const ELEVATIONPROFILEVIEW_EVENTS: ElevationProfileViewEventsList = [
    "startdrag",
    "stopdrag",
    "pointer",
    "mouseenter",
    "mouseleave",
    "dblclick",
    "tracklength",
    "groundlength",
    "warninglength",
    "collisionlength"
];

const TEMPLATE = `<div class="og-elevationprofile">
      <div class="og-elevationprofile-loading" style="display: none;">
        <div class="loadingio-spinner-bars-r354qqyl5v">
          <div class="ldio-p0v5a1f6oz">
            <div></div>
            <div></div>
            <div></div>
            <div></div>
          </div>
        </div>
      </div>
    </div>`;

class ElevationProfileView extends View<ElevationProfile> {
    public override events: EventsHandler<ElevationProfileViewEventsList> & EventsHandler<ViewEventsList>;

    public fillStyle: string;

    protected _pixelsInMeter_x: number;
    protected _pixelsInMeter_y: number;
    protected _canvasScale: number;
    public $canvas: HTMLCanvasElement;
    public $pointerCanvas: HTMLCanvasElement;
    protected _ctx: CanvasRenderingContext2D;
    protected _pointerCtx: CanvasRenderingContext2D;
    protected _onResizeObserver_: () => void;
    protected _resizeObserver: ResizeObserver;

    public $loading: HTMLElement | null;

    protected _isMouseOver: boolean;
    protected _isDragging: boolean;

    protected _clickPosX: number;
    protected _clickLeftDistance: number;
    protected _clickRightDistance: number;

    protected _leftDistance: number;
    protected _rightDistance: number;

    protected _customFrame: boolean;

    protected _timeStartHandler: number;

    constructor(options: IElevationProfileViewParams = {}) {
        super({
            template: TEMPLATE,
            model: new ElevationProfile()
        });

        //@ts-ignore
        this.events = this.events.registerNames(ELEVATIONPROFILEVIEW_EVENTS);

        this.fillStyle = options.fillStyle || FILL_COLOR;

        this._customFrame = false;
        this._leftDistance = 0;
        this._rightDistance = 0;
        this._pixelsInMeter_x = 0;
        this._pixelsInMeter_y = 0;
        this._canvasScale = 2;
        this.$canvas = document.createElement("canvas");
        this.$canvas.style.position = "absolute";
        this._ctx = this.$canvas.getContext("2d")!;

        this.$pointerCanvas = document.createElement("canvas");
        this.$pointerCanvas.style.pointerEvents = "none";
        this.$pointerCanvas.style.position = "absolute";
        this._pointerCtx = this.$pointerCanvas.getContext("2d")!;

        this.$loading = null;

        this._isMouseOver = false;
        this._isDragging = false;
        this._clickPosX = 0;
        this._clickLeftDistance = 0;
        this._clickRightDistance = 0;

        this._timeStartHandler = 0;

        this._onResizeObserver_ = this._onResizeObserver.bind(this);
        this._resizeObserver = new ResizeObserver(this._onResizeObserver_);
    }

    protected _onResizeObserver() {
        this.resize();
    }

    public get canvasScale(): number {
        return this._canvasScale;
    }

    public set canvasScale(scale: number) {
        if (scale !== this._canvasScale) {
            this._canvasScale = scale;
            this.resize();
        }
    }

    public resize() {
        this._resize();
        this.draw();
    }

    public override render(): this {
        super.render();

        this._resizeObserver.observe(this.el!);

        this.el!.appendChild(this.$canvas);
        this.el!.appendChild(this.$pointerCanvas);

        this.model.events.on("profilecollected", (data: ElevationProfileDrawData) => {
            this._hideLoading();
            this.clearPointerCanvas();
            this.draw();
        });

        this.model.events.on("startcollecting", () => {
            clearTimeout(this._timeStartHandler);
            //@ts-ignore
            this._timeStartHandler = setTimeout(() => {
                this._showLoading();
            }, 450);
        });

        this.model.events.on("clear", () => {
            this._customFrame = false;
            this._leftDistance = 0;
            this.clearCanvas();
            this.clearPointerCanvas();
        });

        this.$loading = this.select(".og-elevationprofile-loading");

        this.$canvas.addEventListener("mouseenter", this._onMouseEnter);
        this.$canvas.addEventListener("mouseout", this._onMouseOut);
        this.$canvas.addEventListener("dblclick", this._onMouseDblClick);

        this.$canvas.addEventListener("mousemove", this._onCanvasMouseMove);
        document.body.addEventListener("mousemove", this._onMouseMove);
        document.body.addEventListener("mousedown", this._onMouseDown);
        document.body.addEventListener("mouseup", this._onMouseUp);
        document.body.addEventListener("wheel", this._onMouseWheelFF);

        return this;
    }

    protected _hideLoading() {
        clearTimeout(this._timeStartHandler);
        this.$loading!.style.display = "none";
    }

    protected _showLoading() {
        this.$loading!.style.display = "flex";
    }

    protected _onMouseDblClick = (e: MouseEvent) => {
        //
        // @todo: The same code as in the pointer mouse move
        //
        let rect = this.$canvas.getBoundingClientRect();
        let x = e.clientX - rect.left;

        let pointerDistance = this._leftDistance + ((this._rightDistance - this._leftDistance) * x) / this.clientWidth;
        let groundData = this.model.drawData[1];
        let trackData = this.model.drawData[0];

        let groundPoiIndex;

        if (pointerDistance < 0) {
            groundPoiIndex = 1;
            pointerDistance = 0;
            //x = ((0 - this._leftDistance) * this.clientWidth) / (this._rightDistance - this._leftDistance);
        } else if (pointerDistance > this.model.planeDistance) {
            groundPoiIndex = groundData.length - 1;
            pointerDistance = this.model.planeDistance;
            // x =
            //     ((pointerDistance - this._leftDistance) * this.clientWidth) /
            //     (this._rightDistance - this._leftDistance);
        } else {
            groundPoiIndex =
                -1 -
                binarySearch(groundData, pointerDistance, (a: number, b: GroundItem) => {
                    return a - b[0];
                });
        }

        // Ground point
        let gp0 = groundData[groundPoiIndex - 1],
            gp1 = groundData[groundPoiIndex];
        let d = (pointerDistance - gp0[0]) / (gp1[0] - gp0[0]);
        let groundElv = gp0[1] + d * (gp1[1] - gp0[1]);

        // track point
        let trackPoiIndex = gp0[4];
        let tp0 = trackData[trackPoiIndex],
            tp1 = trackData[trackPoiIndex + 1];
        d = (pointerDistance - tp0[0]) / (tp1[0] - tp0[0]);
        let trackElv = tp0[1] + d * (tp1[1] - tp0[1]);

        this.events.dispatch(
            this.events.dblclick,
            pointerDistance,
            tp0,
            tp1,
            gp0,
            gp1,
            trackPoiIndex,
            groundPoiIndex - 1,
            trackElv - groundElv
        );
    };

    protected _onMouseEnter = (e: MouseEvent) => {
        this._isMouseOver = true;
        this.events.dispatch(this.events.mouseenter, e);
    };

    protected _onMouseOut = (e: MouseEvent) => {
        this._isMouseOver = false;
        this.events.dispatch(this.events.mouseleave, e);
    };

    protected _onMouseDown = (e: MouseEvent) => {
        if (this._isMouseOver) {
            this._isDragging = true;
            document.body.classList.add("og-timeline-unselectable");

            this._clickPosX = e.clientX;

            if (!this._customFrame) {
                this._leftDistance = this.model.minX;
                this._rightDistance = this.model.maxX;
            }

            this._clickLeftDistance = this._leftDistance;
            this._clickRightDistance = this._rightDistance;

            this.events.dispatch(this.events.startdrag, e);
        }
    };

    protected _onMouseUp = (e: MouseEvent) => {
        if (this._isDragging) {
            this._isDragging = false;
            document.body.classList.remove("og-timeline-unselectable");
            this.events.dispatch(this.events.stopdrag, e);
        }
    };

    protected _onCanvasMouseMove = (e: MouseEvent) => {
        if (this.model.pointsReady) {
            if (!this._isDragging) {
                if (!this._customFrame) {
                    this._leftDistance = this.model.minX;
                    this._rightDistance = this.model.maxX;
                }

                let rect = this.$pointerCanvas.getBoundingClientRect();
                let x = e.clientX - rect.left;
                this.redrawPointerCanvas(x);
            } else {
                this.clearPointerCanvas();
            }
        }
    };

    public redrawPointerCanvas(x: number) {
        this.clearPointerCanvas();

        let pointerDistance = this._leftDistance + ((this._rightDistance - this._leftDistance) * x) / this.clientWidth;
        let groundData = this.model.drawData[1];
        let trackData = this.model.drawData[0];

        let groundPoiIndex;

        if (pointerDistance < 0) {
            groundPoiIndex = 1;
            pointerDistance = 0;
            x = ((0 - this._leftDistance) * this.clientWidth) / (this._rightDistance - this._leftDistance);
        } else if (pointerDistance > this.model.planeDistance) {
            groundPoiIndex = groundData.length - 1;
            pointerDistance = this.model.planeDistance;
            x =
                ((pointerDistance - this._leftDistance) * this.clientWidth) /
                (this._rightDistance - this._leftDistance);
        } else {
            groundPoiIndex =
                -1 -
                binarySearch(groundData, pointerDistance, (a: number, b: GroundItem) => {
                    return a - b[0];
                });
        }

        // Ground point
        let gp0 = groundData[groundPoiIndex - 1],
            gp1 = groundData[groundPoiIndex];
        let d = (pointerDistance - gp0[0]) / (gp1[0] - gp0[0]);
        let groundElv = gp0[1] + d * (gp1[1] - gp0[1]);

        // track point
        let trackPoiIndex = gp0[4];
        let tp0 = trackData[trackPoiIndex],
            tp1 = trackData[trackPoiIndex + 1];
        d = (pointerDistance - tp0[0]) / (tp1[0] - tp0[0]);
        let trackElv = tp0[1] + d * (tp1[1] - tp0[1]);

        let trackY = (this.model.maxY - trackElv) * this._pixelsInMeter_y;
        let groundY = (this.model.maxY - groundElv) * this._pixelsInMeter_y;

        this.events.dispatch(
            this.events.pointer,
            pointerDistance,
            tp0,
            tp1,
            gp0,
            gp1,
            trackPoiIndex,
            groundPoiIndex - 1,
            trackElv - groundElv
        );

        let ctx = this._pointerCtx;

        // Vertical grey line
        ctx.lineWidth = 3;
        ctx.strokeStyle = "rgba(64,64,64,0.6)";
        ctx.beginPath();
        ctx.moveTo(x * this._canvasScale, 0);
        ctx.lineTo(x * this._canvasScale, this.clientHeight * this._canvasScale);
        ctx.stroke();

        // Ground point
        ctx.beginPath();
        ctx.arc(x * this._canvasScale, groundY, 4, 0, 2 * Math.PI, false);
        ctx.fillStyle = "#FFB277";
        ctx.fill();
        ctx.lineWidth = 4;
        ctx.strokeStyle = "#FFB277";
        ctx.stroke();

        // Track point
        ctx.beginPath();
        ctx.arc(x * this._canvasScale, trackY, 4, 0, 2 * Math.PI, false);
        ctx.fillStyle = "#FFB277";
        ctx.fill();
        ctx.lineWidth = 4;
        ctx.strokeStyle = "#FFB277";
        ctx.stroke();

        // Vertical white line
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#FFB277";
        ctx.beginPath();
        ctx.moveTo(x * this._canvasScale, groundY);
        ctx.lineTo(x * this._canvasScale, trackY);
        ctx.stroke();

        // Altitude label
        ctx.fillStyle = "white";
        ctx.font = `${28 / devicePixelRatio}px Arial`;
        ctx.textBaseline = "middle";
        ctx.textAlign = "left";
        ctx.fillText(
            `${Math.round(trackElv - groundElv).toString()} m`,
            (x + 5) * this._canvasScale,
            groundY + (trackY - groundY) * 0.5
        );

        // distance from the begining label
        ctx.fillStyle = "white";
        ctx.font = `${28 / devicePixelRatio}px Arial`;
        ctx.textAlign = "right";
        let distStr = distanceFormatExt(pointerDistance);
        ctx.fillText(
            `${distStr[0]} ${distStr[1]}`,
            (x - 5) * this._canvasScale,
            (this.clientHeight - 7) * this._canvasScale
        );
    }

    protected _onMouseMove = (e: MouseEvent) => {
        if (this._isDragging && this.model.pointsReady) {
            let offset = this._clickPosX - e.clientX;
            let distanceOffset = (offset * this._canvasScale) / this._pixelsInMeter_x;
            this.setFrame(this._clickLeftDistance + distanceOffset, this._clickRightDistance + distanceOffset);
        }
    };

    protected _onMouseWheelFF = (e: MouseEventExt) => {
        this._onMouseWheel(e);
    };
    protected _onMouseWheel = (e: MouseEventExt) => {
        if (this._isMouseOver && this.model.pointsReady) {
            if (!this._customFrame) {
                this._leftDistance = this.model.minX;
                this._rightDistance = this.model.maxX;
            }

            this._customFrame = true;

            let padDist = (Math.sign(e.wheelDelta!) * (this._rightDistance - this._leftDistance)) / 20;

            let rect = this.$canvas.getBoundingClientRect();
            let pointerPosX = e.clientX - rect.left,
                pointerCenterOffsetX = pointerPosX - this.$canvas.clientWidth * 0.5;
            let distanceCenterOffsetX = (pointerCenterOffsetX * this._canvasScale) / this._pixelsInMeter_x;

            // Move distance under pointer to a screen center
            let leftDistance = distanceCenterOffsetX + this._leftDistance + padDist;
            let rightDistance = distanceCenterOffsetX + this._rightDistance - padDist;

            // move center back to the mouse pointer
            distanceCenterOffsetX = (-pointerCenterOffsetX * (rightDistance - leftDistance)) / this.clientWidth;

            this.setFrame(leftDistance + distanceCenterOffsetX, rightDistance + distanceCenterOffsetX);

            this.redrawPointerCanvas(pointerPosX);
        }
    };

    public get clientWidth(): number {
        return this.$canvas ? this.$canvas.width / this._canvasScale : 0;
    }

    public get clientHeight(): number {
        return this.$canvas ? this.$canvas.height / this._canvasScale : 0;
    }

    protected _resize() {
        if (this.el) {
            this.$canvas.width = this.el.clientWidth * this._canvasScale;
            this.$canvas.height = this.el.clientHeight * this._canvasScale;
            this.$canvas.style.width = `${this.el.clientWidth}px`;
            this.$canvas.style.height = `${this.el.clientHeight}px`;

            this.$pointerCanvas.width = this.el.clientWidth * this._canvasScale;
            this.$pointerCanvas.height = this.el.clientHeight * this._canvasScale;
            this.$pointerCanvas.style.width = `${this.el.clientWidth}px`;
            this.$pointerCanvas.style.height = `${this.el.clientHeight}px`;

            if (this._customFrame) {
                this._pixelsInMeter_x =
                    (this._canvasScale * this.clientWidth) / (this._rightDistance - this._leftDistance);
            }
        }
    }

    public clearPointerCanvas() {
        this._pointerCtx.fillStyle = "rgba(0,0,0,0)";
        this._pointerCtx.clearRect(0, 0, this.clientWidth * this._canvasScale, this.clientHeight * this._canvasScale);
    }

    public clearCanvas() {
        const grd = this._ctx.createLinearGradient(0, 0, 0, this.clientHeight * this._canvasScale);
        grd.addColorStop(0, "#191a1c");
        grd.addColorStop(1, this.fillStyle);

        this._ctx.fillStyle = grd;
        this._ctx.fillRect(0, 0, this.clientWidth * this._canvasScale, this.clientHeight * this._canvasScale);
    }

    public setFrame(leftDistance: number, rightDistance: number) {
        this._leftDistance = leftDistance;
        this._rightDistance = rightDistance;
        this._customFrame = true;
        this._pixelsInMeter_x = (this._canvasScale * this.clientWidth) / (this._rightDistance - this._leftDistance);
        this.model.setRange(leftDistance, rightDistance);
        this.draw();
    }

    public _updateUnits() {
        if (!this._customFrame) {
            this._pixelsInMeter_x = (this._canvasScale * this.clientWidth) / (this.model.maxX - this.model.minX);
        }
        this._pixelsInMeter_y = (this._canvasScale * this.clientHeight) / (this.model.maxY - this.model.minY);
    }

    public clear() {
        this.model.clear();
        this.clearCanvas();
    }

    public draw() {
        let trackCoords = this.model.drawData[0];
        if (trackCoords.length > 1) {
            this._updateUnits();

            this.clearCanvas();

            let groundCoords = this.model.drawData[1];

            this._drawTrack(trackCoords, groundCoords);
            this._drawTerrain(groundCoords);
            this._drawWarningAndCollision(groundCoords);
            this._drawLabels(trackCoords, groundCoords);
        } else {
            this.clearCanvas();
        }
    }

    protected _drawLabels(coords: TrackItem[], groundCoords: GroundItem[]) {
        let ctx = this._ctx;
        if (ctx) {
            let p0 = coords[0];
            const maxY = this.model.maxY;
            let x = (-this._leftDistance + p0[0]) * this._pixelsInMeter_x,
                yt = (maxY - p0[1]) * this._pixelsInMeter_y,
                yg = (maxY - groundCoords[p0[2]][1]) * this._pixelsInMeter_y;

            // Track points
            ctx.beginPath();
            ctx.fillStyle = "#F7F718";
            ctx.fillRect(x - 4, yt - 4, 8, 8);
            ctx.stroke();

            // Alt label
            ctx.fillStyle = "white";
            ctx.font = `${26 / devicePixelRatio}px Arial`;
            ctx.textBaseline = "bottom";
            ctx.textAlign = "left";
            ctx.fillText(`${Math.round(p0[1] - groundCoords[p0[2]][1]).toString()} m`, x + 1, yt - 10);
            ctx.stroke();

            for (let i = 1, len = coords.length; i < len; i++) {
                let pi = coords[i];
                x = (-this._leftDistance + pi[0]) * this._pixelsInMeter_x;
                yt = (maxY - pi[1]) * this._pixelsInMeter_y;
                yg = (maxY - groundCoords[pi[2]][1]) * this._pixelsInMeter_y;

                // Track points
                ctx.beginPath();
                ctx.fillStyle = "#F7F718";
                ctx.fillRect(x - 4, yt - 4, 8, 8);
                ctx.stroke();

                // Alt label
                ctx.fillStyle = "white";
                ctx.fillText(`${Math.round(pi[1] - groundCoords[pi[2]][1]).toString()} m`, x + 1, yt - 10);
                ctx.stroke();
            }
        }
    }

    protected _drawTrack(coords: TrackItem[], groundCoords: GroundItem[]) {
        let p0 = coords[0];
        let ctx = this._ctx;
        if (ctx) {
            const maxY = this.model.maxY;
            ctx.lineWidth = LINE_WIDTH;
            ctx.strokeStyle = TRACK_COLOR;
            ctx.beginPath();
            ctx.moveTo((-this._leftDistance + p0[0]) * this._pixelsInMeter_x, (maxY - p0[1]) * this._pixelsInMeter_y);
            let trackLength = 0;
            for (let i = 1, len = coords.length; i < len; i++) {
                let pi = coords[i];
                ctx.lineTo(
                    (-this._leftDistance + pi[0]) * this._pixelsInMeter_x,
                    (maxY - pi[1]) * this._pixelsInMeter_y
                );
                let prevP = coords[i - 1];
                let a = pi[0] - prevP[0],
                    b = pi[1] - prevP[1],
                    aa = a * a,
                    bb = b * b;
                trackLength += Math.sqrt(aa + bb);
            }
            ctx.stroke();

            // Track points lines
            ctx.lineWidth = 2;
            ctx.strokeStyle = "rgba(255,255,255,0.7)";
            ctx.beginPath();
            let x = (-this._leftDistance + p0[0]) * this._pixelsInMeter_x,
                yt = (maxY - p0[1]) * this._pixelsInMeter_y,
                yg = (maxY - groundCoords[p0[2]][1]) * this._pixelsInMeter_y;
            ctx.moveTo(x, yt);
            ctx.lineTo(x, yg);

            for (let i = 1, len = coords.length; i < len; i++) {
                let pi = coords[i];
                x = (-this._leftDistance + pi[0]) * this._pixelsInMeter_x;
                yt = (maxY - pi[1]) * this._pixelsInMeter_y;
                yg = (maxY - groundCoords[pi[2]][1]) * this._pixelsInMeter_y;
                ctx.strokeStyle = "rgba(255,255,255,0.7)";
                ctx.moveTo(x, yt);
                ctx.lineTo(x, yg);
            }

            ctx.stroke();

            this.events.dispatch(this.events.tracklength, trackLength);
        }
    }

    protected _drawTerrain(coords: number[][]) {
        let p0 = coords[0];
        let ctx = this._ctx;
        if (ctx) {
            const maxY = this.model.maxY;
            ctx.lineWidth = LINE_WIDTH;
            ctx.strokeStyle = TERRAIN_COLOR;
            ctx.beginPath();
            ctx.moveTo((-this._leftDistance + p0[0]) * this._pixelsInMeter_x, this.$canvas.height);
            ctx.lineTo((-this._leftDistance + p0[0]) * this._pixelsInMeter_x, (maxY - p0[1]) * this._pixelsInMeter_y);
            let groundLength = 0;
            for (let i = 1, len = coords.length; i < len; i++) {
                let pi = coords[i];
                ctx.lineTo(
                    (-this._leftDistance + pi[0]) * this._pixelsInMeter_x,
                    (maxY - pi[1]) * this._pixelsInMeter_y
                );
                let prevP = coords[i - 1];
                let a = pi[0] - prevP[0],
                    b = pi[1] - prevP[1],
                    aa = a * a,
                    bb = b * b;
                groundLength += Math.sqrt(aa + bb);
            }

            ctx.lineTo(
                (-this._leftDistance + coords[coords.length - 1][0]) * this._pixelsInMeter_x,
                this.$canvas.height
            );
            ctx.closePath();
            ctx.stroke();
            ctx.save();
            ctx.fillStyle = TERRAIN_FILL_COLOR;
            ctx.globalAlpha = TERRAIN_ALPHA;
            ctx.fill();
            ctx.restore();
            ctx.globalAlpha = 1;

            this.events.dispatch(this.events.groundlength, groundLength);
        }
    }

    protected _drawWarningAndCollision(coords: number[][]) {
        let ctx = this._ctx;
        if (ctx && coords.length > 1) {
            let maxY = this.model.maxY;
            ctx.lineWidth = LINE_WIDTH;
            ctx.beginPath();

            let warningLength = 0,
                collisionLength = 0;

            for (let i = 0, len = coords.length - 1; i < len; i++) {
                let pi0 = coords[i],
                    pi1 = coords[i + 1];

                if (pi0[2] !== SAFE && pi1[2] !== SAFE) {
                    let a = pi1[0] - pi0[0],
                        b = pi1[1] - pi0[1],
                        aa = a * a,
                        bb = b * b;

                    if (pi0[2] === COLLISION) {
                        ctx.stroke();
                        ctx.beginPath();
                        ctx.strokeStyle = LINE_COLORS[COLLISION];
                        collisionLength += Math.sqrt(aa + bb);
                    } else if (pi0[2] === WARNING) {
                        ctx.stroke();
                        ctx.beginPath();
                        ctx.strokeStyle = LINE_COLORS[WARNING];
                        warningLength += Math.sqrt(aa + bb);
                    }

                    ctx.moveTo(
                        (-this._leftDistance + pi0[0]) * this._pixelsInMeter_x,
                        (maxY - pi0[1]) * this._pixelsInMeter_y
                    );
                    ctx.lineTo(
                        (-this._leftDistance + pi1[0]) * this._pixelsInMeter_x,
                        (maxY - pi1[1]) * this._pixelsInMeter_y
                    );
                }
            }
            ctx.stroke();

            this.events.dispatch(this.events.warninglength, warningLength);

            this.events.dispatch(this.events.collisionlength, collisionLength);
        }
    }
}

export { ElevationProfileView };