public computeSphericalEdges()

in src/graph/edge/EdgeCalculator.ts [578:767]


    public computeSphericalEdges(node: Image, potentialEdges: PotentialEdge[]): NavigationEdge[] {
        if (!node.complete) {
            throw new ArgumentMapillaryError("Image has to be full.");
        }

        if (!isSpherical(node.cameraType)) {
            return [];
        }

        let sphericalEdges: NavigationEdge[] = [];
        let potentialSpherical: PotentialEdge[] = [];
        let potentialSteps: [NavigationDirection, PotentialEdge][] = [];

        for (let potential of potentialEdges) {
            if (potential.distance > this._settings.sphericalMaxDistance) {
                continue;
            }

            if (potential.spherical) {
                if (potential.distance < this._settings.sphericalMinDistance) {
                    continue;
                }

                potentialSpherical.push(potential);
            } else {
                for (let k in this._directions.spherical) {
                    if (!this._directions.spherical.hasOwnProperty(k)) {
                        continue;
                    }

                    let spherical = this._directions.spherical[k];

                    let turn: number = this._spatial.angleDifference(
                        potential.directionChange,
                        potential.motionChange);

                    let turnChange: number = this._spatial.angleDifference(spherical.directionChange, turn);

                    if (Math.abs(turnChange) > this._settings.sphericalMaxStepTurnChange) {
                        continue;
                    }

                    potentialSteps.push([spherical.direction, potential]);

                    // break if step direction found
                    break;
                }
            }
        }

        let maxRotationDifference: number = Math.PI / this._settings.sphericalMaxItems;
        let occupiedAngles: number[] = [];
        let stepAngles: number[] = [];

        for (let index: number = 0; index < this._settings.sphericalMaxItems; index++) {
            let rotation: number = index / this._settings.sphericalMaxItems * 2 * Math.PI;

            let lowestScore: number = Number.MAX_VALUE;
            let edge: PotentialEdge = null;

            for (let potential of potentialSpherical) {
                let motionDifference: number = this._spatial.angleDifference(rotation, potential.motionChange);

                if (Math.abs(motionDifference) > maxRotationDifference) {
                    continue;
                }

                let occupiedDifference: number = Number.MAX_VALUE;
                for (let occupiedAngle of occupiedAngles) {
                    let difference: number = Math.abs(this._spatial.angleDifference(occupiedAngle, potential.motionChange));
                    if (difference < occupiedDifference) {
                        occupiedDifference = difference;
                    }
                }

                if (occupiedDifference <= maxRotationDifference) {
                    continue;
                }

                let score: number =
                    this._coefficients.sphericalPreferredDistance *
                    Math.abs(potential.distance - this._settings.sphericalPreferredDistance) /
                    this._settings.sphericalMaxDistance +
                    this._coefficients.sphericalMotion * Math.abs(motionDifference) / maxRotationDifference +
                    this._coefficients.sphericalSequencePenalty * (potential.sameSequence ? 0 : 1) +
                    this._coefficients.sphericalMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);

                if (score < lowestScore) {
                    lowestScore = score;
                    edge = potential;
                }
            }

            if (edge != null) {
                occupiedAngles.push(edge.motionChange);
                sphericalEdges.push({
                    data: {
                        direction: NavigationDirection.Spherical,
                        worldMotionAzimuth: edge.worldMotionAzimuth,
                    },
                    source: node.id,
                    target: edge.id,
                });
            } else {
                stepAngles.push(rotation);
            }
        }

        let occupiedStepAngles: { [direction: string]: number[] } = {};
        occupiedStepAngles[NavigationDirection.Spherical] = occupiedAngles;
        occupiedStepAngles[NavigationDirection.StepForward] = [];
        occupiedStepAngles[NavigationDirection.StepLeft] = [];
        occupiedStepAngles[NavigationDirection.StepBackward] = [];
        occupiedStepAngles[NavigationDirection.StepRight] = [];

        for (let stepAngle of stepAngles) {
            let occupations: [NavigationDirection, PotentialEdge][] = [];

            for (let k in this._directions.spherical) {
                if (!this._directions.spherical.hasOwnProperty(k)) {
                    continue;
                }

                let spherical = this._directions.spherical[k];

                let allOccupiedAngles: number[] = occupiedStepAngles[NavigationDirection.Spherical]
                    .concat(occupiedStepAngles[spherical.direction])
                    .concat(occupiedStepAngles[spherical.prev])
                    .concat(occupiedStepAngles[spherical.next]);

                let lowestScore: number = Number.MAX_VALUE;
                let edge: [NavigationDirection, PotentialEdge] = null;

                for (let potential of potentialSteps) {
                    if (potential[0] !== spherical.direction) {
                        continue;
                    }

                    let motionChange: number = this._spatial.angleDifference(stepAngle, potential[1].motionChange);

                    if (Math.abs(motionChange) > maxRotationDifference) {
                        continue;
                    }

                    let minOccupiedDifference: number = Number.MAX_VALUE;
                    for (let occupiedAngle of allOccupiedAngles) {
                        let occupiedDifference: number =
                            Math.abs(this._spatial.angleDifference(occupiedAngle, potential[1].motionChange));

                        if (occupiedDifference < minOccupiedDifference) {
                            minOccupiedDifference = occupiedDifference;
                        }
                    }

                    if (minOccupiedDifference <= maxRotationDifference) {
                        continue;
                    }

                    let score: number = this._coefficients.sphericalPreferredDistance *
                        Math.abs(potential[1].distance - this._settings.sphericalPreferredDistance) /
                        this._settings.sphericalMaxDistance +
                        this._coefficients.sphericalMotion * Math.abs(motionChange) / maxRotationDifference +
                        this._coefficients.sphericalMergeCCPenalty * (potential[1].sameMergeCC ? 0 : 1);

                    if (score < lowestScore) {
                        lowestScore = score;
                        edge = potential;
                    }
                }

                if (edge != null) {
                    occupations.push(edge);
                    sphericalEdges.push({
                        data: {
                            direction: edge[0],
                            worldMotionAzimuth: edge[1].worldMotionAzimuth,
                        },
                        source: node.id,
                        target: edge[1].id,
                    });
                }
            }

            for (let occupation of occupations) {
                occupiedStepAngles[occupation[0]].push(occupation[1].motionChange);
            }
        }

        return sphericalEdges;
    }