WIP: automaton page
This commit is contained in:
parent
d85ee64708
commit
e1ee890f51
10 changed files with 186 additions and 41 deletions
|
@ -188,7 +188,7 @@ export default class OverpassFeatureSource implements FeatureSource {
|
|||
if (data === 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})));
|
||||
return [bounds, date, layersToDownload];
|
||||
} catch (e) {
|
||||
|
|
|
@ -155,7 +155,8 @@ export default class FeaturePipeline {
|
|||
|
||||
if (id === "current_view") {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer)
|
||||
// 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))
|
||||
|
||||
},
|
||||
|
@ -449,8 +451,11 @@ export default class FeaturePipeline {
|
|||
return updater;
|
||||
}
|
||||
|
||||
private applyMetaTags(src: FeatureSourceForLayer) {
|
||||
private applyMetaTags(src: FeatureSourceForLayer, state: any) {
|
||||
const self = this
|
||||
if(src === undefined){
|
||||
throw "Src is undefined"
|
||||
}
|
||||
window.setTimeout(
|
||||
() => {
|
||||
const layerDef = src.layer.layerDef;
|
||||
|
@ -462,6 +467,7 @@ export default class FeaturePipeline {
|
|||
getFeatureById: (id: string) => self.state.allElements.ContainingFeatures.get(id)
|
||||
},
|
||||
layerDef,
|
||||
state,
|
||||
{
|
||||
includeDates: true,
|
||||
// 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")
|
||||
this.perLayerHierarchy.forEach(hierarchy => {
|
||||
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()
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import SimpleMetaTaggers from "./SimpleMetaTagger";
|
||||
import SimpleMetaTaggers, {SimpleMetaTagger} from "./SimpleMetaTagger";
|
||||
import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions";
|
||||
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 }[],
|
||||
params: ExtraFuncParams,
|
||||
layer: LayerConfig,
|
||||
state,
|
||||
options?: {
|
||||
includeDates?: true | boolean,
|
||||
includeNonDates?: true | boolean
|
||||
|
@ -33,7 +33,7 @@ export default class MetaTagging {
|
|||
return;
|
||||
}
|
||||
|
||||
const metatagsToApply: SimpleMetaTaggers[] = []
|
||||
const metatagsToApply: SimpleMetaTagger[] = []
|
||||
for (const metatag of SimpleMetaTaggers.metatags) {
|
||||
if (metatag.includesDates) {
|
||||
if (options.includeDates ?? true) {
|
||||
|
@ -47,7 +47,7 @@ export default class MetaTagging {
|
|||
}
|
||||
|
||||
// The calculated functions - per layer - which add the new keys
|
||||
const layerFuncs = this.createRetaggingFunc(layer)
|
||||
const layerFuncs = this.createRetaggingFunc(layer, state)
|
||||
|
||||
let atLeastOneFeatureChanged = false;
|
||||
|
||||
|
@ -58,24 +58,17 @@ export default class MetaTagging {
|
|||
let somethingChanged = false
|
||||
for (const metatag of metatagsToApply) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (!metatag.keys.some(key => feature.properties[key] === undefined)) {
|
||||
// All keys are already defined, we probably already ran this one
|
||||
continue
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (metatag.isLazy) {
|
||||
somethingChanged = true;
|
||||
|
||||
// @ts-ignore
|
||||
metatag.applyMetaTagsOnFeature(feature, freshness, layer)
|
||||
|
||||
metatag.applyMetaTagsOnFeature(feature, freshness, layer, state)
|
||||
} else {
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer)
|
||||
const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer, state)
|
||||
/* Note that the expression:
|
||||
* `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)`
|
||||
* Is WRONG
|
||||
|
@ -86,7 +79,6 @@ export default class MetaTagging {
|
|||
somethingChanged = newValueAdded || somethingChanged
|
||||
}
|
||||
} catch (e) {
|
||||
// @ts-ignore
|
||||
console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack)
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +93,7 @@ export default class MetaTagging {
|
|||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
|
||||
state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
|
||||
atLeastOneFeatureChanged = true
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +162,7 @@ export default class MetaTagging {
|
|||
|
||||
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) {
|
||||
|
||||
const calculatedTags: [string, string, boolean][] = layer.calculatedTags;
|
||||
|
@ -196,7 +188,7 @@ export default class MetaTagging {
|
|||
for (const f of functions) {
|
||||
f(feature);
|
||||
}
|
||||
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping();
|
||||
state?.allElements?.getEventSourceById(feature.properties.id)?.ping();
|
||||
} catch (e) {
|
||||
console.error("Invalid syntax in calculated tags or some other error: ", e)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {GeoOperations} from "./GeoOperations";
|
||||
import State from "../State";
|
||||
import {Utils} from "../Utils";
|
||||
import opening_hours from "opening_hours";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
|
@ -15,7 +14,7 @@ export class SimpleMetaTagger {
|
|||
public readonly doc: string;
|
||||
public readonly isLazy: 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
|
||||
|
@ -23,7 +22,7 @@ export class SimpleMetaTagger {
|
|||
* @param f: apply the changes. Returns true if something changed
|
||||
*/
|
||||
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.doc = docs.doc;
|
||||
this.isLazy = docs.isLazy
|
||||
|
@ -54,7 +53,7 @@ export class CountryTagger extends SimpleMetaTagger {
|
|||
doc: "The country code of the property (with latlon2country)",
|
||||
includesDates: false
|
||||
},
|
||||
((feature, _) => {
|
||||
((feature, _, __, state) => {
|
||||
let centerPoint: any = GeoOperations.centerpoint(feature);
|
||||
const lat = centerPoint.geometry.coordinates[1];
|
||||
const lon = centerPoint.geometry.coordinates[0];
|
||||
|
@ -66,7 +65,7 @@ export class CountryTagger extends SimpleMetaTagger {
|
|||
const oldCountry = feature.properties["_country"];
|
||||
feature.properties["_country"] = countries[0].trim().toLowerCase();
|
||||
if (oldCountry !== feature.properties["_country"]) {
|
||||
const tagsSource = State.state?.allElements?.getEventSourceById(feature.properties.id);
|
||||
const tagsSource = state?.allElements?.getEventSourceById(feature.properties.id);
|
||||
tagsSource?.ping();
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -210,8 +209,8 @@ export default class SimpleMetaTaggers {
|
|||
keys: ["Theme-defined keys"],
|
||||
|
||||
},
|
||||
(feature => {
|
||||
const units = Utils.NoNull([].concat(...State.state?.layoutToUse?.layers?.map(layer => layer.units ?? [])));
|
||||
((feature, _, __, state) => {
|
||||
const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units ?? [])));
|
||||
if (units.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -279,7 +278,7 @@ export default class SimpleMetaTaggers {
|
|||
includesDates: true,
|
||||
isLazy: true
|
||||
},
|
||||
(feature => {
|
||||
((feature, _, __ ,state) => {
|
||||
if (Utils.runningFromConsole) {
|
||||
// We are running from console, thus probably creating a cache
|
||||
// isOpen is irrelevant
|
||||
|
@ -292,7 +291,7 @@ export default class SimpleMetaTaggers {
|
|||
get: () => {
|
||||
delete feature.properties._isOpen
|
||||
feature.properties._isOpen = undefined
|
||||
const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id);
|
||||
const tagsSource = state.allElements.getEventSourceById(feature.properties.id);
|
||||
tagsSource.addCallbackAndRunD(tags => {
|
||||
if (tags.opening_hours === undefined || tags._country === undefined) {
|
||||
return;
|
||||
|
|
|
@ -11,7 +11,6 @@ import {Utils} from "../../Utils";
|
|||
import ChangeToElementsActor from "../Actors/ChangeToElementsActor";
|
||||
import PendingChangesUploader from "../Actors/PendingChangesUploader";
|
||||
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
|
||||
|
|
|
@ -9,9 +9,6 @@ import {Utils} from "../../Utils";
|
|||
import Locale from "../../UI/i18n/Locale";
|
||||
import ElementsState from "./ElementsState";
|
||||
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,
|
||||
|
|
149
UI/AutomatonGui.ts
Normal file
149
UI/AutomatonGui.ts
Normal 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")
|
|
@ -7,7 +7,6 @@ import Svg from "../../Svg";
|
|||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
||||
|
||||
|
||||
export default class DeleteImage extends Toggle {
|
||||
|
||||
constructor(key: string, tags: UIEventSource<any>) {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"id": "missing_streets",
|
||||
"title": {
|
||||
"nl": "GRB import helper"
|
||||
"nl": "Fix ontbrekende straten"
|
||||
},
|
||||
"shortDescription": {
|
||||
"nl": "Grb import helper tool"
|
||||
"nl": "Voegt ontbrekende straten toe aan gebouwen met huisnumer adhv CRAB"
|
||||
},
|
||||
"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": [
|
||||
"nl"
|
||||
|
@ -36,7 +36,7 @@
|
|||
"point"
|
||||
],
|
||||
"icon": {
|
||||
"render": "./assets/themes/grb_import/robot.svg"
|
||||
"render": "./assets/svg/robot.svg"
|
||||
},
|
||||
"iconSize": "15,15,center"
|
||||
}
|
||||
|
|
|
@ -1052,6 +1052,10 @@ video {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.w-24 {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue