169 lines
No EOL
7.1 KiB
TypeScript
169 lines
No EOL
7.1 KiB
TypeScript
import Combine from "../Base/Combine";
|
|
import {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 Table from "../Base/Table";
|
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
import {FlowStep} from "./FlowStep";
|
|
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
|
import {AllTagsPanel} from "../SpecialVisualizations";
|
|
import Title from "../Base/Title";
|
|
|
|
class PreviewPanel extends ScrollableFullScreen {
|
|
|
|
constructor(tags, layer) {
|
|
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 DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{
|
|
public readonly IsValid: UIEventSource<boolean>;
|
|
public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }>
|
|
|
|
constructor(
|
|
state: UserRelatedState,
|
|
geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) {
|
|
const t = Translations.t.importHelper;
|
|
|
|
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("Which layer does this import match with?",
|
|
[{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
|
|
})
|
|
|
|
const matching: UIEventSource<{ properties: any, geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => {
|
|
if (layer === undefined) {
|
|
return undefined;
|
|
}
|
|
const matching: { properties: any, geometry: { coordinates: [number, number] } }[] = []
|
|
|
|
for (const feature of withId) {
|
|
if (layer.source.osmTags.matchesProperties(feature.properties)) {
|
|
matching.push(feature)
|
|
}
|
|
}
|
|
|
|
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)
|
|
})
|
|
map.SetClass("w-full").SetStyle("height: 500px")
|
|
|
|
new ShowDataMultiLayer({
|
|
layers: new UIEventSource<FilteredLayer[]>(AllKnownLayouts.AllPublicLayers()
|
|
.filter(l => l.source.geojsonSource === undefined)
|
|
.map(l => ({
|
|
layerDef: l,
|
|
isDisplayed: new UIEventSource<boolean>(true),
|
|
appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined)
|
|
}))),
|
|
zoomToFeatures: true,
|
|
features: new StaticFeatureSource(matching, false),
|
|
leafletMap: map.leafletMap,
|
|
popup: (tag, layer) => new PreviewPanel(tag, layer).SetClass("font-lg")
|
|
})
|
|
var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates]))))
|
|
|
|
super([
|
|
new Title(geojson.features.length + " features to import"),
|
|
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) {
|
|
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 new FixedUiElement(`${diff} features will _not_ match this layer. Make sure that all obligatory objects are present: ${obligatory}`).SetClass("alert");
|
|
})),
|
|
map
|
|
]);
|
|
|
|
this.Value = bbox.map(bbox =>
|
|
({
|
|
bbox,
|
|
geojson,
|
|
layer: layerPicker.GetValue().data
|
|
}), [layerPicker.GetValue()])
|
|
this.IsValid = matching.map(matching => {
|
|
if (matching === undefined) {
|
|
return false
|
|
}
|
|
const diff = geojson.features.length - matching.length;
|
|
return diff === 0;
|
|
})
|
|
|
|
}
|
|
} |