2025-07-10 18:26:31 +02:00
import {
SpecialVisualization ,
SpecialVisualizationState ,
SpecialVisualizationSvelte ,
} from "../SpecialVisualization"
2025-01-28 23:37:42 +01:00
import Maproulette from "../../Logic/Maproulette"
import SvelteUIElement from "../Base/SvelteUIElement"
2025-01-29 20:37:04 +01:00
import { PointImportButtonViz } from "../Popup/ImportButtons/PointImportButtonViz"
import WayImportButtonViz from "../Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
import { PlantNetDetectionViz } from "../Popup/PlantNetDetectionViz"
import Constants from "../../Models/Constants"
import { Store , Stores , UIEventSource } from "../../Logic/UIEventSource"
import { Feature , GeoJsonProperties } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import BaseUIElement from "../BaseUIElement"
import LinkedDataLoader from "../../Logic/Web/LinkedDataLoader"
import Toggle from "../Input/Toggle"
import ComparisonTool from "../Comparison/ComparisonTool.svelte"
import { Utils } from "../../Utils"
2025-06-19 01:51:09 +02:00
import TagApplyViz from "./TagApplyViz"
2025-06-27 18:36:02 +02:00
import { ServerSourceInfo } from "../../Models/SourceOverview"
2025-07-06 02:29:48 +02:00
import MaprouletteSetStatus from "../MapRoulette/MaprouletteSetStatus.svelte"
2025-01-28 23:37:42 +01:00
2025-07-06 02:29:48 +02:00
class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
2025-06-26 05:20:12 +02:00
funcName = "maproulette_set_status"
group = "data_import"
docs = "Change the status of the given MapRoulette task"
2025-06-29 22:42:28 +02:00
needsUrls = [ Maproulette . defaultEndpointInfo ]
2025-06-26 05:20:12 +02:00
example =
" The following example sets the status to '2' (false positive)\n" +
"\n" +
"```json\n" +
"{\n" +
2025-07-10 18:26:31 +02:00
' "id": "mark_duplicate",\n' +
' "render": {\n' +
' "special": {\n' +
' "type": "maproulette_set_status",\n' +
' "message": {\n' +
' "en": "Mark as not found or false positive"\n' +
2025-06-26 05:20:12 +02:00
" },\n" +
2025-07-10 18:26:31 +02:00
' "status": "2",\n' +
' "image": "close"\n' +
2025-06-26 05:20:12 +02:00
" }\n" +
" }\n" +
"}\n" +
"```"
args = [
{
name : "message" ,
doc : "A message to show to the user" ,
} ,
{
name : "image" ,
doc : "Image to show" ,
defaultValue : "confirm" ,
} ,
{
name : "message_confirm" ,
doc : "What to show when the task is closed, either by the user or was already closed." ,
} ,
{
name : "status" ,
doc : "A statuscode to apply when the button is clicked. 1 = `close`, 2 = `false_positive`, 3 = `skip`, 4 = `deleted`, 5 = `already fixed` (on the map, e.g. for duplicates), 6 = `too hard`" ,
defaultValue : "1" ,
} ,
{
name : "maproulette_id" ,
doc : "The property name containing the maproulette id" ,
defaultValue : "mr_taskId" ,
} ,
{
name : "ask_feedback" ,
doc : "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added" ,
defaultValue : "" ,
} ,
]
constr ( state , tagsSource , args ) {
2025-07-10 18:26:31 +02:00
let [ message , image , message_closed , statusToSet , maproulette_id_key , askFeedback ] = args
2025-06-26 05:20:12 +02:00
if ( image === "" ) {
image = "confirm"
}
if ( maproulette_id_key === "" || maproulette_id_key === undefined ) {
maproulette_id_key = "mr_taskId"
}
statusToSet = statusToSet ? ? "1"
return new SvelteUIElement ( MaprouletteSetStatus , {
state ,
tags : tagsSource ,
message ,
image ,
message_closed ,
statusToSet ,
maproulette_id_key ,
askFeedback ,
} )
}
}
class LinkedDataFromWebsite extends SpecialVisualization {
funcName = "linked_data_from_website"
group = "data_import"
2025-07-10 18:26:31 +02:00
docs =
"Attempts to load (via a proxy) the specified website and parsed ld+json from there. Suitable data will be offered to import into OSM. Note: this element is added by default"
2025-06-26 05:20:12 +02:00
args = [
{
name : "key" ,
defaultValue : "website" ,
doc : "Attempt to load ld+json from the specified URL. This can be in an embedded <script type='ld+json'>" ,
} ,
{
name : "useProxy" ,
defaultValue : "yes" ,
doc : "If 'yes', uses the provided proxy server. This proxy server will scrape HTML and search for a script with `lang='ld+json'`. If `no`, the data will be downloaded and expects a linked-data-json directly" ,
} ,
{
name : "host" ,
doc : "If not using a proxy, define what host the website is allowed to connect to" ,
} ,
{
name : "mode" ,
doc : "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM" ,
} ,
{
name : "collapsed" ,
defaultValue : "yes" ,
doc : "If the containing accordion should be closed" ,
} ,
]
2025-06-27 18:36:02 +02:00
needsUrls : ServerSourceInfo [ ] = [
Constants . linkedDataProxyInfo ,
{
url : "http://www.schema.org" ,
description : "Only needed for the velopark-theme" ,
category : "feature" ,
openData : true ,
} ,
{
url : "https://data.velopark.be" ,
description : "Only needed for the velopark-theme" ,
openData : true ,
selfhostable : false ,
category : "feature" ,
} ,
]
2025-06-26 05:20:12 +02:00
constr (
state : SpecialVisualizationState ,
tags : UIEventSource < Record < string , string > > ,
argument : string [ ] ,
feature : Feature ,
2025-07-10 18:26:31 +02:00
layer : LayerConfig
2025-06-26 05:20:12 +02:00
) : BaseUIElement {
const key = argument [ 0 ] ? ? "website"
const useProxy = argument [ 1 ] !== "no"
const readonly = argument [ 3 ] === "readonly"
const isClosed = ( argument [ 4 ] ? ? "yes" ) === "yes"
2025-07-10 18:26:31 +02:00
const countryStore : Store < string | undefined > = tags . mapD ( ( tags ) = > tags . _country )
2025-06-26 05:20:12 +02:00
const sourceUrl : Store < string | undefined > = tags . mapD ( ( tags ) = > {
if ( ! tags [ key ] || tags [ key ] === "undefined" ) {
return null
}
return tags [ key ]
} )
2025-07-10 18:26:31 +02:00
const externalData : Store < { success : GeoJsonProperties } | { error } > = sourceUrl . bindD (
( url ) = > {
const country = countryStore . data
if ( url . startsWith ( "https://data.velopark.be/" ) ) {
2025-06-26 05:20:12 +02:00
return Stores . FromPromiseWithErr (
( async ( ) = > {
try {
2025-07-10 18:26:31 +02:00
const loadAll = layer . id . toLowerCase ( ) . indexOf ( "maproulette" ) >= 0 // Dirty hack
const features = await LinkedDataLoader . fetchVeloparkEntry (
2025-06-26 05:20:12 +02:00
url ,
2025-07-10 18:26:31 +02:00
loadAll
2025-06-26 05:20:12 +02:00
)
2025-07-10 18:26:31 +02:00
const feature =
features . find ( ( f ) = > f . properties [ "ref:velopark" ] === url ) ? ?
features [ 0 ]
const properties = feature . properties
properties [ "ref:velopark" ] = url
console . log ( "Got properties from velopark:" , properties )
return properties
2025-06-26 05:20:12 +02:00
} catch ( e ) {
2025-07-10 18:26:31 +02:00
console . error ( e )
throw e
2025-06-26 05:20:12 +02:00
}
2025-07-10 18:26:31 +02:00
} ) ( )
2025-06-26 05:20:12 +02:00
)
2025-07-10 18:26:31 +02:00
}
if ( country === undefined ) {
return undefined
}
return Stores . FromPromiseWithErr (
( async ( ) = > {
try {
return await LinkedDataLoader . fetchJsonLd (
url ,
{ country } ,
useProxy ? "proxy" : "fetch-lod"
)
} catch ( e ) {
console . log (
"Could not get with proxy/download LOD, attempting to download directly. Error for " ,
url ,
"is" ,
e
)
return await LinkedDataLoader . fetchJsonLd ( url , { country } , "fetch-raw" )
}
} ) ( )
)
} ,
[ countryStore ]
)
2025-06-26 05:20:12 +02:00
externalData . addCallbackAndRunD ( ( lod ) = >
2025-07-10 18:26:31 +02:00
console . log ( "linked_data_from_website received the following data:" , lod )
2025-06-26 05:20:12 +02:00
)
return new Toggle (
new SvelteUIElement ( ComparisonTool , {
feature ,
state ,
tags ,
layer ,
externalData ,
sourceUrl ,
readonly ,
collapsed : isClosed ,
} ) ,
undefined ,
2025-07-10 18:26:31 +02:00
sourceUrl . map ( ( url ) = > ! ! url )
2025-06-26 05:20:12 +02:00
)
}
}
class CompareData extends SpecialVisualization {
funcName = "compare_data"
group = "data_import"
needsUrls = ( args ) = > args [ 1 ] . split ( ";" )
args = [
{
name : "url" ,
required : true ,
doc : "The attribute containing the url where to fetch more data" ,
} ,
{
name : "host" ,
required : true ,
doc : "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. " ,
} ,
{
name : "readonly" ,
required : false ,
doc : "If 'yes', will not show 'apply'-buttons" ,
} ,
]
2025-07-10 18:26:31 +02:00
docs =
"Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM"
2025-06-26 05:20:12 +02:00
constr (
state : SpecialVisualizationState ,
tagSource : UIEventSource < Record < string , string > > ,
args : string [ ] ,
feature : Feature ,
2025-07-10 18:26:31 +02:00
layer : LayerConfig
2025-06-26 05:20:12 +02:00
) : BaseUIElement {
const url = args [ 0 ]
const readonly = args [ 3 ] === "yes"
const externalData = Stores . FromPromiseWithErr ( Utils . downloadJson ( url ) )
return new SvelteUIElement ( ComparisonTool , {
url ,
state ,
tags : tagSource ,
layer ,
feature ,
readonly ,
externalData ,
} )
}
}
2025-01-29 20:37:04 +01:00
export class DataImportSpecialVisualisations {
public static initList ( ) : ( SpecialVisualization & { group } ) [ ] {
2025-02-10 02:04:58 +01:00
return [
2025-06-19 01:51:09 +02:00
new TagApplyViz ( ) ,
2025-02-10 02:04:58 +01:00
new PointImportButtonViz ( ) ,
new WayImportButtonViz ( ) ,
new ConflateImportButtonViz ( ) ,
new PlantNetDetectionViz ( ) ,
2025-07-06 02:29:48 +02:00
new MaprouletteSetStatusVis ( ) ,
2025-06-26 05:20:12 +02:00
new LinkedDataFromWebsite ( ) ,
new CompareData ( ) ,
2025-02-10 02:04:58 +01:00
]
}
2025-01-28 23:37:42 +01:00
}