mapcomplete/UI/ImportFlow/MapPreview.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

205 lines
7.4 KiB
TypeScript
Raw Normal View History

import Combine from "../Base/Combine"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { BBox } from "../../Logic/BBox"
import UserRelatedState from "../../Logic/State/UserRelatedState"
import Translations from "../i18n/Translations"
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
import Constants from "../../Models/Constants"
import { DropDown } from "../Input/DropDown"
import { Utils } from "../../Utils"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import BaseLayer from "../../Models/BaseLayer"
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"
import Loc from "../../Models/Loc"
import Minimap from "../Base/Minimap"
import Attribution from "../BigComponents/Attribution"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import FilteredLayer, { FilterState } from "../../Models/FilteredLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import Toggle from "../Input/Toggle"
import { VariableUiElement } from "../Base/VariableUIElement"
import { FixedUiElement } from "../Base/FixedUiElement"
import { FlowStep } from "./FlowStep"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import Title from "../Base/Title"
import CheckBoxes from "../Input/Checkboxes"
import { AllTagsPanel } from "../AllTagsPanel"
2022-07-08 03:14:55 +02:00
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
2023-01-12 01:16:22 +01:00
import { Feature, Point } from "geojson"
import DivContainer from "../Base/DivContainer"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
class PreviewPanel extends ScrollableFullScreen {
constructor(tags: UIEventSource<any>) {
super(
(_) => new FixedUiElement("Element to import"),
2022-01-26 21:40:38 +01:00
(_) => new Combine(["The tags are:", new AllTagsPanel(tags)]).SetClass("flex flex-col"),
"element"
)
}
}
/**
* Shows the data to import on a map, asks for the correct layer to be selected
*/
export class MapPreview
extends Combine
2023-01-12 01:16:22 +01:00
implements FlowStep<{ bbox: BBox; layer: LayerConfig; features: Feature<Point>[] }>
{
public readonly IsValid: Store<boolean>
public readonly Value: Store<{ bbox: BBox; layer: LayerConfig; features: any[] }>
2022-01-26 21:40:38 +01:00
2023-01-12 01:16:22 +01:00
constructor(state: UserRelatedState, geojson: { features: Feature[] }) {
const t = Translations.t.importHelper.mapPreview
const propertyKeys = new Set<string>()
for (const f of geojson.features) {
Object.keys(f.properties).forEach((key) => propertyKeys.add(key))
}
const availableLayers = AllKnownLayouts.AllPublicLayers().filter(
(l) => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0
2022-09-08 21:40:48 +02:00
)
const layerPicker = new DropDown(
t.selectLayer,
[{ shown: t.selectLayer, value: undefined }].concat(
availableLayers.map((l) => ({
shown: l.name,
value: l,
}))
2022-09-08 21:40:48 +02:00
)
)
let autodetected = new UIEventSource(false)
for (const layer of availableLayers) {
const mismatched = geojson.features.some(
(f) => !layer.source.osmTags.matchesProperties(f.properties)
)
if (!mismatched) {
console.log("Autodected layer", layer.id)
layerPicker.GetValue().setData(layer)
layerPicker.GetValue().addCallback((_) => autodetected.setData(false))
autodetected.setData(true)
break
}
}
const withId = geojson.features.map((f, i) => {
const copy = Utils.Clone(f)
copy.properties.id = "to-import/" + i
return copy
})
2023-01-12 01:16:22 +01:00
// Create a store which has only features matching the selected layer
const matching: Store<Feature[]> = layerPicker.GetValue().map((layer: LayerConfig) => {
if (layer === undefined) {
console.log("No matching layer found")
return []
}
const matching: Feature[] = []
2023-01-12 01:16:22 +01:00
for (const feature of withId) {
if (layer.source.osmTags.matchesProperties(feature.properties)) {
matching.push(feature)
}
2023-01-12 01:16:22 +01:00
}
console.log("Matching features: ", matching)
2023-01-12 01:16:22 +01:00
return matching
})
const background = new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto)
const location = new UIEventSource<Loc>({ lat: 0, lon: 0, zoom: 1 })
const currentBounds = new UIEventSource<BBox>(undefined)
const map = Minimap.createMiniMap({
allowMoving: true,
location,
background,
bounds: currentBounds,
attribution: new Attribution(
location,
state.osmConnection.userDetails,
undefined,
currentBounds
2022-09-08 21:40:48 +02:00
),
})
2022-07-08 03:14:55 +02:00
const layerControl = new BackgroundMapSwitch(
{
backgroundLayer: background,
locationControl: location,
},
background
)
map.SetClass("w-full").SetStyle("height: 500px")
2023-01-12 01:16:22 +01:00
layerPicker.GetValue().addCallbackAndRunD((layerToShow) => {
new ShowDataLayer({
layerToShow,
zoomToFeatures: true,
features: StaticFeatureSource.fromDateless(
matching.map((features) => features.map((feature) => ({ feature })))
),
leafletMap: map.leafletMap,
popup: (tag) => new PreviewPanel(tag),
})
})
2023-01-12 01:16:22 +01:00
const bbox = matching.map((feats) =>
BBox.bboxAroundAll(
feats.map((f) => new BBox([(<Feature<Point>>f).geometry.coordinates]))
)
2022-09-08 21:40:48 +02:00
)
2022-01-26 21:40:38 +01:00
const mismatchIndicator = new VariableUiElement(
matching.map((matching) => {
if (matching === undefined) {
return undefined
}
const diff = geojson.features.length - matching.length
if (diff === 0) {
return undefined
}
const obligatory = layerPicker
.GetValue()
.data?.source?.osmTags?.asHumanString(false, false, {})
return t.mismatch.Subs({ count: diff, tags: obligatory }).SetClass("alert")
})
2022-09-08 21:40:48 +02:00
)
2022-01-26 21:40:38 +01:00
const confirm = new CheckBoxes([t.confirm])
super([
new Title(t.title, 1),
layerPicker,
2023-01-12 01:16:22 +01:00
new Toggle(t.autodetected.SetClass("thanks"), undefined, autodetected),
2022-01-26 21:40:38 +01:00
mismatchIndicator,
map,
2023-01-12 01:16:22 +01:00
new DivContainer("fullscreen"),
2022-07-08 03:14:55 +02:00
layerControl,
confirm,
])
this.Value = bbox.map(
(bbox) => ({
bbox,
2023-01-12 01:16:22 +01:00
features: matching.data,
layer: layerPicker.GetValue().data,
}),
2023-01-12 01:16:22 +01:00
[layerPicker.GetValue(), matching]
)
2022-01-26 21:40:38 +01:00
this.IsValid = matching.map(
(matching) => {
if (matching === undefined) {
return false
2022-09-08 21:40:48 +02:00
}
2022-01-26 21:40:38 +01:00
if (confirm.GetValue().data.length !== 1) {
return false
2022-09-08 21:40:48 +02:00
}
const diff = geojson.features.length - matching.length
return diff === 0
2022-09-08 21:40:48 +02:00
},
[confirm.GetValue()]
2022-09-08 21:40:48 +02:00
)
}
}