Add metadata in changeset with (binned) distance to changed feature

This commit is contained in:
Pieter Vander Vennet 2021-11-09 01:49:07 +01:00
parent e8ce53d5eb
commit 8e66313ef1
21 changed files with 178 additions and 41 deletions

View file

@ -8,6 +8,11 @@ import {Utils} from "../../Utils";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import SimpleMetaTagger from "../SimpleMetaTagger";
import CreateNewNodeAction from "./Actions/CreateNewNodeAction";
import FeatureSource from "../FeatureSource/FeatureSource";
import {ElementStorage} from "../ElementStorage";
import {GeoLocationPointProperties} from "../Actors/GeoLocationHandler";
import {GeoOperations} from "../GeoOperations";
import {ChangesetTag} from "./ChangesetHandler";
/**
* Handles all changes made to OSM.
@ -27,6 +32,8 @@ export class Changes {
private readonly previouslyCreated: OsmObject[] = []
private readonly _leftRightSensitive: boolean;
private _state : { allElements: ElementStorage; historicalUserLocations: FeatureSource }
constructor(leftRightSensitive: boolean = false) {
this._leftRightSensitive = leftRightSensitive;
@ -113,14 +120,71 @@ export class Changes {
})
}
public async applyAction(action: OsmChangeAction): Promise<void> {
this.applyChanges(await action.Perform(this))
}
private calculateDistanceToChanges(change: OsmChangeAction, changeDescriptions: ChangeDescription[]){
public async applyActions(actions: OsmChangeAction[]) {
for (const action of actions) {
await this.applyAction(action)
if (this._state === undefined) {
// No state loaded -> we can't calculate...
return;
}
if(!change.trackStatistics){
// Probably irrelevant, such as a new helper node
return;
}
const now = new Date()
const recentLocationPoints = this._state.historicalUserLocations.features.data.map(ff => ff.feature)
.filter(feat => feat.geometry.type === "Point")
.filter(feat => {
const visitTime = new Date((<GeoLocationPointProperties>feat.properties).date)
// In seconds
const diff = (now.getTime() - visitTime.getTime()) / 1000
return diff < Constants.nearbyVisitTime;
})
if(recentLocationPoints.length === 0){
// Probably no GPS enabled/no fix
return;
}
// The applicable points, contain information in their properties about location, time and GPS accuracy
// They are all GeoLocationPointProperties
// We walk every change and determine the closest distance possible
// Only if the change itself does _not_ contain any coordinates, we fall back and search the original feature in the state
const changedObjectCoordinates : [number, number][] = []
const feature = this._state.allElements.ContainingFeatures.get(change.mainObjectId)
if(feature !== undefined){
changedObjectCoordinates.push(GeoOperations.centerpointCoordinates(feature))
}
for (const changeDescription of changeDescriptions) {
const chng : {lat: number, lon: number} | {coordinates : [number,number][]} | {members} = changeDescription.changes
if(chng === undefined){
continue
}
if(chng["lat"] !== undefined){
changedObjectCoordinates.push([chng["lat"],chng["lon"]])
}
if(chng["coordinates"] !== undefined){
changedObjectCoordinates.push(...chng["coordinates"])
}
}
const leastDistance = Math.min(...changedObjectCoordinates.map(coor =>
Math.min(...recentLocationPoints.map(gpsPoint => {
const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint)
const dist = GeoOperations.distanceBetween(coor, otherCoor) * 1000;
console.log("Comparing ", coor, "and ", otherCoor, " --> ", dist)
return dist
}))
))
return leastDistance
}
public async applyAction(action: OsmChangeAction): Promise<void> {
const changeDescriptions = await action.Perform(this)
const distanceToObject = this.calculateDistanceToChanges(action, changeDescriptions)
changeDescriptions[0].meta.distanceToObject = distanceToObject
this.applyChanges(changeDescriptions)
}
public applyChanges(changes: ChangeDescription[]) {
@ -130,6 +194,13 @@ export class Changes {
this.allChanges.data.push(...changes)
this.allChanges.ping()
}
public useLocationHistory(state: {
allElements: ElementStorage,
historicalUserLocations: FeatureSource
}){
this._state= state
}
public registerIdRewrites(mappings: Map<string, string>): void {
CreateNewNodeAction.registerIdRewrites(mappings)
@ -162,7 +233,6 @@ export class Changes {
return true
}
const meta = pending[0].meta
const perType = Array.from(
Utils.Hist(pending.filter(descr => descr.meta.changeType !== undefined && descr.meta.changeType !== null)
@ -177,16 +247,46 @@ export class Changes {
key: descr.meta.changeType + ":" + descr.type + "/" + descr.id,
value: descr.meta.specialMotivation
}))
const metatags = [{
const distances = Utils.NoNull(pending.map(descr => descr.meta.distanceToObject));
distances.sort((a, b) => a - b)
const perBinCount = Constants.distanceToChangeObjectBins.map(_ => 0)
let j = 0;
const maxDistances = Constants.distanceToChangeObjectBins
for (let i = 0; i < maxDistances.length; i++){
const maxDistance = maxDistances[i];
// distances is sorted in ascending order, so as soon as one is to big, all the resting elements will be bigger too
while(j < distances.length && distances[j] < maxDistance){
perBinCount[i] ++
j++
}
}
const perBinMessage = Utils.NoNull(perBinCount.map((count, i) => {
if(count === 0){
return undefined
}
return {
key: "change_within_"+maxDistances[i]+"m",
value: count,
aggregate:true
}
}))
// This method is only called with changedescriptions for this theme
const theme = pending[0].meta.theme
const metatags : ChangesetTag[] = [{
key: "comment",
value: "Adding data with #MapComplete for theme #" + meta.theme
value: "Adding data with #MapComplete for theme #" + theme
},
{
key: "theme",
value: meta.theme
value: theme
},
...perType,
...motivations
...motivations,
...perBinMessage
]
await State.state.osmConnection.changesetHandler.UploadChangeset(