entity/geometry/GeometryHandler.tstypescript
import * as mercator from "../../mercator";
import { RADIANS } from "../../math";
import { isCubeProj } from "../../proj/cube";
import { Extent } from "../../Extent";
import { Handler } from "../../webgl/Handler";
import type { WebGLBufferExt } from "../../webgl/Handler";
import { doubleToTwoFloatsV2 } from "../../math/doubleToTwoFloats";
import { Vector } from "../../layer/Vector";
import { Vec2 } from "../../math/Vec2";
import type { NumberArray2 } from "../../math/Vec2";
import { Node } from "../../quadTree/Node";
import { Vec3 } from "../../math/Vec3";
import { Vec4 } from "../../math/Vec4";
import type {
    CoordinatesType,
    Geometry,
    ILineStringCoordinates,
    IMultiLineStringCoordinates,
    IMultiPolygonCoordinates,
    IPolygonCoordinates
} from "./Geometry";

import { GeometryTypeEnum } from "./Geometry";

import earcut, { flatten } from "earcut";
import { concatArrays } from "../../utils/shared";
import { triangulateOnGrid } from "./triangulateOnGrid";

type GeometryOffsetKey =
    | "_polyVerticesHandlerIndex"
    | "_polyIndexesHandlerIndex"
    | "_lineVerticesHandlerIndex"
    | "_lineOrdersHandlerIndex"
    | "_lineColorsHandlerIndex"
    | "_lineThicknessHandlerIndex"
    | "_lineIndexesHandlerIndex";

type GeometryLengthKey =
    | "_polyVerticesLength"
    | "_polyIndexesLength"
    | "_lineVerticesLength"
    | "_lineOrdersLength"
    | "_lineColorsLength"
    | "_lineThicknessLength"
    | "_lineIndexesLength";

const POLYVERTICES_BUFFER = 0;
const POLYINDEXES_BUFFER = 1;
const POLYCOLORS_BUFFER = 2;
const LINEVERTICES_BUFFER = 3;
const LINEINDEXES_BUFFER = 4;
const LINEORDERS_BUFFER = 5;
const LINECOLORS_BUFFER = 6;
const LINETHICKNESS_BUFFER = 7;
const LINESTROKES_BUFFER = 8;
const LINESTROKECOLORS_BUFFER = 9;
const POLYPICKINGCOLORS_BUFFER = 10;
const LINEPICKINGCOLORS_BUFFER = 11;

function doubleToTwoFloats(v: NumberArray2, high: Vec2, low: Vec2) {
    let x = v[0],
        y = v[1];

    if (x >= 0.0) {
        let doubleHigh = Math.floor(x / 65536.0) * 65536.0;
        high.x = Math.fround(doubleHigh);
        low.x = Math.fround(x - doubleHigh);
    } else {
        let doubleHigh = Math.floor(-x / 65536.0) * 65536.0;
        high.x = Math.fround(-doubleHigh);
        low.x = Math.fround(x + doubleHigh);
    }

    if (y >= 0.0) {
        let doubleHigh = Math.floor(y / 65536.0) * 65536.0;
        high.y = Math.fround(doubleHigh);
        low.y = Math.fround(y - doubleHigh);
    } else {
        let doubleHigh = Math.floor(-y / 65536.0) * 65536.0;
        high.y = Math.fround(-doubleHigh);
        low.y = Math.fround(y + doubleHigh);
    }
}

let tempHigh = new Vec2(),
    tempLow = new Vec2(),
    tempHighLow = new Vec2();

// A direction is a unit vector, its high floats are the values and the low ones are what is left of them
function pushDirection(x: number, y: number, z: number, copies: number, outHigh: number[], outLow: number[]) {
    const hx = Math.fround(x),
        hy = Math.fround(y),
        hz = Math.fround(z),
        lx = Math.fround(x - hx),
        ly = Math.fround(y - hy),
        lz = Math.fround(z - hz);

    for (let i = 0; i < copies; i++) {
        outHigh.push(hx, hy, hz);
        outLow.push(lx, ly, lz);
    }
}

// The surface normal of the ellipsoid at the geodetic point
function toDirection(p: CoordinatesType, out: number[]) {
    const lon = p[0] * RADIANS,
        lat = p[1] * RADIANS,
        c = Math.cos(lat);
    out[0] = c * Math.cos(lon);
    out[1] = c * Math.sin(lon);
    out[2] = Math.sin(lat);
    return out;
}

const _d0 = [0, 0, 0],
    _d1 = [0, 0, 0];

/**
 * Longest segment of a line or a polygon edge on the cube, in degrees.
 */
export const DENSIFY_STEP = 1.0;

/**
 * Returns the path with points between the ones further apart than the step in longitude or latitude, the closed path
 * gets them on the edge from its last point to the first one too.
 * @param {Array.<CoordinatesType>} path - Geodetic points.
 * @param {boolean} isClosed - The last point goes back to the first one.
 * @param {number} [step] - Longest segment in degrees.
 * @returns {Array.<CoordinatesType>} -
 */
export function densifyPath(
    path: CoordinatesType[],
    isClosed: boolean,
    step: number = DENSIFY_STEP
): CoordinatesType[] {
    const out: CoordinatesType[] = [];
    const n = path.length;

    for (let i = 0; i < n; i++) {
        const a = path[i];
        out.push(a);

        const b = i < n - 1 ? path[i + 1] : isClosed && n > 2 ? path[0] : null;
        if (!b) continue;

        const k = Math.ceil(Math.max(Math.abs(b[0] - a[0]), Math.abs(b[1] - a[1])) / step);

        for (let j = 1; j < k; j++) {
            const t = j / k,
                lon = a[0] + (b[0] - a[0]) * t,
                lat = a[1] + (b[1] - a[1]) * t;

            out.push(a.length > 2 && b.length > 2 ? [lon, lat, a[2]! + (b[2]! - a[2]!) * t] : [lon, lat]);
        }
    }

    return out;
}

/**
 * Returns the path without the points equal to the point before them, a closed path also without its last points
 * equal to the first one, as a GeoJSON ring has it. A segment of no length has no direction, so a line can't join
 * the segments around it.
 * @param {Array.<CoordinatesType>} path - Geodetic points.
 * @param {boolean} isClosed - The last point goes back to the first one.
 * @returns {Array.<CoordinatesType>} -
 */
export function removeRepeatedPoints(path: CoordinatesType[], isClosed: boolean): CoordinatesType[] {
    const out: CoordinatesType[] = [];

    for (const p of path) {
        const q = out[out.length - 1];
        if (!q || p[0] !== q[0] || p[1] !== q[1]) {
            out.push(p);
        }
    }

    if (isClosed) {
        while (out.length > 1 && out[out.length - 1][0] === out[0][0] && out[out.length - 1][1] === out[0][1]) {
            out.pop();
        }
    }

    return out;
}

// Appends the Web Mercator coordinates of the points from the index
function pushMercator(points: CoordinatesType[], from: number, out: number[]) {
    for (let i = from; i < points.length; i++) {
        out.push(mercator.forward_lon(points[i][0]), mercator.forward_lat(points[i][1]));
    }
}

class GeometryHandler {
    static __counter__: number = 0;

    protected __id: number;

    protected _layer: Vector;

    protected _handler: Handler | null;

    protected _geometries: Geometry[];

    protected _updatedGeometryArr: Geometry[];
    protected _updatedGeometry: Record<number, boolean>;

    protected _removeGeometryExtentArr: Extent[];
    protected _removeGeometryExtents: Record<number, boolean>;

    // Polygon arrays
    protected _polyVerticesHighMerc: number[];
    protected _polyVerticesLowMerc: number[];
    protected _polyDirectionsHigh: number[];
    protected _polyDirectionsLow: number[];
    protected _polyColors: number[];
    protected _polyPickingColors: number[];
    protected _polyIndexes: number[];

    // Line arrays
    protected _lineVerticesHighMerc: number[];
    protected _lineVerticesLowMerc: number[];
    protected _lineDirectionsHigh: number[];
    protected _lineDirectionsLow: number[];
    protected _lineOrders: number[];
    protected _lineIndexes: number[];
    protected _lineColors: number[];
    protected _linePickingColors: number[];
    protected _lineThickness: number[];
    protected _lineStrokes: number[];
    protected _lineStrokeColors: number[];

    // Buffers
    public _polyVerticesHighBufferMerc: WebGLBufferExt | null;
    public _polyVerticesLowBufferMerc: WebGLBufferExt | null;
    public _polyDirectionsHighBuffer: WebGLBufferExt | null;
    public _polyDirectionsLowBuffer: WebGLBufferExt | null;
    public _polyColorsBuffer: WebGLBufferExt | null;
    public _polyPickingColorsBuffer: WebGLBufferExt | null;
    public _polyIndexesBuffer: WebGLBufferExt | null;

    public _lineVerticesHighBufferMerc: WebGLBufferExt | null;
    public _lineVerticesLowBufferMerc: WebGLBufferExt | null;
    public _lineDirectionsHighBuffer: WebGLBufferExt | null;
    public _lineDirectionsLowBuffer: WebGLBufferExt | null;
    public _lineColorsBuffer: WebGLBufferExt | null;
    public _linePickingColorsBuffer: WebGLBufferExt | null;
    public _lineThicknessBuffer: WebGLBufferExt | null;
    public _lineStrokesBuffer: WebGLBufferExt | null;
    public _lineStrokeColorsBuffer: WebGLBufferExt | null;
    public _lineOrdersBuffer: WebGLBufferExt | null;
    public _lineIndexesBuffer: WebGLBufferExt | null;

    protected _buffersUpdateCallbacks: Function[];

    protected _changedBuffers: boolean[];

    /**
     * The planet is a cube
     */
    public _isCube: boolean;

    constructor(layer: Vector) {
        this.__id = GeometryHandler.__counter__++;

        this._layer = layer;

        this._handler = null;

        this._geometries = [];

        this._updatedGeometryArr = [];
        this._updatedGeometry = {};

        this._removeGeometryExtentArr = [];
        this._removeGeometryExtents = {};

        // Polygon arrays
        this._polyVerticesHighMerc = [];
        this._polyVerticesLowMerc = [];
        this._polyDirectionsHigh = [];
        this._polyDirectionsLow = [];
        this._polyColors = [];
        this._polyPickingColors = [];
        this._polyIndexes = [];

        // Line arrays
        this._lineVerticesHighMerc = [];
        this._lineVerticesLowMerc = [];
        this._lineDirectionsHigh = [];
        this._lineDirectionsLow = [];
        this._lineOrders = [];
        this._lineIndexes = [];
        this._lineColors = [];
        this._linePickingColors = [];
        this._lineThickness = [];
        this._lineStrokes = [];
        this._lineStrokeColors = [];

        // Buffers
        this._polyVerticesHighBufferMerc = null;
        this._polyVerticesLowBufferMerc = null;
        this._polyDirectionsHighBuffer = null;
        this._polyDirectionsLowBuffer = null;
        this._polyColorsBuffer = null;
        this._polyPickingColorsBuffer = null;
        this._polyIndexesBuffer = null;

        this._lineVerticesHighBufferMerc = null;
        this._lineVerticesLowBufferMerc = null;
        this._lineDirectionsHighBuffer = null;
        this._lineDirectionsLowBuffer = null;
        this._lineColorsBuffer = null;
        this._linePickingColorsBuffer = null;
        this._lineThicknessBuffer = null;
        this._lineStrokesBuffer = null;
        this._lineStrokeColorsBuffer = null;
        this._lineOrdersBuffer = null;
        this._lineIndexesBuffer = null;

        this._buffersUpdateCallbacks = [];
        this._buffersUpdateCallbacks[POLYVERTICES_BUFFER] = this.createPolyVerticesBuffer;
        this._buffersUpdateCallbacks[POLYINDEXES_BUFFER] = this.createPolyIndexesBuffer;
        this._buffersUpdateCallbacks[POLYCOLORS_BUFFER] = this.createPolyColorsBuffer;
        this._buffersUpdateCallbacks[LINEVERTICES_BUFFER] = this.createLineVerticesBuffer;
        this._buffersUpdateCallbacks[LINEINDEXES_BUFFER] = this.createLineIndexesBuffer;
        this._buffersUpdateCallbacks[LINEORDERS_BUFFER] = this.createLineOrdersBuffer;
        this._buffersUpdateCallbacks[LINECOLORS_BUFFER] = this.createLineColorsBuffer;
        this._buffersUpdateCallbacks[LINETHICKNESS_BUFFER] = this.createLineThicknessBuffer;
        this._buffersUpdateCallbacks[LINESTROKES_BUFFER] = this.createLineStrokesBuffer;
        this._buffersUpdateCallbacks[LINESTROKECOLORS_BUFFER] = this.createLineStrokeColorsBuffer;
        this._buffersUpdateCallbacks[POLYPICKINGCOLORS_BUFFER] = this.createPolyPickingColorsBuffer;
        this._buffersUpdateCallbacks[LINEPICKINGCOLORS_BUFFER] = this.createLinePickingColorsBuffer;

        this._changedBuffers = new Array(this._buffersUpdateCallbacks.length);

        this._isCube = false;
    }

    /**
     * Appends the directions of the line vertices in the order of appendLineData: each point of a path four times,
     * after the point before the first one and before the point after the last one.
     */
    static appendLineDirections(
        pathArr: CoordinatesType[][],
        isClosed: boolean,
        outHigh: number[],
        outLow: number[],
        outHigh2: number[],
        outLow2: number[]
    ) {
        const push = (x: number, y: number, z: number) => {
            pushDirection(x, y, z, 4, outHigh, outLow);
            pushDirection(x, y, z, 4, outHigh2, outLow2);
        };

        for (let j = 0; j < pathArr.length; j++) {
            const path = pathArr[j];

            if (path.length === 0) {
                continue;
            }

            const n = path.length;

            if (isClosed) {
                toDirection(path[n - 1], _d0);
                push(_d0[0], _d0[1], _d0[2]);
            } else {
                toDirection(path[0], _d0);
                toDirection(path[1] || path[0], _d1);
                push(2 * _d0[0] - _d1[0], 2 * _d0[1] - _d1[1], 2 * _d0[2] - _d1[2]);
            }

            for (let i = 0; i < n; i++) {
                toDirection(path[i], _d0);
                push(_d0[0], _d0[1], _d0[2]);
            }

            if (isClosed) {
                toDirection(path[0], _d0);
                push(_d0[0], _d0[1], _d0[2]);
            } else {
                toDirection(path[n - 1], _d0);
                toDirection(path[n - 2] || path[n - 1], _d1);
                push(2 * _d0[0] - _d1[0], 2 * _d0[1] - _d1[1], 2 * _d0[2] - _d1[2]);
            }
        }
    }

    // The directions of the polygon vertices, the rings one after another as flatten() puts them
    static appendPolygonDirections(rings: CoordinatesType[][], outHigh: number[], outLow: number[]) {
        for (let j = 0; j < rings.length; j++) {
            for (let k = 0; k < rings[j].length; k++) {
                toDirection(rings[j][k], _d0);
                pushDirection(_d0[0], _d0[1], _d0[2], 1, outHigh, outLow);
            }
        }
    }

    static appendLineData(
        pathArr: CoordinatesType[][],
        isClosed: boolean,
        color: Vec4,
        pickingColor: Vec3,
        thickness: number,
        strokeColor: Vec4,
        strokeSize: number,
        outVerticesHigh: number[],
        outVerticesLow: number[],
        outOrders: number[],
        outIndexes: number[],
        outColors: number[],
        outPickingColors: number[],
        outThickness: number[],
        outStrokeColors: number[],
        outStrokes: number[],
        outVerticesHigh2: number[],
        outVerticesLow2: number[]
    ) {
        // Index of the first vertex appended below. After bringToFront() the last
        // index entries belong to another geometry, so they can't be used for this.
        var index = outVerticesHigh.length * 0.5;
        outIndexes.push(index, index);

        var t = thickness,
            c = [color.x, color.y, color.z, color.w],
            s = strokeSize,
            sc = [strokeColor.x, strokeColor.y, strokeColor.z, strokeColor.w],
            p = [pickingColor.x, pickingColor.y, pickingColor.z, 1.0];

        for (let j = 0; j < pathArr.length; j++) {
            var path = pathArr[j];

            if (path.length === 0) {
                continue;
            }

            let startIndex = index;
            let last: CoordinatesType;
            if (isClosed) {
                last = path[path.length - 1];
            } else {
                let p0 = path[0],
                    p1 = path[1];

                if (!p1) {
                    p1 = p0;
                }

                last = [p0[0] + p0[0] - p1[0], p0[1] + p0[1] - p1[1]];
            }

            doubleToTwoFloats(last as NumberArray2, tempHigh, tempLow);

            outVerticesHigh.push(
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y
            );
            outVerticesLow.push(tempLow.x, tempLow.y, tempLow.x, tempLow.y, tempLow.x, tempLow.y, tempLow.x, tempLow.y);

            outVerticesHigh2.push(
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y
            );
            outVerticesLow2.push(
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y
            );

            outOrders.push(1, -1, 2, -2);

            outThickness.push(t, t, t, t);
            outStrokes.push(s, s, s, s);
            outColors.push(
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3]
            );
            outStrokeColors.push(
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3]
            );
            outPickingColors.push(
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3]
            );

            for (let i = 0; i < path.length; i++) {
                let cur = path[i];

                doubleToTwoFloats(cur as NumberArray2, tempHigh, tempLow);

                outVerticesHigh.push(
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y
                );
                outVerticesLow.push(
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y
                );

                outVerticesHigh2.push(
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y,
                    tempHigh.x,
                    tempHigh.y
                );
                outVerticesLow2.push(
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y,
                    tempLow.x,
                    tempLow.y
                );

                outOrders.push(1, -1, 2, -2);
                outThickness.push(t, t, t, t);
                outStrokes.push(s, s, s, s);
                outColors.push(
                    c[0],
                    c[1],
                    c[2],
                    c[3],
                    c[0],
                    c[1],
                    c[2],
                    c[3],
                    c[0],
                    c[1],
                    c[2],
                    c[3],
                    c[0],
                    c[1],
                    c[2],
                    c[3]
                );
                outStrokeColors.push(
                    sc[0],
                    sc[1],
                    sc[2],
                    sc[3],
                    sc[0],
                    sc[1],
                    sc[2],
                    sc[3],
                    sc[0],
                    sc[1],
                    sc[2],
                    sc[3],
                    sc[0],
                    sc[1],
                    sc[2],
                    sc[3]
                );
                outPickingColors.push(
                    p[0],
                    p[1],
                    p[2],
                    p[3],
                    p[0],
                    p[1],
                    p[2],
                    p[3],
                    p[0],
                    p[1],
                    p[2],
                    p[3],
                    p[0],
                    p[1],
                    p[2],
                    p[3]
                );
                outIndexes.push(index++, index++, index++, index++);
            }

            let first: CoordinatesType;
            if (isClosed) {
                first = path[0];
                outIndexes.push(startIndex, startIndex + 1, startIndex + 1, startIndex + 1);
            } else {
                let p0 = path[path.length - 1],
                    p1 = path[path.length - 2];

                if (!p1) {
                    p1 = p0;
                }

                first = [p0[0] + p0[0] - p1[0], p0[1] + p0[1] - p1[1]];
                outIndexes.push(index - 1, index - 1, index - 1, index - 1);
            }

            doubleToTwoFloats(first as NumberArray2, tempHigh, tempLow);

            outVerticesHigh.push(
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y
            );
            outVerticesLow.push(tempLow.x, tempLow.y, tempLow.x, tempLow.y, tempLow.x, tempLow.y, tempLow.x, tempLow.y);

            outVerticesHigh2.push(
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y,
                tempHigh.x,
                tempHigh.y
            );
            outVerticesLow2.push(
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y,
                tempLow.x,
                tempLow.y
            );

            outOrders.push(1, -1, 2, -2);
            outThickness.push(t, t, t, t);
            outStrokes.push(s, s, s, s);
            outColors.push(
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3],
                c[0],
                c[1],
                c[2],
                c[3]
            );
            outStrokeColors.push(
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3],
                sc[0],
                sc[1],
                sc[2],
                sc[3]
            );
            outPickingColors.push(
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3],
                p[0],
                p[1],
                p[2],
                p[3]
            );

            if (j < pathArr.length - 1) {
                index += 8;
                outIndexes.push(index, index);
            }
        }
    }

    public assignHandler(handler: Handler) {
        this._handler = handler;
        this._isCube = isCubeProj(this._layer._planet!.quadTreeStrategy.projection);
        this.refresh();
        if (handler.isInitialized()) {
            this.update();
        }
    }

    /**
     * @public
     * @param {Geometry} geometry - Geometry object.
     */
    public add(geometry: Geometry) {
        //
        // Triangulates polygon and sets geometry data.
        if (geometry._handlerIndex === -1) {
            geometry._handler = this;
            geometry._handlerIndex = this._geometries.length;

            this._geometries.push(geometry);

            let pickingColor = geometry._entity!._pickingColor.scaleTo(1 / 255);

            geometry._polyVerticesHighMerc = [];
            geometry._polyVerticesLowMerc = [];
            geometry._lineVerticesHighMerc = [];
            geometry._lineVerticesLowMerc = [];
            geometry._polyDirectionsHigh = [];
            geometry._polyDirectionsLow = [];
            geometry._lineDirectionsHigh = [];
            geometry._lineDirectionsLow = [];

            const isCube = this._isCube;

            if ((geometry._coordinates as IPolygonCoordinates)[0].length) {
                if (geometry.type === GeometryTypeEnum.POLYGON) {
                    let coordinates = (geometry._coordinates as IPolygonCoordinates).map((ring) =>
                        removeRepeatedPoints(ring, true)
                    );
                    if (isCube) {
                        coordinates = coordinates.map((ring) => densifyPath(ring, true));
                    }
                    let ci: IPolygonCoordinates = [];
                    for (let j = 0; j < coordinates.length; j++) {
                        ci[j] = [];
                        for (let k = 0; k < coordinates[j].length; k++) {
                            ci[j][k] = [
                                mercator.forward_lon(coordinates[j][k][0]),
                                mercator.forward_lat(coordinates[j][k][1])
                            ];
                        }
                    }

                    let data = flatten(ci);
                    let indexes: number[];
                    let points: CoordinatesType[] = [];

                    if (isCube) {
                        ({ points, indexes } = triangulateOnGrid(coordinates, DENSIFY_STEP));
                        pushMercator(points, data.vertices.length * 0.5, data.vertices);
                    } else {
                        indexes = earcut(data.vertices, data.holes, 2);
                    }

                    geometry._polyVerticesHandlerIndex = this._polyVerticesHighMerc.length;
                    geometry._polyIndexesHandlerIndex = this._polyIndexes.length;

                    for (let i = 0; i < indexes.length; i++) {
                        this._polyIndexes.push(indexes[i] + geometry._polyVerticesHandlerIndex * 0.5);
                    }

                    let color = geometry._style.fillColor;

                    let verticesHigh = [],
                        verticesLow = [];

                    for (let i = 0; i < data.vertices.length * 0.5; i++) {
                        this._polyColors.push(color.x, color.y, color.z, color.w);
                        this._polyPickingColors.push(pickingColor.x, pickingColor.y, pickingColor.z, 1.0);
                    }

                    for (let i = 0; i < data.vertices.length; i++) {
                        doubleToTwoFloatsV2(data.vertices[i], tempHighLow);
                        verticesHigh[i] = tempHighLow.x;
                        verticesLow[i] = tempHighLow.y;
                    }

                    geometry._polyVerticesHighMerc = verticesHigh;
                    geometry._polyVerticesLowMerc = verticesLow;

                    concatArrays(this._polyVerticesHighMerc, verticesHigh);
                    concatArrays(this._polyVerticesLowMerc, verticesLow);

                    if (isCube) {
                        GeometryHandler.appendPolygonDirections(
                            [points],
                            geometry._polyDirectionsHigh,
                            geometry._polyDirectionsLow
                        );
                        concatArrays(this._polyDirectionsHigh, geometry._polyDirectionsHigh);
                        concatArrays(this._polyDirectionsLow, geometry._polyDirectionsLow);
                    }

                    geometry._polyVerticesLength = data.vertices.length;
                    geometry._polyIndexesLength = indexes.length;

                    // Creates polygon stroke data
                    geometry._lineVerticesHandlerIndex = this._lineVerticesHighMerc.length;
                    geometry._lineOrdersHandlerIndex = this._lineOrders.length;
                    geometry._lineIndexesHandlerIndex = this._lineIndexes.length;
                    geometry._lineColorsHandlerIndex = this._lineColors.length;
                    geometry._lineThicknessHandlerIndex = this._lineThickness.length;

                    if (isCube) {
                        GeometryHandler.appendLineDirections(
                            coordinates,
                            true,
                            this._lineDirectionsHigh,
                            this._lineDirectionsLow,
                            geometry._lineDirectionsHigh,
                            geometry._lineDirectionsLow
                        );
                    }

                    GeometryHandler.appendLineData(
                        ci,
                        true,
                        geometry._style.lineColor,
                        pickingColor,
                        geometry._style.lineWidth,
                        geometry._style.strokeColor,
                        geometry._style.strokeWidth,
                        this._lineVerticesHighMerc,
                        this._lineVerticesLowMerc,
                        this._lineOrders,
                        this._lineIndexes,
                        this._lineColors,
                        this._linePickingColors,
                        this._lineThickness,
                        this._lineStrokeColors,
                        this._lineStrokes,
                        geometry._lineVerticesHighMerc,
                        geometry._lineVerticesLowMerc
                    );

                    geometry._lineVerticesLength =
                        this._lineVerticesHighMerc.length - geometry._lineVerticesHandlerIndex;
                    geometry._lineOrdersLength = this._lineOrders.length - geometry._lineOrdersHandlerIndex;
                    geometry._lineIndexesLength = this._lineIndexes.length - geometry._lineIndexesHandlerIndex;
                    geometry._lineColorsLength = this._lineColors.length - geometry._lineColorsHandlerIndex;
                    geometry._lineThicknessLength = this._lineThickness.length - geometry._lineThicknessHandlerIndex;
                } else if (geometry.type === GeometryTypeEnum.MULTIPOLYGON) {
                    let coordinates = (geometry._coordinates as IMultiPolygonCoordinates).map((polygon) =>
                        polygon.map((ring) => removeRepeatedPoints(ring, true))
                    );
                    if (isCube) {
                        coordinates = coordinates.map((polygon) => polygon.map((ring) => densifyPath(ring, true)));
                    }
                    let vertices: number[] = [],
                        indexes: number[] = [];

                    // Creates polygon stroke data
                    geometry._lineVerticesHandlerIndex = this._lineVerticesHighMerc.length;
                    geometry._lineOrdersHandlerIndex = this._lineOrders.length;
                    geometry._lineIndexesHandlerIndex = this._lineIndexes.length;
                    geometry._lineColorsHandlerIndex = this._lineColors.length;
                    geometry._lineThicknessHandlerIndex = this._lineThickness.length;

                    for (let i = 0; i < coordinates.length; i++) {
                        let cci: CoordinatesType[][] = coordinates[i];
                        let ci: CoordinatesType[][] = [];
                        for (let j = 0; j < cci.length; j++) {
                            ci[j] = [];
                            for (let k = 0; k < coordinates[i][j].length; k++) {
                                ci[j][k] = [mercator.forward_lon(cci[j][k][0]), mercator.forward_lat(cci[j][k][1])];
                            }
                        }
                        let data = flatten(ci);
                        let dataIndexes: number[];
                        let points: CoordinatesType[] = [];

                        if (isCube) {
                            ({ points, indexes: dataIndexes } = triangulateOnGrid(cci, DENSIFY_STEP));
                            pushMercator(points, data.vertices.length * 0.5, data.vertices);
                        } else {
                            dataIndexes = earcut(data.vertices, data.holes, 2);
                        }

                        for (let j = 0; j < dataIndexes.length; j++) {
                            indexes.push(dataIndexes[j] + vertices.length * 0.5);
                        }

                        concatArrays(vertices, data.vertices);

                        if (isCube) {
                            GeometryHandler.appendLineDirections(
                                cci,
                                true,
                                this._lineDirectionsHigh,
                                this._lineDirectionsLow,
                                geometry._lineDirectionsHigh,
                                geometry._lineDirectionsLow
                            );
                        }

                        if (isCube) {
                            GeometryHandler.appendPolygonDirections(
                                [points],
                                geometry._polyDirectionsHigh,
                                geometry._polyDirectionsLow
                            );
                        }

                        GeometryHandler.appendLineData(
                            ci,
                            true,
                            geometry._style.lineColor,
                            pickingColor,
                            geometry._style.lineWidth,
                            geometry._style.strokeColor,
                            geometry._style.strokeWidth,
                            this._lineVerticesHighMerc,
                            this._lineVerticesLowMerc,
                            this._lineOrders,
                            this._lineIndexes,
                            this._lineColors,
                            this._linePickingColors,
                            this._lineThickness,
                            this._lineStrokeColors,
                            this._lineStrokes,
                            geometry._lineVerticesHighMerc,
                            geometry._lineVerticesLowMerc
                        );
                    }

                    geometry._polyVerticesHandlerIndex = this._polyVerticesHighMerc.length;
                    geometry._polyIndexesHandlerIndex = this._polyIndexes.length;

                    for (let i = 0; i < indexes.length; i++) {
                        this._polyIndexes.push(indexes[i] + geometry._polyVerticesHandlerIndex * 0.5);
                    }

                    let color = geometry._style.fillColor;

                    let verticesHigh = [],
                        verticesLow = [];

                    for (let i = 0; i < vertices.length * 0.5; i++) {
                        this._polyColors.push(color.x, color.y, color.z, color.w);
                        this._polyPickingColors.push(pickingColor.x, pickingColor.y, pickingColor.z, 1.0);
                    }

                    for (let i = 0; i < vertices.length; i++) {
                        doubleToTwoFloatsV2(vertices[i], tempHighLow);
                        verticesHigh[i] = tempHighLow.x;
                        verticesLow[i] = tempHighLow.y;
                    }

                    geometry._polyVerticesHighMerc = verticesHigh;
                    geometry._polyVerticesLowMerc = verticesLow;

                    concatArrays(this._polyVerticesHighMerc, verticesHigh);
                    concatArrays(this._polyVerticesLowMerc, verticesLow);

                    if (isCube) {
                        concatArrays(this._polyDirectionsHigh, geometry._polyDirectionsHigh);
                        concatArrays(this._polyDirectionsLow, geometry._polyDirectionsLow);
                    }

                    geometry._polyVerticesLength = vertices.length;
                    geometry._polyIndexesLength = indexes.length;

                    geometry._lineVerticesLength =
                        this._lineVerticesHighMerc.length - geometry._lineVerticesHandlerIndex;
                    geometry._lineOrdersLength = this._lineOrders.length - geometry._lineOrdersHandlerIndex;
                    geometry._lineIndexesLength = this._lineIndexes.length - geometry._lineIndexesHandlerIndex;
                    geometry._lineColorsLength = this._lineColors.length - geometry._lineColorsHandlerIndex;
                    geometry._lineThicknessLength = this._lineThickness.length - geometry._lineThicknessHandlerIndex;
                } else if (geometry.type === GeometryTypeEnum.LINESTRING) {
                    let coordinates = removeRepeatedPoints(geometry._coordinates as ILineStringCoordinates, false);
                    if (isCube) {
                        coordinates = densifyPath(coordinates, false);
                    }
                    let ci = new Array(coordinates.length);
                    for (let j = 0; j < coordinates.length; j++) {
                        ci[j] = [mercator.forward_lon(coordinates[j][0]), mercator.forward_lat(coordinates[j][1])];
                    }

                    // Creates polygon stroke data
                    geometry._lineVerticesHandlerIndex = this._lineVerticesHighMerc.length;
                    geometry._lineOrdersHandlerIndex = this._lineOrders.length;
                    geometry._lineIndexesHandlerIndex = this._lineIndexes.length;
                    geometry._lineColorsHandlerIndex = this._lineColors.length;
                    geometry._lineThicknessHandlerIndex = this._lineThickness.length;

                    if (isCube) {
                        GeometryHandler.appendLineDirections(
                            [coordinates],
                            false,
                            this._lineDirectionsHigh,
                            this._lineDirectionsLow,
                            geometry._lineDirectionsHigh,
                            geometry._lineDirectionsLow
                        );
                    }

                    GeometryHandler.appendLineData(
                        [ci],
                        false,
                        geometry._style.lineColor,
                        pickingColor,
                        geometry._style.lineWidth,
                        geometry._style.strokeColor,
                        geometry._style.strokeWidth,
                        this._lineVerticesHighMerc,
                        this._lineVerticesLowMerc,
                        this._lineOrders,
                        this._lineIndexes,
                        this._lineColors,
                        this._linePickingColors,
                        this._lineThickness,
                        this._lineStrokeColors,
                        this._lineStrokes,
                        geometry._lineVerticesHighMerc,
                        geometry._lineVerticesLowMerc
                    );

                    geometry._lineVerticesLength =
                        this._lineVerticesHighMerc.length - geometry._lineVerticesHandlerIndex;
                    geometry._lineOrdersLength = this._lineOrders.length - geometry._lineOrdersHandlerIndex;
                    geometry._lineIndexesLength = this._lineIndexes.length - geometry._lineIndexesHandlerIndex;
                    geometry._lineColorsLength = this._lineColors.length - geometry._lineColorsHandlerIndex;
                    geometry._lineThicknessLength = this._lineThickness.length - geometry._lineThicknessHandlerIndex;
                } else if (geometry.type === GeometryTypeEnum.MULTILINESTRING) {
                    let coordinates = (geometry._coordinates as IMultiLineStringCoordinates).map((line) =>
                        removeRepeatedPoints(line, false)
                    );
                    if (isCube) {
                        coordinates = coordinates.map((line) => densifyPath(line, false));
                    }
                    let ci: IMultiLineStringCoordinates = [];
                    for (let j = 0; j < coordinates.length; j++) {
                        ci[j] = [];
                        for (let k = 0; k < coordinates[j].length; k++) {
                            ci[j][k] = [
                                mercator.forward_lon(coordinates[j][k][0]),
                                mercator.forward_lat(coordinates[j][k][1])
                            ];
                        }
                    }

                    // Creates polygon stroke data
                    geometry._lineVerticesHandlerIndex = this._lineVerticesHighMerc.length;
                    geometry._lineOrdersHandlerIndex = this._lineOrders.length;
                    geometry._lineIndexesHandlerIndex = this._lineIndexes.length;
                    geometry._lineColorsHandlerIndex = this._lineColors.length;
                    geometry._lineThicknessHandlerIndex = this._lineThickness.length;

                    if (isCube) {
                        GeometryHandler.appendLineDirections(
                            coordinates,
                            false,
                            this._lineDirectionsHigh,
                            this._lineDirectionsLow,
                            geometry._lineDirectionsHigh,
                            geometry._lineDirectionsLow
                        );
                    }

                    GeometryHandler.appendLineData(
                        ci,
                        false,
                        geometry._style.lineColor,
                        pickingColor,
                        geometry._style.lineWidth,
                        geometry._style.strokeColor,
                        geometry._style.strokeWidth,
                        this._lineVerticesHighMerc,
                        this._lineVerticesLowMerc,
                        this._lineOrders,
                        this._lineIndexes,
                        this._lineColors,
                        this._linePickingColors,
                        this._lineThickness,
                        this._lineStrokeColors,
                        this._lineStrokes,
                        geometry._lineVerticesHighMerc,
                        geometry._lineVerticesLowMerc
                    );

                    geometry._lineVerticesLength =
                        this._lineVerticesHighMerc.length - geometry._lineVerticesHandlerIndex;
                    geometry._lineOrdersLength = this._lineOrders.length - geometry._lineOrdersHandlerIndex;
                    geometry._lineIndexesLength = this._lineIndexes.length - geometry._lineIndexesHandlerIndex;
                    geometry._lineColorsLength = this._lineColors.length - geometry._lineColorsHandlerIndex;
                    geometry._lineThicknessLength = this._lineThickness.length - geometry._lineThicknessHandlerIndex;
                }
            }

            // Refresh visibility
            this.setGeometryVisibility(geometry);

            !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
            this._updatedGeometry[geometry.__id] = true;
            this.refresh();
        }
    }

    /**
     * Removes one geometry. Works in any array order, including after bringToFront(): every range
     * after the removed one in an array moves down, and every index past its vertices goes down
     * by its vertex count. Nothing moves when its ranges are at the ends of the arrays.
     * @public
     * @param {Geometry} geometry - Geometry to remove; ignored if it belongs to another handler.
     */
    public remove(geometry: Geometry) {
        const index = geometry._handlerIndex;
        if (geometry._handler !== this || index === -1) {
            return;
        }

        const all = this._geometries;
        all.splice(index, 1);
        for (let i = index; i < all.length; i++) {
            all[i]._handlerIndex = i;
        }

        // A length of -1 means the geometry has no data of that kind, e.g. a line string has no polygon.
        const pv = geometry._polyVerticesHandlerIndex,
            pvl = geometry._polyVerticesLength,
            pi = geometry._polyIndexesHandlerIndex,
            pil = geometry._polyIndexesLength,
            lv = geometry._lineVerticesHandlerIndex,
            lvl = geometry._lineVerticesLength,
            lo = geometry._lineOrdersHandlerIndex,
            lol = geometry._lineOrdersLength,
            lc = geometry._lineColorsHandlerIndex,
            lcl = geometry._lineColorsLength,
            lt = geometry._lineThicknessHandlerIndex,
            ltl = geometry._lineThicknessLength,
            li = geometry._lineIndexesHandlerIndex,
            lil = geometry._lineIndexesLength;

        // Whether anything follows the removed range in its arrays
        let shift = false;

        if (pil > 0) {
            shift ||= pi + pil < this._polyIndexes.length;
            this._polyIndexes.splice(pi, pil);
        }
        if (pvl > 0) {
            if (pv + pvl < this._polyVerticesHighMerc.length) {
                shift = true;
                const first = pv * 0.5,
                    n = pvl * 0.5,
                    a = this._polyIndexes;
                for (let i = 0; i < a.length; i++) {
                    if (a[i] >= first) a[i] -= n;
                }
            }
            this._polyVerticesHighMerc.splice(pv, pvl);
            this._polyVerticesLowMerc.splice(pv, pvl);
            if (this._isCube) {
                this._polyDirectionsHigh.splice(pv * 1.5, pvl * 1.5);
                this._polyDirectionsLow.splice(pv * 1.5, pvl * 1.5);
            }
            this._polyColors.splice(pv * 2, pvl * 2);
            this._polyPickingColors.splice(pv * 2, pvl * 2);
        }

        if (lil > 0) {
            shift ||= li + lil < this._lineIndexes.length;
            this._lineIndexes.splice(li, lil);
        }
        if (lvl > 0) {
            if (lv + lvl < this._lineVerticesHighMerc.length) {
                shift = true;
                const first = lv * 0.5,
                    n = lvl * 0.5,
                    a = this._lineIndexes;
                for (let i = 0; i < a.length; i++) {
                    if (a[i] >= first) a[i] -= n;
                }
            }
            this._lineVerticesHighMerc.splice(lv, lvl);
            this._lineVerticesLowMerc.splice(lv, lvl);
            if (this._isCube) {
                this._lineDirectionsHigh.splice(lv * 1.5, lvl * 1.5);
                this._lineDirectionsLow.splice(lv * 1.5, lvl * 1.5);
            }
        }
        if (lol > 0) {
            shift ||= lo + lol < this._lineOrders.length;
            this._lineOrders.splice(lo, lol);
        }
        if (lcl > 0) {
            shift ||= lc + lcl < this._lineColors.length;
            this._lineColors.splice(lc, lcl);
            this._linePickingColors.splice(lc, lcl);
            this._lineStrokeColors.splice(lc, lcl);
        }
        if (ltl > 0) {
            shift ||= lt + ltl < this._lineThickness.length;
            this._lineThickness.splice(lt, ltl);
            this._lineStrokes.splice(lt, ltl);
        }

        if (shift) {
            // Offsets of -1 (no data of that kind) are never above a real offset.
            for (let i = 0; i < all.length; i++) {
                const g = all[i];
                if (pvl > 0 && g._polyVerticesHandlerIndex > pv) g._polyVerticesHandlerIndex -= pvl;
                if (pil > 0 && g._polyIndexesHandlerIndex > pi) g._polyIndexesHandlerIndex -= pil;
                if (lvl > 0 && g._lineVerticesHandlerIndex > lv) g._lineVerticesHandlerIndex -= lvl;
                if (lol > 0 && g._lineOrdersHandlerIndex > lo) g._lineOrdersHandlerIndex -= lol;
                if (lcl > 0 && g._lineColorsHandlerIndex > lc) g._lineColorsHandlerIndex -= lcl;
                if (ltl > 0 && g._lineThicknessHandlerIndex > lt) g._lineThicknessHandlerIndex -= ltl;
                if (lil > 0 && g._lineIndexesHandlerIndex > li) g._lineIndexesHandlerIndex -= lil;
            }
        }

        this._detachGeometry(geometry);

        this.refresh();
    }

    /**
     * Removes geometries with one compaction pass over the shared arrays.
     * Works in any array order, including after bringToFront().
     * @public
     * @param {Geometry[]} geometries - Geometries to remove; others are ignored.
     */
    public removeGeometries(geometries: Geometry[]) {
        const removed = new Set<Geometry>();
        for (let i = 0; i < geometries.length; i++) {
            const g = geometries[i];
            if (g._handler === this && g._handlerIndex !== -1) {
                removed.add(g);
            }
        }
        if (!removed.size) return;

        const all = this._geometries;

        // Moves each kept geometry's range down to the write position. Ranges are visited in
        // array order, which differs from the geometry list after bringToFront(). Arrays in one
        // group share the geometry's offset field; scale is the number of values per offset unit.
        // Index arrays also subtract how far their geometry's vertices moved.
        // Returns how far each moved range went.
        const compact = (
            arrays: [number[], number][],
            offsetKey: GeometryOffsetKey,
            lengthKey: GeometryLengthKey,
            verticesMoved?: Map<Geometry, number>
        ) => {
            const moved = new Map<Geometry, number>();

            const ranges: Geometry[] = [];
            let sorted = true;
            for (let k = 0; k < all.length; k++) {
                const g = all[k];

                // The geometry has no data of this kind
                if (g[lengthKey] < 0) {
                    continue;
                }

                if (ranges.length && g[offsetKey] < ranges[ranges.length - 1][offsetKey]) {
                    sorted = false;
                }

                ranges.push(g);
            }

            if (!ranges.length) {
                return moved;
            }

            if (!sorted) {
                ranges.sort((a, b) => a[offsetKey] - b[offsetKey]);
            }

            let write = ranges[0][offsetKey];
            for (let k = 0; k < ranges.length; k++) {
                const g = ranges[k];

                if (removed.has(g)) continue;

                const offset = g[offsetKey],
                    length = g[lengthKey],
                    shift = verticesMoved ? (verticesMoved.get(g) || 0) * 0.5 : 0;

                if (offset !== write || shift) {
                    for (const [a, scale] of arrays) {
                        const from = offset * scale,
                            to = write * scale,
                            n = length * scale;

                        for (let i = 0; i < n; i++) {
                            a[to + i] = a[from + i] - shift;
                        }
                    }
                    offset !== write && moved.set(g, offset - write);
                }
                g[offsetKey] = write;
                write += length;
            }

            for (const [a, scale] of arrays) {
                a.length = write * scale;
            }

            return moved;
        };

        const directions = (high: number[], low: number[]): [number[], number][] =>
            this._isCube
                ? [
                      [high, 1.5],
                      [low, 1.5]
                  ]
                : [];

        const polyVerticesMoved = compact(
            [
                [this._polyVerticesHighMerc, 1],
                [this._polyVerticesLowMerc, 1],
                ...directions(this._polyDirectionsHigh, this._polyDirectionsLow),
                [this._polyColors, 2],
                [this._polyPickingColors, 2]
            ],
            "_polyVerticesHandlerIndex",
            "_polyVerticesLength"
        );
        compact([[this._polyIndexes, 1]], "_polyIndexesHandlerIndex", "_polyIndexesLength", polyVerticesMoved);
        const lineVerticesMoved = compact(
            [
                [this._lineVerticesHighMerc, 1],
                [this._lineVerticesLowMerc, 1],
                ...directions(this._lineDirectionsHigh, this._lineDirectionsLow)
            ],
            "_lineVerticesHandlerIndex",
            "_lineVerticesLength"
        );
        compact([[this._lineOrders, 1]], "_lineOrdersHandlerIndex", "_lineOrdersLength");
        compact(
            [
                [this._lineColors, 1],
                [this._linePickingColors, 1],
                [this._lineStrokeColors, 1]
            ],
            "_lineColorsHandlerIndex",
            "_lineColorsLength"
        );
        compact(
            [
                [this._lineThickness, 1],
                [this._lineStrokes, 1]
            ],
            "_lineThicknessHandlerIndex",
            "_lineThicknessLength"
        );
        compact([[this._lineIndexes, 1]], "_lineIndexesHandlerIndex", "_lineIndexesLength", lineVerticesMoved);

        // Nothing before the first removed geometry moves in the list.
        let start = all.length;
        for (const g of removed) {
            start = Math.min(start, g._handlerIndex);
        }

        let j = start;
        for (let k = start; k < all.length; k++) {
            const g = all[k];
            if (!removed.has(g)) {
                g._handlerIndex = j;
                all[j++] = g;
            }
        }

        all.length = j;

        for (const g of removed) {
            this._detachGeometry(g);
        }

        this.refresh();
    }

    protected _detachGeometry(geometry: Geometry) {
        geometry._pickingReady = false;

        geometry._handler = null;
        geometry._handlerIndex = -1;

        geometry._polyVerticesHighMerc = [];
        geometry._polyVerticesLowMerc = [];
        geometry._polyDirectionsHigh = [];
        geometry._polyDirectionsLow = [];
        geometry._polyVerticesLength = -1;
        geometry._polyIndexesLength = -1;
        geometry._polyVerticesHandlerIndex = -1;
        geometry._polyIndexesHandlerIndex = -1;

        geometry._lineVerticesHighMerc = [];
        geometry._lineVerticesLowMerc = [];
        geometry._lineDirectionsHigh = [];
        geometry._lineDirectionsLow = [];
        geometry._lineVerticesLength = -1;
        geometry._lineOrdersLength = -1;
        geometry._lineIndexesLength = -1;
        geometry._lineColorsLength = -1;
        geometry._lineThicknessLength = -1;
        geometry._lineVerticesHandlerIndex = -1;
        geometry._lineOrdersHandlerIndex = -1;
        geometry._lineIndexesHandlerIndex = -1;
        geometry._lineThicknessHandlerIndex = -1;
        geometry._lineColorsHandlerIndex = -1;

        !this._removeGeometryExtents[geometry.__id] && this._removeGeometryExtentArr.push(geometry.getExtent());
        this._removeGeometryExtents[geometry.__id] = true;
    }

    protected _refreshRecursevely(geometry: Geometry, treeNode: Node) {
        if (treeNode.ready) {
            let lid = this._layer._id;
            for (let i = 0; i < treeNode.nodes.length; i++) {
                let ni = treeNode.nodes[i];
                if (geometry.overlaps(ni.segment.getExtentLonLat())) {
                    this._refreshRecursevely(geometry, ni);
                    let m = ni.segment.materials[lid];
                    if (m && m.isReady) {
                        if (!m.segment.node.isRenderedOrFading()) {
                            m.layer.clearMaterial(m);
                        } else {
                            m.pickingReady = m.pickingReady && geometry._pickingReady;
                            m.isReady = false;
                            m._updateTexture = m.texture;
                            m._updatePickingMask = m.pickingMask;
                        }

                        geometry._pickingReady = true;
                    }
                }
            }
        }
    }

    protected _refreshRecursevelyExt(extent: Extent, treeNode: Node) {
        if (treeNode.ready) {
            let lid = this._layer.__id;
            for (let i = 0; i < treeNode.nodes.length; i++) {
                let ni = treeNode.nodes[i];
                if (extent.overlaps(ni.segment.getExtentLonLat())) {
                    this._refreshRecursevelyExt(extent, ni);
                    let m = ni.segment.materials[lid];
                    if (m && m.isReady) {
                        if (!m.textureExists || !m.segment.node.isRenderedOrFading()) {
                            m.layer.clearMaterial(m);
                        } else {
                            m.pickingReady = false;
                            m.isReady = false;
                            m._updateTexture = m.texture;
                            m._updatePickingMask = m.pickingMask;
                        }
                    }
                }
            }
        }
    }

    protected _refreshPlanetNode(treeNode: Node) {
        let i;

        let e = this._removeGeometryExtentArr;
        for (i = 0; i < e.length; i++) {
            this._refreshRecursevelyExt(e[i], treeNode);
        }

        let g = this._updatedGeometryArr;
        for (i = 0; i < g.length; i++) {
            this._refreshRecursevely(g[i], treeNode);
        }
    }

    protected _updatePlanet() {
        let p = this._layer._planet;
        if (p) {
            let ql = p.quadTreeStrategy.quadTreeList;
            for (let i = 0; i < ql.length; i++) {
                this._refreshPlanetNode(ql[i]);
            }
            // p.quadTreeStrategy.quadTreeList.forEach((quadTree: Node) => {
            //     this._refreshPlanetNode(quadTree);
            // });
        }
        this._updatedGeometryArr.length = 0;
        this._updatedGeometryArr = [];
        this._updatedGeometry = {};

        this._removeGeometryExtentArr.length = 0;
        this._removeGeometryExtentArr = [];
        this._removeGeometryExtents = {};
    }

    protected refresh() {
        let i = this._changedBuffers.length;
        while (i--) {
            this._setChangedBuffer(i);
        }
    }

    protected _setChangedBuffer(index: number) {
        this._changedBuffers[index] = true;
        this._handler && (this._handler.needRedraw = true);
    }

    public update() {
        if (this._handler) {
            let needUpdate = false;
            let i = this._changedBuffers.length;
            while (i--) {
                if (this._changedBuffers[i]) {
                    needUpdate = true;
                    this._buffersUpdateCallbacks[i].call(this);
                    this._changedBuffers[i] = false;
                }
            }
            needUpdate && this._updatePlanet();
        }
    }

    public setGeometryVisibility(geometry: Geometry) {
        let v = geometry.getVisibility() ? 1.0 : 0.0;

        let a = this._polyVerticesHighMerc,
            b = this._polyVerticesLowMerc;

        let l = geometry._polyVerticesLength;
        let ind = geometry._polyVerticesHandlerIndex;
        for (let i = 0; i < l; i++) {
            a[ind + i] = geometry._polyVerticesHighMerc[i] * v;
            b[ind + i] = geometry._polyVerticesLowMerc[i] * v;
        }

        a = this._lineVerticesHighMerc;
        b = this._lineVerticesLowMerc;

        l = geometry._lineVerticesLength;
        ind = geometry._lineVerticesHandlerIndex;
        for (let i = 0; i < l; i++) {
            a[ind + i] = geometry._lineVerticesHighMerc[i] * v;
            b[ind + i] = geometry._lineVerticesLowMerc[i] * v;
        }

        if (this._isCube) {
            const hide = (to: number[], from: number[], index: number) => {
                for (let i = 0; i < from.length; i++) {
                    to[index + i] = from[i] * v;
                }
            };
            hide(this._polyDirectionsHigh, geometry._polyDirectionsHigh, geometry._polyVerticesHandlerIndex * 1.5);
            hide(this._polyDirectionsLow, geometry._polyDirectionsLow, geometry._polyVerticesHandlerIndex * 1.5);
            hide(this._lineDirectionsHigh, geometry._lineDirectionsHigh, geometry._lineVerticesHandlerIndex * 1.5);
            hide(this._lineDirectionsLow, geometry._lineDirectionsLow, geometry._lineVerticesHandlerIndex * 1.5);
        }

        this._setChangedBuffer(POLYVERTICES_BUFFER);
        this._setChangedBuffer(LINEVERTICES_BUFFER);

        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public setPolyColorArr(geometry: Geometry, color: Vec4) {
        let index = geometry._polyVerticesHandlerIndex * 2, // ... / 2 * 4
            size = index + geometry._polyVerticesLength * 2; // ... / 2 * 4
        let a = this._polyColors;
        for (let i = index; i < size; i += 4) {
            a[i] = color.x;
            a[i + 1] = color.y;
            a[i + 2] = color.z;
            a[i + 3] = color.w;
        }
        this._setChangedBuffer(POLYCOLORS_BUFFER);
        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public setLineStrokeColorArr(geometry: Geometry, color: Vec4) {
        let index = geometry._lineColorsHandlerIndex,
            size = index + geometry._lineColorsLength;
        let a = this._lineStrokeColors;
        for (let i = index; i < size; i += 4) {
            a[i] = color.x;
            a[i + 1] = color.y;
            a[i + 2] = color.z;
            a[i + 3] = color.w;
        }
        this._setChangedBuffer(LINESTROKECOLORS_BUFFER);
        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public setLineColorArr(geometry: Geometry, color: Vec4) {
        let index = geometry._lineColorsHandlerIndex,
            size = index + geometry._lineColorsLength;
        let a = this._lineColors;
        for (let i = index; i < size; i += 4) {
            a[i] = color.x;
            a[i + 1] = color.y;
            a[i + 2] = color.z;
            a[i + 3] = color.w;
        }
        this._setChangedBuffer(LINECOLORS_BUFFER);
        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public setLineStrokeArr(geometry: Geometry, width: number) {
        // let index = geometry._lineStrokesHandlerIndex,
        //     size = index + geometry._lineStrokesLength;
        // let a = this._lineStrokes;
        // for (let i = index; i < size; i++) {
        //     a[i] = width;
        // }
        // this._changedBuffers[LINESTROKES_BUFFER] = true;
        // !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        // this._updatedGeometry[geometry.__id] = true;
    }

    public setLineThicknessArr(geometry: Geometry, width: number) {
        let index = geometry._lineThicknessHandlerIndex,
            size = index + geometry._lineThicknessLength;
        let a = this._lineThickness;
        for (let i = index; i < size; i++) {
            a[i] = width;
        }
        this._setChangedBuffer(LINETHICKNESS_BUFFER);
        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public bringToFront(geometry: Geometry) {
        // A length of -1 means the geometry has no data of that kind, e.g. a line string has no polygon.
        const polyLength = geometry._polyIndexesLength,
            lineLength = geometry._lineIndexesLength;

        let polyIndexes =
            polyLength >= 0 ? this._polyIndexes.splice(geometry._polyIndexesHandlerIndex, polyLength) : [];

        let lineIndexes =
            lineLength >= 0 ? this._lineIndexes.splice(geometry._lineIndexesHandlerIndex, lineLength) : [];

        this._geometries.splice(geometry._handlerIndex, 1);

        let g = this._geometries;
        for (let i = geometry._handlerIndex; i < g.length; i++) {
            let gi = g[i];

            gi._handlerIndex = i;

            if (polyLength > 0 && gi._polyIndexesLength >= 0) {
                gi._polyIndexesHandlerIndex -= polyLength;
            }

            if (lineLength > 0 && gi._lineIndexesLength >= 0) {
                gi._lineIndexesHandlerIndex -= lineLength;
            }
        }

        if (polyLength >= 0) {
            geometry._polyIndexesHandlerIndex = this._polyIndexes.length;
        }

        if (lineLength >= 0) {
            geometry._lineIndexesHandlerIndex = this._lineIndexes.length;
        }

        geometry._handlerIndex = this._geometries.length;
        this._geometries.push(geometry);

        concatArrays(this._polyIndexes, polyIndexes);
        concatArrays(this._lineIndexes, lineIndexes);

        this._setChangedBuffer(POLYINDEXES_BUFFER);
        this._setChangedBuffer(LINEINDEXES_BUFFER);

        !this._updatedGeometry[geometry.__id] && this._updatedGeometryArr.push(geometry);
        this._updatedGeometry[geometry.__id] = true;
    }

    public createPolyVerticesBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._polyVerticesHighBufferMerc!);
        this._polyVerticesHighBufferMerc = h.createArrayBuffer(
            new Float32Array(this._polyVerticesHighMerc),
            2,
            this._polyVerticesHighMerc.length / 2
        );

        h.gl!.deleteBuffer(this._polyVerticesLowBufferMerc!);
        this._polyVerticesLowBufferMerc = h.createArrayBuffer(
            new Float32Array(this._polyVerticesLowMerc),
            2,
            this._polyVerticesLowMerc.length / 2
        );

        if (this._isCube) {
            h.gl!.deleteBuffer(this._polyDirectionsHighBuffer!);
            h.gl!.deleteBuffer(this._polyDirectionsLowBuffer!);
            this._polyDirectionsHighBuffer = h.createArrayBuffer(
                new Float32Array(this._polyDirectionsHigh),
                3,
                this._polyDirectionsHigh.length / 3
            );
            this._polyDirectionsLowBuffer = h.createArrayBuffer(
                new Float32Array(this._polyDirectionsLow),
                3,
                this._polyDirectionsLow.length / 3
            );
        }
    }

    public createPolyIndexesBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._polyIndexesBuffer!);
        this._polyIndexesBuffer = h.createElementArrayBuffer(
            new Uint32Array(this._polyIndexes),
            1,
            this._polyIndexes.length
        );
    }

    public createPolyColorsBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._polyColorsBuffer!);
        this._polyColorsBuffer = h.createArrayBuffer(
            new Float32Array(this._polyColors),
            4,
            this._polyColors.length / 4
        );
    }

    public createPolyPickingColorsBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._polyPickingColorsBuffer!);
        this._polyPickingColorsBuffer = h.createArrayBuffer(
            new Float32Array(this._polyPickingColors),
            4,
            this._polyPickingColors.length / 4
        );
    }

    public createLineVerticesBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineVerticesHighBufferMerc!);
        this._lineVerticesHighBufferMerc = h.createArrayBuffer(
            new Float32Array(this._lineVerticesHighMerc),
            2,
            this._lineVerticesHighMerc.length / 2
        );

        h.gl!.deleteBuffer(this._lineVerticesLowBufferMerc!);
        this._lineVerticesLowBufferMerc = h.createArrayBuffer(
            new Float32Array(this._lineVerticesLowMerc),
            2,
            this._lineVerticesLowMerc.length / 2
        );

        if (this._isCube) {
            h.gl!.deleteBuffer(this._lineDirectionsHighBuffer!);
            h.gl!.deleteBuffer(this._lineDirectionsLowBuffer!);
            this._lineDirectionsHighBuffer = h.createArrayBuffer(
                new Float32Array(this._lineDirectionsHigh),
                3,
                this._lineDirectionsHigh.length / 3
            );
            this._lineDirectionsLowBuffer = h.createArrayBuffer(
                new Float32Array(this._lineDirectionsLow),
                3,
                this._lineDirectionsLow.length / 3
            );
        }
    }

    public createLineIndexesBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineIndexesBuffer!);
        this._lineIndexesBuffer = h.createElementArrayBuffer(
            new Uint32Array(this._lineIndexes),
            1,
            this._lineIndexes.length
        );
    }

    public createLineOrdersBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineOrdersBuffer!);
        this._lineOrdersBuffer = h.createArrayBuffer(
            new Float32Array(this._lineOrders),
            1,
            this._lineOrders.length / 2
        );
    }

    public createLineColorsBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineColorsBuffer!);
        this._lineColorsBuffer = h.createArrayBuffer(
            new Float32Array(this._lineColors),
            4,
            this._lineColors.length / 4
        );
    }

    public createLinePickingColorsBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._linePickingColorsBuffer!);
        this._linePickingColorsBuffer = h.createArrayBuffer(
            new Float32Array(this._linePickingColors),
            4,
            this._linePickingColors.length / 4
        );
    }

    public createLineThicknessBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineThicknessBuffer!);
        this._lineThicknessBuffer = h.createArrayBuffer(
            new Float32Array(this._lineThickness),
            1,
            this._lineThickness.length
        );
    }

    public createLineStrokesBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineStrokesBuffer!);
        this._lineStrokesBuffer = h.createArrayBuffer(new Float32Array(this._lineStrokes), 1, this._lineStrokes.length);
    }

    public createLineStrokeColorsBuffer() {
        let h = this._handler!;
        h.gl!.deleteBuffer(this._lineStrokeColorsBuffer!);
        this._lineStrokeColorsBuffer = h.createArrayBuffer(
            new Float32Array(this._lineStrokeColors),
            4,
            this._lineStrokeColors.length / 4
        );
    }

    public clear() {
        this._geometries = [];
        this._polyVerticesHighMerc = [];
        this._polyVerticesLowMerc = [];
        this._polyDirectionsHigh = [];
        this._polyDirectionsLow = [];
        this._polyIndexes = [];
        this._polyColors = [];
        this._polyPickingColors = [];

        this._lineVerticesHighMerc = [];
        this._lineVerticesLowMerc = [];
        this._lineDirectionsHigh = [];
        this._lineDirectionsLow = [];
        this._lineOrders = [];
        this._lineIndexes = [];
        this._lineColors = [];
        this._linePickingColors = [];
        this._lineThickness = [];
        this._lineStrokeColors = [];
        this._lineStrokes = [];

        this._deleteBuffers();

        this._polyVerticesHighBufferMerc = null;
        this._polyVerticesLowBufferMerc = null;
        this._polyDirectionsHighBuffer = null;
        this._polyDirectionsLowBuffer = null;
        this._polyIndexesBuffer = null;
        this._polyColorsBuffer = null;
        this._polyPickingColorsBuffer = null;

        this._lineVerticesHighBufferMerc = null;
        this._lineVerticesLowBufferMerc = null;
        this._lineDirectionsHighBuffer = null;
        this._lineDirectionsLowBuffer = null;
        this._lineIndexesBuffer = null;
        this._lineOrdersBuffer = null;
        this._lineColorsBuffer = null;
        this._linePickingColorsBuffer = null;
        this._lineThicknessBuffer = null;
        this._lineStrokeColorsBuffer = null;
        this._lineStrokesBuffer = null;

        this._updatedGeometryArr = [];
        this._updatedGeometry = {};

        this._removeGeometryExtentArr = [];
        this._removeGeometryExtents = {};

        this.refresh();
    }

    public _deleteBuffers() {
        if (this._layer._planet && this._layer._planet.renderer) {
            let h = this._layer._planet.renderer.handler,
                gl = h.gl!;

            if (gl) {
                gl.deleteBuffer(this._polyVerticesHighBufferMerc!);
                gl.deleteBuffer(this._polyVerticesLowBufferMerc!);
                gl.deleteBuffer(this._polyDirectionsHighBuffer!);
                gl.deleteBuffer(this._polyDirectionsLowBuffer!);
                gl.deleteBuffer(this._polyIndexesBuffer!);
                gl.deleteBuffer(this._polyColorsBuffer!);
                gl.deleteBuffer(this._polyPickingColorsBuffer!);

                gl.deleteBuffer(this._lineVerticesHighBufferMerc);
                gl.deleteBuffer(this._lineVerticesLowBufferMerc);
                gl.deleteBuffer(this._lineDirectionsHighBuffer);
                gl.deleteBuffer(this._lineDirectionsLowBuffer);
                gl.deleteBuffer(this._lineIndexesBuffer);
                gl.deleteBuffer(this._lineOrdersBuffer);
                gl.deleteBuffer(this._lineColorsBuffer);
                gl.deleteBuffer(this._linePickingColorsBuffer);
                gl.deleteBuffer(this._lineThicknessBuffer);
                gl.deleteBuffer(this._lineStrokeColorsBuffer);
                gl.deleteBuffer(this._lineStrokesBuffer);
            }
        }
    }
}

export { GeometryHandler };