forked from MapComplete/MapComplete
More refactoring, move minimap behind facade
This commit is contained in:
parent
c11ff652b8
commit
d5c1ba4cd1
79 changed files with 1848 additions and 1118 deletions
|
@ -10,6 +10,9 @@ export default class Img extends BaseUIElement {
|
|||
fallbackImage?: string
|
||||
}) {
|
||||
super();
|
||||
if(src === undefined || src === "undefined"){
|
||||
throw "Undefined src for image"
|
||||
}
|
||||
this._src = src;
|
||||
this._rawSvg = rawSvg;
|
||||
this._options = options;
|
||||
|
|
|
@ -1,208 +1,30 @@
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
import * as L from "leaflet";
|
||||
import {Map} from "leaflet";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import {Utils} from "../../Utils";
|
||||
import {BBox} from "../../Logic/GeoOperations";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
export default class Minimap extends BaseUIElement {
|
||||
export interface MinimapOptions {
|
||||
background?: UIEventSource<BaseLayer>,
|
||||
location?: UIEventSource<Loc>,
|
||||
bounds?: UIEventSource<BBox>,
|
||||
allowMoving?: boolean,
|
||||
leafletOptions?: any,
|
||||
attribution?: BaseUIElement | boolean,
|
||||
onFullyLoaded?: (leaflet: L.Map) => void,
|
||||
leafletMap?: UIEventSource<any>,
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>
|
||||
}
|
||||
|
||||
private static _nextId = 0;
|
||||
public readonly leafletMap: UIEventSource<Map>
|
||||
private readonly _id: string;
|
||||
private readonly _background: UIEventSource<BaseLayer>;
|
||||
private readonly _location: UIEventSource<Loc>;
|
||||
private _isInited = false;
|
||||
private _allowMoving: boolean;
|
||||
private readonly _leafletoptions: any;
|
||||
private readonly _onFullyLoaded: (leaflet: L.Map) => void
|
||||
private readonly _attribution: BaseUIElement | boolean;
|
||||
private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
|
||||
export default class Minimap {
|
||||
/**
|
||||
* A stub implementation. The actual implementation is injected later on, but only in the browser.
|
||||
* importing leaflet crashes node-ts, which is pretty annoying considering the fact that a lot of scripts use it
|
||||
*/
|
||||
|
||||
constructor(options?: {
|
||||
background?: UIEventSource<BaseLayer>,
|
||||
location?: UIEventSource<Loc>,
|
||||
allowMoving?: boolean,
|
||||
leafletOptions?: any,
|
||||
attribution?: BaseUIElement | boolean,
|
||||
onFullyLoaded?: (leaflet: L.Map) => void,
|
||||
leafletMap?: UIEventSource<Map>,
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>
|
||||
}
|
||||
) {
|
||||
super()
|
||||
options = options ?? {}
|
||||
this.leafletMap = options.leafletMap ?? new UIEventSource<Map>(undefined)
|
||||
this._background = options?.background ?? new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto)
|
||||
this._location = options?.location ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1})
|
||||
this._id = "minimap" + Minimap._nextId;
|
||||
this._allowMoving = options.allowMoving ?? true;
|
||||
this._leafletoptions = options.leafletOptions ?? {}
|
||||
this._onFullyLoaded = options.onFullyLoaded
|
||||
this._attribution = options.attribution
|
||||
this._lastClickLocation = options.lastClickLocation;
|
||||
Minimap._nextId++
|
||||
/**
|
||||
* Construct a minimap
|
||||
*/
|
||||
public static createMiniMap: (options: MinimapOptions) => BaseUIElement & { readonly leafletMap: UIEventSource<any> }
|
||||
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const div = document.createElement("div")
|
||||
div.id = this._id;
|
||||
div.style.height = "100%"
|
||||
div.style.width = "100%"
|
||||
div.style.minWidth = "40px"
|
||||
div.style.minHeight = "40px"
|
||||
div.style.position = "relative"
|
||||
const wrapper = document.createElement("div")
|
||||
wrapper.appendChild(div)
|
||||
const self = this;
|
||||
// @ts-ignore
|
||||
const resizeObserver = new ResizeObserver(_ => {
|
||||
self.InitMap();
|
||||
self.leafletMap?.data?.invalidateSize()
|
||||
});
|
||||
|
||||
resizeObserver.observe(div);
|
||||
return wrapper;
|
||||
|
||||
}
|
||||
|
||||
private InitMap() {
|
||||
if (this._constructedHtmlElement === undefined) {
|
||||
// This element isn't initialized yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById(this._id) === null) {
|
||||
// not yet attached, we probably got some other event
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isInited) {
|
||||
return;
|
||||
}
|
||||
this._isInited = true;
|
||||
const location = this._location;
|
||||
const self = this;
|
||||
let currentLayer = this._background.data.layer()
|
||||
const options = {
|
||||
center: <[number, number]>[location.data?.lat ?? 0, location.data?.lon ?? 0],
|
||||
zoom: location.data?.zoom ?? 2,
|
||||
layers: [currentLayer],
|
||||
zoomControl: false,
|
||||
attributionControl: this._attribution !== undefined,
|
||||
dragging: this._allowMoving,
|
||||
scrollWheelZoom: this._allowMoving,
|
||||
doubleClickZoom: this._allowMoving,
|
||||
keyboard: this._allowMoving,
|
||||
touchZoom: this._allowMoving,
|
||||
// Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving,
|
||||
fadeAnimation: this._allowMoving,
|
||||
}
|
||||
|
||||
Utils.Merge(this._leafletoptions, options)
|
||||
|
||||
const map = L.map(this._id, options);
|
||||
if (self._onFullyLoaded !== undefined) {
|
||||
|
||||
currentLayer.on("load", () => {
|
||||
console.log("Fully loaded all tiles!")
|
||||
self._onFullyLoaded(map)
|
||||
})
|
||||
}
|
||||
|
||||
// Users are not allowed to zoom to the 'copies' on the left and the right, stuff goes wrong then
|
||||
// We give a bit of leeway for people on the edges
|
||||
// Also see: https://www.reddit.com/r/openstreetmap/comments/ih4zzc/mapcomplete_a_new_easytouse_editor/g31ubyv/
|
||||
|
||||
map.setMaxBounds(
|
||||
[[-100, -200], [100, 200]]
|
||||
);
|
||||
|
||||
if (this._attribution !== undefined) {
|
||||
if (this._attribution === true) {
|
||||
map.attributionControl.setPrefix(false)
|
||||
} else {
|
||||
map.attributionControl.setPrefix(
|
||||
"<span id='leaflet-attribution'></span>");
|
||||
}
|
||||
}
|
||||
|
||||
this._background.addCallbackAndRun(layer => {
|
||||
const newLayer = layer.layer()
|
||||
if (currentLayer !== undefined) {
|
||||
map.removeLayer(currentLayer);
|
||||
}
|
||||
currentLayer = newLayer;
|
||||
if (self._onFullyLoaded !== undefined) {
|
||||
|
||||
currentLayer.on("load", () => {
|
||||
console.log("Fully loaded all tiles!")
|
||||
self._onFullyLoaded(map)
|
||||
})
|
||||
}
|
||||
map.addLayer(newLayer);
|
||||
map.setMaxZoom(layer.max_zoom ?? map.getMaxZoom())
|
||||
if (self._attribution !== true && self._attribution !== false) {
|
||||
self._attribution?.AttachTo('leaflet-attribution')
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
let isRecursing = false;
|
||||
map.on("moveend", function () {
|
||||
if (isRecursing) {
|
||||
return
|
||||
}
|
||||
if (map.getZoom() === location.data.zoom &&
|
||||
map.getCenter().lat === location.data.lat &&
|
||||
map.getCenter().lng === location.data.lon) {
|
||||
return;
|
||||
}
|
||||
location.data.zoom = map.getZoom();
|
||||
location.data.lat = map.getCenter().lat;
|
||||
location.data.lon = map.getCenter().lng;
|
||||
isRecursing = true;
|
||||
location.ping();
|
||||
isRecursing = false; // This is ugly, I know
|
||||
})
|
||||
|
||||
|
||||
location.addCallback(loc => {
|
||||
const mapLoc = map.getCenter()
|
||||
const dlat = Math.abs(loc.lat - mapLoc[0])
|
||||
const dlon = Math.abs(loc.lon - mapLoc[1])
|
||||
|
||||
if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) {
|
||||
return;
|
||||
}
|
||||
map.setView([loc.lat, loc.lon], loc.zoom)
|
||||
})
|
||||
|
||||
location.map(loc => loc.zoom)
|
||||
.addCallback(zoom => {
|
||||
if (Math.abs(map.getZoom() - zoom) > 0.1) {
|
||||
map.setZoom(zoom, {});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (this._lastClickLocation) {
|
||||
const lastClickLocation = this._lastClickLocation
|
||||
map.on("click", function (e) {
|
||||
// @ts-ignore
|
||||
lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng})
|
||||
});
|
||||
|
||||
map.on("contextmenu", function (e) {
|
||||
// @ts-ignore
|
||||
lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng});
|
||||
});
|
||||
}
|
||||
|
||||
this.leafletMap.setData(map)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import {Utils} from "../../Utils";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import {BBox} from "../../Logic/GeoOperations";
|
||||
import * as L from "leaflet";
|
||||
import {Map} from "leaflet";
|
||||
import Minimap, {MinimapOptions} from "./Minimap";
|
||||
|
||||
export default class MinimapImplementation extends BaseUIElement {
|
||||
private static _nextId = 0;
|
||||
public readonly leafletMap: UIEventSource<Map>
|
||||
private readonly _id: string;
|
||||
private readonly _background: UIEventSource<BaseLayer>;
|
||||
private readonly _location: UIEventSource<Loc>;
|
||||
private _isInited = false;
|
||||
private _allowMoving: boolean;
|
||||
private readonly _leafletoptions: any;
|
||||
private readonly _onFullyLoaded: (leaflet: L.Map) => void
|
||||
private readonly _attribution: BaseUIElement | boolean;
|
||||
private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
|
||||
private readonly _bounds: UIEventSource<BBox> | undefined;
|
||||
|
||||
private constructor(options: MinimapOptions) {
|
||||
super()
|
||||
options = options ?? {}
|
||||
this.leafletMap = options.leafletMap ?? new UIEventSource<Map>(undefined)
|
||||
this._background = options?.background ?? new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto)
|
||||
this._location = options?.location ?? new UIEventSource<Loc>({lat: 0, lon: 0, zoom: 1})
|
||||
this._bounds = options?.bounds;
|
||||
this._id = "minimap" + MinimapImplementation._nextId;
|
||||
this._allowMoving = options.allowMoving ?? true;
|
||||
this._leafletoptions = options.leafletOptions ?? {}
|
||||
this._onFullyLoaded = options.onFullyLoaded
|
||||
this._attribution = options.attribution
|
||||
this._lastClickLocation = options.lastClickLocation;
|
||||
MinimapImplementation._nextId++
|
||||
|
||||
}
|
||||
|
||||
public static initialize() {
|
||||
Minimap.createMiniMap = options => new MinimapImplementation(options)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const div = document.createElement("div")
|
||||
div.id = this._id;
|
||||
div.style.height = "100%"
|
||||
div.style.width = "100%"
|
||||
div.style.minWidth = "40px"
|
||||
div.style.minHeight = "40px"
|
||||
div.style.position = "relative"
|
||||
const wrapper = document.createElement("div")
|
||||
wrapper.appendChild(div)
|
||||
const self = this;
|
||||
// @ts-ignore
|
||||
const resizeObserver = new ResizeObserver(_ => {
|
||||
self.InitMap();
|
||||
self.leafletMap?.data?.invalidateSize()
|
||||
});
|
||||
|
||||
resizeObserver.observe(div);
|
||||
return wrapper;
|
||||
|
||||
}
|
||||
|
||||
private InitMap() {
|
||||
if (this._constructedHtmlElement === undefined) {
|
||||
// This element isn't initialized yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById(this._id) === null) {
|
||||
// not yet attached, we probably got some other event
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isInited) {
|
||||
return;
|
||||
}
|
||||
this._isInited = true;
|
||||
const location = this._location;
|
||||
const self = this;
|
||||
let currentLayer = this._background.data.layer()
|
||||
const options = {
|
||||
center: <[number, number]>[location.data?.lat ?? 0, location.data?.lon ?? 0],
|
||||
zoom: location.data?.zoom ?? 2,
|
||||
layers: [currentLayer],
|
||||
zoomControl: false,
|
||||
attributionControl: this._attribution !== undefined,
|
||||
dragging: this._allowMoving,
|
||||
scrollWheelZoom: this._allowMoving,
|
||||
doubleClickZoom: this._allowMoving,
|
||||
keyboard: this._allowMoving,
|
||||
touchZoom: this._allowMoving,
|
||||
// Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving,
|
||||
fadeAnimation: this._allowMoving,
|
||||
}
|
||||
|
||||
Utils.Merge(this._leafletoptions, options)
|
||||
|
||||
const map = L.map(this._id, options);
|
||||
if (self._onFullyLoaded !== undefined) {
|
||||
|
||||
currentLayer.on("load", () => {
|
||||
console.log("Fully loaded all tiles!")
|
||||
self._onFullyLoaded(map)
|
||||
})
|
||||
}
|
||||
|
||||
// Users are not allowed to zoom to the 'copies' on the left and the right, stuff goes wrong then
|
||||
// We give a bit of leeway for people on the edges
|
||||
// Also see: https://www.reddit.com/r/openstreetmap/comments/ih4zzc/mapcomplete_a_new_easytouse_editor/g31ubyv/
|
||||
|
||||
map.setMaxBounds(
|
||||
[[-100, -200], [100, 200]]
|
||||
);
|
||||
|
||||
if (this._attribution !== undefined) {
|
||||
if (this._attribution === true) {
|
||||
map.attributionControl.setPrefix(false)
|
||||
} else {
|
||||
map.attributionControl.setPrefix(
|
||||
"<span id='leaflet-attribution'></span>");
|
||||
}
|
||||
}
|
||||
|
||||
this._background.addCallbackAndRun(layer => {
|
||||
const newLayer = layer.layer()
|
||||
if (currentLayer !== undefined) {
|
||||
map.removeLayer(currentLayer);
|
||||
}
|
||||
currentLayer = newLayer;
|
||||
if (self._onFullyLoaded !== undefined) {
|
||||
|
||||
currentLayer.on("load", () => {
|
||||
console.log("Fully loaded all tiles!")
|
||||
self._onFullyLoaded(map)
|
||||
})
|
||||
}
|
||||
map.addLayer(newLayer);
|
||||
map.setMaxZoom(layer.max_zoom ?? map.getMaxZoom())
|
||||
if (self._attribution !== true && self._attribution !== false) {
|
||||
self._attribution?.AttachTo('leaflet-attribution')
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
let isRecursing = false;
|
||||
map.on("moveend", function () {
|
||||
if (isRecursing) {
|
||||
return
|
||||
}
|
||||
if (map.getZoom() === location.data.zoom &&
|
||||
map.getCenter().lat === location.data.lat &&
|
||||
map.getCenter().lng === location.data.lon) {
|
||||
return;
|
||||
}
|
||||
location.data.zoom = map.getZoom();
|
||||
location.data.lat = map.getCenter().lat;
|
||||
location.data.lon = map.getCenter().lng;
|
||||
isRecursing = true;
|
||||
location.ping();
|
||||
|
||||
if (self._bounds !== undefined) {
|
||||
self._bounds.setData(BBox.fromLeafletBounds(map.getBounds()))
|
||||
}
|
||||
|
||||
|
||||
isRecursing = false; // This is ugly, I know
|
||||
})
|
||||
|
||||
|
||||
location.addCallback(loc => {
|
||||
const mapLoc = map.getCenter()
|
||||
const dlat = Math.abs(loc.lat - mapLoc[0])
|
||||
const dlon = Math.abs(loc.lon - mapLoc[1])
|
||||
|
||||
if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) {
|
||||
return;
|
||||
}
|
||||
map.setView([loc.lat, loc.lon], loc.zoom)
|
||||
})
|
||||
|
||||
location.map(loc => loc.zoom)
|
||||
.addCallback(zoom => {
|
||||
if (Math.abs(map.getZoom() - zoom) > 0.1) {
|
||||
map.setZoom(zoom, {});
|
||||
}
|
||||
})
|
||||
|
||||
if (self._bounds !== undefined) {
|
||||
self._bounds.setData(BBox.fromLeafletBounds(map.getBounds()))
|
||||
}
|
||||
|
||||
|
||||
if (this._lastClickLocation) {
|
||||
const lastClickLocation = this._lastClickLocation
|
||||
map.on("click", function (e) {
|
||||
// @ts-ignore
|
||||
lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng})
|
||||
});
|
||||
|
||||
map.on("contextmenu", function (e) {
|
||||
// @ts-ignore
|
||||
lastClickLocation?.setData({lat: e.latlng.lat, lon: e.latlng.lng});
|
||||
});
|
||||
}
|
||||
|
||||
this.leafletMap.setData(map)
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ export default class AllDownloads extends ScrollableFullScreen {
|
|||
freeDivId: "belowmap",
|
||||
background: State.state.backgroundLayer,
|
||||
location: State.state.locationControl,
|
||||
features: State.state.featurePipeline.features,
|
||||
features: State.state.featurePipeline,
|
||||
layout: State.state.layoutToUse,
|
||||
}).isRunning.addCallbackAndRun(isRunning => isExporting.setData(isRunning))
|
||||
}
|
||||
|
|
|
@ -5,19 +5,19 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||
import Constants from "../../Models/Constants";
|
||||
import Loc from "../../Models/Loc";
|
||||
import * as L from "leaflet"
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {BBox} from "../../Logic/GeoOperations";
|
||||
|
||||
/**
|
||||
* The bottom right attribution panel in the leaflet map
|
||||
*/
|
||||
export default class Attribution extends Combine {
|
||||
|
||||
constructor(location: UIEventSource<Loc>,
|
||||
constructor(location: UIEventSource<Loc>,
|
||||
userDetails: UIEventSource<UserDetails>,
|
||||
layoutToUse: UIEventSource<LayoutConfig>,
|
||||
leafletMap: UIEventSource<L.Map>) {
|
||||
currentBounds: UIEventSource<BBox>) {
|
||||
|
||||
const mapComplete = new Link(`Mapcomplete ${Constants.vNumber}`, 'https://github.com/pietervdvn/MapComplete', true);
|
||||
const reportBug = new Link(Svg.bug_ui().SetClass("small-image"), "https://github.com/pietervdvn/MapComplete/issues", true);
|
||||
|
@ -43,7 +43,7 @@ export default class Attribution extends Combine {
|
|||
if (userDetails.csCount < Constants.userJourney.tagsVisibleAndWikiLinked) {
|
||||
return undefined;
|
||||
}
|
||||
const bounds: any = leafletMap?.data?.getBounds();
|
||||
const bounds: any = currentBounds.data;
|
||||
if (bounds === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export default class Attribution extends Combine {
|
|||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
return new Link(Svg.josm_logo_ui().SetClass("small-image"), josmLink, true);
|
||||
},
|
||||
[location, leafletMap]
|
||||
[location, currentBounds]
|
||||
)
|
||||
)
|
||||
super([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]);
|
||||
|
|
|
@ -26,10 +26,13 @@ export default class AttributionPanel extends Combine {
|
|||
((layoutToUse.data.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.data.maintainer}),
|
||||
layoutToUse.data.credits,
|
||||
"<br/>",
|
||||
new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.leafletMap),
|
||||
new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.currentBounds),
|
||||
"<br/>",
|
||||
|
||||
new VariableUiElement(contributions.map(contributions => {
|
||||
if(contributions === undefined){
|
||||
return ""
|
||||
}
|
||||
const sorted = Array.from(contributions, ([name, value]) => ({
|
||||
name,
|
||||
value
|
||||
|
|
|
@ -2,54 +2,113 @@ import {SubtleButton} from "../Base/SubtleButton";
|
|||
import Svg from "../../Svg";
|
||||
import Translations from "../i18n/Translations";
|
||||
import State from "../../State";
|
||||
import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource";
|
||||
import {Utils} from "../../Utils";
|
||||
import Combine from "../Base/Combine";
|
||||
import CheckBoxes from "../Input/Checkboxes";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
import {BBox, GeoOperations} from "../../Logic/GeoOperations";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import Title from "../Base/Title";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import SimpleMetaTagger from "../../Logic/SimpleMetaTagger";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {meta} from "@turf/turf";
|
||||
|
||||
export class DownloadPanel extends Toggle {
|
||||
|
||||
constructor() {
|
||||
const state: {
|
||||
featurePipeline: FeaturePipeline,
|
||||
layoutToUse: UIEventSource<LayoutConfig>,
|
||||
currentBounds: UIEventSource<BBox>
|
||||
} = State.state
|
||||
|
||||
|
||||
const t = Translations.t.general.download
|
||||
const somethingLoaded = State.state.featurePipeline.features.map(features => features.length > 0);
|
||||
const name = State.state.layoutToUse.data.id;
|
||||
|
||||
const includeMetaToggle = new CheckBoxes([t.includeMetaData.Clone()])
|
||||
const metaisIncluded = includeMetaToggle.GetValue().map(selected => selected.length > 0)
|
||||
|
||||
|
||||
const buttonGeoJson = new SubtleButton(Svg.floppy_ui(),
|
||||
new Combine([t.downloadGeojson.Clone().SetClass("font-bold"),
|
||||
t.downloadGeoJsonHelper.Clone()]).SetClass("flex flex-col"))
|
||||
.onClick(() => {
|
||||
const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline, {metadata: metaisIncluded.data})
|
||||
const name = State.state.layoutToUse.data.id;
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson),
|
||||
const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data)
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, " "),
|
||||
`MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.geojson`, {
|
||||
mimetype: "application/vnd.geo+json"
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
const buttonCSV = new SubtleButton(Svg.floppy_ui(), new Combine(
|
||||
[t.downloadCSV.Clone().SetClass("font-bold"),
|
||||
t.downloadCSVHelper.Clone()]).SetClass("flex flex-col"))
|
||||
.onClick(() => {
|
||||
const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline, {metadata: metaisIncluded.data})
|
||||
const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data)
|
||||
const csv = GeoOperations.toCSV(geojson.features)
|
||||
const name = State.state.layoutToUse.data.id;
|
||||
|
||||
Utils.offerContentsAsDownloadableFile(csv,
|
||||
`MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.csv`, {
|
||||
mimetype: "text/csv"
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
const downloadButtons = new Combine(
|
||||
[new Title(t.title), buttonGeoJson, buttonCSV, includeMetaToggle, t.licenseInfo.Clone().SetClass("link-underline")])
|
||||
[new Title(t.title),
|
||||
buttonGeoJson,
|
||||
buttonCSV,
|
||||
includeMetaToggle,
|
||||
t.licenseInfo.Clone().SetClass("link-underline")])
|
||||
.SetClass("w-full flex flex-col border-4 border-gray-300 rounded-3xl p-4")
|
||||
|
||||
super(
|
||||
downloadButtons,
|
||||
t.noDataLoaded.Clone(),
|
||||
somethingLoaded)
|
||||
state.featurePipeline.somethingLoaded)
|
||||
}
|
||||
|
||||
private static getCleanGeoJson(state: {
|
||||
featurePipeline: FeaturePipeline,
|
||||
currentBounds: UIEventSource<BBox>
|
||||
}, includeMetaData: boolean) {
|
||||
|
||||
const resultFeatures = []
|
||||
const featureList = state.featurePipeline.GetAllFeaturesWithin(state.currentBounds.data);
|
||||
for (const tile of featureList) {
|
||||
for (const feature of tile) {
|
||||
const cleaned = {
|
||||
type: feature.type,
|
||||
geometry: feature.geometry,
|
||||
properties: {...feature.properties}
|
||||
}
|
||||
|
||||
if (!includeMetaData) {
|
||||
for (const key in cleaned.properties) {
|
||||
if (key === "_lon" || key === "_lat") {
|
||||
continue;
|
||||
}
|
||||
if (key.startsWith("_")) {
|
||||
delete feature.properties[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const datedKeys = [].concat(SimpleMetaTagger.metatags.filter(tagging => tagging.includesDates).map(tagging => tagging.keys))
|
||||
for (const key of datedKeys) {
|
||||
delete feature.properties[key]
|
||||
}
|
||||
|
||||
resultFeatures.push(feature)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type:"FeatureCollection",
|
||||
features: featureList
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
|
@ -62,9 +63,15 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
|
||||
const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown)
|
||||
const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown)]
|
||||
|
||||
const now = new Date()
|
||||
const date = now.getFullYear()+"-"+Utils.TwoDigits(now.getMonth()+1)+"-"+Utils.TwoDigits(now.getDate())
|
||||
const osmcha_link = `https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%22${date}%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D`
|
||||
|
||||
tabsWithAboutMc.push({
|
||||
header: Svg.help,
|
||||
content: new Combine([Translations.t.general.aboutMapcomplete.Clone(), "<br/>Version " + Constants.vNumber])
|
||||
content: new Combine([Translations.t.general.aboutMapcomplete.Clone()
|
||||
.Subs({"osmcha_link": osmcha_link}), "<br/>Version " + Constants.vNumber])
|
||||
.SetClass("link-underline")
|
||||
}
|
||||
);
|
||||
|
|
|
@ -52,7 +52,7 @@ export default class ImportButton extends Toggle {
|
|||
const withLoadingCheck = new Toggle(
|
||||
t.stillLoading,
|
||||
new Combine([button, appliedTags]).SetClass("flex flex-col"),
|
||||
State.state.layerUpdater.runningQuery
|
||||
State.state.featurePipeline.runningQuery
|
||||
)
|
||||
super(t.hasBeenImported, withLoadingCheck, isImported)
|
||||
}
|
||||
|
|
|
@ -9,18 +9,21 @@ import MapControlButton from "../MapControlButton";
|
|||
import Svg from "../../Svg";
|
||||
import AllDownloads from "./AllDownloads";
|
||||
import FilterView from "./FilterView";
|
||||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import {BBox} from "../../Logic/GeoOperations";
|
||||
import Loc from "../../Models/Loc";
|
||||
|
||||
export default class LeftControls extends Combine {
|
||||
|
||||
constructor(featureSource: FeatureSource) {
|
||||
constructor(state: {featurePipeline: FeaturePipeline, currentBounds: UIEventSource<BBox>, locationControl: UIEventSource<Loc>}) {
|
||||
|
||||
const toggledCopyright = new ScrollableFullScreen(
|
||||
() => Translations.t.general.attribution.attributionTitle.Clone(),
|
||||
() =>
|
||||
new AttributionPanel(
|
||||
State.state.layoutToUse,
|
||||
new ContributorCount(featureSource).Contributors
|
||||
new ContributorCount(state).Contributors
|
||||
),
|
||||
undefined
|
||||
);
|
||||
|
|
|
@ -65,10 +65,6 @@ export default class SimpleAddUI extends Toggle {
|
|||
State.state.selectedElement.setData(State.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get(
|
||||
newElementAction.newElementId
|
||||
))
|
||||
|
||||
}
|
||||
|
||||
const addUi = new VariableUiElement(
|
||||
|
@ -104,7 +100,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
new Toggle(
|
||||
Translations.t.general.add.stillLoading.Clone().SetClass("alert"),
|
||||
addUi,
|
||||
State.state.layerUpdater.runningQuery
|
||||
State.state.featurePipeline.runningQuery
|
||||
),
|
||||
Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"),
|
||||
State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints)
|
||||
|
@ -150,7 +146,6 @@ export default class SimpleAddUI extends Toggle {
|
|||
}
|
||||
|
||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||
console.log("Opening precise input ", preset.preciseInput, "with tags", tags)
|
||||
preciseInput = new LocationInput({
|
||||
mapBackground: backgroundLayer,
|
||||
centerLocation: locationSrc,
|
||||
|
@ -215,10 +210,7 @@ export default class SimpleAddUI extends Toggle {
|
|||
const disableFiltersOrConfirm = new Toggle(
|
||||
openLayerOrConfirm,
|
||||
disableFilter,
|
||||
preset.layerToAddTo.appliedFilters.map(filters => {
|
||||
console.log("Current filters are ", filters)
|
||||
return filters === undefined || filters.normalize().and.length === 0;
|
||||
})
|
||||
preset.layerToAddTo.appliedFilters.map(filters => filters === undefined || filters.normalize().and.length === 0)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ export default class CenterMessageBox extends VariableUiElement {
|
|||
|
||||
constructor() {
|
||||
const state = State.state;
|
||||
const updater = State.state.layerUpdater;
|
||||
const updater = State.state.featurePipeline;
|
||||
const t = Translations.t.centerMessage;
|
||||
const message = updater.runningQuery.map(
|
||||
isRunning => {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
|
||||
|
||||
import jsPDF from "jspdf";
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Minimap from "./Base/Minimap";
|
||||
import Loc from "../Models/Loc";
|
||||
import {BBox} from "../Logic/GeoOperations";
|
||||
import BaseLayer from "../Models/BaseLayer";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import Translations from "./i18n/Translations";
|
||||
import State from "../State";
|
||||
import Constants from "../Models/Constants";
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
||||
import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline";
|
||||
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
|
||||
/**
|
||||
* Creates screenshoter to take png screenshot
|
||||
* Creates jspdf and downloads it
|
||||
|
@ -8,21 +24,6 @@
|
|||
* - add new layout in "PDFLayout"
|
||||
* -> in there are more instructions
|
||||
*/
|
||||
|
||||
import jsPDF from "jspdf";
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Minimap from "./Base/Minimap";
|
||||
import Loc from "../Models/Loc";
|
||||
import {BBox} from "../Logic/GeoOperations";
|
||||
import ShowDataLayer from "./ShowDataLayer";
|
||||
import BaseLayer from "../Models/BaseLayer";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import Translations from "./i18n/Translations";
|
||||
import State from "../State";
|
||||
import Constants from "../Models/Constants";
|
||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
||||
|
||||
export default class ExportPDF {
|
||||
// dimensions of the map in milimeter
|
||||
public isRunning = new UIEventSource(true)
|
||||
|
@ -39,7 +40,7 @@ export default class ExportPDF {
|
|||
freeDivId: string,
|
||||
location: UIEventSource<Loc>,
|
||||
background?: UIEventSource<BaseLayer>
|
||||
features: UIEventSource<{ feature: any }[]>,
|
||||
features: FeaturePipeline,
|
||||
layout: UIEventSource<LayoutConfig>
|
||||
}
|
||||
) {
|
||||
|
@ -57,7 +58,7 @@ export default class ExportPDF {
|
|||
zoom: l.zoom + 1
|
||||
}
|
||||
|
||||
const minimap = new Minimap({
|
||||
const minimap = Minimap.createMiniMap({
|
||||
location: new UIEventSource<Loc>(loc), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot
|
||||
background: options.background,
|
||||
allowMoving: false,
|
||||
|
@ -83,24 +84,21 @@ export default class ExportPDF {
|
|||
minimap.AttachTo(options.freeDivId)
|
||||
|
||||
// Next: we prepare the features. Only fully contained features are shown
|
||||
const bounded = options.features.map(feats => {
|
||||
|
||||
const leaflet = minimap.leafletMap.data;
|
||||
if (leaflet === undefined) {
|
||||
return feats
|
||||
}
|
||||
minimap.leafletMap .addCallbackAndRunD(leaflet => {
|
||||
const bounds = BBox.fromLeafletBounds(leaflet.getBounds().pad(0.2))
|
||||
return feats.filter(f => BBox.get(f.feature).isContainedIn(bounds))
|
||||
|
||||
}, [minimap.leafletMap])
|
||||
|
||||
// Add the features to the minimap
|
||||
new ShowDataLayer(
|
||||
bounded,
|
||||
minimap.leafletMap,
|
||||
options.layout,
|
||||
false
|
||||
)
|
||||
options.features.GetTilesPerLayerWithin(bounds, tile => {
|
||||
console.log("REndering", tile.name)
|
||||
new ShowDataLayer(
|
||||
{
|
||||
features: tile,
|
||||
leafletMap: minimap.leafletMap,
|
||||
layerToShow: tile.layer.layerDef,
|
||||
enablePopups: false
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import Img from "../Base/Img";
|
|||
import ImageAttributionSource from "../../Logic/ImageProviders/ImageAttributionSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Loading from "../Base/Loading";
|
||||
|
||||
|
||||
export class AttributedImage extends Combine {
|
||||
|
@ -16,8 +17,13 @@ export class AttributedImage extends Combine {
|
|||
img = new Img(urlSource);
|
||||
attr = new Attribution(imgSource.GetAttributionFor(urlSource), imgSource.SourceIcon())
|
||||
} else {
|
||||
img = new VariableUiElement(preparedUrl.map(url => new Img(url, false, {fallbackImage: './assets/svg/blocked.svg'})))
|
||||
attr = new VariableUiElement(preparedUrl.map(url => new Attribution(imgSource.GetAttributionFor(urlSource), imgSource.SourceIcon())))
|
||||
img = new VariableUiElement(preparedUrl.map(url => {
|
||||
if(url === undefined){
|
||||
return new Loading()
|
||||
}
|
||||
return new Img(url, false, {fallbackImage: './assets/svg/blocked.svg'});
|
||||
}))
|
||||
attr = new VariableUiElement(preparedUrl.map(_ => new Attribution(imgSource.GetAttributionFor(urlSource), imgSource.SourceIcon())))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@ import BaseUIElement from "../BaseUIElement";
|
|||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {Utils} from "../../Utils";
|
||||
import Loc from "../../Models/Loc";
|
||||
import Minimap from "../Base/Minimap";
|
||||
|
||||
|
||||
/**
|
||||
* Selects a direction in degrees
|
||||
*/
|
||||
export default class DirectionInput extends InputElement<string> {
|
||||
public static constructMinimap: ((any) => BaseUIElement);
|
||||
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private readonly _location: UIEventSource<Loc>;
|
||||
private readonly value: UIEventSource<string>;
|
||||
|
@ -40,7 +40,7 @@ export default class DirectionInput extends InputElement<string> {
|
|||
|
||||
let map: BaseUIElement = new FixedUiElement("")
|
||||
if (!Utils.runningFromConsole) {
|
||||
map = DirectionInput.constructMinimap({
|
||||
map = Minimap.createMiniMap({
|
||||
background: this.background,
|
||||
allowMoving: false,
|
||||
location: this._location
|
||||
|
|
|
@ -5,7 +5,7 @@ import Svg from "../../Svg";
|
|||
import {Utils} from "../../Utils";
|
||||
import Loc from "../../Models/Loc";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
import DirectionInput from "./DirectionInput";
|
||||
import Minimap from "../Base/Minimap";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -41,7 +41,7 @@ export default class LengthInput extends InputElement<string> {
|
|||
// @ts-ignore
|
||||
let map = undefined
|
||||
if (!Utils.runningFromConsole) {
|
||||
map = DirectionInput.constructMinimap({
|
||||
map = Minimap.createMiniMap({
|
||||
background: this.background,
|
||||
allowMoving: false,
|
||||
location: this._location,
|
||||
|
|
|
@ -8,35 +8,31 @@ import Svg from "../../Svg";
|
|||
import State from "../../State";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
import ShowDataLayer from "../ShowDataLayer";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import * as L from "leaflet";
|
||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
|
||||
export default class LocationInput extends InputElement<Loc> {
|
||||
|
||||
private static readonly matchLayout = new UIEventSource(new LayoutConfig({
|
||||
description: "Matchpoint style",
|
||||
icon: "./assets/svg/crosshair-empty.svg",
|
||||
id: "matchpoint",
|
||||
language: ["en"],
|
||||
layers: [{
|
||||
private static readonly matchLayer = new LayerConfig(
|
||||
{
|
||||
id: "matchpoint", source: {
|
||||
osmTags: {and: []}
|
||||
},
|
||||
icon: "./assets/svg/crosshair-empty.svg"
|
||||
}],
|
||||
maintainer: "MapComplete",
|
||||
startLat: 0,
|
||||
startLon: 0,
|
||||
startZoom: 0,
|
||||
title: "Location input",
|
||||
version: "0"
|
||||
|
||||
}));
|
||||
}, "matchpoint icon", true
|
||||
)
|
||||
|
||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined)
|
||||
private _centerLocation: UIEventSource<Loc>;
|
||||
private readonly mapBackground: UIEventSource<BaseLayer>;
|
||||
/**
|
||||
* The features to which the input should be snapped
|
||||
* @private
|
||||
*/
|
||||
private readonly _snapTo: UIEventSource<{ feature: any }[]>
|
||||
private readonly _value: UIEventSource<Loc>
|
||||
private readonly _snappedPoint: UIEventSource<any>
|
||||
|
@ -143,7 +139,7 @@ export default class LocationInput extends InputElement<Loc> {
|
|||
protected InnerConstructElement(): HTMLElement {
|
||||
try {
|
||||
const clickLocation = new UIEventSource<Loc>(undefined);
|
||||
const map = new Minimap(
|
||||
const map = Minimap.createMiniMap(
|
||||
{
|
||||
location: this._centerLocation,
|
||||
background: this.mapBackground,
|
||||
|
@ -198,7 +194,6 @@ export default class LocationInput extends InputElement<Loc> {
|
|||
})
|
||||
|
||||
if (this._snapTo !== undefined) {
|
||||
new ShowDataLayer(this._snapTo, map.leafletMap, State.state.layoutToUse, false, false)
|
||||
|
||||
const matchPoint = this._snappedPoint.map(loc => {
|
||||
if (loc === undefined) {
|
||||
|
@ -207,16 +202,25 @@ export default class LocationInput extends InputElement<Loc> {
|
|||
return [{feature: loc}];
|
||||
})
|
||||
if (this._snapTo) {
|
||||
let layout = LocationInput.matchLayout
|
||||
if (this._snappedPointTags !== undefined) {
|
||||
layout = State.state.layoutToUse
|
||||
if (this._snappedPointTags === undefined) {
|
||||
// No special tags - we show a default crosshair
|
||||
new ShowDataLayer({
|
||||
features: new StaticFeatureSource(matchPoint),
|
||||
enablePopups: false,
|
||||
zoomToFeatures: false,
|
||||
leafletMap: map.leafletMap,
|
||||
layerToShow: LocationInput.matchLayer
|
||||
})
|
||||
}else{
|
||||
new ShowDataMultiLayer({
|
||||
features: new StaticFeatureSource(matchPoint),
|
||||
enablePopups: false,
|
||||
zoomToFeatures: false,
|
||||
leafletMap: map.leafletMap,
|
||||
layers: State.state.filteredLayers
|
||||
}
|
||||
)
|
||||
}
|
||||
new ShowDataLayer(
|
||||
matchPoint,
|
||||
map.leafletMap,
|
||||
layout,
|
||||
false, false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Minimap from "../Base/Minimap";
|
||||
import State from "../../State";
|
||||
import ShowDataLayer from "../ShowDataLayer";
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
import {LeafletMouseEvent} from "leaflet";
|
||||
import Combine from "../Base/Combine";
|
||||
|
@ -13,10 +13,16 @@ import Translations from "../i18n/Translations";
|
|||
import SplitAction from "../../Logic/Osm/Actions/SplitAction";
|
||||
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
|
||||
import Title from "../Base/Title";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
|
||||
export default class SplitRoadWizard extends Toggle {
|
||||
private static splitLayout = new UIEventSource(SplitRoadWizard.GetSplitLayout())
|
||||
private static splitLayerStyling = new LayerConfig({
|
||||
id: "splitpositions",
|
||||
source: {osmTags: "_cutposition=yes"},
|
||||
icon: "./assets/svg/plus.svg"
|
||||
}, "(BUILTIN) SplitRoadWizard.ts", true)
|
||||
|
||||
/**
|
||||
* A UI Element used for splitting roads
|
||||
|
@ -36,7 +42,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
const splitClicked = new UIEventSource<boolean>(false);
|
||||
|
||||
// Minimap on which you can select the points to be splitted
|
||||
const miniMap = new Minimap({background: State.state.backgroundLayer, allowMoving: false});
|
||||
const miniMap = Minimap.createMiniMap({background: State.state.backgroundLayer, allowMoving: false});
|
||||
miniMap.SetStyle("width: 100%; height: 24rem;");
|
||||
|
||||
// Define how a cut is displayed on the map
|
||||
|
@ -45,8 +51,20 @@ export default class SplitRoadWizard extends Toggle {
|
|||
const roadElement = State.state.allElements.ContainingFeatures.get(id)
|
||||
const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]);
|
||||
// Datalayer displaying the road and the cut points (if any)
|
||||
new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true);
|
||||
new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false)
|
||||
new ShowDataMultiLayer({
|
||||
features: new StaticFeatureSource(roadEventSource, true),
|
||||
layers: State.state.filteredLayers,
|
||||
leafletMap: miniMap.leafletMap,
|
||||
enablePopups: false,
|
||||
zoomToFeatures: true
|
||||
})
|
||||
new ShowDataLayer({
|
||||
features: new StaticFeatureSource(splitPoints, true),
|
||||
leafletMap: miniMap.leafletMap,
|
||||
zoomToFeatures: false,
|
||||
enablePopups: false,
|
||||
layerToShow: SplitRoadWizard.splitLayerStyling
|
||||
})
|
||||
|
||||
/**
|
||||
* Handles a click on the overleaf map.
|
||||
|
@ -135,21 +153,4 @@ export default class SplitRoadWizard extends Toggle {
|
|||
const confirm = new Toggle(mapView, splitToggle, splitClicked);
|
||||
super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit)
|
||||
}
|
||||
|
||||
private static GetSplitLayout(): LayoutConfig {
|
||||
return new LayoutConfig({
|
||||
maintainer: "mapcomplete",
|
||||
language: ["en"],
|
||||
startLon: 0,
|
||||
startLat: 0,
|
||||
description: "Split points visualisations - built in at SplitRoadWizard.ts",
|
||||
icon: "", startZoom: 0,
|
||||
title: "Split locations",
|
||||
version: "",
|
||||
|
||||
id: "splitpositions",
|
||||
layers: [{id: "splitpositions", source: {osmTags: "_cutposition=yes"}, icon: "./assets/svg/plus.svg"}]
|
||||
}, true, "(BUILTIN) SplitRoadWizard.ts")
|
||||
|
||||
}
|
||||
}
|
|
@ -1,19 +1,11 @@
|
|||
/**
|
||||
* The data layer shows all the given geojson elements with the appropriate icon etc
|
||||
*/
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import * as L from "leaflet"
|
||||
import State from "../State";
|
||||
import FeatureInfoBox from "./Popup/FeatureInfoBox";
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import FeatureSource from "../Logic/FeatureSource/FeatureSource";
|
||||
|
||||
export interface ShowDataLayerOptions {
|
||||
features: FeatureSource,
|
||||
leafletMap: UIEventSource<L.Map>,
|
||||
enablePopups?: true | boolean,
|
||||
zoomToFeatures? : false | boolean,
|
||||
}
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import FeatureInfoBox from "../Popup/FeatureInfoBox";
|
||||
import State from "../../State";
|
||||
import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
|
||||
|
||||
export default class ShowDataLayer {
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
export interface ShowDataLayerOptions {
|
||||
features: FeatureSource,
|
||||
leafletMap: UIEventSource<L.Map>,
|
||||
enablePopups?: true | boolean,
|
||||
zoomToFeatures?: false | boolean,
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import FilteredLayer from "../Models/FilteredLayer";
|
||||
import ShowDataLayer, {ShowDataLayerOptions} from "./ShowDataLayer/ShowDataLayer";
|
||||
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
|
||||
|
||||
/**
|
||||
* SHows geojson on the given leaflet map, but attempts to figure out the correct layer first
|
||||
*/
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import ShowDataLayer from "./ShowDataLayer";
|
||||
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
|
||||
|
||||
export default class ShowDataMultiLayer {
|
||||
constructor(options: ShowDataLayerOptions & { layers: UIEventSource<FilteredLayer[]> }) {
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import {ImageCarousel} from "./Image/ImageCarousel";
|
|||
import Combine from "./Base/Combine";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {ImageUploadFlow} from "./Image/ImageUploadFlow";
|
||||
|
||||
import ShareButton from "./BigComponents/ShareButton";
|
||||
import Svg from "../Svg";
|
||||
import ReviewElement from "./Reviews/ReviewElement";
|
||||
|
@ -13,7 +12,6 @@ import MangroveReviews from "../Logic/Web/MangroveReviews";
|
|||
import Translations from "./i18n/Translations";
|
||||
import ReviewForm from "./Reviews/ReviewForm";
|
||||
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization";
|
||||
|
||||
import State from "../State";
|
||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
||||
import BaseUIElement from "./BaseUIElement";
|
||||
|
@ -26,6 +24,9 @@ import BaseLayer from "../Models/BaseLayer";
|
|||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||
import ImportButton from "./BigComponents/ImportButton";
|
||||
import {Tag} from "../Logic/Tags/Tag";
|
||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
|
||||
import Minimap from "./Base/Minimap";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
|
@ -37,14 +38,6 @@ export interface SpecialVisualization {
|
|||
|
||||
export default class SpecialVisualizations {
|
||||
|
||||
|
||||
static constructMiniMap: (options?: {
|
||||
background?: UIEventSource<BaseLayer>,
|
||||
location?: UIEventSource<Loc>,
|
||||
allowMoving?: boolean,
|
||||
leafletOptions?: any
|
||||
}) => BaseUIElement;
|
||||
static constructShowDataLayer: (features: UIEventSource<{ feature: any; freshness: Date }[]>, leafletMap: UIEventSource<any>, layoutToUse: UIEventSource<any>, enablePopups?: boolean, zoomToFeatures?: boolean) => any;
|
||||
public static specialVisualizations: SpecialVisualization[] =
|
||||
[
|
||||
{
|
||||
|
@ -153,7 +146,7 @@ export default class SpecialVisualizations {
|
|||
lon: Number(properties._lon),
|
||||
zoom: zoom
|
||||
})
|
||||
const minimap = SpecialVisualizations.constructMiniMap(
|
||||
const minimap = Minimap.createMiniMap(
|
||||
{
|
||||
background: state.backgroundLayer,
|
||||
location: locationSource,
|
||||
|
@ -169,12 +162,14 @@ export default class SpecialVisualizations {
|
|||
}
|
||||
})
|
||||
|
||||
SpecialVisualizations.constructShowDataLayer(
|
||||
featuresToShow,
|
||||
minimap["leafletMap"],
|
||||
State.state.layoutToUse,
|
||||
false,
|
||||
true
|
||||
new ShowDataMultiLayer(
|
||||
{
|
||||
leafletMap: minimap["leafletMap"],
|
||||
enablePopups : false,
|
||||
zoomToFeatures: true,
|
||||
layers: State.state.filteredLayers,
|
||||
features: new StaticFeatureSource(featuresToShow, true)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue