Add export_as_geojson, open_in_id and open_in_josm special renderings
This commit is contained in:
parent
b0f0a57bc7
commit
7f829a3578
8 changed files with 144 additions and 60 deletions
|
@ -19,9 +19,82 @@ import Loc from "../../Models/Loc";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import PrivacyPolicy from "./PrivacyPolicy";
|
|
||||||
import ContributorCount from "../../Logic/ContributorCount";
|
import ContributorCount from "../../Logic/ContributorCount";
|
||||||
|
|
||||||
|
export class OpenIdEditor extends VariableUiElement {
|
||||||
|
constructor(state : {locationControl: UIEventSource<Loc>}, iconStyle? : string, objectId?: string) {
|
||||||
|
const t = Translations.t.general.attribution
|
||||||
|
super(state.locationControl.map(location => {
|
||||||
|
let elementSelect = "";
|
||||||
|
if(objectId !== undefined){
|
||||||
|
const parts = objectId.split("/")
|
||||||
|
const tp = parts[0]
|
||||||
|
if(parts.length === 2 && !isNaN(Number(parts[1])) && (tp === "node" || tp === "way" || tp === "relation")){
|
||||||
|
elementSelect = "&"+ tp+"="+parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}`
|
||||||
|
return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, {url: idLink, newTab: true})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenMapillary extends VariableUiElement {
|
||||||
|
constructor(state : {locationControl: UIEventSource<Loc>}, iconStyle? : string) {
|
||||||
|
const t = Translations.t.general.attribution
|
||||||
|
super( state.locationControl.map(location => {
|
||||||
|
const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}`
|
||||||
|
return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, {
|
||||||
|
url: mapillaryLink,
|
||||||
|
newTab: true
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenJosm extends Combine {
|
||||||
|
|
||||||
|
constructor(state : {osmConnection: OsmConnection, currentBounds: UIEventSource<BBox>,}, iconStyle? : string) {
|
||||||
|
const t = Translations.t.general.attribution
|
||||||
|
|
||||||
|
const josmState = new UIEventSource<string>(undefined)
|
||||||
|
// Reset after 15s
|
||||||
|
josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined))
|
||||||
|
|
||||||
|
const stateIndication = new VariableUiElement(josmState.map(state => {
|
||||||
|
if (state === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
state = state.toUpperCase()
|
||||||
|
if (state === "OK") {
|
||||||
|
return t.josmOpened.SetClass("thanks")
|
||||||
|
}
|
||||||
|
return t.josmNotOpened.SetClass("alert")
|
||||||
|
}));
|
||||||
|
|
||||||
|
const toggle = new Toggle(
|
||||||
|
new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => {
|
||||||
|
const bounds: any = state.currentBounds.data;
|
||||||
|
if (bounds === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const top = bounds.getNorth();
|
||||||
|
const bottom = bounds.getSouth();
|
||||||
|
const right = bounds.getEast();
|
||||||
|
const left = bounds.getWest();
|
||||||
|
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||||
|
Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR"))
|
||||||
|
}), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible))
|
||||||
|
|
||||||
|
super([stateIndication, toggle]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attribution panel shown on mobile
|
* The attribution panel shown on mobile
|
||||||
*/
|
*/
|
||||||
|
@ -39,10 +112,7 @@ export default class CopyrightPanel extends Combine {
|
||||||
|
|
||||||
const t = Translations.t.general.attribution
|
const t = Translations.t.general.attribution
|
||||||
const layoutToUse = state.layoutToUse
|
const layoutToUse = state.layoutToUse
|
||||||
const josmState = new UIEventSource<string>(undefined)
|
const iconStyle = "height: 1.5rem; width: auto"
|
||||||
// Reset after 15s
|
|
||||||
josmState.stabilized(15000).addCallbackD(_ => josmState.setData(undefined))
|
|
||||||
const iconStyle = "height: 1.5rem; width: auto"
|
|
||||||
const actionButtons = [
|
const actionButtons = [
|
||||||
new SubtleButton(Svg.liberapay_ui().SetStyle(iconStyle), t.donate, {
|
new SubtleButton(Svg.liberapay_ui().SetStyle(iconStyle), t.donate, {
|
||||||
url: "https://liberapay.com/pietervdvn/",
|
url: "https://liberapay.com/pietervdvn/",
|
||||||
|
@ -56,42 +126,9 @@ export default class CopyrightPanel extends Combine {
|
||||||
url: Utils.OsmChaLinkFor(31, state.layoutToUse.id),
|
url: Utils.OsmChaLinkFor(31, state.layoutToUse.id),
|
||||||
newTab: true
|
newTab: true
|
||||||
}),
|
}),
|
||||||
new VariableUiElement(state.locationControl.map(location => {
|
new OpenIdEditor(state, iconStyle),
|
||||||
const idLink = `https://www.openstreetmap.org/edit?editor=id#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}`
|
new OpenMapillary(state, iconStyle),
|
||||||
return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, {url: idLink, newTab: true})
|
new OpenJosm(state, iconStyle)
|
||||||
})),
|
|
||||||
|
|
||||||
new VariableUiElement(state.locationControl.map(location => {
|
|
||||||
const mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}`
|
|
||||||
return new SubtleButton(Svg.mapillary_black_ui().SetStyle(iconStyle), t.openMapillary, {
|
|
||||||
url: mapillaryLink,
|
|
||||||
newTab: true
|
|
||||||
})
|
|
||||||
})),
|
|
||||||
new VariableUiElement(josmState.map(state => {
|
|
||||||
if (state === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
state = state.toUpperCase()
|
|
||||||
if (state === "OK") {
|
|
||||||
return t.josmOpened.SetClass("thanks")
|
|
||||||
}
|
|
||||||
return t.josmNotOpened.SetClass("alert")
|
|
||||||
})),
|
|
||||||
new Toggle(
|
|
||||||
new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => {
|
|
||||||
const bounds: any = state.currentBounds.data;
|
|
||||||
if (bounds === undefined) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const top = bounds.getNorth();
|
|
||||||
const bottom = bounds.getSouth();
|
|
||||||
const right = bounds.getEast();
|
|
||||||
const left = bounds.getWest();
|
|
||||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
|
||||||
Utils.download(josmLink).then(answer => josmState.setData(answer.replace(/\n/g, '').trim())).catch(_ => josmState.setData("ERROR"))
|
|
||||||
}), undefined, state.osmConnection.userDetails.map(ud => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible)),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const iconAttributions = Utils.NoNull(Array.from(layoutToUse.ExtractImages()))
|
const iconAttributions = Utils.NoNull(Array.from(layoutToUse.ExtractImages()))
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {Utils} from "../../Utils";
|
||||||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||||
import MoveWizard from "./MoveWizard";
|
import MoveWizard from "./MoveWizard";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
|
|
||||||
export default class FeatureInfoBox extends ScrollableFullScreen {
|
export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
|
|
||||||
|
@ -189,8 +188,13 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
|
||||||
new VariableUiElement(
|
new VariableUiElement(
|
||||||
State.state.featureSwitchIsDebugging.map(isDebugging => {
|
State.state.featureSwitchIsDebugging.map(isDebugging => {
|
||||||
if (isDebugging) {
|
if (isDebugging) {
|
||||||
const config: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, "");
|
const config_all_tags: TagRenderingConfig = new TagRenderingConfig({render: "{all_tags()}"}, "");
|
||||||
return new TagRenderingAnswer(tags, config, "all_tags")
|
const config_download: TagRenderingConfig = new TagRenderingConfig({render: "{export_as_geojson()}"}, "");
|
||||||
|
const config_id: TagRenderingConfig = new TagRenderingConfig({render: "{open_in_iD()}"}, "");
|
||||||
|
|
||||||
|
return new Combine([new TagRenderingAnswer(tags, config_all_tags, "all_tags"),
|
||||||
|
new TagRenderingAnswer(tags, config_download, ""),
|
||||||
|
new TagRenderingAnswer(tags, config_id, "")])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,6 +37,7 @@ import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||||
import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton";
|
import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton";
|
||||||
import TagApplyButton from "./Popup/TagApplyButton";
|
import TagApplyButton from "./Popup/TagApplyButton";
|
||||||
import AutoApplyButton from "./Popup/AutoApplyButton";
|
import AutoApplyButton from "./Popup/AutoApplyButton";
|
||||||
|
import {OpenIdEditor} from "./BigComponents/CopyrightPanel";
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
|
@ -543,7 +544,7 @@ export default class SpecialVisualizations {
|
||||||
const t = Translations.t.general.download;
|
const t = Translations.t.general.download;
|
||||||
|
|
||||||
return new SubtleButton(Svg.download_ui(),
|
return new SubtleButton(Svg.download_ui(),
|
||||||
new Combine([t.downloadGpx.SetClass("font-bold text-lg"),
|
new Combine([t.downloadFeatureAsGpx.SetClass("font-bold text-lg"),
|
||||||
t.downloadGpxHelper.SetClass("subtle")]).SetClass("flex flex-col")
|
t.downloadGpxHelper.SetClass("subtle")]).SetClass("flex flex-col")
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
console.log("Exporting as GPX!")
|
console.log("Exporting as GPX!")
|
||||||
|
@ -560,6 +561,41 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
funcName: "export_as_geojson",
|
||||||
|
docs: "Exports the selected feature as GeoJson-file",
|
||||||
|
args: [],
|
||||||
|
constr: (state, tagSource, args) => {
|
||||||
|
const t = Translations.t.general.download;
|
||||||
|
|
||||||
|
return new SubtleButton(Svg.download_ui(),
|
||||||
|
new Combine([t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||||
|
t.downloadGeoJsonHelper.SetClass("subtle")]).SetClass("flex flex-col")
|
||||||
|
).onClick(() => {
|
||||||
|
console.log("Exporting as Geojson")
|
||||||
|
const tags = tagSource.data
|
||||||
|
const feature = state.allElements.ContainingFeatures.get(tags.id)
|
||||||
|
const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags)
|
||||||
|
const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
|
||||||
|
const data = JSON.stringify(feature, null, " ");
|
||||||
|
Utils.offerContentsAsDownloadableFile(data, title + "_mapcomplete_export.geojson", {
|
||||||
|
mimetype: "application/vnd.geo+json"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
funcName: "open_in_iD",
|
||||||
|
docs: "Opens the current view in the iD-editor",
|
||||||
|
args: [],
|
||||||
|
constr: (state, feature ) => {
|
||||||
|
return new OpenIdEditor(state, undefined, feature.data.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
funcName: "clear_location_history",
|
funcName: "clear_location_history",
|
||||||
docs: "A button to remove the travelled track information from the device",
|
docs: "A button to remove the travelled track information from the device",
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
{
|
{
|
||||||
"location": "point",
|
"location": "point",
|
||||||
"icon": {
|
"icon": {
|
||||||
"render": "addSmall:#000",
|
"render": "addSmall:#000",
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{
|
{
|
||||||
"if": "detach=yes",
|
"if": "detach=yes",
|
||||||
|
|
|
@ -3,11 +3,16 @@
|
||||||
"description": "The default rendering for a locationInput which snaps onto another object",
|
"description": "The default rendering for a locationInput which snaps onto another object",
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"and": []}
|
"and": []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"mapRendering": [{
|
"mapRendering": [
|
||||||
"location": ["point","centroid"],
|
{
|
||||||
"icon": "./assets/svg/crosshair-empty.svg"
|
"location": [
|
||||||
}]
|
"point",
|
||||||
|
"centroid"
|
||||||
|
],
|
||||||
|
"icon": "./assets/svg/crosshair-empty.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -99,8 +99,7 @@
|
||||||
"osmTags": "building~*",
|
"osmTags": "building~*",
|
||||||
"maxCacheAge": 0
|
"maxCacheAge": 0
|
||||||
},
|
},
|
||||||
"calculatedTags": [
|
"calculatedTags": [],
|
||||||
],
|
|
||||||
"mapRendering": [
|
"mapRendering": [
|
||||||
{
|
{
|
||||||
"width": {
|
"width": {
|
||||||
|
@ -319,7 +318,6 @@
|
||||||
"render": "Service road"
|
"render": "Service road"
|
||||||
},
|
},
|
||||||
"tagRenderings": []
|
"tagRenderings": []
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "generic_osm_object",
|
"id": "generic_osm_object",
|
||||||
|
@ -475,7 +473,10 @@
|
||||||
"description": "Geometry which comes from GRB with tools to import them",
|
"description": "Geometry which comes from GRB with tools to import them",
|
||||||
"source": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"and": ["HUISNR~*","man_made!=mast"]
|
"and": [
|
||||||
|
"HUISNR~*",
|
||||||
|
"man_made!=mast"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"geoJson": "https://betadata.grbosm.site/grb?bbox={x_min},{y_min},{x_max},{y_max}",
|
"geoJson": "https://betadata.grbosm.site/grb?bbox={x_min},{y_min},{x_max},{y_max}",
|
||||||
"geoJsonZoomLevel": 18,
|
"geoJsonZoomLevel": 18,
|
||||||
|
@ -508,7 +509,8 @@
|
||||||
"id": "Import-button",
|
"id": "Import-button",
|
||||||
"render": "{import_way_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}",
|
"render": "{import_way_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}",
|
||||||
"mappings": [
|
"mappings": [
|
||||||
{"#": "Hide import button if intersection with other objects are detected",
|
{
|
||||||
|
"#": "Hide import button if intersection with other objects are detected",
|
||||||
"if": "_intersects_with_other_features~*",
|
"if": "_intersects_with_other_features~*",
|
||||||
"then": "This GRB building intersects with the following features: {_intersects_with_other_features}.<br/>Fix the overlap and try again"
|
"then": "This GRB building intersects with the following features: {_intersects_with_other_features}.<br/>Fix the overlap and try again"
|
||||||
},
|
},
|
||||||
|
@ -555,7 +557,6 @@
|
||||||
"if": "_osm_obj:addr:housenumber~*",
|
"if": "_osm_obj:addr:housenumber~*",
|
||||||
"then": "The overlapping building only has a housenumber known: {_osm_obj:addr:housenumber}"
|
"then": "The overlapping building only has a housenumber known: {_osm_obj:addr:housenumber}"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"if": "_osm_obj:id=",
|
"if": "_osm_obj:id=",
|
||||||
"then": "No overlapping OpenStreetMap-building found"
|
"then": "No overlapping OpenStreetMap-building found"
|
||||||
|
|
|
@ -198,7 +198,8 @@
|
||||||
"downloadAsPdf": "Download a PDF of the current map",
|
"downloadAsPdf": "Download a PDF of the current map",
|
||||||
"downloadAsPdfHelper": "Ideal to print the current map",
|
"downloadAsPdfHelper": "Ideal to print the current map",
|
||||||
"downloadGeojson": "Download visible data as GeoJSON",
|
"downloadGeojson": "Download visible data as GeoJSON",
|
||||||
"downloadGpx": "Download as GPX-file",
|
"downloadFeatureAsGpx": "Download as GPX-file",
|
||||||
|
"downloadFeatureAsGeojson": "Download as GeoJson-file",
|
||||||
"downloadGpxHelper": "A GPX-file can be used with most navigation devices and applications",
|
"downloadGpxHelper": "A GPX-file can be used with most navigation devices and applications",
|
||||||
"uploadGpx": "Upload your track to OpenStreetMap",
|
"uploadGpx": "Upload your track to OpenStreetMap",
|
||||||
"exporting": "Exporting…",
|
"exporting": "Exporting…",
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default class T {
|
||||||
* Returns an empty list if successful
|
* Returns an empty list if successful
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public Run(): ({ testsuite: string, name: string, msg: string } []) {
|
public Run(): { testsuite: string, name: string, msg: string } [] {
|
||||||
const failures: { testsuite: string, name: string, msg: string } [] = []
|
const failures: { testsuite: string, name: string, msg: string } [] = []
|
||||||
for (const [name, test] of this._tests) {
|
for (const [name, test] of this._tests) {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue