forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			204 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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"
 | |
| import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
 | |
| 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"),
 | |
|             (_) => 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
 | |
|     implements FlowStep<{ bbox: BBox; layer: LayerConfig; features: Feature<Point>[] }>
 | |
| {
 | |
|     public readonly IsValid: Store<boolean>
 | |
|     public readonly Value: Store<{ bbox: BBox; layer: LayerConfig; features: any[] }>
 | |
| 
 | |
|     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
 | |
|         )
 | |
|         const layerPicker = new DropDown(
 | |
|             t.selectLayer,
 | |
|             [{ shown: t.selectLayer, value: undefined }].concat(
 | |
|                 availableLayers.map((l) => ({
 | |
|                     shown: l.name,
 | |
|                     value: l,
 | |
|                 }))
 | |
|             )
 | |
|         )
 | |
| 
 | |
|         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
 | |
|         })
 | |
| 
 | |
|         // 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[] = []
 | |
| 
 | |
|             for (const feature of withId) {
 | |
|                 if (layer.source.osmTags.matchesProperties(feature.properties)) {
 | |
|                     matching.push(feature)
 | |
|                 }
 | |
|             }
 | |
|             console.log("Matching features: ", matching)
 | |
| 
 | |
|             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
 | |
|             ),
 | |
|         })
 | |
|         const layerControl = new BackgroundMapSwitch(
 | |
|             {
 | |
|                 backgroundLayer: background,
 | |
|                 locationControl: location,
 | |
|             },
 | |
|             background
 | |
|         )
 | |
|         map.SetClass("w-full").SetStyle("height: 500px")
 | |
| 
 | |
|         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),
 | |
|             })
 | |
|         })
 | |
| 
 | |
|         const bbox = matching.map((feats) =>
 | |
|             BBox.bboxAroundAll(
 | |
|                 feats.map((f) => new BBox([(<Feature<Point>>f).geometry.coordinates]))
 | |
|             )
 | |
|         )
 | |
| 
 | |
|         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")
 | |
|             })
 | |
|         )
 | |
| 
 | |
|         const confirm = new CheckBoxes([t.confirm])
 | |
|         super([
 | |
|             new Title(t.title, 1),
 | |
|             layerPicker,
 | |
|             new Toggle(t.autodetected.SetClass("thanks"), undefined, autodetected),
 | |
| 
 | |
|             mismatchIndicator,
 | |
|             map,
 | |
|             new DivContainer("fullscreen"),
 | |
|             layerControl,
 | |
|             confirm,
 | |
|         ])
 | |
| 
 | |
|         this.Value = bbox.map(
 | |
|             (bbox) => ({
 | |
|                 bbox,
 | |
|                 features: matching.data,
 | |
|                 layer: layerPicker.GetValue().data,
 | |
|             }),
 | |
|             [layerPicker.GetValue(), matching]
 | |
|         )
 | |
| 
 | |
|         this.IsValid = matching.map(
 | |
|             (matching) => {
 | |
|                 if (matching === undefined) {
 | |
|                     return false
 | |
|                 }
 | |
|                 if (confirm.GetValue().data.length !== 1) {
 | |
|                     return false
 | |
|                 }
 | |
|                 const diff = geojson.features.length - matching.length
 | |
|                 return diff === 0
 | |
|             },
 | |
|             [confirm.GetValue()]
 | |
|         )
 | |
|     }
 | |
| }
 |