forked from MapComplete/MapComplete
Refactoring of GPS-location (uses featureSource too now), factoring out state, add ReplaceGeometryAction and conflation example
This commit is contained in:
parent
1db54f3c8e
commit
2484848cd6
37 changed files with 1035 additions and 467 deletions
|
@ -14,6 +14,9 @@ import Toggle from "../Input/Toggle";
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {Utils} from "../../Utils";
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
|
@ -24,7 +27,10 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState) {
|
||||
const layoutToUse = state.layoutToUse;
|
||||
super(
|
||||
|
@ -39,7 +45,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState,
|
||||
isShown: UIEventSource<boolean>):
|
||||
{ header: string | BaseUIElement; content: BaseUIElement }[] {
|
||||
|
@ -77,7 +84,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {
|
||||
|
||||
const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown)
|
||||
|
|
|
@ -9,7 +9,6 @@ import Toggle from "../Input/Toggle";
|
|||
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import Loading from "../Base/Loading";
|
||||
import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
|
||||
import CreateNewWayAction from "../../Logic/Osm/Actions/CreateNewWayAction";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
|
@ -26,6 +25,13 @@ import SpecialVisualizations, {SpecialVisualization} from "../SpecialVisualizati
|
|||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Svg from "../../Svg";
|
||||
import {Utils} from "../../Utils";
|
||||
import Minimap from "../Base/Minimap";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
|
||||
|
||||
|
||||
export interface ImportButtonState {
|
||||
|
@ -38,6 +44,8 @@ export interface ImportButtonState {
|
|||
feature: any,
|
||||
minZoom: number,
|
||||
state: {
|
||||
backgroundLayer: UIEventSource<BaseLayer>;
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>;
|
||||
featureSwitchUserbadge: UIEventSource<boolean>;
|
||||
featurePipeline: FeaturePipeline;
|
||||
allElements: ElementStorage;
|
||||
|
@ -48,8 +56,14 @@ export interface ImportButtonState {
|
|||
locationControl: UIEventSource<{ zoom: number }>
|
||||
},
|
||||
guiState: { filterViewIsOpened: UIEventSource<boolean> },
|
||||
snapToLayers?: string[],
|
||||
snapToLayersMaxDist?: number
|
||||
|
||||
snapSettings?: {
|
||||
snapToLayers: string[],
|
||||
snapToLayersMaxDist?: number
|
||||
},
|
||||
conflationSettings?: {
|
||||
conflateWayId: string
|
||||
}
|
||||
}
|
||||
|
||||
export class ImportButtonSpecialViz implements SpecialVisualization {
|
||||
|
@ -83,7 +97,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
|
||||
#### Specifying which tags to copy or add
|
||||
|
||||
The first argument of the import button takes a \`;\`-seperated list of tags to add.
|
||||
The argument \`tags\` of the import button takes a \`;\`-seperated list of tags to add.
|
||||
|
||||
${Utils.Special_visualizations_tagsToApplyHelpText}
|
||||
|
||||
|
@ -113,8 +127,9 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
doc: "How far the contributor must zoom in before being able to import the point",
|
||||
defaultValue: "18"
|
||||
}, {
|
||||
name: "Snap onto layer(s)",
|
||||
doc: "If a way of the given layer is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
name: "Snap onto layer(s)/replace geometry with this other way",
|
||||
doc: " - If the value corresponding with this key starts with 'way/' and the feature is a LineString or Polygon, the original OSM-way geometry will be changed to match the new geometry\n" +
|
||||
" - If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
|
||||
}, {
|
||||
name: "snap max distance",
|
||||
doc: "The maximum distance that this point will move to snap onto a layer (in meters)",
|
||||
|
@ -130,7 +145,7 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
const id = tagSource.data.id;
|
||||
const feature = state.allElements.ContainingFeatures.get(id)
|
||||
let minZoom = args[4] == "" ? 18 : Number(args[4])
|
||||
if(isNaN(minZoom)){
|
||||
if (isNaN(minZoom)) {
|
||||
console.warn("Invalid minzoom:", minZoom)
|
||||
minZoom = 18
|
||||
}
|
||||
|
@ -145,13 +160,29 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
img = () => Svg.add_ui()
|
||||
}
|
||||
|
||||
const snapToLayers = args[5]?.split(";").filter(s => s !== "")
|
||||
const snapToLayersMaxDist = Number(args[6] ?? 6)
|
||||
|
||||
if (targetLayer === undefined) {
|
||||
const e = "Target layer not defined: error in import button for theme: " + state.layoutToUse.id + ": layer " + args[0] + " not found"
|
||||
console.error(e)
|
||||
return new FixedUiElement(e).SetClass("alert")
|
||||
let snapSettings = undefined
|
||||
let conflationSettings = undefined
|
||||
const possibleWayId = tagSource.data[args[5]]
|
||||
if (possibleWayId?.startsWith("way/")) {
|
||||
// This is a conflation
|
||||
conflationSettings = {
|
||||
conflateWayId: possibleWayId
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
const snapToLayers = args[5]?.split(";").filter(s => s !== "")
|
||||
const snapToLayersMaxDist = Number(args[6] ?? 6)
|
||||
|
||||
if (targetLayer === undefined) {
|
||||
const e = "Target layer not defined: error in import button for theme: " + state.layoutToUse.id + ": layer " + args[0] + " not found"
|
||||
console.error(e)
|
||||
return new FixedUiElement(e).SetClass("alert")
|
||||
}
|
||||
snapSettings = {
|
||||
snapToLayers,
|
||||
snapToLayersMaxDist
|
||||
}
|
||||
}
|
||||
|
||||
return new ImportButton(
|
||||
|
@ -160,8 +191,8 @@ ${Utils.Special_visualizations_tagsToApplyHelpText}
|
|||
feature, newTags, message, minZoom,
|
||||
originalTags: tagSource,
|
||||
targetLayer,
|
||||
snapToLayers,
|
||||
snapToLayersMaxDist
|
||||
snapSettings,
|
||||
conflationSettings
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -201,7 +232,7 @@ export default class ImportButton extends Toggle {
|
|||
|
||||
const importClicked = new UIEventSource(false);
|
||||
const importFlow = new Toggle(
|
||||
new Lazy(() => ImportButton.createConfirmPanel(o, isImported, importClicked)),
|
||||
ImportButton.createConfirmPanel(o, isImported, importClicked),
|
||||
importButton,
|
||||
importClicked
|
||||
)
|
||||
|
@ -228,7 +259,121 @@ export default class ImportButton extends Toggle {
|
|||
)
|
||||
}
|
||||
|
||||
public static createConfirmPanel(
|
||||
public static createConfirmPanel(o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>) {
|
||||
const geometry = o.feature.geometry
|
||||
if (geometry.type === "Point") {
|
||||
return new Lazy(() => ImportButton.createConfirmPanelForPoint(o, isImported, importClicked))
|
||||
}
|
||||
|
||||
|
||||
if (geometry.type === "Polygon" || geometry.type == "LineString") {
|
||||
return new Lazy(() => ImportButton.createConfirmForWay(o, isImported, importClicked))
|
||||
}
|
||||
console.error("Invalid type to import", geometry.type)
|
||||
return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert")
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static createConfirmForWay(o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>): BaseUIElement {
|
||||
|
||||
const confirmationMap = Minimap.createMiniMap({
|
||||
allowMoving: false,
|
||||
background: o.state.backgroundLayer
|
||||
})
|
||||
confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl")
|
||||
|
||||
const relevantFeatures = Utils.NoNull([o.feature, o.state.allElements?.ContainingFeatures?.get(o.conflationSettings?.conflateWayId)])
|
||||
// SHow all relevant data - including (eventually) the way of which the geometry will be replaced
|
||||
new ShowDataMultiLayer({
|
||||
leafletMap: confirmationMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: true,
|
||||
features: new StaticFeatureSource(relevantFeatures, false),
|
||||
allElements: o.state.allElements,
|
||||
layers: o.state.filteredLayers
|
||||
})
|
||||
|
||||
const theme = o.state.layoutToUse.id
|
||||
|
||||
|
||||
const changes = o.state.changes
|
||||
let confirm: () => Promise<string>
|
||||
if (o.conflationSettings !== undefined) {
|
||||
|
||||
let replaceGeometryAction = new ReplaceGeometryAction(
|
||||
o.state,
|
||||
o.feature,
|
||||
o.conflationSettings.conflateWayId,
|
||||
{
|
||||
theme: o.state.layoutToUse.id,
|
||||
newTags: o.newTags.data
|
||||
}
|
||||
)
|
||||
|
||||
replaceGeometryAction.GetPreview().then(changePreview => {
|
||||
new ShowDataLayer({
|
||||
leafletMap: confirmationMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: false,
|
||||
features: changePreview,
|
||||
allElements: o.state.allElements,
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("conflation")
|
||||
})
|
||||
})
|
||||
|
||||
confirm = async () => {
|
||||
changes.applyAction (replaceGeometryAction)
|
||||
return o.feature.properties.id
|
||||
}
|
||||
|
||||
} else {
|
||||
confirm = async () => {
|
||||
const geom = o.feature.geometry
|
||||
let coordinates: [number, number][]
|
||||
if (geom.type === "LineString") {
|
||||
coordinates = geom.coordinates
|
||||
} else if (geom.type === "Polygon") {
|
||||
coordinates = geom.coordinates[0]
|
||||
}
|
||||
const action = new CreateNewWayAction(o.newTags.data, coordinates.map(lngLat => ({
|
||||
lat: lngLat[1],
|
||||
lon: lngLat[0]
|
||||
})), {theme})
|
||||
return action.newElementId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const confirmButton = new SubtleButton(o.image(), o.message)
|
||||
confirmButton.onClick(async () => {
|
||||
{
|
||||
if (isImported.data) {
|
||||
return
|
||||
}
|
||||
o.originalTags.data["_imported"] = "yes"
|
||||
o.originalTags.ping() // will set isImported as per its definition
|
||||
|
||||
const idToSelect = await confirm()
|
||||
|
||||
o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(idToSelect))
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
const cancel = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel).onClick(() => {
|
||||
importClicked.setData(false)
|
||||
})
|
||||
|
||||
|
||||
return new Combine([confirmationMap, confirmButton, cancel]).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
public static createConfirmPanelForPoint(
|
||||
o: ImportButtonState,
|
||||
isImported: UIEventSource<boolean>,
|
||||
importClicked: UIEventSource<boolean>): BaseUIElement {
|
||||
|
@ -239,39 +384,43 @@ export default class ImportButton extends Toggle {
|
|||
}
|
||||
o.originalTags.data["_imported"] = "yes"
|
||||
o.originalTags.ping() // will set isImported as per its definition
|
||||
const newElementAction = ImportButton.createAddActionForFeature(o.newTags.data, o.feature, o.state.layoutToUse.id)
|
||||
const geometry = o.feature.geometry
|
||||
const lat = geometry.coordinates[1]
|
||||
const lon = geometry.coordinates[0];
|
||||
const newElementAction = new CreateNewNodeAction(o.newTags.data, lat, lon, {
|
||||
theme: o.state.layoutToUse.id,
|
||||
changeType: "import"
|
||||
})
|
||||
|
||||
await o.state.changes.applyAction(newElementAction)
|
||||
o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
console.log("Did set selected element to", o.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
importClicked.setData(false)
|
||||
}
|
||||
|
||||
if (o.feature.geometry.type === "Point") {
|
||||
const presetInfo = <PresetInfo>{
|
||||
tags: o.newTags.data,
|
||||
icon: o.image,
|
||||
description: o.description,
|
||||
layerToAddTo: o.targetLayer,
|
||||
name: o.message,
|
||||
title: o.message,
|
||||
preciseInput: { snapToLayers: o.snapToLayers,
|
||||
maxSnapDistance: o.snapToLayersMaxDist}
|
||||
const presetInfo = <PresetInfo>{
|
||||
tags: o.newTags.data,
|
||||
icon: o.image,
|
||||
description: o.description,
|
||||
layerToAddTo: o.targetLayer,
|
||||
name: o.message,
|
||||
title: o.message,
|
||||
preciseInput: {
|
||||
snapToLayers: o.snapSettings?.snapToLayers,
|
||||
maxSnapDistance: o.snapSettings?.snapToLayersMaxDist
|
||||
}
|
||||
|
||||
const [lon, lat] = o.feature.geometry.coordinates
|
||||
console.log("Creating an import dialog at location", lon, lat)
|
||||
return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), {
|
||||
lon,
|
||||
lat
|
||||
}, confirm, cancel)
|
||||
}
|
||||
|
||||
const [lon, lat] = o.feature.geometry.coordinates
|
||||
return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), {
|
||||
lon,
|
||||
lat
|
||||
}, confirm, cancel)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -279,41 +428,4 @@ export default class ImportButton extends Toggle {
|
|||
const type = feature.geometry.type
|
||||
return type === "Point" || type === "LineString" || (type === "Polygon" && feature.geometry.coordinates.length === 1)
|
||||
}
|
||||
|
||||
private static createAddActionForFeature(newTags: Tag[], feature: any, theme: string):
|
||||
OsmChangeAction & { newElementId: string } {
|
||||
const geometry = feature.geometry
|
||||
const type = geometry.type
|
||||
if (type === "Point") {
|
||||
const lat = geometry.coordinates[1]
|
||||
const lon = geometry.coordinates[0];
|
||||
return new CreateNewNodeAction(newTags, lat, lon, {
|
||||
theme,
|
||||
changeType: "import"
|
||||
})
|
||||
}
|
||||
|
||||
if (type === "LineString") {
|
||||
return new CreateNewWayAction(
|
||||
newTags,
|
||||
geometry.coordinates.map(coor => ({lon: coor[0], lat: coor[1]})),
|
||||
{
|
||||
theme
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (type === "Polygon") {
|
||||
return new CreateNewWayAction(
|
||||
newTags,
|
||||
geometry.coordinates[0].map(coor => ({lon: coor[0], lat: coor[1]})),
|
||||
{
|
||||
theme
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import Loc from "../../Models/Loc";
|
|||
import {BBox} from "../../Logic/BBox";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
|
||||
export default class LeftControls extends Combine {
|
||||
|
||||
|
@ -26,7 +28,9 @@ export default class LeftControls extends Combine {
|
|||
featureSwitchEnableExport: UIEventSource<boolean>,
|
||||
featureSwitchExportAsPdf: UIEventSource<boolean>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||
featureSwitchFilter: UIEventSource<boolean>
|
||||
featureSwitchFilter: UIEventSource<boolean>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
osmConnection: OsmConnection
|
||||
},
|
||||
guiState: {
|
||||
downloadControlIsOpened: UIEventSource<boolean>,
|
||||
|
|
|
@ -4,17 +4,30 @@ import MapControlButton from "../MapControlButton";
|
|||
import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler";
|
||||
import Svg from "../../Svg";
|
||||
import MapState from "../../Logic/State/MapState";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
|
||||
export default class RightControls extends Combine {
|
||||
|
||||
constructor(state:MapState) {
|
||||
|
||||
const geolocatioHandler = new GeoLocationHandler(
|
||||
state.currentGPSLocation,
|
||||
state.leafletMap,
|
||||
state.layoutToUse
|
||||
)
|
||||
|
||||
new ShowDataLayer({
|
||||
layerToShow: AllKnownLayers.sharedLayers.get("gps_location"),
|
||||
leafletMap: state.leafletMap,
|
||||
enablePopups: true,
|
||||
features: geolocatioHandler.currentLocation
|
||||
})
|
||||
|
||||
const geolocationButton = new Toggle(
|
||||
new MapControlButton(
|
||||
new GeoLocationHandler(
|
||||
state.currentGPSLocation,
|
||||
state.leafletMap,
|
||||
state.layoutToUse
|
||||
), {
|
||||
geolocatioHandler
|
||||
, {
|
||||
dontStyle: true
|
||||
}
|
||||
),
|
||||
|
|
|
@ -8,11 +8,14 @@ import Toggle from "../Input/Toggle";
|
|||
import Translations from "../i18n/Translations";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import MapState from "../../Logic/State/MapState";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
|
||||
export default class ShareScreen extends Combine {
|
||||
|
||||
constructor(state: MapState) {
|
||||
constructor(state: {layoutToUse: LayoutConfig, locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>}) {
|
||||
const layout = state?.layoutToUse;
|
||||
const tr = Translations.t.general.sharescreen;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue