245 lines
No EOL
8.3 KiB
TypeScript
245 lines
No EOL
8.3 KiB
TypeScript
import OsmChangeAction from "./OsmChangeAction";
|
|
import {Changes} from "../Changes";
|
|
import {ChangeDescription} from "./ChangeDescription";
|
|
import {OsmObject, OsmRelation, OsmWay} from "../OsmObject";
|
|
|
|
export interface RelationSplitInput {
|
|
relation: OsmRelation,
|
|
originalWayId: number,
|
|
allWayIdsInOrder: number[],
|
|
originalNodes: number[],
|
|
allWaysNodesInOrder: number[][]
|
|
}
|
|
|
|
abstract class AbstractRelationSplitHandler extends OsmChangeAction {
|
|
protected readonly _input: RelationSplitInput;
|
|
protected readonly _theme: string;
|
|
|
|
constructor(input: RelationSplitInput, theme: string) {
|
|
super("relation/" + input.relation.id, false)
|
|
this._input = input;
|
|
this._theme = theme;
|
|
}
|
|
|
|
/**
|
|
* Returns which node should border the member at the given index
|
|
*/
|
|
protected async targetNodeAt(i: number, first: boolean) {
|
|
const member = this._input.relation.members[i]
|
|
if (member === undefined) {
|
|
return undefined
|
|
}
|
|
if (member.type === "node") {
|
|
return member.ref
|
|
}
|
|
if (member.type === "way") {
|
|
const osmWay = <OsmWay>await OsmObject.DownloadObjectAsync("way/" + member.ref)
|
|
const nodes = osmWay.nodes
|
|
if (first) {
|
|
return nodes[0]
|
|
} else {
|
|
return nodes[nodes.length - 1]
|
|
}
|
|
}
|
|
if (member.type === "relation") {
|
|
return undefined
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When a way is split and this way is part of a relation, the relation should be updated too to have the new segment if relevant.
|
|
*/
|
|
export default class RelationSplitHandler extends AbstractRelationSplitHandler {
|
|
|
|
constructor(input: RelationSplitInput, theme: string) {
|
|
super(input, theme)
|
|
}
|
|
|
|
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
|
if (this._input.relation.tags["type"] === "restriction") {
|
|
// This is a turn restriction
|
|
return new TurnRestrictionRSH(this._input, this._theme).CreateChangeDescriptions(changes)
|
|
}
|
|
return new InPlaceReplacedmentRTSH(this._input, this._theme).CreateChangeDescriptions(changes)
|
|
}
|
|
|
|
|
|
}
|
|
|
|
export class TurnRestrictionRSH extends AbstractRelationSplitHandler {
|
|
|
|
constructor(input: RelationSplitInput, theme: string) {
|
|
super(input, theme);
|
|
}
|
|
|
|
public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
|
|
|
const relation = this._input.relation
|
|
const members = relation.members
|
|
|
|
const selfMembers = members.filter(m => m.type === "way" && m.ref === this._input.originalWayId)
|
|
|
|
if (selfMembers.length > 1) {
|
|
console.warn("Detected a turn restriction where this way has multiple occurances. This is an error")
|
|
}
|
|
const selfMember = selfMembers[0]
|
|
|
|
if (selfMember.role === "via") {
|
|
// A via way can be replaced in place
|
|
return new InPlaceReplacedmentRTSH(this._input, this._theme).CreateChangeDescriptions(changes);
|
|
}
|
|
|
|
|
|
// We have to keep only the way with a common point with the rest of the relation
|
|
// Let's figure out which member is neighbouring our way
|
|
|
|
let commonStartPoint: number = await this.targetNodeAt(members.indexOf(selfMember), true)
|
|
let commonEndPoint: number = await this.targetNodeAt(members.indexOf(selfMember), false)
|
|
|
|
// In normal circumstances, only one of those should be defined
|
|
let commonPoint = commonStartPoint ?? commonEndPoint
|
|
|
|
// Let's select the way to keep
|
|
const idToKeep: { id: number } = this._input.allWaysNodesInOrder.map((nodes, i) => ({
|
|
nodes: nodes,
|
|
id: this._input.allWayIdsInOrder[i]
|
|
}))
|
|
.filter(nodesId => {
|
|
const nds = nodesId.nodes
|
|
return nds[0] == commonPoint || nds[nds.length - 1] == commonPoint
|
|
})[0]
|
|
|
|
if (idToKeep === undefined) {
|
|
console.error("No common point found, this was a broken turn restriction!", relation.id)
|
|
return []
|
|
}
|
|
|
|
const originalWayId = this._input.originalWayId
|
|
if (idToKeep.id === originalWayId) {
|
|
console.log("Turn_restriction fixer: the original ID can be kept, nothing to do")
|
|
return []
|
|
}
|
|
|
|
const newMembers: {
|
|
ref: number,
|
|
type: "way" | "node" | "relation",
|
|
role: string
|
|
} [] = relation.members.map(m => {
|
|
if (m.type === "way" && m.ref === originalWayId) {
|
|
return {
|
|
ref: idToKeep.id,
|
|
type: "way",
|
|
role: m.role
|
|
}
|
|
}
|
|
return m
|
|
})
|
|
|
|
|
|
return [
|
|
{
|
|
type: "relation",
|
|
id: relation.id,
|
|
changes: {
|
|
members: newMembers
|
|
},
|
|
meta: {
|
|
theme: this._theme,
|
|
changeType: "relation-fix:turn_restriction"
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A simple strategy to split relations:
|
|
* -> Download the way members just before and just after the original way
|
|
* -> Make sure they are still aligned
|
|
*
|
|
* Note that the feature might appear multiple times.
|
|
*/
|
|
export class InPlaceReplacedmentRTSH extends AbstractRelationSplitHandler {
|
|
|
|
constructor(input: RelationSplitInput, theme: string) {
|
|
super(input, theme);
|
|
}
|
|
|
|
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
|
|
|
const wayId = this._input.originalWayId
|
|
const relation = this._input.relation
|
|
const members = relation.members
|
|
const originalNodes = this._input.originalNodes;
|
|
const firstNode = originalNodes[0]
|
|
const lastNode = originalNodes[originalNodes.length - 1]
|
|
const newMembers: { type: "node" | "way" | "relation", ref: number, role: string }[] = []
|
|
|
|
for (let i = 0; i < members.length; i++) {
|
|
const member = members[i];
|
|
if (member.type !== "way" || member.ref !== wayId) {
|
|
newMembers.push(member)
|
|
continue;
|
|
}
|
|
|
|
const nodeIdBefore = await this.targetNodeAt(i - 1, false)
|
|
const nodeIdAfter = await this.targetNodeAt(i + 1, true)
|
|
|
|
const firstNodeMatches = nodeIdBefore === undefined || nodeIdBefore === firstNode
|
|
const lastNodeMatches = nodeIdAfter === undefined || nodeIdAfter === lastNode
|
|
|
|
if (firstNodeMatches && lastNodeMatches) {
|
|
// We have a classic situation, forward situation
|
|
for (const wId of this._input.allWayIdsInOrder) {
|
|
newMembers.push({
|
|
ref: wId,
|
|
type: "way",
|
|
role: member.role
|
|
})
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const firstNodeMatchesRev = nodeIdBefore === undefined || nodeIdBefore === lastNode
|
|
const lastNodeMatchesRev = nodeIdAfter === undefined || nodeIdAfter === firstNode
|
|
if (firstNodeMatchesRev || lastNodeMatchesRev) {
|
|
// We (probably) have a reversed situation, backward situation
|
|
for (let i1 = this._input.allWayIdsInOrder.length - 1; i1 >= 0; i1--) {
|
|
// Iterate BACKWARDS
|
|
const wId = this._input.allWayIdsInOrder[i1];
|
|
newMembers.push({
|
|
ref: wId,
|
|
type: "way",
|
|
role: member.role
|
|
})
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Euhm, allright... Something weird is going on, but let's not care too much
|
|
// Lets pretend this is forward going
|
|
for (const wId of this._input.allWayIdsInOrder) {
|
|
newMembers.push({
|
|
ref: wId,
|
|
type: "way",
|
|
role: member.role
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
return [{
|
|
id: relation.id,
|
|
type: "relation",
|
|
changes: {members: newMembers},
|
|
meta: {
|
|
changeType: "relation-fix",
|
|
theme: this._theme
|
|
}
|
|
}];
|
|
}
|
|
|
|
} |