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;
}