From a8959fc9344f7d1479a2b86974c77f4339c72188 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 17 Aug 2022 02:42:59 +0200 Subject: [PATCH] Integrating plantnet-API, add rudimentary UI --- Logic/Web/PlantNet.ts | 4 +-- Logic/Web/Wikidata.ts | 19 +++++++++++ UI/Wikipedia/WikidataPreviewBox.ts | 13 ++++---- test.ts | 53 ++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/Logic/Web/PlantNet.ts b/Logic/Web/PlantNet.ts index 11757f3175..d980cf4033 100644 --- a/Logic/Web/PlantNet.ts +++ b/Logic/Web/PlantNet.ts @@ -14,7 +14,7 @@ export default class PlantNet { for (const image of imageUrls) { url += "&images="+encodeURIComponent(image) } - return Utils.downloadJson(url) + return Utils.downloadJsonCached(url, 365*24*60*60*1000) } public static exampleResult: PlantNetResult = { @@ -316,7 +316,7 @@ export default class PlantNet { "version": "2022-06-14 (6.0)", "remainingIdentificationRequests": 499 } - +public static exampleResultPrunus : PlantNetResult = {"query":{"project":"all","images":["https://i.imgur.com/VJp1qG1.jpg"],"organs":["auto"],"includeRelatedImages":false},"language":"en","preferedReferential":"the-plant-list","bestMatch":"Malus halliana Koehne","results":[{"score":0.23548,"species":{"scientificNameWithoutAuthor":"Malus halliana","scientificNameAuthorship":"Koehne","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Hall crab apple","Adirondack Crabapple","Hall's crabapple"],"scientificName":"Malus halliana Koehne"},"gbif":{"id":"3001220"}},{"score":0.1514,"species":{"scientificNameWithoutAuthor":"Prunus campanulata","scientificNameAuthorship":"Maxim.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Formosan cherry","Bellflower cherry","Taiwan cherry"],"scientificName":"Prunus campanulata Maxim."},"gbif":{"id":"3021408"}},{"score":0.14758,"species":{"scientificNameWithoutAuthor":"Malus coronaria","scientificNameAuthorship":"(L.) Mill.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Sweet crab apple","American crabapple","Fragrant crabapple"],"scientificName":"Malus coronaria (L.) Mill."},"gbif":{"id":"3001166"}},{"score":0.13092,"species":{"scientificNameWithoutAuthor":"Prunus serrulata","scientificNameAuthorship":"Lindl.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Japanese flowering cherry","Japanese cherry","Oriental cherry"],"scientificName":"Prunus serrulata Lindl."},"gbif":{"id":"3022609"}},{"score":0.10147,"species":{"scientificNameWithoutAuthor":"Malus floribunda","scientificNameAuthorship":"Siebold ex Van Houtte","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Japanese flowering Crabapple","Japanese crab","Japanese crab apple"],"scientificName":"Malus floribunda Siebold ex Van Houtte"},"gbif":{"id":"3001365"}},{"score":0.05122,"species":{"scientificNameWithoutAuthor":"Prunus sargentii","scientificNameAuthorship":"Rehder","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Sargent's cherry","Northern Japanese hill cherry","Sargent Cherry"],"scientificName":"Prunus sargentii Rehder"},"gbif":{"id":"3020955"}},{"score":0.02576,"species":{"scientificNameWithoutAuthor":"Malus × spectabilis","scientificNameAuthorship":"(Sol.) Borkh.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Asiatic apple","Chinese crab","Chinese flowering apple"],"scientificName":"Malus × spectabilis (Sol.) Borkh."},"gbif":{"id":"3001108"}},{"score":0.01802,"species":{"scientificNameWithoutAuthor":"Prunus triloba","scientificNameAuthorship":"Lindl.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Flowering almond","Flowering plum"],"scientificName":"Prunus triloba Lindl."},"gbif":{"id":"3023130"}},{"score":0.01206,"species":{"scientificNameWithoutAuthor":"Prunus japonica","scientificNameAuthorship":"Thunb.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Chinese bush cherry","Japanese bush cherry","Oriental bush cherry"],"scientificName":"Prunus japonica Thunb."},"gbif":{"id":"3020565"}},{"score":0.01161,"species":{"scientificNameWithoutAuthor":"Prunus × yedoensis","scientificNameAuthorship":"Matsum.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Yoshino cherry","Potomac cherry","Tokyo cherry"],"scientificName":"Prunus × yedoensis Matsum."},"gbif":{"id":"3021335"}},{"score":0.00914,"species":{"scientificNameWithoutAuthor":"Prunus mume","scientificNameAuthorship":"(Siebold) Siebold & Zucc.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Japanese apricot","Ume","Chinese Plum"],"scientificName":"Prunus mume (Siebold) Siebold & Zucc."},"gbif":{"id":"3021046"}},{"score":0.0088,"species":{"scientificNameWithoutAuthor":"Malus niedzwetzkyana","scientificNameAuthorship":"Dieck ex Koehne","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Apple","Paradise apple","Kulturapfel"],"scientificName":"Malus niedzwetzkyana Dieck ex Koehne"},"gbif":{"id":"3001327"}},{"score":0.00734,"species":{"scientificNameWithoutAuthor":"Malus hupehensis","scientificNameAuthorship":"(Pamp.) Rehder","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Chinese crab apple","Hupeh crab","Tea crab apple"],"scientificName":"Malus hupehensis (Pamp.) Rehder"},"gbif":{"id":"3001077"}},{"score":0.00688,"species":{"scientificNameWithoutAuthor":"Malus angustifolia","scientificNameAuthorship":"(Aiton) Michx.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Southern crab apple","Narrow-leaved crabapple","Southern crabapple"],"scientificName":"Malus angustifolia (Aiton) Michx."},"gbif":{"id":"3001548"}},{"score":0.00614,"species":{"scientificNameWithoutAuthor":"Prunus subhirtella","scientificNameAuthorship":"Miq.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Rosebud cherry","Spring cherry","Autumn cherry"],"scientificName":"Prunus subhirtella Miq."},"gbif":{"id":"3021229"}},{"score":0.00267,"species":{"scientificNameWithoutAuthor":"Robinia viscosa","scientificNameAuthorship":"Vent.","genus":{"scientificNameWithoutAuthor":"Robinia","scientificNameAuthorship":"","scientificName":"Robinia"},"family":{"scientificNameWithoutAuthor":"Leguminosae","scientificNameAuthorship":"","scientificName":"Leguminosae"},"commonNames":["Clammy locust","Rose acacia","Clammy-bark locust"],"scientificName":"Robinia viscosa Vent."},"gbif":{"id":"5352245"}},{"score":0.0026,"species":{"scientificNameWithoutAuthor":"Handroanthus impetiginosus","scientificNameAuthorship":"(Mart. ex DC.) Mattos","genus":{"scientificNameWithoutAuthor":"Handroanthus","scientificNameAuthorship":"","scientificName":"Handroanthus"},"family":{"scientificNameWithoutAuthor":"Bignoniaceae","scientificNameAuthorship":"","scientificName":"Bignoniaceae"},"commonNames":["Pink trumpet-tree","Taheebo","Pink Trumpet Tree"],"scientificName":"Handroanthus impetiginosus (Mart. ex DC.) Mattos"},"gbif":{"id":"4092242"}},{"score":0.00187,"species":{"scientificNameWithoutAuthor":"Prunus glandulosa","scientificNameAuthorship":"Thunb.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Chinese bush cherry","Dwarf flowering almond","Flowering almond"],"scientificName":"Prunus glandulosa Thunb."},"gbif":{"id":"3022160"}},{"score":0.00162,"species":{"scientificNameWithoutAuthor":"Prunus persica","scientificNameAuthorship":"(L.) Batsch","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Peach","هلو","Peach tree"],"scientificName":"Prunus persica (L.) Batsch"},"gbif":{"id":"3022511"}},{"score":0.00162,"species":{"scientificNameWithoutAuthor":"Prunus cerasifera","scientificNameAuthorship":"Ehrh.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Cherry plum, myrobalan","Cherry plum","Myrobalan plum"],"scientificName":"Prunus cerasifera Ehrh."},"gbif":{"id":"3021730"}},{"score":0.00159,"species":{"scientificNameWithoutAuthor":"Malus prattii","scientificNameAuthorship":"(Hemsl.) C.K.Schneid.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Pratt apple","Pratt's Crab Apple"],"scientificName":"Malus prattii (Hemsl.) C.K.Schneid."},"gbif":{"id":"3001504"}},{"score":0.00159,"species":{"scientificNameWithoutAuthor":"Prunus pedunculata","scientificNameAuthorship":"(Pall.) Maxim.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":[],"scientificName":"Prunus pedunculata (Pall.) Maxim."},"gbif":{"id":"3022277"}},{"score":0.00153,"species":{"scientificNameWithoutAuthor":"Cercis siliquastrum","scientificNameAuthorship":"L.","genus":{"scientificNameWithoutAuthor":"Cercis","scientificNameAuthorship":"","scientificName":"Cercis"},"family":{"scientificNameWithoutAuthor":"Leguminosae","scientificNameAuthorship":"","scientificName":"Leguminosae"},"commonNames":["Judastree","Lovetree","Judas-tree"],"scientificName":"Cercis siliquastrum L."},"gbif":{"id":"5353590"}},{"score":0.00128,"species":{"scientificNameWithoutAuthor":"Malus sylvestris","scientificNameAuthorship":"(L.) Mill.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Crab apple","European crab apple","Lopâr"],"scientificName":"Malus sylvestris (L.) Mill."},"gbif":{"id":"3001509"}},{"score":0.0012,"species":{"scientificNameWithoutAuthor":"Magnolia × soulangeana","scientificNameAuthorship":"Soul.-Bod.","genus":{"scientificNameWithoutAuthor":"Magnolia","scientificNameAuthorship":"","scientificName":"Magnolia"},"family":{"scientificNameWithoutAuthor":"Magnoliaceae","scientificNameAuthorship":"","scientificName":"Magnoliaceae"},"commonNames":["Chinese magnolia","Saucer magnolia"],"scientificName":"Magnolia × soulangeana Soul.-Bod."},"gbif":{"id":"7925303"}},{"score":0.00118,"species":{"scientificNameWithoutAuthor":"Cercis canadensis","scientificNameAuthorship":"L.","genus":{"scientificNameWithoutAuthor":"Cercis","scientificNameAuthorship":"","scientificName":"Cercis"},"family":{"scientificNameWithoutAuthor":"Leguminosae","scientificNameAuthorship":"","scientificName":"Leguminosae"},"commonNames":["Eastern redbud","Judastree","Redbud"],"scientificName":"Cercis canadensis L."},"gbif":{"id":"5353583"}},{"score":0.00114,"species":{"scientificNameWithoutAuthor":"Malus × prunifolia","scientificNameAuthorship":"(Willd.) Borkh.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Plumleaf crab apple","Chinese apple","Crab apple"],"scientificName":"Malus × prunifolia (Willd.) Borkh."},"gbif":{"id":"3001157"}},{"score":0.00111,"species":{"scientificNameWithoutAuthor":"Prunus serrula","scientificNameAuthorship":"Franch.","genus":{"scientificNameWithoutAuthor":"Prunus","scientificNameAuthorship":"","scientificName":"Prunus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Birchbark cherry"],"scientificName":"Prunus serrula Franch."},"gbif":{"id":"3023582"}},{"score":0.00106,"species":{"scientificNameWithoutAuthor":"Malus pumila","scientificNameAuthorship":"Mill.","genus":{"scientificNameWithoutAuthor":"Malus","scientificNameAuthorship":"","scientificName":"Malus"},"family":{"scientificNameWithoutAuthor":"Rosaceae","scientificNameAuthorship":"","scientificName":"Rosaceae"},"commonNames":["Apple","Paradise apple","Kulturapfel"],"scientificName":"Malus pumila Mill."},"gbif":{"id":"3001093"}},{"score":0.00101,"species":{"scientificNameWithoutAuthor":"Viburnum farreri","scientificNameAuthorship":"Stearn","genus":{"scientificNameWithoutAuthor":"Viburnum","scientificNameAuthorship":"","scientificName":"Viburnum"},"family":{"scientificNameWithoutAuthor":"Adoxaceae","scientificNameAuthorship":"","scientificName":"Adoxaceae"},"commonNames":["Fragrant viburnum","Culver's root","Farrer's Viburnum"],"scientificName":"Viburnum farreri Stearn"},"gbif":{"id":"6369599"}}],"version":"2022-06-14 (6.0)","remainingIdentificationRequests":498} } export interface PlantNetResult { diff --git a/Logic/Web/Wikidata.ts b/Logic/Web/Wikidata.ts index 68f7242a3c..910b26d0d5 100644 --- a/Logic/Web/Wikidata.ts +++ b/Logic/Web/Wikidata.ts @@ -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(keys: string[], statements: string[]):Promise< (T & Record) []> { + 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 diff --git a/UI/Wikipedia/WikidataPreviewBox.ts b/UI/Wikipedia/WikidataPreviewBox.ts index ca997d42d6..2a15fcaf4c 100644 --- a/UI/Wikipedia/WikidataPreviewBox.ts +++ b/UI/Wikipedia/WikidataPreviewBox.ts @@ -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,7 @@ export default class WikidataPreviewBox extends VariableUiElement { } ] - constructor(wikidataId: UIEventSource, options?: {noImages?: boolean}) { + constructor(wikidataId: Store, options?: {noImages?: boolean, whileLoading?: BaseUIElement | string, extraItems?: (BaseUIElement | string)[]}) { let inited = false; const wikidata = wikidataId .stabilized(250) @@ -71,7 +71,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 +87,7 @@ export default class WikidataPreviewBox extends VariableUiElement { } - public static WikidataResponsePreview(wikidata: WikidataResponse, options?: {noImages?: boolean}): BaseUIElement { + public static WikidataResponsePreview(wikidata: WikidataResponse, options?: {noImages?: boolean, extraItems?: (BaseUIElement | string)[]}): BaseUIElement { let link = new Link( new Combine([ wikidata.id, @@ -100,7 +100,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,8 +109,6 @@ 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"), diff --git a/test.ts b/test.ts index e69de29bb2..2f79181f64 100644 --- a/test.ts +++ b/test.ts @@ -0,0 +1,53 @@ +import PlantNet from "./Logic/Web/PlantNet"; +import {UIEventSource} from "./Logic/UIEventSource"; +import {VariableUiElement} from "./UI/Base/VariableUIElement"; +import List from "./UI/Base/List"; +import Combine from "./UI/Base/Combine"; +import {FixedUiElement} from "./UI/Base/FixedUiElement"; +import Wikidata from "./Logic/Web/Wikidata"; +import WikidataPreviewBox from "./UI/Wikipedia/WikidataPreviewBox"; +import Loading from "./UI/Base/Loading"; + +function build(images: UIEventSource) { + return new VariableUiElement(images + .bind(images => { + if (images.length === 0) { + return null + } + return new UIEventSource({success: PlantNet.exampleResultPrunus}) /*/ UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0,5))); //*/ + }) + .map(result => { + if (result === undefined) { + return new Loading() + } + if (result === null) { + return "Take images of the tree to automatically detect the tree type" + } + if (result["error"] !== undefined) { + return result["error"] + } + console.log(result) + const success = result["success"] + return new Combine(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 + "\""])); + + return new WikidataPreviewBox(wikidata.map(wd => wd == undefined ? undefined : wd[0]?.species?.value), + { + whileLoading: new Loading("Loading information about " + species.species.scientificNameWithoutAuthor), + extraItems: [Math.round(species.score * 100) + "% match"] + }) + .SetClass("border-2 border-subtle rounded-xl block mb-2") + } + )).SetClass("flex flex-col") + } + )) + + +} + +const images = ["https://i.imgur.com/VJp1qG1.jpg"] + +build(new UIEventSource(images)).AttachTo("maindiv") \ No newline at end of file