Merge plant_detection
This commit is contained in:
commit
25ec71aff1
15 changed files with 780 additions and 158 deletions
|
@ -2,15 +2,17 @@ import OsmChangeAction from "./OsmChangeAction";
|
|||
import {Changes} from "../Changes";
|
||||
import {ChangeDescription} from "./ChangeDescription";
|
||||
import {TagsFilter} from "../../Tags/TagsFilter";
|
||||
import {OsmTags} from "../../../Models/OsmFeature";
|
||||
|
||||
export default class ChangeTagAction extends OsmChangeAction {
|
||||
private readonly _elementId: string;
|
||||
private readonly _tagsFilter: TagsFilter;
|
||||
private readonly _currentTags: any;
|
||||
private readonly _currentTags: Record<string, string> | OsmTags;
|
||||
private readonly _meta: { theme: string, changeType: string };
|
||||
|
||||
constructor(elementId: string,
|
||||
tagsFilter: TagsFilter, currentTags: any, meta: {
|
||||
tagsFilter: TagsFilter,
|
||||
currentTags: Record<string, string>, meta: {
|
||||
theme: string,
|
||||
changeType: "answer" | "soft-delete" | "add-image" | string
|
||||
}) {
|
||||
|
|
346
Logic/Web/PlantNet.ts
Normal file
346
Logic/Web/PlantNet.ts
Normal file
File diff suppressed because one or more lines are too long
|
@ -354,6 +354,25 @@ export default class Wikidata {
|
|||
throw "Unknown id type: " + id
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SPARQL-query, return the result
|
||||
*
|
||||
* @param keys: how variables are named. Every key not ending with 'Label' should appear in at least one statement
|
||||
* @param statements
|
||||
* @constructor
|
||||
*/
|
||||
public static async Sparql<T>(keys: string[], statements: string[]):Promise< (T & Record<string, {type: string, value: string}>) []> {
|
||||
const query = "SELECT "+keys.map(k => k.startsWith("?") ? k : "?"+k).join(" ")+"\n" +
|
||||
"WHERE\n" +
|
||||
"{\n" +
|
||||
statements.map(stmt => stmt.endsWith(".") ? stmt : stmt+".").join("\n") +
|
||||
" SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE]\". }\n" +
|
||||
"}"
|
||||
const url = wds.sparqlQuery(query)
|
||||
const result = await Utils.downloadJsonCached(url, 24 * 60 * 60 * 1000)
|
||||
return result.results.bindings
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a wikidata page
|
||||
* @returns the entity of the given value
|
||||
|
|
|
@ -3,12 +3,11 @@ import BaseUIElement from "../BaseUIElement";
|
|||
|
||||
export class Button extends BaseUIElement {
|
||||
private _text: BaseUIElement;
|
||||
private _onclick: () => void;
|
||||
|
||||
constructor(text: string | BaseUIElement, onclick: (() => void)) {
|
||||
constructor(text: string | BaseUIElement, onclick: (() => void | Promise<void>)) {
|
||||
super();
|
||||
this._text = Translations.W(text);
|
||||
this._onclick = onclick;
|
||||
this.onClick(onclick)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
|
@ -20,7 +19,6 @@ export class Button extends BaseUIElement {
|
|||
const button = document.createElement("button")
|
||||
button.type = "button"
|
||||
button.appendChild(el)
|
||||
button.onclick = this._onclick
|
||||
form.appendChild(button)
|
||||
return form;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Utils } from "../Utils";
|
||||
|
||||
/**
|
||||
* A thin wrapper around a html element, which allows to generate a HTML-element.
|
||||
*
|
||||
|
@ -11,7 +9,7 @@ export default abstract class BaseUIElement {
|
|||
protected isDestroyed = false;
|
||||
protected readonly clss: Set<string> = new Set<string>();
|
||||
protected style: string;
|
||||
private _onClick: () => void;
|
||||
private _onClick: () => void | Promise<void>;
|
||||
|
||||
public onClick(f: (() => void)) {
|
||||
this._onClick = f;
|
||||
|
@ -127,12 +125,15 @@ export default abstract class BaseUIElement {
|
|||
|
||||
if (this._onClick !== undefined) {
|
||||
const self = this;
|
||||
el.onclick = (e) => {
|
||||
el.onclick = async (e) => {
|
||||
// @ts-ignore
|
||||
if (e.consumed) {
|
||||
return;
|
||||
}
|
||||
self._onClick();
|
||||
const v = self._onClick();
|
||||
if(typeof v === "object"){
|
||||
await v
|
||||
}
|
||||
// @ts-ignore
|
||||
e.consumed = true;
|
||||
}
|
||||
|
|
121
UI/BigComponents/PlantNetSpeciesSearch.ts
Normal file
121
UI/BigComponents/PlantNetSpeciesSearch.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import PlantNet from "../../Logic/Web/PlantNet";
|
||||
import Loading from "../Base/Loading";
|
||||
import Wikidata from "../../Logic/Web/Wikidata";
|
||||
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox";
|
||||
import {Button} from "../Base/Button";
|
||||
import Combine from "../Base/Combine";
|
||||
import Title from "../Base/Title";
|
||||
import WikipediaBox from "../Wikipedia/WikipediaBox";
|
||||
import Translations from "../i18n/Translations";
|
||||
import List from "../Base/List";
|
||||
import Svg from "../../Svg";
|
||||
|
||||
|
||||
export default class PlantNetSpeciesSearch extends VariableUiElement {
|
||||
/***
|
||||
* Given images, queries plantnet to search a species matching those images.
|
||||
* A list of species will be presented to the user, after which they can confirm an item.
|
||||
* The wikidata-url is returned in the callback when the user selects one
|
||||
*/
|
||||
constructor(images: Store<string[]>, onConfirm: (wikidataUrl: string) => Promise<void>) {
|
||||
const t = Translations.t.plantDetection
|
||||
super(
|
||||
images
|
||||
.bind(images => {
|
||||
if (images.length === 0) {
|
||||
return null
|
||||
}
|
||||
return UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0,5)));
|
||||
})
|
||||
.map(result => {
|
||||
if (images.data.length === 0) {
|
||||
return new Combine([t.takeImages, t.howTo.intro, new List(
|
||||
[
|
||||
t.howTo.li0,
|
||||
t.howTo.li1,
|
||||
t.howTo.li2,
|
||||
t.howTo.li3
|
||||
]
|
||||
)]).SetClass("flex flex-col")
|
||||
}
|
||||
if (result === undefined) {
|
||||
return new Loading(t.querying.Subs(images.data))
|
||||
}
|
||||
|
||||
if (result["error"] !== undefined) {
|
||||
return t.error.Subs(<any>result).SetClass("alert")
|
||||
}
|
||||
console.log(result)
|
||||
const success = result["success"]
|
||||
|
||||
const selectedSpecies = new UIEventSource<string>(undefined)
|
||||
const speciesInformation = success.results
|
||||
.filter(species => species.score >= 0.005)
|
||||
.map(species => {
|
||||
const wikidata = UIEventSource.FromPromise(Wikidata.Sparql<{ species }>(["?species", "?speciesLabel"],
|
||||
["?species wdt:P846 \"" + species.gbif.id + "\""]));
|
||||
|
||||
const confirmButton = new Button(t.seeInfo, async() => {
|
||||
await selectedSpecies.setData(wikidata.data[0].species?.value)
|
||||
}).SetClass("btn")
|
||||
|
||||
const match = t.matchPercentage.Subs({match: Math.round(species.score * 100)}).SetClass("font-bold")
|
||||
|
||||
const extraItems = new Combine([match, confirmButton]).SetClass("flex flex-col")
|
||||
|
||||
return new WikidataPreviewBox(wikidata.map(wd => wd == undefined ? undefined : wd[0]?.species?.value),
|
||||
{
|
||||
whileLoading: new Loading(
|
||||
t.loadingWikidata.Subs({species: species.species.scientificNameWithoutAuthor})),
|
||||
extraItems: [new Combine([extraItems])],
|
||||
|
||||
imageStyle: "max-width: 8rem; width: unset; height: 8rem"
|
||||
})
|
||||
.SetClass("border-2 border-subtle rounded-xl block mb-2")
|
||||
}
|
||||
);
|
||||
const plantOverview = new Combine([
|
||||
new Title(t.overviewTitle),
|
||||
t.overviewIntro,
|
||||
t.overviewVerify.SetClass("font-bold"),
|
||||
...speciesInformation]).SetClass("flex flex-col")
|
||||
|
||||
|
||||
return new VariableUiElement(selectedSpecies.map(wikidataSpecies => {
|
||||
if (wikidataSpecies === undefined) {
|
||||
return plantOverview
|
||||
}
|
||||
const buttons = new Combine([
|
||||
new Button(
|
||||
new Combine([
|
||||
Svg.back_svg().SetClass("w-6 mr-1 bg-white rounded-full p-1"),
|
||||
t.back]).SetClass("flex"),
|
||||
() => {
|
||||
selectedSpecies.setData(undefined)
|
||||
}).SetClass("btn btn-secondary"),
|
||||
|
||||
new Button(
|
||||
new Combine([Svg.confirm_svg().SetClass("w-6 mr-1"), t.confirm]).SetClass("flex")
|
||||
, () => {
|
||||
onConfirm(wikidataSpecies)
|
||||
}).SetClass("btn"),
|
||||
|
||||
]).SetClass("flex justify-between");
|
||||
|
||||
return new Combine([
|
||||
new WikipediaBox([wikidataSpecies], {
|
||||
firstParagraphOnly: false,
|
||||
noImages: false,
|
||||
addHeader: false
|
||||
}).SetClass("h-96"),
|
||||
buttons
|
||||
]).SetClass("flex flex-col self-end")
|
||||
}))
|
||||
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
}
|
|
@ -130,16 +130,17 @@ export class ImageUploadFlow extends Toggle {
|
|||
return undefined;
|
||||
}
|
||||
if (l == 1) {
|
||||
return t.uploadingPicture.Clone().SetClass("alert")
|
||||
return new Loading( t.uploadingPicture).SetClass("alert")
|
||||
} else {
|
||||
return t.uploadingMultiple.Subs({count: "" + l}).SetClass("alert")
|
||||
return new Loading(t.uploadingMultiple.Subs({count: "" + l})).SetClass("alert")
|
||||
}
|
||||
})),
|
||||
new VariableUiElement(uploader.failed.map(q => q.length).map(l => {
|
||||
if (l == 0) {
|
||||
return undefined
|
||||
}
|
||||
return new Loading(t.uploadFailed).SetClass("alert");
|
||||
console.log(l)
|
||||
return t.uploadFailed.SetClass("block alert");
|
||||
})),
|
||||
new VariableUiElement(uploadedCount.map(l => {
|
||||
if (l == 0) {
|
||||
|
|
|
@ -61,6 +61,8 @@ import StatisticsPanel from "./BigComponents/StatisticsPanel";
|
|||
import {OsmFeature} from "../Models/OsmFeature";
|
||||
import EditableTagRendering from "./Popup/EditableTagRendering";
|
||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
|
||||
import {ProvidedImage} from "../Logic/ImageProviders/ImageProvider";
|
||||
import PlantNetSpeciesSearch from "./BigComponents/PlantNetSpeciesSearch";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
|
@ -196,7 +198,7 @@ class NearbyImageVis implements SpecialVisualization {
|
|||
new ChangeTagAction(
|
||||
id,
|
||||
new And(tags),
|
||||
tagSource,
|
||||
tagSource.data,
|
||||
{
|
||||
theme: state?.layoutToUse.id,
|
||||
changeType: "link-image"
|
||||
|
@ -1302,6 +1304,53 @@ export default class SpecialVisualizations {
|
|||
const [layerId, __] = tagRenderingId.split(".")
|
||||
return [layerId]
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "plantnet_detection",
|
||||
|
||||
docs: "Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). ",
|
||||
args: [{
|
||||
name: "image_key",
|
||||
defaultValue: AllImageProviders.defaultKeys.join(","),
|
||||
doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated "
|
||||
}],
|
||||
constr: (state, tags, args) => {
|
||||
let imagePrefixes: string[] = undefined;
|
||||
if (args.length > 0) {
|
||||
imagePrefixes = [].concat(...args.map(a => a.split(",")));
|
||||
}
|
||||
|
||||
const detect = new UIEventSource(false)
|
||||
const toggle = new Toggle(
|
||||
new Lazy(() => {
|
||||
const allProvidedImages: Store<ProvidedImage[]> = AllImageProviders.LoadImagesFor(tags, imagePrefixes)
|
||||
const allImages: Store<string[]> = allProvidedImages.map(pi => pi.map(pi => pi.url))
|
||||
return new PlantNetSpeciesSearch(allImages, async selectedWikidata => {
|
||||
selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
|
||||
const change = new ChangeTagAction(tags.data.id,
|
||||
new And([new Tag("species:wikidata", selectedWikidata),
|
||||
new Tag("source:species:wikidata","PlantNet.org AI")
|
||||
]),
|
||||
tags.data,
|
||||
{
|
||||
theme: state.layoutToUse.id,
|
||||
changeType: "plantnet-ai-detection"
|
||||
}
|
||||
)
|
||||
await state.changes.applyAction(change)
|
||||
})
|
||||
}),
|
||||
new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() => detect.setData(true)),
|
||||
detect
|
||||
)
|
||||
|
||||
return new Combine([
|
||||
toggle,
|
||||
new Combine([Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"),
|
||||
Translations.t.plantDetection.poweredByPlantnet])
|
||||
.SetClass("flex p-2 bg-gray-200 rounded-xl self-end")
|
||||
]).SetClass("flex flex-col")
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata";
|
||||
import {Translation, TypedTranslation} from "../i18n/Translation";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
|
@ -57,7 +57,11 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
}
|
||||
]
|
||||
|
||||
constructor(wikidataId: UIEventSource<string>, options?: {noImages?: boolean}) {
|
||||
constructor(wikidataId: Store<string>, options?: {
|
||||
noImages?: boolean,
|
||||
imageStyle?: string,
|
||||
whileLoading?: BaseUIElement | string,
|
||||
extraItems?: (BaseUIElement | string)[]}) {
|
||||
let inited = false;
|
||||
const wikidata = wikidataId
|
||||
.stabilized(250)
|
||||
|
@ -71,7 +75,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
|
||||
super(wikidata.map(maybeWikidata => {
|
||||
if (maybeWikidata === null || !inited) {
|
||||
return undefined;
|
||||
return options?.whileLoading;
|
||||
}
|
||||
|
||||
if (maybeWikidata === undefined) {
|
||||
|
@ -87,7 +91,10 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
|
||||
}
|
||||
|
||||
public static WikidataResponsePreview(wikidata: WikidataResponse, options?: {noImages?: boolean}): BaseUIElement {
|
||||
public static WikidataResponsePreview(wikidata: WikidataResponse, options?: {
|
||||
noImages?: boolean,
|
||||
imageStyle?: string,
|
||||
extraItems?: (BaseUIElement | string)[]}): BaseUIElement {
|
||||
let link = new Link(
|
||||
new Combine([
|
||||
wikidata.id,
|
||||
|
@ -100,7 +107,8 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
[Translation.fromMap(wikidata.labels)?.SetClass("font-bold"),
|
||||
link]).SetClass("flex justify-between"),
|
||||
Translation.fromMap(wikidata.descriptions),
|
||||
WikidataPreviewBox.QuickFacts(wikidata, options)
|
||||
WikidataPreviewBox.QuickFacts(wikidata, options),
|
||||
...(options.extraItems ?? [])
|
||||
]).SetClass("flex flex-col link-underline")
|
||||
|
||||
|
||||
|
@ -108,11 +116,9 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
if (wikidata.claims.get("P18")?.size > 0) {
|
||||
imageUrl = Array.from(wikidata.claims.get("P18"))[0]
|
||||
}
|
||||
|
||||
|
||||
if (imageUrl && !options?.noImages) {
|
||||
imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageUrl).url
|
||||
info = new Combine([new Img(imageUrl).SetStyle("max-width: 5rem; width: unset; height: 4rem").SetClass("rounded-xl mr-2"),
|
||||
info = new Combine([new Img(imageUrl).SetStyle(options?.imageStyle ?? "max-width: 5rem; width: unset; height: 4rem").SetClass("rounded-xl mr-2"),
|
||||
info.SetClass("w-full")]).SetClass("flex")
|
||||
}
|
||||
|
||||
|
|
|
@ -105,80 +105,49 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "tree-leaf_type",
|
||||
"id": "plantnet",
|
||||
"render": "{plantnet_detection()}",
|
||||
"condition": "species:wikidata="
|
||||
},
|
||||
{
|
||||
"id": "tree-species-wikidata",
|
||||
"question": {
|
||||
"nl": "Is dit een naald- of loofboom?",
|
||||
"en": "Is this a broadleaved or needleleaved tree?",
|
||||
"it": "Si tratta di un albero latifoglia o aghifoglia?",
|
||||
"fr": "Cet arbre est-il un feuillu ou un résineux ?",
|
||||
"de": "Ist dies ein Laub- oder Nadelbaum?",
|
||||
"es": "¿Es un árbol de hoja ancha o de hoja aguja?"
|
||||
"en": "What species is this tree?",
|
||||
"de": "Um welche Baumart handelt es sich?",
|
||||
"id": "Spesies pohon apa ini?",
|
||||
"es": "¿De qué especie es este árbol?",
|
||||
"nl": "Wat is de boomsoort?",
|
||||
"fr": "Quelle est l'espèce de cet arbre ?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=broadleaved"
|
||||
"render": {
|
||||
"*": "{wikipedia(species:wikidata):max-height: 25rem}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "species:wikidata",
|
||||
"type": "wikidata",
|
||||
"helperArgs": [
|
||||
"species",
|
||||
{
|
||||
"instanceOf": [
|
||||
10884,
|
||||
16521
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Loofboom",
|
||||
"en": "Broadleaved",
|
||||
"it": "Latifoglia",
|
||||
"fr": "Feuillu",
|
||||
"de": "Laubbaum",
|
||||
"ca": "De fulla ampla",
|
||||
"es": "Latifoliada",
|
||||
"id": "Berdaun lebar"
|
||||
},
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/broadleaved.svg",
|
||||
"class": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=needleleaved"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Naaldboom",
|
||||
"en": "Needleleaved",
|
||||
"it": "Aghifoglia",
|
||||
"fr": "Résineux",
|
||||
"de": "Nadelbaum",
|
||||
"ca": "Amb fulles d'agulla",
|
||||
"es": "Hoja aguja",
|
||||
"id": "Berdaun jarum"
|
||||
},
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/needleleaved.svg",
|
||||
"class": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=leafless"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Permanent bladloos",
|
||||
"en": "Permanently leafless",
|
||||
"it": "Privo di foglie (permanente)",
|
||||
"fr": "Sans feuilles (Permanent)",
|
||||
"de": "Dauerhaft blattlos",
|
||||
"es": "Permanentemente sin hojas",
|
||||
"id": "Tanpa daun permanen"
|
||||
},
|
||||
"hideInAnswer": true,
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/leafless.svg",
|
||||
"class": "small"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tree-wikipedia",
|
||||
"#": "If this tree has a wikipedia article, show it. People can _only_ set the species though!",
|
||||
"render": {
|
||||
"*": "{wikipedia()}"
|
||||
},
|
||||
"condition": {
|
||||
"or": [
|
||||
"wikipedia~*",
|
||||
"wikidata~*"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tree-denotation",
|
||||
|
@ -313,6 +282,88 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tree-leaf_type",
|
||||
"question": {
|
||||
"nl": "Is dit een naald- of loofboom?",
|
||||
"en": "Is this a broadleaved or needleleaved tree?",
|
||||
"it": "Si tratta di un albero latifoglia o aghifoglia?",
|
||||
"fr": "Cet arbre est-il un feuillu ou un résineux ?",
|
||||
"de": "Ist dies ein Laub- oder Nadelbaum?",
|
||||
"es": "¿Es un árbol de hoja ancha o de hoja aguja?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=broadleaved"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Loofboom",
|
||||
"en": "Broadleaved",
|
||||
"it": "Latifoglia",
|
||||
"fr": "Feuillu",
|
||||
"de": "Laubbaum",
|
||||
"ca": "De fulla ampla",
|
||||
"es": "Latifoliada",
|
||||
"id": "Berdaun lebar"
|
||||
},
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/broadleaved.svg",
|
||||
"class": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=needleleaved"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Naaldboom",
|
||||
"en": "Needleleaved",
|
||||
"it": "Aghifoglia",
|
||||
"fr": "Résineux",
|
||||
"de": "Nadelbaum",
|
||||
"ca": "Amb fulles d'agulla",
|
||||
"es": "Hoja aguja",
|
||||
"id": "Berdaun jarum"
|
||||
},
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/needleleaved.svg",
|
||||
"class": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"and": [
|
||||
"leaf_type=leafless"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"nl": "Permanent bladloos",
|
||||
"en": "Permanently leafless",
|
||||
"it": "Privo di foglie (permanente)",
|
||||
"fr": "Sans feuilles (Permanent)",
|
||||
"de": "Dauerhaft blattlos",
|
||||
"es": "Permanentemente sin hojas",
|
||||
"id": "Tanpa daun permanen"
|
||||
},
|
||||
"hideInAnswer": true,
|
||||
"icon": {
|
||||
"path": "./assets/layers/tree_node/leafless.svg",
|
||||
"class": "small"
|
||||
}
|
||||
}
|
||||
],
|
||||
"condition": {
|
||||
"#": "If the wikidata entry is known, no need to ask for redundant data such as leaf type",
|
||||
"and": [
|
||||
"species:wikidata="
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tree-decidouous",
|
||||
"question": {
|
||||
|
@ -360,48 +411,10 @@
|
|||
}
|
||||
],
|
||||
"condition": {
|
||||
"#": "If the wikidata entry is known, no need to ask for redundant data such as leaf type",
|
||||
"and": [
|
||||
"leaf_type!~^leafless$"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tree-species-wikidata",
|
||||
"question": {
|
||||
"en": "What species is this tree?",
|
||||
"de": "Um welche Baumart handelt es sich?",
|
||||
"id": "Spesies pohon apa ini?",
|
||||
"es": "¿De qué especie es este árbol?",
|
||||
"nl": "Wat is de boomsoort?",
|
||||
"fr": "Quelle est l'espèce de cet arbre ?"
|
||||
},
|
||||
"render": {
|
||||
"*": "{wikipedia(species:wikidata):max-height: 25rem}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "species:wikidata",
|
||||
"type": "wikidata",
|
||||
"helperArgs": [
|
||||
"species",
|
||||
{
|
||||
"instanceOf": [
|
||||
10884,
|
||||
16521
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tree-wikipedia",
|
||||
"#": "If this tree has a wikipedia article, show it. People can _only_ set the species though!",
|
||||
"render": {
|
||||
"*": "{wikipedia()}"
|
||||
},
|
||||
"condition": {
|
||||
"or": [
|
||||
"wikipedia~*",
|
||||
"wikidata~*"
|
||||
"leaf_type!~^leafless$",
|
||||
"species:wikidata="
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -985,6 +985,16 @@
|
|||
"authors": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "plantnet_logo.svg",
|
||||
"license": "Logo (fair usage)",
|
||||
"authors": [
|
||||
"https://plantnet.org"
|
||||
],
|
||||
"sources": [
|
||||
"https://plantnet.org"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "plus.svg",
|
||||
"license": "CC0; trivial",
|
||||
|
|
22
assets/svg/plantnet_logo.svg
Normal file
22
assets/svg/plantnet_logo.svg
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
width="197"
|
||||
height="116.32127"
|
||||
viewBox="0 0 197 116.32127"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs6" />
|
||||
<g
|
||||
id="g8"
|
||||
transform="translate(-1.375196,-41.199913)">
|
||||
<path
|
||||
style="fill:#8fb636;stroke:none"
|
||||
d="m 93.3752,135.52119 12,-21 -4,25 c 23.045,-11.772 32.998,-47.038298 33,-70.999998 0,-5.9908 0.838,-25.2828 -7.213,-27.1721 -7.654,-1.7961 -17.603,13.1208 -21.041,18.1721 -13.3558,19.6213 -22.8241,53.184998 -12.746,75.999998 M 42.375196,50.521192 c -3.2439,25.6902 16.759104,62.735998 41.000004,72.999998 -0.0234,-8.487 -1.0391,-16.513 0.4637,-24.999998 1.0531,-5.9466 5.1683,-13.9996 4.0093,-20 -0.914,-4.7316 -6.0155,-8.7352 -9.473,-11.6998 -9.978,-8.5558 -22.5558,-16.0213 -36.000004,-16.3002 m -41,72.999998 c 4.19365,8.258 15.8253,12.614 24,15.989 22.0533,9.107 46.351404,15.02 70.000004,18.011 -6.7774,-45.892 -61.032604,-47.725 -94.000004,-34 m 102.000004,34 c 22.945,-1.699 47.762,-8.999 69,-17.604 9.137,-3.702 20.019,-8.293 26,-16.396 -34.944,-13.554 -85.475,-12.001 -95,34 z"
|
||||
id="path881" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -858,6 +858,10 @@ video {
|
|||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-3 {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
@ -886,6 +890,10 @@ video {
|
|||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mr-2 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
@ -906,10 +914,6 @@ video {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
@ -962,10 +966,6 @@ video {
|
|||
margin-top: -3rem;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-0 {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
@ -1074,6 +1074,10 @@ video {
|
|||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-14 {
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
@ -1098,6 +1102,10 @@ video {
|
|||
height: 8rem;
|
||||
}
|
||||
|
||||
.h-96 {
|
||||
height: 24rem;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
@ -1162,6 +1170,14 @@ video {
|
|||
width: 2rem;
|
||||
}
|
||||
|
||||
.w-14 {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
.w-1\/3 {
|
||||
width: 33.333333%;
|
||||
}
|
||||
|
||||
.w-4 {
|
||||
width: 1rem;
|
||||
}
|
||||
|
@ -1407,6 +1423,10 @@ video {
|
|||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.rounded-3xl {
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
@ -1423,10 +1443,6 @@ video {
|
|||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.rounded-sm {
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
|
@ -1436,14 +1452,14 @@ video {
|
|||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.border-4 {
|
||||
border-width: 4px;
|
||||
}
|
||||
|
@ -1504,6 +1520,11 @@ video {
|
|||
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(229, 231, 235, var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-400 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(248, 113, 113, var(--tw-bg-opacity));
|
||||
|
@ -1524,11 +1545,6 @@ video {
|
|||
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(229, 231, 235, var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(243, 244, 246, var(--tw-bg-opacity));
|
||||
|
|
|
@ -685,6 +685,27 @@
|
|||
"typeText": "Type some text to add a comment",
|
||||
"warnAnonymous": "You are not logged in. We won't be able to contact you to resolve your issue."
|
||||
},
|
||||
"plantDetection": {
|
||||
"back": "Back to species overview",
|
||||
"confirm": "Select species",
|
||||
"error": "Something went wrong while detecting the tree species: {error}",
|
||||
"howTo": {
|
||||
"intro": "For optimal results,",
|
||||
"li0": "take a picture which show the tree in the center without much background",
|
||||
"li1": "take a picture which shows a single leaf",
|
||||
"li2": "take a picture which shows the bark",
|
||||
"li3": "take a picture of the flowers"
|
||||
},
|
||||
"loadingWikidata": "Loading information about {species}",
|
||||
"matchPercentage": "{match}% match",
|
||||
"overviewIntro": "The AI on <a href='https://plantnet.org/' target='_blank'>PlantNet.org</a> thinks the images show the species below.",
|
||||
"overviewTitle": "Automatically detected species",
|
||||
"overviewVerify": "Please verify that correct species and link it to the tree",
|
||||
"poweredByPlantnet": "Powered by <a href='https://plantnet.org' target='_blank'>plantnet.org</a>",
|
||||
"querying": "Querying plantnet.org with {length} images",
|
||||
"seeInfo": "See more information about the species",
|
||||
"takeImages": "Take images of the tree to automatically detect the tree type"
|
||||
},
|
||||
"privacy": {
|
||||
"editing": "When you make a change to the map, this change is recorded on OpenStreetMap and is publicly available to anyone. A changeset made with MapComplete includes the following data: <ul><li>The changes you made</li><li>Your username</li><li>When this change is made</li><li>The theme you used while making the change</li><li>The language of the user interface</li><li>An indication of how close you were to changed objects. Other mappers can use this information to determine if a change was made based on survey or on remote research</li></ul> Please refer to <a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>the privacy policy on OpenStreetMap.org</a> for detailed information. We'd like to remind you that you can use a fictional name when signing up.",
|
||||
"editingTitle": "When making changes",
|
||||
|
|
3
test.ts
3
test.ts
|
@ -1,3 +0,0 @@
|
|||
import StatisticsGUI from "./UI/StatisticsGUI";
|
||||
|
||||
new StatisticsGUI().AttachTo("maindiv")
|
Loading…
Reference in a new issue