forked from MapComplete/MapComplete
Fix import flow for more advanced scenarios
This commit is contained in:
parent
913dc07eea
commit
700b48f693
18 changed files with 871 additions and 575 deletions
|
@ -39,6 +39,9 @@ export default class SharedTagRenderings {
|
||||||
}
|
}
|
||||||
|
|
||||||
dict.forEach((value, key) => {
|
dict.forEach((value, key) => {
|
||||||
|
if(key === "id"){
|
||||||
|
return;
|
||||||
|
}
|
||||||
value.id = value.id ?? key;
|
value.id = value.id ?? key;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -62,4 +62,9 @@ export default class Toggleable extends Combine {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collapse() : Toggleable{
|
||||||
|
this.isVisible.setData(false)
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -22,9 +22,12 @@ export default class Histogram<T> extends VariableUiElement {
|
||||||
constructor(values: UIEventSource<string[]>,
|
constructor(values: UIEventSource<string[]>,
|
||||||
title: string | BaseUIElement,
|
title: string | BaseUIElement,
|
||||||
countTitle: string | BaseUIElement,
|
countTitle: string | BaseUIElement,
|
||||||
assignColor?: (t0: string) => string
|
options?:{
|
||||||
|
assignColor?: (t0: string) => string,
|
||||||
|
sortMode?: "name" | "name-rev" | "count" | "count-rev"
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">("name")
|
const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">(options?.sortMode ??"name")
|
||||||
const sortName = new VariableUiElement(sortMode.map(m => {
|
const sortName = new VariableUiElement(sortMode.map(m => {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case "name":
|
case "name":
|
||||||
|
@ -107,11 +110,11 @@ export default class Histogram<T> extends VariableUiElement {
|
||||||
return Histogram.defaultPalette[index % Histogram.defaultPalette.length]
|
return Histogram.defaultPalette[index % Histogram.defaultPalette.length]
|
||||||
};
|
};
|
||||||
let actualAssignColor = undefined;
|
let actualAssignColor = undefined;
|
||||||
if (assignColor === undefined) {
|
if (options?.assignColor === undefined) {
|
||||||
actualAssignColor = fallbackColor;
|
actualAssignColor = fallbackColor;
|
||||||
} else {
|
} else {
|
||||||
actualAssignColor = (keyValue: string) => {
|
actualAssignColor = (keyValue: string) => {
|
||||||
return assignColor(keyValue) ?? fallbackColor(keyValue)
|
return options.assignColor(keyValue) ?? fallbackColor(keyValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,11 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
tagRenderings: new Map()
|
tagRenderings: new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
const layerConfig = known_layers.filter(l => l.id === params.layer.id)[0]
|
|
||||||
|
const layerConfig = known_layers.layers.filter(l => l.id === params.layer.id)[0]
|
||||||
|
if(layerConfig === undefined){
|
||||||
|
console.error("WEIRD: layer not found in the builtin layer overview")
|
||||||
|
}
|
||||||
const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson> layerConfig, "CompareToAlreadyExistingNotes")
|
const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson> layerConfig, "CompareToAlreadyExistingNotes")
|
||||||
const importLayer = new LayerConfig(importLayerJson, "import-layer-dynamic")
|
const importLayer = new LayerConfig(importLayerJson, "import-layer-dynamic")
|
||||||
const flayer: FilteredLayer = {
|
const flayer: FilteredLayer = {
|
||||||
|
@ -48,8 +52,8 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
isDisplayed: new UIEventSource<boolean>(true),
|
isDisplayed: new UIEventSource<boolean>(true),
|
||||||
layerDef: importLayer
|
layerDef: importLayer
|
||||||
}
|
}
|
||||||
const unfiltered = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001))
|
const allNotesWithinBbox = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001))
|
||||||
unfiltered.features.map(f => MetaTagging.addMetatags(
|
allNotesWithinBbox.features.map(f => MetaTagging.addMetatags(
|
||||||
f,
|
f,
|
||||||
{
|
{
|
||||||
memberships: new RelationsTracker(),
|
memberships: new RelationsTracker(),
|
||||||
|
@ -65,30 +69,30 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const data = new FilteringFeatureSource(state, undefined, unfiltered)
|
const alreadyOpenImportNotes = new FilteringFeatureSource(state, undefined, allNotesWithinBbox)
|
||||||
data.features.addCallbackD(features => console.log("Loaded and filtered features are", features))
|
alreadyOpenImportNotes.features.addCallbackD(features => console.log("Loaded and filtered features are", features))
|
||||||
const map = Minimap.createMiniMap()
|
const map = Minimap.createMiniMap()
|
||||||
map.SetClass("w-full").SetStyle("height: 500px")
|
map.SetClass("w-full").SetStyle("height: 500px")
|
||||||
|
|
||||||
const comparison = Minimap.createMiniMap({
|
const comparisonMap = Minimap.createMiniMap({
|
||||||
location: map.location,
|
location: map.location,
|
||||||
|
|
||||||
})
|
})
|
||||||
comparison.SetClass("w-full").SetStyle("height: 500px")
|
comparisonMap.SetClass("w-full").SetStyle("height: 500px")
|
||||||
|
|
||||||
new ShowDataLayer({
|
new ShowDataLayer({
|
||||||
layerToShow: importLayer,
|
layerToShow: importLayer,
|
||||||
state,
|
state,
|
||||||
zoomToFeatures: true,
|
zoomToFeatures: true,
|
||||||
leafletMap: map.leafletMap,
|
leafletMap: map.leafletMap,
|
||||||
features: data,
|
features: alreadyOpenImportNotes,
|
||||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state)
|
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const maxDistance = new UIEventSource<number>(5)
|
const maxDistance = new UIEventSource<number>(5)
|
||||||
|
|
||||||
const partitionedImportPoints = ImportUtils.partitionFeaturesIfNearby(params.geojson, data.features
|
const partitionedImportPoints = ImportUtils.partitionFeaturesIfNearby(params.geojson, alreadyOpenImportNotes.features
|
||||||
.map(ff => ({features: ff.map(ff => ff.feature)})), maxDistance)
|
.map(ff => ({features: ff.map(ff => ff.feature)})), maxDistance)
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,16 +100,22 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
layerToShow: new LayerConfig(import_candidate),
|
layerToShow: new LayerConfig(import_candidate),
|
||||||
state,
|
state,
|
||||||
zoomToFeatures: true,
|
zoomToFeatures: true,
|
||||||
leafletMap: comparison.leafletMap,
|
leafletMap: comparisonMap.leafletMap,
|
||||||
features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false),
|
features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false),
|
||||||
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state)
|
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state)
|
||||||
})
|
})
|
||||||
|
|
||||||
super([
|
super([
|
||||||
new Title("Compare with already existing 'to-import'-notes"),
|
new Title("Compare with already existing 'to-import'-notes"),
|
||||||
new Toggle(
|
new VariableUiElement(
|
||||||
new Loading("Fetching notes from OSM"),
|
alreadyOpenImportNotes.features.map(notesWithImport => {
|
||||||
new Combine([
|
if(allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0){
|
||||||
|
return new Loading("Fetching notes from OSM")
|
||||||
|
}
|
||||||
|
if(notesWithImport.length === 0){
|
||||||
|
return new FixedUiElement("No previous note to import found").SetClass("thanks")
|
||||||
|
}
|
||||||
|
return new Combine([
|
||||||
map,
|
map,
|
||||||
"The following (red) elements are elements to import which are nearby a matching element that is already up for import. These won't be imported",
|
"The following (red) elements are elements to import which are nearby a matching element that is already up for import. These won't be imported",
|
||||||
|
|
||||||
|
@ -114,9 +124,10 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
new VariableUiElement(partitionedImportPoints.map(({noNearby}) => noNearby.length + " elements can be imported")).SetClass("thanks p-8"),
|
new VariableUiElement(partitionedImportPoints.map(({noNearby}) => noNearby.length + " elements can be imported")).SetClass("thanks p-8"),
|
||||||
partitionedImportPoints.map(({noNearby}) => noNearby.length === 0)
|
partitionedImportPoints.map(({noNearby}) => noNearby.length === 0)
|
||||||
).SetClass("w-full"),
|
).SetClass("w-full"),
|
||||||
comparison,
|
comparisonMap,
|
||||||
]).SetClass("flex flex-col"),
|
]).SetClass("flex flex-col")
|
||||||
unfiltered.features.map(ff => ff === undefined || ff.length === 0)
|
|
||||||
|
}, [allNotesWithinBbox.features])
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +139,18 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
|
||||||
layer: params.layer
|
layer: params.layer
|
||||||
}))
|
}))
|
||||||
|
|
||||||
this.IsValid = data.features.map(ff => ff.length > 0 && partitionedImportPoints.data.noNearby.length > 0, [partitionedImportPoints])
|
this.IsValid = alreadyOpenImportNotes.features.map(ff => {
|
||||||
|
if(allNotesWithinBbox.features.data.length === 0){
|
||||||
|
// Not yet loaded
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(ff.length == 0){
|
||||||
|
// No import notes at all
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return partitionedImportPoints.data.noNearby.length > 0; // at least _something_ can be imported
|
||||||
|
}, [partitionedImportPoints, allNotesWithinBbox.features])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import Translations from "../i18n/Translations";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
|
|
||||||
export interface FlowStep<T> extends BaseUIElement {
|
export interface FlowStep<T> extends BaseUIElement {
|
||||||
readonly IsValid: UIEventSource<boolean>
|
readonly IsValid: UIEventSource<boolean>
|
||||||
|
@ -95,10 +96,12 @@ export class FlowPanel<T> extends Toggle {
|
||||||
})
|
})
|
||||||
|
|
||||||
let elements: (BaseUIElement | string)[] = []
|
let elements: (BaseUIElement | string)[] = []
|
||||||
|
const isError = new UIEventSource(false)
|
||||||
if (initial !== undefined) {
|
if (initial !== undefined) {
|
||||||
// Startup the flow
|
// Startup the flow
|
||||||
elements = [
|
elements = [
|
||||||
initial,
|
initial,
|
||||||
|
|
||||||
new Combine([
|
new Combine([
|
||||||
backbutton,
|
backbutton,
|
||||||
new Toggle(
|
new Toggle(
|
||||||
|
@ -107,14 +110,25 @@ export class FlowPanel<T> extends Toggle {
|
||||||
Svg.back_svg().SetStyle("transform: rotate(180deg);"),
|
Svg.back_svg().SetStyle("transform: rotate(180deg);"),
|
||||||
isConfirm ? t.confirm : t.next
|
isConfirm ? t.confirm : t.next
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
|
try {
|
||||||
|
|
||||||
const v = initial.Value.data;
|
const v = initial.Value.data;
|
||||||
nextStep.setData(constructNextstep(v, backButtonForNextStep))
|
nextStep.setData(constructNextstep(v, backButtonForNextStep))
|
||||||
currentStepActive.setData(false)
|
currentStepActive.setData(false)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
isError.setData(true)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
"Select a valid value to continue",
|
"Select a valid value to continue",
|
||||||
initial.IsValid
|
initial.IsValid
|
||||||
)
|
),
|
||||||
]).SetClass("flex w-full justify-end space-x-2")
|
new Toggle(
|
||||||
|
new FixedUiElement("Something went wrong...").SetClass("alert"),
|
||||||
|
undefined,
|
||||||
|
isError),
|
||||||
|
]).SetClass("flex w-full justify-end space-x-2"),
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {LoginToggle} from "../Popup/LoginButton";
|
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import LanguagePicker from "../LanguagePicker";
|
import LanguagePicker from "../LanguagePicker";
|
||||||
import BackToIndex from "../BigComponents/BackToIndex";
|
import BackToIndex from "../BigComponents/BackToIndex";
|
||||||
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
import MoreScreen from "../BigComponents/MoreScreen";
|
|
||||||
import MinimapImplementation from "../Base/MinimapImplementation";
|
import MinimapImplementation from "../Base/MinimapImplementation";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Constants from "../../Models/Constants";
|
|
||||||
import {FlowPanelFactory} from "./FlowStep";
|
import {FlowPanelFactory} from "./FlowStep";
|
||||||
import {RequestFile} from "./RequestFile";
|
import {RequestFile} from "./RequestFile";
|
||||||
import {DataPanel} from "./DataPanel";
|
import {PreviewPanel} from "./PreviewPanel";
|
||||||
import ConflationChecker from "./ConflationChecker";
|
import ConflationChecker from "./ConflationChecker";
|
||||||
import {AskMetadata} from "./AskMetadata";
|
import {AskMetadata} from "./AskMetadata";
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
|
@ -21,8 +18,11 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import List from "../Base/List";
|
import List from "../Base/List";
|
||||||
import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes";
|
import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes";
|
||||||
|
import Introdution from "./Introdution";
|
||||||
|
import LoginToImport from "./LoginToImport";
|
||||||
|
import {MapPreview} from "./MapPreview";
|
||||||
|
|
||||||
export default class ImportHelperGui extends LoginToggle {
|
export default class ImportHelperGui extends Combine {
|
||||||
constructor() {
|
constructor() {
|
||||||
const t = Translations.t.importHelper;
|
const t = Translations.t.importHelper;
|
||||||
|
|
||||||
|
@ -33,8 +33,11 @@ export default class ImportHelperGui extends LoginToggle {
|
||||||
|
|
||||||
const {flow, furthestStep, titles} =
|
const {flow, furthestStep, titles} =
|
||||||
FlowPanelFactory
|
FlowPanelFactory
|
||||||
.start("Select file", new RequestFile())
|
.start("Introduction", new Introdution())
|
||||||
.then("Inspect data", geojson => new DataPanel(state, geojson))
|
.then("Login", _ => new LoginToImport(state))
|
||||||
|
.then("Select file", _ => new RequestFile())
|
||||||
|
.then("Inspect attributes", geojson => new PreviewPanel(state, geojson))
|
||||||
|
.then("Inspect data", geojson => new MapPreview(state, geojson))
|
||||||
.then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v))
|
.then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v))
|
||||||
.then("Compare with existing data", v => new ConflationChecker(state, v))
|
.then("Compare with existing data", v => new ConflationChecker(state, v))
|
||||||
.then("License and community check", v => new ConfirmProcess(v))
|
.then("License and community check", v => new ConfirmProcess(v))
|
||||||
|
@ -71,24 +74,12 @@ export default class ImportHelperGui extends LoginToggle {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
super(
|
super([
|
||||||
new Toggle(
|
|
||||||
new Combine([
|
new Combine([
|
||||||
leftBar,
|
leftBar,
|
||||||
flow.SetClass("m-8 w-full mb-24")
|
flow.SetClass("m-8 w-full mb-24")
|
||||||
]).SetClass("h-full block md:flex")
|
]).SetClass("h-full block md:flex")])
|
||||||
|
|
||||||
,
|
|
||||||
new Combine([
|
|
||||||
t.lockNotice.Subs(Constants.userJourney),
|
|
||||||
MoreScreen.CreateProffessionalSerivesButton()
|
|
||||||
])
|
|
||||||
|
|
||||||
,
|
|
||||||
state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.importHelperUnlock)),
|
|
||||||
|
|
||||||
"Login needed...",
|
|
||||||
state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
1
UI/ImportFlow/ImportInspector.ts
Normal file
1
UI/ImportFlow/ImportInspector.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default class ImportInspector {}
|
|
@ -7,9 +7,11 @@ export class ImportUtils {
|
||||||
if (osmData?.features === undefined) {
|
if (osmData?.features === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
if(osmData.features.length === 0){
|
||||||
|
return {noNearby: toPartitionFeatureCollection.features, hasNearby: []}
|
||||||
|
}
|
||||||
const maxDist = cutoffDistanceInMeters.data
|
const maxDist = cutoffDistanceInMeters.data
|
||||||
|
|
||||||
|
|
||||||
const hasNearby = []
|
const hasNearby = []
|
||||||
const noNearby = []
|
const noNearby = []
|
||||||
for (const toImportElement of toPartitionFeatureCollection.features) {
|
for (const toImportElement of toPartitionFeatureCollection.features) {
|
||||||
|
|
20
UI/ImportFlow/Introdution.ts
Normal file
20
UI/ImportFlow/Introdution.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import {FlowStep} from "./FlowStep";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import Title from "../Base/Title";
|
||||||
|
|
||||||
|
export default class Introdution extends Combine implements FlowStep<void> {
|
||||||
|
readonly IsValid: UIEventSource<boolean> = new UIEventSource<boolean>(true);
|
||||||
|
readonly Value: UIEventSource<void> = new UIEventSource<void>(undefined);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super([
|
||||||
|
new Title( Translations.t.importHelper.title),
|
||||||
|
Translations.t.importHelper.description,
|
||||||
|
Translations.t.importHelper.importFormat,
|
||||||
|
]);
|
||||||
|
this.SetClass("flex flex-col")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
UI/ImportFlow/LoginToImport.ts
Normal file
52
UI/ImportFlow/LoginToImport.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import {FlowStep} from "./FlowStep";
|
||||||
|
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import Title from "../Base/Title";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import {LoginToggle} from "../Popup/LoginButton";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
import Constants from "../../Models/Constants";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
|
import Svg from "../../Svg";
|
||||||
|
import MoreScreen from "../BigComponents/MoreScreen";
|
||||||
|
|
||||||
|
export default class LoginToImport extends Combine implements FlowStep<UserRelatedState> {
|
||||||
|
readonly IsValid: UIEventSource<boolean>;
|
||||||
|
readonly Value: UIEventSource<UserRelatedState>;
|
||||||
|
|
||||||
|
constructor(state: UserRelatedState) {
|
||||||
|
const t = Translations.t.importHelper
|
||||||
|
const isValid = state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.importHelperUnlock)
|
||||||
|
super([
|
||||||
|
new Title(t.userAccountTitle),
|
||||||
|
new LoginToggle(
|
||||||
|
new VariableUiElement(state.osmConnection.userDetails.map(ud => {
|
||||||
|
if (ud === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return new Combine([
|
||||||
|
new Img(ud.img ?? "./assets/svgs/help.svg").SetClass("w-16 h-16 rounded-full"),
|
||||||
|
t.loggedInWith.Subs(ud),
|
||||||
|
new SubtleButton(Svg.logout_svg().SetClass("h-8"), Translations.t.general.logout)
|
||||||
|
.onClick(() => state.osmConnection.LogOut())
|
||||||
|
]);
|
||||||
|
})),
|
||||||
|
new Combine([t.loginRequired,
|
||||||
|
MoreScreen.CreateProffessionalSerivesButton()
|
||||||
|
]),
|
||||||
|
state
|
||||||
|
),
|
||||||
|
new Toggle(undefined,
|
||||||
|
new Combine(
|
||||||
|
[t.lockNotice.Subs(Constants.userJourney).SetClass("alert"),
|
||||||
|
MoreScreen.CreateProffessionalSerivesButton()])
|
||||||
|
|
||||||
|
, isValid)
|
||||||
|
])
|
||||||
|
this.Value = new UIEventSource<UserRelatedState>(state)
|
||||||
|
this.IsValid = isValid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,13 +17,13 @@ import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
|
||||||
import FilteredLayer, {FilterState} from "../../Models/FilteredLayer";
|
import FilteredLayer, {FilterState} from "../../Models/FilteredLayer";
|
||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import Table from "../Base/Table";
|
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import {FlowStep} from "./FlowStep";
|
import {FlowStep} from "./FlowStep";
|
||||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||||
import {AllTagsPanel} from "../SpecialVisualizations";
|
import {AllTagsPanel} from "../SpecialVisualizations";
|
||||||
import Title from "../Base/Title";
|
import Title from "../Base/Title";
|
||||||
|
import CheckBoxes from "../Input/Checkboxes";
|
||||||
|
|
||||||
class PreviewPanel extends ScrollableFullScreen {
|
class PreviewPanel extends ScrollableFullScreen {
|
||||||
|
|
||||||
|
@ -42,14 +42,14 @@ class PreviewPanel extends ScrollableFullScreen {
|
||||||
/**
|
/**
|
||||||
* Shows the data to import on a map, asks for the correct layer to be selected
|
* Shows the data to import on a map, asks for the correct layer to be selected
|
||||||
*/
|
*/
|
||||||
export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{
|
export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{
|
||||||
public readonly IsValid: UIEventSource<boolean>;
|
public readonly IsValid: UIEventSource<boolean>;
|
||||||
public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }>
|
public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
state: UserRelatedState,
|
state: UserRelatedState,
|
||||||
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
|
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
|
||||||
const t = Translations.t.importHelper;
|
const t = Translations.t.importHelper.mapPreview;
|
||||||
|
|
||||||
const propertyKeys = new Set<string>()
|
const propertyKeys = new Set<string>()
|
||||||
for (const f of geojson.features) {
|
for (const f of geojson.features) {
|
||||||
|
@ -58,7 +58,7 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer:
|
||||||
|
|
||||||
|
|
||||||
const availableLayers = AllKnownLayouts.AllPublicLayers().filter(l => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0)
|
const availableLayers = AllKnownLayouts.AllPublicLayers().filter(l => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0)
|
||||||
const layerPicker = new DropDown("Which layer does this import match with?",
|
const layerPicker = new DropDown(t.selectLayer,
|
||||||
[{shown: t.selectLayer, value: undefined}].concat(availableLayers.map(l => ({
|
[{shown: t.selectLayer, value: undefined}].concat(availableLayers.map(l => ({
|
||||||
shown: l.name,
|
shown: l.name,
|
||||||
value: l
|
value: l
|
||||||
|
@ -126,18 +126,8 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer:
|
||||||
})
|
})
|
||||||
var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates]))))
|
var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates]))))
|
||||||
|
|
||||||
super([
|
|
||||||
new Title(geojson.features.length + " features to import"),
|
const mismatchIndicator = new VariableUiElement(matching.map(matching => {
|
||||||
layerPicker,
|
|
||||||
new Toggle("Automatically detected layer", undefined, autodetected),
|
|
||||||
new Table(["", "Key", "Values", "Unique values seen"],
|
|
||||||
Array.from(propertyKeys).map(key => {
|
|
||||||
const uniqueValues = Utils.Dedup(Utils.NoNull(geojson.features.map(f => f.properties[key])))
|
|
||||||
uniqueValues.sort()
|
|
||||||
return [geojson.features.filter(f => f.properties[key] !== undefined).length + "", key, uniqueValues.join(", "), "" + uniqueValues.length]
|
|
||||||
})
|
|
||||||
).SetClass("zebra-table table-auto"),
|
|
||||||
new VariableUiElement(matching.map(matching => {
|
|
||||||
if (matching === undefined) {
|
if (matching === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -146,9 +136,18 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer:
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const obligatory = layerPicker.GetValue().data?.source?.osmTags?.asHumanString(false, false, {});
|
const obligatory = layerPicker.GetValue().data?.source?.osmTags?.asHumanString(false, false, {});
|
||||||
return new FixedUiElement(`${diff} features will _not_ match this layer. Make sure that all obligatory objects are present: ${obligatory}`).SetClass("alert");
|
return t.mismatch.Subs({count: diff, tags: obligatory}).SetClass("alert")
|
||||||
})),
|
}))
|
||||||
map
|
|
||||||
|
const confirm = new CheckBoxes([t.confirm]);
|
||||||
|
super([
|
||||||
|
new Title(t.title, 1),
|
||||||
|
layerPicker,
|
||||||
|
new Toggle(t.autodetected.SetClass("thank"), undefined, autodetected),
|
||||||
|
|
||||||
|
mismatchIndicator ,
|
||||||
|
map,
|
||||||
|
confirm
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.Value = bbox.map(bbox =>
|
this.Value = bbox.map(bbox =>
|
||||||
|
@ -157,13 +156,17 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer:
|
||||||
geojson,
|
geojson,
|
||||||
layer: layerPicker.GetValue().data
|
layer: layerPicker.GetValue().data
|
||||||
}), [layerPicker.GetValue()])
|
}), [layerPicker.GetValue()])
|
||||||
|
|
||||||
this.IsValid = matching.map(matching => {
|
this.IsValid = matching.map(matching => {
|
||||||
if (matching === undefined) {
|
if (matching === undefined) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if(confirm.GetValue().data.length !== 1){
|
||||||
|
return false
|
||||||
|
}
|
||||||
const diff = geojson.features.length - matching.length;
|
const diff = geojson.features.length - matching.length;
|
||||||
return diff === 0;
|
return diff === 0;
|
||||||
})
|
}, [confirm.GetValue()])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
98
UI/ImportFlow/PreviewPanel.ts
Normal file
98
UI/ImportFlow/PreviewPanel.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import Combine from "../Base/Combine";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import {Utils} from "../../Utils";
|
||||||
|
import {FlowStep} from "./FlowStep";
|
||||||
|
import Title from "../Base/Title";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Histogram from "../BigComponents/Histogram";
|
||||||
|
import Toggleable from "../Base/Toggleable";
|
||||||
|
import List from "../Base/List";
|
||||||
|
import CheckBoxes from "../Input/Checkboxes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the data to import on a map, asks for the correct layer to be selected
|
||||||
|
*/
|
||||||
|
export class PreviewPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }>{
|
||||||
|
public readonly IsValid: UIEventSource<boolean>;
|
||||||
|
public readonly Value: UIEventSource< { features: { properties: any, geometry: { coordinates: [number, number] } }[] }>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
state: UserRelatedState,
|
||||||
|
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
|
||||||
|
const t = Translations.t.importHelper;
|
||||||
|
console.log("Datapanel received", geojson)
|
||||||
|
|
||||||
|
|
||||||
|
const propertyKeys = new Set<string>()
|
||||||
|
for (const f of geojson.features) {
|
||||||
|
Object.keys(f.properties).forEach(key => propertyKeys.add(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributeOverview : BaseUIElement[] = []
|
||||||
|
|
||||||
|
const n = geojson.features.length;
|
||||||
|
for (const key of Array.from(propertyKeys)) {
|
||||||
|
|
||||||
|
const values = Utils.NoNull(geojson.features.map(f => f.properties[key]))
|
||||||
|
const allSame = !values.some(v => v !== values[0])
|
||||||
|
if(allSame){
|
||||||
|
attributeOverview.push(new Title(key+"="+values[0]))
|
||||||
|
if(values.length === n){
|
||||||
|
attributeOverview.push(t.allAttributesSame)
|
||||||
|
}else{
|
||||||
|
attributeOverview.push(t.someHaveSame.Subs({count: values.length, percentage: Math.floor(100 * values.length / n)}))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueCount = new Set(values).size
|
||||||
|
if(uniqueCount !== values.length){
|
||||||
|
attributeOverview.push()
|
||||||
|
// There are some overlapping values: histogram time!
|
||||||
|
let hist : BaseUIElement = new Histogram(
|
||||||
|
new UIEventSource<string[]>(values),
|
||||||
|
"Value",
|
||||||
|
"Occurence",
|
||||||
|
{
|
||||||
|
sortMode: "count-rev"
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
const title = new Title(key+"=*")
|
||||||
|
if(uniqueCount > 15){
|
||||||
|
hist = new Toggleable(title,
|
||||||
|
hist.SetClass("block")
|
||||||
|
).Collapse()
|
||||||
|
|
||||||
|
}else{
|
||||||
|
attributeOverview.push(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeOverview.push(hist)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// All values are different, we add a boring (but collapsable) list
|
||||||
|
attributeOverview.push(new Toggleable(
|
||||||
|
new Title(key+"=*"),
|
||||||
|
new List(values)
|
||||||
|
) )
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirm = new CheckBoxes([t.inspectLooksCorrect])
|
||||||
|
|
||||||
|
super([
|
||||||
|
new Title(t.inspectDataTitle.Subs({count:geojson.features.length })),
|
||||||
|
...attributeOverview,
|
||||||
|
confirm
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.Value = new UIEventSource<{features: {properties: any; geometry: {coordinates: [number, number]}}[]}>(geojson)
|
||||||
|
this.IsValid = confirm.GetValue().map(selected => selected.length == 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import InputElementMap from "../Input/InputElementMap";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
import FileSelectorButton from "../Input/FileSelectorButton";
|
import FileSelectorButton from "../Input/FileSelectorButton";
|
||||||
import {FlowStep} from "./FlowStep";
|
import {FlowStep} from "./FlowStep";
|
||||||
|
import { parse } from "papaparse";
|
||||||
|
|
||||||
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
|
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
|
||||||
constructor(label: BaseUIElement) {
|
constructor(label: BaseUIElement) {
|
||||||
|
@ -42,8 +43,8 @@ export class RequestFile extends Combine implements FlowStep<any> {
|
||||||
public readonly Value: UIEventSource<any>
|
public readonly Value: UIEventSource<any>
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const t = Translations.t.importHelper;
|
const t = Translations.t.importHelper.selectFile;
|
||||||
const csvSelector = new FileSelector(new SubtleButton(undefined, t.selectFile))
|
const csvSelector = new FileSelector(new SubtleButton(undefined, t.description))
|
||||||
const loadedFiles = new VariableUiElement(csvSelector.GetValue().map(file => {
|
const loadedFiles = new VariableUiElement(csvSelector.GetValue().map(file => {
|
||||||
if (file === undefined) {
|
if (file === undefined) {
|
||||||
return t.noFilesLoaded.SetClass("alert")
|
return t.noFilesLoaded.SetClass("alert")
|
||||||
|
@ -59,39 +60,53 @@ export class RequestFile extends Combine implements FlowStep<any> {
|
||||||
return UIEventSource.FromPromise(v.contents)
|
return UIEventSource.FromPromise(v.contents)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const asGeoJson: UIEventSource<any | { error: string }> = text.map(src => {
|
const asGeoJson: UIEventSource<any | { error: string | BaseUIElement }> = text.map(src => {
|
||||||
if (src === undefined) {
|
if (src === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(src)
|
const parsed = JSON.parse(src)
|
||||||
if (parsed["type"] !== "FeatureCollection") {
|
if (parsed["type"] !== "FeatureCollection") {
|
||||||
return {error: "The loaded JSON-file is not a geojson-featurecollection"}
|
return {error: t.errNotFeatureCollection}
|
||||||
}
|
}
|
||||||
if (parsed.features.some(f => f.geometry.type != "Point")) {
|
if (parsed.features.some(f => f.geometry.type != "Point")) {
|
||||||
return {error: "The loaded JSON-file should only contain points"}
|
return {error: t.errPointsOnly}
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Loading as CSV
|
// Loading as CSV
|
||||||
const lines = src.split("\n")
|
var lines : string[][] = <any> parse(src).data;
|
||||||
const header = lines[0].split(",")
|
const header = lines[0]
|
||||||
lines.splice(0, 1)
|
lines.splice(0, 1)
|
||||||
if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) {
|
if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) {
|
||||||
return {error: "The header does not contain `lat` or `lon`"}
|
return {error: t.errNoLatOrLon}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header.some(h => h.trim() == "")) {
|
||||||
|
return {error:t.errNoName}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (new Set(header).size !== header.length) {
|
||||||
|
return {error:t.errDuplicate}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const features = []
|
const features = []
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const attrs = lines[i];
|
||||||
if (line.trim() === "") {
|
if(attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")){
|
||||||
|
// empty line
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const attrs = line.split(",")
|
|
||||||
const properties = {}
|
const properties = {}
|
||||||
for (let i = 0; i < header.length; i++) {
|
for (let i = 0; i < header.length; i++) {
|
||||||
properties[header[i]] = attrs[i];
|
const v = attrs[i]
|
||||||
|
if(v === undefined || v === ""){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
properties[header[i]] = v;
|
||||||
}
|
}
|
||||||
const coordinates = [Number(properties["lon"]), Number(properties["lat"])]
|
const coordinates = [Number(properties["lon"]), Number(properties["lat"])]
|
||||||
delete properties["lat"]
|
delete properties["lat"]
|
||||||
|
@ -125,18 +140,21 @@ export class RequestFile extends Combine implements FlowStep<any> {
|
||||||
if (v?.error === undefined) {
|
if (v?.error === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return new FixedUiElement(v?.error).SetClass("alert");
|
return v.error.Clone().SetClass("alert");
|
||||||
}))
|
}))
|
||||||
|
|
||||||
super([
|
super([
|
||||||
|
|
||||||
new Title(t.title, 1),
|
new Title(t.title, 1),
|
||||||
t.description,
|
t.fileFormatDescription,
|
||||||
|
t.fileFormatDescriptionCsv,
|
||||||
|
t.fileFormatDescriptionGeoJson,
|
||||||
csvSelector,
|
csvSelector,
|
||||||
loadedFiles,
|
loadedFiles,
|
||||||
errorIndicator
|
errorIndicator
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
this.SetClass("flex flex-col wi")
|
||||||
this.IsValid = asGeoJson.map(geojson => geojson !== undefined && geojson["error"] === undefined)
|
this.IsValid = asGeoJson.map(geojson => geojson !== undefined && geojson["error"] === undefined)
|
||||||
this.Value = asGeoJson
|
this.Value = asGeoJson
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,7 +455,7 @@ export default class SpecialVisualizations {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return new Histogram(listSource, args[1], args[2], assignColors)
|
return new Histogram(listSource, args[1], args[2], {assignColor: assignColors})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
"readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback"
|
"readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"logout": "Log out",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
|
@ -466,18 +467,47 @@
|
||||||
"layerName": "Possible {title}",
|
"layerName": "Possible {title}",
|
||||||
"description": "A layer which imports entries for {title}",
|
"description": "A layer which imports entries for {title}",
|
||||||
"popupTitle": "Possible {title}",
|
"popupTitle": "Possible {title}",
|
||||||
"importButton": "import_button({layerId}, _tags, There might be a {title} here,./assets/svg/addSmall.svg,,,id)",
|
"importButton": "import_button({layerId}, _tags, I have found a {title} here - add it to the map,./assets/svg/addSmall.svg,,,id)",
|
||||||
|
"notFound": "I could not find {title} - remove it",
|
||||||
|
"alreadyMapped": "There already is another {title} on the map - this point is a duplicate",
|
||||||
"importHandled": "<div class='thanks'>This feature has been handled! Thanks for your effort</div>"
|
"importHandled": "<div class='thanks'>This feature has been handled! Thanks for your effort</div>"
|
||||||
},
|
},
|
||||||
"importHelper": {
|
"importHelper": {
|
||||||
"title": "Import helper",
|
"title": "Import helper",
|
||||||
"description": "The import helper converts an external dataset to notes",
|
"description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.",
|
||||||
|
"importFormat": "A text in a note should have the following format in order to be picked up: <br/><div class='literal-code'>[A bit of introduction]<br/>https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import<br/>[all tags of the feature] </div>",
|
||||||
|
"userAccountTitle": "Select user account",
|
||||||
|
"loggedInWith": "You are currently logged in as {name} and have made {csCount} changesets",
|
||||||
|
"loginRequired": "You have to be logged in to continue",
|
||||||
|
"locked": "You need at least {importHelperUnlock} to use the import helper",
|
||||||
"lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.",
|
"lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.",
|
||||||
"selectFile": "Select a .csv or .geojson file to get started",
|
|
||||||
"loadedFilesAre": "Currently loaded file is {file}",
|
|
||||||
"noFilesLoaded": "No file is currently loaded",
|
|
||||||
"selectLayer": "Select a layer...",
|
"selectLayer": "Select a layer...",
|
||||||
"selectFileTitle": "Select file",
|
"selectFile": {
|
||||||
"validateDataTitle": "Validate data"
|
"title": "Select file",
|
||||||
|
"description": "Select a .csv or .geojson file to get started",
|
||||||
|
"fileFormatDescription": "Select a <b class='code'>.csv</b> or a <b class='code'>.geojson</b> file",
|
||||||
|
"fileFormatDescriptionCsv": "In the CSV-file, there should be a column <span class='literal-code'>lat</span> and <span class='literal-code'>lon</span> with the coordinates in WGS84. There should be an additional column for every attribute.",
|
||||||
|
"fileFormatDescriptionGeoJson": "In the geojson file, only points should be present. The properties should be exactly those properties that should go into OpenStreetMap",
|
||||||
|
"errNoName": "Some columns don't have a name",
|
||||||
|
"noFilesLoaded": "No file is currently loaded",
|
||||||
|
"errDuplicate": "Some columns have the same name",
|
||||||
|
"loadedFilesAre": "Currently loaded file is {file}",
|
||||||
|
"errNoLatOrLon":"The header does not contain `lat` or `lon`",
|
||||||
|
"errPointsOnly": "The loaded JSON-file should only contain points",
|
||||||
|
"errNotFeatureCollection": "The loaded JSON-file is not a geojson-featurecollection"
|
||||||
|
},
|
||||||
|
"mapPreview": {
|
||||||
|
"title": "Map preview",
|
||||||
|
"autodetected": "The layer was automatically deducted based on the properties",
|
||||||
|
"selectLayer": "Which layer does this import match with?",
|
||||||
|
"mismatch": "{count} features did not match the selected layer. Make sure that the tags to indicate the type are present, namely {tags}",
|
||||||
|
"confirm": "The features are on the right location on the map"
|
||||||
|
},
|
||||||
|
"validateDataTitle": "Validate data",
|
||||||
|
"allAttributesSame": "All features to import have this tag",
|
||||||
|
"someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total",
|
||||||
|
"inspectDataTitle": "Inspect data of {count} features to import",
|
||||||
|
"inspectDidAutoDected": "Layer was chosen automatically",
|
||||||
|
"inspectLooksCorrect": "These values look correct"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
package-lock.json
generated
34
package-lock.json
generated
|
@ -20,6 +20,7 @@
|
||||||
"@types/leaflet-providers": "^1.2.0",
|
"@types/leaflet-providers": "^1.2.0",
|
||||||
"@types/leaflet.markercluster": "^1.4.3",
|
"@types/leaflet.markercluster": "^1.4.3",
|
||||||
"@types/lz-string": "^1.3.34",
|
"@types/lz-string": "^1.3.34",
|
||||||
|
"@types/papaparse": "^5.3.1",
|
||||||
"@types/prompt-sync": "^4.1.0",
|
"@types/prompt-sync": "^4.1.0",
|
||||||
"@types/wikidata-sdk": "^6.1.0",
|
"@types/wikidata-sdk": "^6.1.0",
|
||||||
"country-language": "^0.1.7",
|
"country-language": "^0.1.7",
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^1.0.2",
|
"osm-auth": "^1.0.2",
|
||||||
"osmtogeojson": "^3.0.0-beta.4",
|
"osmtogeojson": "^3.0.0-beta.4",
|
||||||
|
"papaparse": "^5.3.1",
|
||||||
"parcel": "^1.2.4",
|
"parcel": "^1.2.4",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
"svg-resizer": "github:vieron/svg-resizer",
|
"svg-resizer": "github:vieron/svg-resizer",
|
||||||
|
@ -3254,8 +3256,15 @@
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "7.10.14",
|
"version": "7.10.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz",
|
||||||
"integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==",
|
"integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA=="
|
||||||
"dev": true
|
},
|
||||||
|
"node_modules/@types/papaparse": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -10090,6 +10099,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||||
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
|
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
|
||||||
},
|
},
|
||||||
|
"node_modules/papaparse": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
|
||||||
|
},
|
||||||
"node_modules/parcel": {
|
"node_modules/parcel": {
|
||||||
"version": "1.12.4",
|
"version": "1.12.4",
|
||||||
"resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz",
|
||||||
|
@ -19227,8 +19241,15 @@
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "7.10.14",
|
"version": "7.10.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz",
|
||||||
"integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==",
|
"integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA=="
|
||||||
"dev": true
|
},
|
||||||
|
"@types/papaparse": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"@types/parse-json": {
|
"@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -24635,6 +24656,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||||
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
|
"integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
|
||||||
},
|
},
|
||||||
|
"papaparse": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA=="
|
||||||
|
},
|
||||||
"parcel": {
|
"parcel": {
|
||||||
"version": "1.12.4",
|
"version": "1.12.4",
|
||||||
"resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz",
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
"@types/leaflet-providers": "^1.2.0",
|
"@types/leaflet-providers": "^1.2.0",
|
||||||
"@types/leaflet.markercluster": "^1.4.3",
|
"@types/leaflet.markercluster": "^1.4.3",
|
||||||
"@types/lz-string": "^1.3.34",
|
"@types/lz-string": "^1.3.34",
|
||||||
|
"@types/papaparse": "^5.3.1",
|
||||||
"@types/prompt-sync": "^4.1.0",
|
"@types/prompt-sync": "^4.1.0",
|
||||||
"@types/wikidata-sdk": "^6.1.0",
|
"@types/wikidata-sdk": "^6.1.0",
|
||||||
"country-language": "^0.1.7",
|
"country-language": "^0.1.7",
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
"opening_hours": "^3.6.0",
|
"opening_hours": "^3.6.0",
|
||||||
"osm-auth": "^1.0.2",
|
"osm-auth": "^1.0.2",
|
||||||
"osmtogeojson": "^3.0.0-beta.4",
|
"osmtogeojson": "^3.0.0-beta.4",
|
||||||
|
"papaparse": "^5.3.1",
|
||||||
"parcel": "^1.2.4",
|
"parcel": "^1.2.4",
|
||||||
"prompt-sync": "^4.2.0",
|
"prompt-sync": "^4.2.0",
|
||||||
"svg-resizer": "github:vieron/svg-resizer",
|
"svg-resizer": "github:vieron/svg-resizer",
|
||||||
|
|
|
@ -72,10 +72,16 @@ class LayerOverviewUtils {
|
||||||
const dict = new Map<string, TagRenderingConfigJson>();
|
const dict = new Map<string, TagRenderingConfigJson>();
|
||||||
|
|
||||||
for (const key in questions["default"]) {
|
for (const key in questions["default"]) {
|
||||||
|
if(key==="id"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
questions[key].id = key;
|
questions[key].id = key;
|
||||||
dict.set(key, <TagRenderingConfigJson>questions[key])
|
dict.set(key, <TagRenderingConfigJson>questions[key])
|
||||||
}
|
}
|
||||||
for (const key in icons["default"]) {
|
for (const key in icons["default"]) {
|
||||||
|
if(key==="id"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (typeof icons[key] !== "object") {
|
if (typeof icons[key] !== "object") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -105,7 +111,7 @@ class LayerOverviewUtils {
|
||||||
"themes": Array.from(sharedThemes.values())
|
"themes": Array.from(sharedThemes.values())
|
||||||
}))
|
}))
|
||||||
|
|
||||||
writeFileSync("./assets/generated/known_layers.json", JSON.stringify(Array.from(sharedLayers.values())))
|
writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())}))
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue