WIP: automaton page

This commit is contained in:
pietervdvn 2021-12-12 17:35:08 +01:00
parent d85ee64708
commit e1ee890f51
10 changed files with 186 additions and 41 deletions

View file

@ -188,7 +188,7 @@ export default class OverpassFeatureSource implements FeatureSource {
if (data === undefined) { if (data === undefined) {
return undefined return undefined
} }
data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined)); data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined, this.state));
self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); self.features.setData(data.features.map(f => ({feature: f, freshness: date})));
return [bounds, date, layersToDownload]; return [bounds, date, layersToDownload];
} catch (e) { } catch (e) {

View file

@ -155,7 +155,8 @@ export default class FeaturePipeline {
if (id === "current_view") { if (id === "current_view") {
handlePriviligedFeatureSource(state.currentView) handlePriviligedFeatureSource(state.currentView)
state.currentView.features.map(ffs => ffs[0]?.feature?.properties?.id).withEqualityStabilized((x,y) => x === y).addCallbackAndRunD(_ => self.applyMetaTags(state.currentView)) state.currentView.features.map(ffs => ffs[0]?.feature?.properties?.id).withEqualityStabilized((x,y) => x === y)
.addCallbackAndRunD(_ => self.applyMetaTags(state.currentView, state))
continue continue
} }
@ -291,7 +292,8 @@ export default class FeaturePipeline {
// We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this // We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer) perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
// AT last, we always apply the metatags whenever possible // AT last, we always apply the metatags whenever possible
perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer)) // @ts-ignore
perLayer.features.addCallbackAndRunD(_ => self.applyMetaTags(perLayer, state))
perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer)) perLayer.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(perLayer))
}, },
@ -449,8 +451,11 @@ export default class FeaturePipeline {
return updater; return updater;
} }
private applyMetaTags(src: FeatureSourceForLayer) { private applyMetaTags(src: FeatureSourceForLayer, state: any) {
const self = this const self = this
if(src === undefined){
throw "Src is undefined"
}
window.setTimeout( window.setTimeout(
() => { () => {
const layerDef = src.layer.layerDef; const layerDef = src.layer.layerDef;
@ -462,6 +467,7 @@ export default class FeaturePipeline {
getFeatureById: (id: string) => self.state.allElements.ContainingFeatures.get(id) getFeatureById: (id: string) => self.state.allElements.ContainingFeatures.get(id)
}, },
layerDef, layerDef,
state,
{ {
includeDates: true, includeDates: true,
// We assume that the non-dated metatags are already set by the cache generator // We assume that the non-dated metatags are already set by the cache generator
@ -479,10 +485,10 @@ export default class FeaturePipeline {
console.debug("Updating the meta tagging of all tiles as new data got loaded") console.debug("Updating the meta tagging of all tiles as new data got loaded")
this.perLayerHierarchy.forEach(hierarchy => { this.perLayerHierarchy.forEach(hierarchy => {
hierarchy.loadedTiles.forEach(tile => { hierarchy.loadedTiles.forEach(tile => {
self.applyMetaTags(tile) self.applyMetaTags(tile, <any> this.state)
}) })
}) })
this.applyMetaTags(this.state.currentView) this.applyMetaTags(this.state.currentView, <any> this.state)
self.metataggingRecalculated.ping() self.metataggingRecalculated.ping()
} }

View file

@ -1,7 +1,6 @@
import SimpleMetaTaggers from "./SimpleMetaTagger"; import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger";
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions"; import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import State from "../State";
/** /**
@ -24,6 +23,7 @@ export default class MetaTagging {
public static addMetatags(features: { feature: any; freshness: Date }[], public static addMetatags(features: { feature: any; freshness: Date }[],
params: ExtraFuncParams, params: ExtraFuncParams,
layer: LayerConfig, layer: LayerConfig,
state,
options?: { options?: {
includeDates?: true | boolean, includeDates?: true | boolean,
includeNonDates?: true | boolean includeNonDates?: true | boolean
@ -33,7 +33,7 @@ export default class MetaTagging {
return; return;
} }
const metatagsToApply: SimpleMetaTaggers[] = [] const metatagsToApply: SimpleMetaTagger[] = []
for (const metatag of SimpleMetaTaggers.metatags) { for (const metatag of SimpleMetaTaggers.metatags) {
if (metatag.includesDates) { if (metatag.includesDates) {
if (options.includeDates ?? true) { if (options.includeDates ?? true) {
@ -47,7 +47,7 @@ export default class MetaTagging {
} }
// The calculated functions - per layer - which add the new keys // The calculated functions - per layer - which add the new keys
const layerFuncs = this.createRetaggingFunc(layer) const layerFuncs = this.createRetaggingFunc(layer, state)
let atLeastOneFeatureChanged = false; let atLeastOneFeatureChanged = false;
@ -58,24 +58,17 @@ export default class MetaTagging {
let somethingChanged = false let somethingChanged = false
for (const metatag of metatagsToApply) { for (const metatag of metatagsToApply) {
try { try {
// @ts-ignore
if (!metatag.keys.some(key => feature.properties[key] === undefined)) { if (!metatag.keys.some(key => feature.properties[key] === undefined)) {
// All keys are already defined, we probably already ran this one // All keys are already defined, we probably already ran this one
continue continue
} }
// @ts-ignore
if (metatag.isLazy) { if (metatag.isLazy) {
somethingChanged = true; somethingChanged = true;
// @ts-ignore metatag.applyMetaTagsOnFeature(feature, freshness, layer, state)
metatag.applyMetaTagsOnFeature(feature, freshness, layer)
} else { } else {
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer, state)
// @ts-ignore
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer)
/* Note that the expression: /* Note that the expression:
* `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)`
* Is WRONG * Is WRONG
@ -86,7 +79,6 @@ export default class MetaTagging {
somethingChanged = newValueAdded || somethingChanged somethingChanged = newValueAdded || somethingChanged
} }
} catch (e) { } catch (e) {
// @ts-ignore
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
} }
} }
@ -101,7 +93,7 @@ export default class MetaTagging {
} }
if (somethingChanged) { if (somethingChanged) {
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping() state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
atLeastOneFeatureChanged = true atLeastOneFeatureChanged = true
} }
} }
@ -170,7 +162,7 @@ export default class MetaTagging {
private static retaggingFuncCache = new Map<string, ((feature: any) => void)[]>() private static retaggingFuncCache = new Map<string, ((feature: any) => void)[]>()
private static createRetaggingFunc(layer: LayerConfig): private static createRetaggingFunc(layer: LayerConfig, state):
((params: ExtraFuncParams, feature: any) => void) { ((params: ExtraFuncParams, feature: any) => void) {
const calculatedTags: [string, string, boolean][] = layer.calculatedTags; const calculatedTags: [string, string, boolean][] = layer.calculatedTags;
@ -196,7 +188,7 @@ export default class MetaTagging {
for (const f of functions) { for (const f of functions) {
f(feature); f(feature);
} }
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); state?.allElements?.getEventSourceById(feature.properties.id)?.ping();
} catch (e) { } catch (e) {
console.error("Invalid syntax in calculated tags or some other error: ", e) console.error("Invalid syntax in calculated tags or some other error: ", e)
} }

View file

@ -1,5 +1,4 @@
import {GeoOperations} from "./GeoOperations"; import {GeoOperations} from "./GeoOperations";
import State from "../State";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
import opening_hours from "opening_hours"; import opening_hours from "opening_hours";
import Combine from "../UI/Base/Combine"; import Combine from "../UI/Base/Combine";
@ -15,7 +14,7 @@ export class SimpleMetaTagger {
public readonly doc: string; public readonly doc: string;
public readonly isLazy: boolean; public readonly isLazy: boolean;
public readonly includesDates: boolean public readonly includesDates: boolean
public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean; public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig, state) => boolean;
/*** /***
* A function that adds some extra data to a feature * A function that adds some extra data to a feature
@ -23,7 +22,7 @@ export class SimpleMetaTagger {
* @param f: apply the changes. Returns true if something changed * @param f: apply the changes. Returns true if something changed
*/ */
constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean }, constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean },
f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) { f: ((feature: any, freshness: Date, layer: LayerConfig, state) => boolean)) {
this.keys = docs.keys; this.keys = docs.keys;
this.doc = docs.doc; this.doc = docs.doc;
this.isLazy = docs.isLazy this.isLazy = docs.isLazy
@ -54,7 +53,7 @@ export class CountryTagger extends SimpleMetaTagger {
doc: "The country code of the property (with latlon2country)", doc: "The country code of the property (with latlon2country)",
includesDates: false includesDates: false
}, },
((feature, _) => { ((feature, _, __, state) => {
let centerPoint: any = GeoOperations.centerpoint(feature); let centerPoint: any = GeoOperations.centerpoint(feature);
const lat = centerPoint.geometry.coordinates[1]; const lat = centerPoint.geometry.coordinates[1];
const lon = centerPoint.geometry.coordinates[0]; const lon = centerPoint.geometry.coordinates[0];
@ -66,7 +65,7 @@ export class CountryTagger extends SimpleMetaTagger {
const oldCountry = feature.properties["_country"]; const oldCountry = feature.properties["_country"];
feature.properties["_country"] = countries[0].trim().toLowerCase(); feature.properties["_country"] = countries[0].trim().toLowerCase();
if (oldCountry !== feature.properties["_country"]) { if (oldCountry !== feature.properties["_country"]) {
const tagsSource = State.state?.allElements?.getEventSourceById(feature.properties.id); const tagsSource = state?.allElements?.getEventSourceById(feature.properties.id);
tagsSource?.ping(); tagsSource?.ping();
} }
} catch (e) { } catch (e) {
@ -210,8 +209,8 @@ export default class SimpleMetaTaggers {
keys: ["Theme-defined keys"], keys: ["Theme-defined keys"],
}, },
(feature => { ((feature, _, __, state) => {
const units = Utils.NoNull([].concat(...State.state?.layoutToUse?.layers?.map(layer => layer.units ?? []))); const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units ?? [])));
if (units.length == 0) { if (units.length == 0) {
return; return;
} }
@ -279,7 +278,7 @@ export default class SimpleMetaTaggers {
includesDates: true, includesDates: true,
isLazy: true isLazy: true
}, },
(feature => { ((feature, _, __ ,state) => {
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {
// We are running from console, thus probably creating a cache // We are running from console, thus probably creating a cache
// isOpen is irrelevant // isOpen is irrelevant
@ -292,7 +291,7 @@ export default class SimpleMetaTaggers {
get: () => { get: () => {
delete feature.properties._isOpen delete feature.properties._isOpen
feature.properties._isOpen = undefined feature.properties._isOpen = undefined
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); const tagsSource = state.allElements.getEventSourceById(feature.properties.id);
tagsSource.addCallbackAndRunD(tags => { tagsSource.addCallbackAndRunD(tags => {
if (tags.opening_hours === undefined || tags._country === undefined) { if (tags.opening_hours === undefined || tags._country === undefined) {
return; return;

View file

@ -11,7 +11,6 @@ import {Utils} from "../../Utils";
import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; import ChangeToElementsActor from "../Actors/ChangeToElementsActor";
import PendingChangesUploader from "../Actors/PendingChangesUploader"; import PendingChangesUploader from "../Actors/PendingChangesUploader";
import TitleHandler from "../Actors/TitleHandler"; import TitleHandler from "../Actors/TitleHandler";
import FeatureSource from "../FeatureSource/FeatureSource";
/** /**
* The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc

View file

@ -9,9 +9,6 @@ import {Utils} from "../../Utils";
import Locale from "../../UI/i18n/Locale"; import Locale from "../../UI/i18n/Locale";
import ElementsState from "./ElementsState"; import ElementsState from "./ElementsState";
import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater";
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource";
import FeatureSource from "../FeatureSource/FeatureSource";
import {Feature} from "@turf/turf";
/** /**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,

149
UI/AutomatonGui.ts Normal file
View file

@ -0,0 +1,149 @@
import BaseUIElement from "./BaseUIElement";
import Combine from "./Base/Combine";
import Svg from "../Svg";
import Title from "./Base/Title";
import Toggle from "./Input/Toggle";
import {SubtleButton} from "./Base/SubtleButton";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import UserRelatedState from "../Logic/State/UserRelatedState";
import ValidatedTextField from "./Input/ValidatedTextField";
import {Utils} from "../Utils";
import {UIEventSource} from "../Logic/UIEventSource";
import {VariableUiElement} from "./Base/VariableUIElement";
import {FixedUiElement} from "./Base/FixedUiElement";
import {Tiles} from "../Models/TileRange";
import {LocalStorageSource} from "../Logic/Web/LocalStorageSource";
import {DropDown} from "./Input/DropDown";
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import MinimapImplementation from "./Base/MinimapImplementation";
import State from "../State";
import {OsmConnection} from "../Logic/Osm/OsmConnection";
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
export default class AutomatonGui extends Combine {
constructor() {
const osmConnection = new OsmConnection({
allElements: undefined,
changes: undefined,
layoutName: "automaton",
singlePage: true
});
super([
new Combine([Svg.robot_svg().SetClass("w-24 h-24 p-4 rounded-full subtle-background"),
new Combine([new Title("MapComplete Automaton", 1),
"This page helps to automate certain tasks for a theme. Expert use only."
]).SetClass("flex flex-col m-4")
]).SetClass("flex"),
new Toggle(
AutomatonGui.GenerateMainPanel(),
new SubtleButton(Svg.osm_logo_svg(), "Login to get started"),
osmConnection.isLoggedIn
)])
}
private static AutomationPanel(layoutToUse: LayoutConfig, tiles: UIEventSource<number[]>): BaseUIElement {
const handledTiles = new UIEventSource(0)
const state = new FeaturePipelineState(layoutToUse)
const nextTile = tiles.map(indices => {
if (indices === undefined) {
return "No tiles loaded - can not automate";
}
const currentTile = handledTiles.data
const tileIndex = indices[currentTile]
if (tileIndex === undefined) {
return "All done!";
}
return "" + tileIndex
}, [handledTiles])
return new Combine([
new VariableUiElement(handledTiles.map(i => "" + i)),
new VariableUiElement(nextTile)
])
}
private static GenerateMainPanel(): BaseUIElement {
const themeSelect = new DropDown<string>("Select a theme",
AllKnownLayouts.layoutsList.map(l => ({value: l.id, shown: l.id}))
)
LocalStorageSource.Get("automation-theme-id").syncWith(themeSelect.GetValue())
const tilepath = ValidatedTextField.InputForType("url", {
placeholder: "Specifiy the path of the overview",
})
tilepath.SetClass("w-full")
LocalStorageSource.Get("automation-tile_path").syncWith(tilepath.GetValue(), true)
const tilesToRunOver = tilepath.GetValue().bind(path => {
if (path === undefined) {
return undefined
}
return UIEventSource.FromPromiseWithErr(Utils.downloadJson(path))
})
const tilesPerIndex = tilesToRunOver.map(tiles => {
if (tiles === undefined || tiles["error"] !== undefined) {
return undefined
}
let indexes = [];
const tilesS = tiles["success"]
const z = Number(tilesS["zoom"])
for (const key in tilesS) {
if (key === "zoom") {
continue
}
const x = Number(key)
const ys = tilesS[key]
indexes.push(...ys.map(y => Tiles.tile_index(z, x, y)))
}
return indexes
})
return new Combine([
themeSelect,
"Specify the path to a tile overview. This is a hosted .json of the format {x : [y0, y1, y2], x1: [y0, ...]} where x is a string and y are numbers",
tilepath,
new VariableUiElement(tilesToRunOver.map(t => {
if (t === undefined) {
return "No path given or still loading..."
}
if (t["error"] !== undefined) {
return new FixedUiElement("Invalid URL or data: " + t["error"]).SetClass("alert")
}
return new FixedUiElement("Loaded " + tilesPerIndex.data.length + " tiles to automated over").SetClass("thanks")
})),
new VariableUiElement(themeSelect.GetValue().map(id => AllKnownLayouts.allKnownLayouts.get(id)).map(layoutToUse => {
if (layoutToUse === undefined) {
return new FixedUiElement("Select a valid layout")
}
return AutomatonGui.AutomationPanel(layoutToUse, tilesPerIndex)
}))
]).SetClass("flex flex-col")
}
}
MinimapImplementation.initialize()
new AutomatonGui()
.SetClass("block p-4")
.AttachTo("main")

View file

@ -7,7 +7,6 @@ import Svg from "../../Svg";
import {Tag} from "../../Logic/Tags/Tag"; import {Tag} from "../../Logic/Tags/Tag";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
export default class DeleteImage extends Toggle { export default class DeleteImage extends Toggle {
constructor(key: string, tags: UIEventSource<any>) { constructor(key: string, tags: UIEventSource<any>) {

View file

@ -1,13 +1,13 @@
{ {
"id": "missing_streets", "id": "missing_streets",
"title": { "title": {
"nl": "GRB import helper" "nl": "Fix ontbrekende straten"
}, },
"shortDescription": { "shortDescription": {
"nl": "Grb import helper tool" "nl": "Voegt ontbrekende straten toe aan gebouwen met huisnumer adhv CRAB"
}, },
"description": { "description": {
"nl": "Dit thema voegt semi-automatisch straatnamen toe aan gebouwen met huisnummer en overeenkomstig CRAB-adres." "nl": "Dit thema voegt automatisch straatnamen toe aan gebouwen met huisnummer en overeenkomstig CRAB-adres."
}, },
"language": [ "language": [
"nl" "nl"
@ -36,7 +36,7 @@
"point" "point"
], ],
"icon": { "icon": {
"render": "./assets/themes/grb_import/robot.svg" "render": "./assets/svg/robot.svg"
}, },
"iconSize": "15,15,center" "iconSize": "15,15,center"
} }

View file

@ -1052,6 +1052,10 @@ video {
width: 100%; width: 100%;
} }
.w-24 {
width: 6rem;
}
.w-10 { .w-10 {
width: 2.5rem; width: 2.5rem;
} }