From 5f04a695172934d4052ba7ee90ae378e66893d6a Mon Sep 17 00:00:00 2001
From: Pieter Vander Vennet
Date: Wed, 20 Sep 2023 01:47:32 +0200
Subject: [PATCH] Refactoring: port PlantNet-detection to svelte, re-integrate
wikipedia component
---
langs/en.json | 6 +-
package.json | 2 +-
public/css/index-tailwind-output.css | 18 +--
src/Logic/Web/PlantNet.ts | 42 +++---
src/UI/Base/BackButton.svelte | 3 +-
src/UI/Base/NextButton.svelte | 2 +-
src/UI/BigComponents/PlantNetSpeciesSearch.ts | 127 ------------------
src/UI/PlantNet/PlantNet.svelte | 123 +++++++++++++++++
src/UI/PlantNet/PlantNetSpeciesList.svelte | 37 +++++
src/UI/PlantNet/SpeciesButton.svelte | 56 ++++++++
src/UI/Popup/PlantNetDetectionViz.ts | 68 ++++------
src/UI/SpecialVisualizations.ts | 2 +-
src/UI/StylesheetTestGui.svelte | 4 +
src/UI/Wikipedia/WikidataPreviewBox.ts | 2 +-
src/UI/Wikipedia/WikidataSearchBox.ts | 2 +-
src/UI/Wikipedia/WikipediaArticle.svelte | 6 +-
src/UI/Wikipedia/WikipediaPanel.svelte | 2 +-
src/index.css | 5 +
18 files changed, 297 insertions(+), 210 deletions(-)
delete mode 100644 src/UI/BigComponents/PlantNetSpeciesSearch.ts
create mode 100644 src/UI/PlantNet/PlantNet.svelte
create mode 100644 src/UI/PlantNet/PlantNetSpeciesList.svelte
create mode 100644 src/UI/PlantNet/SpeciesButton.svelte
diff --git a/langs/en.json b/langs/en.json
index cc9974402..7d75d0a7b 100644
--- a/langs/en.json
+++ b/langs/en.json
@@ -380,6 +380,7 @@
"born": "Born: {value}",
"died": "Died: {value}"
},
+ "readMore": "Read the rest of the article",
"searchToShort": "Your search query is too short, enter a longer text",
"searchWikidata": "Search on Wikidata",
"wikipediaboxTitle": "Wikipedia"
@@ -498,7 +499,9 @@
},
"plantDetection": {
"back": "Back to species overview",
+ "button": "Automatically detect the plant species using the AI of Plantnet.org",
"confirm": "Select species",
+ "done": "The species has been applied",
"error": "Something went wrong while detecting the tree species: {error}",
"howTo": {
"intro": "For optimal results,",
@@ -515,7 +518,8 @@
"poweredByPlantnet": "Powered by plantnet.org ",
"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"
+ "takeImages": "Take images of the tree to automatically detect the tree type",
+ "tryAgain": "Select a different species"
},
"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: The changes you made Your username When this change is made The theme you used while making the change The language of the user interface 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 Please refer to the privacy policy on OpenStreetMap.org for detailed information. We'd like to remind you that you can use a fictional name when signing up.",
diff --git a/package.json b/package.json
index fff429293..534568b5d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mapcomplete",
- "version": "0.32.0",
+ "version": "0.33.0",
"repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues",
diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css
index 91a0cd7e0..e068f5f49 100644
--- a/public/css/index-tailwind-output.css
+++ b/public/css/index-tailwind-output.css
@@ -1096,6 +1096,10 @@ video {
height: 2.75rem;
}
+.h-10 {
+ height: 2.5rem;
+}
+
.h-48 {
height: 12rem;
}
@@ -1104,10 +1108,6 @@ video {
height: 10rem;
}
-.h-10 {
- height: 2.5rem;
-}
-
.h-80 {
height: 20rem;
}
@@ -1709,11 +1709,6 @@ video {
padding-right: 0.5rem;
}
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-
.pl-1 {
padding-left: 0.25rem;
}
@@ -2209,6 +2204,11 @@ input[type=text] {
border-radius: 0.5rem;
}
+.border-region {
+ border: 2px dashed var(--interactive-background);
+ border-radius: 0.5rem;
+}
+
/******************* Styling of input elements **********************/
/**
diff --git a/src/Logic/Web/PlantNet.ts b/src/Logic/Web/PlantNet.ts
index d22cae763..dab705ad0 100644
--- a/src/Logic/Web/PlantNet.ts
+++ b/src/Logic/Web/PlantNet.ts
@@ -985,6 +985,27 @@ export default class PlantNet {
}
}
+export interface PlantNetSpeciesMatch {
+ score: number
+ gbif: { id: string /*Actually a number*/ }
+ species: {
+ scientificNameWithoutAuthor: string
+ scientificNameAuthorship: string
+ genus: {
+ scientificNameWithoutAuthor: string
+ scientificNameAuthorship: string
+ scientificName: string
+ }
+ family: {
+ scientificNameWithoutAuthor: string
+ scientificNameAuthorship: string
+ scientificName: string
+ }
+ commonNames: string[]
+ scientificName: string
+ }
+}
+
export interface PlantNetResult {
query: {
project: string
@@ -995,26 +1016,7 @@ export interface PlantNetResult {
language: string
preferedReferential: string
bestMatch: string
- results: {
- score: number
- gbif: { id: string /*Actually a number*/ }
- species: {
- scientificNameWithoutAuthor: string
- scientificNameAuthorship: string
- genus: {
- scientificNameWithoutAuthor: string
- scientificNameAuthorship: string
- scientificName: string
- }
- family: {
- scientificNameWithoutAuthor: string
- scientificNameAuthorship: string
- scientificName: string
- }
- commonNames: string[]
- scientificName: string
- }
- }[]
+ results: PlantNetSpeciesMatch[]
version: string
remainingIdentificationRequests: number
}
diff --git a/src/UI/Base/BackButton.svelte b/src/UI/Base/BackButton.svelte
index 40f4d1eea..5f48bf7e6 100644
--- a/src/UI/Base/BackButton.svelte
+++ b/src/UI/Base/BackButton.svelte
@@ -10,12 +10,13 @@
const dispatch = createEventDispatcher<{ click }>()
export let clss: string | undefined = undefined
+ export let imageClass: string | undefined = undefined
dispatch("click")}
options={{ extraClasses: twMerge("flex items-center", clss) }}
>
-
+
diff --git a/src/UI/Base/NextButton.svelte b/src/UI/Base/NextButton.svelte
index a546fd8c1..6b4a64dd8 100644
--- a/src/UI/Base/NextButton.svelte
+++ b/src/UI/Base/NextButton.svelte
@@ -20,6 +20,6 @@
-
+
diff --git a/src/UI/BigComponents/PlantNetSpeciesSearch.ts b/src/UI/BigComponents/PlantNetSpeciesSearch.ts
deleted file mode 100644
index b7e503f6e..000000000
--- a/src/UI/BigComponents/PlantNetSpeciesSearch.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-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 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, onConfirm: (wikidataUrl: string) => Promise) {
- 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(result).SetClass("alert")
- }
- console.log(result)
- const success = result["success"]
-
- const selectedSpecies = new UIEventSource(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
- }
- return 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")
- })
- )
- })
- )
- }
-}
diff --git a/src/UI/PlantNet/PlantNet.svelte b/src/UI/PlantNet/PlantNet.svelte
new file mode 100644
index 000000000..170c7fc18
--- /dev/null
+++ b/src/UI/PlantNet/PlantNet.svelte
@@ -0,0 +1,123 @@
+
+
+
+
+ {#if collapsedMode}
+
+
+
+ {:else if $error !== undefined}
+
+ {:else if $imageUrls.length === 0}
+
+
+
{collapsedMode = true}}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {:else if selectedOption === undefined}
+
speciesSelected(species.detail)}>
+ {collapsedMode = true}}>
+
+
+ {:else if !done}
+
+
+
+
+
+
+ {selectedOption = undefined}}>
+
+
+ { done = true; onConfirm(selectedOption); }} >
+
+
+
+
+ {:else}
+
+
+
{done = false; selectedOption = undefined}}>
+
+
+ {/if}
+
+
+
+
+
+
diff --git a/src/UI/PlantNet/PlantNetSpeciesList.svelte b/src/UI/PlantNet/PlantNetSpeciesList.svelte
new file mode 100644
index 000000000..5cb734156
--- /dev/null
+++ b/src/UI/PlantNet/PlantNetSpeciesList.svelte
@@ -0,0 +1,37 @@
+
+
+{#if $options === undefined}
+
+
+
+{:else}
+
+
+
+
+
+
+
+
+
+
+ {#each $options as species}
+
+ {/each}
+
+{/if}
diff --git a/src/UI/PlantNet/SpeciesButton.svelte b/src/UI/PlantNet/SpeciesButton.svelte
new file mode 100644
index 000000000..de7f87333
--- /dev/null
+++ b/src/UI/PlantNet/SpeciesButton.svelte
@@ -0,0 +1,56 @@
+
+
+{
+ console.log("Dispatching: ", $wikidataId)
+ return dispatch("selected", $wikidataId); }}>
+ {#if $wikidata === undefined}
+
+
+
+ {:else}
+ new WikidataPreviewBox(wikidataId,
+ { imageStyle: "max-width: 8rem; width: unset; height: 8rem",
+ extraItems: [t.matchPercentage
+ .Subs({ match: Math.round(species.score * 100) })
+ .SetClass("thanks w-fit self-center")]
+ }).SetClass("w-full")}>
+ {/if}
+
diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts
index 198fc7a93..69397cabd 100644
--- a/src/UI/Popup/PlantNetDetectionViz.ts
+++ b/src/UI/Popup/PlantNetDetectionViz.ts
@@ -1,18 +1,14 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
-import Toggle from "../Input/Toggle"
-import Lazy from "../Base/Lazy"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"
import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
-import { SubtleButton } from "../Base/SubtleButton"
-import Combine from "../Base/Combine"
-import Svg from "../../Svg"
-import Translations from "../i18n/Translations"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
+import SvelteUIElement from "../Base/SvelteUIElement"
+import PlantNet from "../PlantNet/PlantNet.svelte"
export class PlantNetDetectionViz implements SpecialVisualization {
funcName = "plantnet_detection"
@@ -37,45 +33,29 @@ export class PlantNetDetectionViz implements SpecialVisualization {
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
}
- const detect = new UIEventSource(false)
- const toggle = new Toggle(
- new Lazy(() => {
- const allProvidedImages: Store = AllImageProviders.LoadImagesFor(
- tags,
- imagePrefixes
- )
- const allImages: Store = 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.layout.id,
- changeType: "plantnet-ai-detection",
- }
- )
- await state.changes.applyAction(change)
- })
- }),
- new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() =>
- detect.setData(true)
- ),
- detect
+ const allProvidedImages: Store = AllImageProviders.LoadImagesFor(
+ tags,
+ imagePrefixes
)
+ const imageUrls: Store = allProvidedImages.map((pi) => pi.map((pi) => pi.url))
- 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")
+ async function applySpecies(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.layout.id,
+ changeType: "plantnet-ai-detection",
+ }
+ )
+ await state.changes.applyAction(change)
+ }
+
+ return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies })
}
}
diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts
index 213451e97..26a94ec7f 100644
--- a/src/UI/SpecialVisualizations.ts
+++ b/src/UI/SpecialVisualizations.ts
@@ -538,7 +538,7 @@ export default class SpecialVisualizations {
const keys = args[0].split(";").map((k) => k.trim())
const wikiIds: Store = tagsSource.map((tags) => {
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
- return tags[key]?.split(";")?.map((id) => id.trim())
+ return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
})
return new SvelteUIElement(WikipediaPanel, {
wikiIds,
diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte
index 4ad143006..7d40c735f 100644
--- a/src/UI/StylesheetTestGui.svelte
+++ b/src/UI/StylesheetTestGui.svelte
@@ -29,6 +29,10 @@
areas, where some buttons might appear.
+
+ Highly interactive area (mostly: active question)
+
+
diff --git a/src/UI/Wikipedia/WikidataPreviewBox.ts b/src/UI/Wikipedia/WikidataPreviewBox.ts
index a9e6327fa..41f2afb2c 100644
--- a/src/UI/Wikipedia/WikidataPreviewBox.ts
+++ b/src/UI/Wikipedia/WikidataPreviewBox.ts
@@ -126,7 +126,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
new Combine([
Translation.fromMap(wikidata.labels)?.SetClass("font-bold"),
link,
- ]).SetClass("flex justify-between"),
+ ]).SetClass("flex justify-between flex-wrap-reverse"),
Translation.fromMap(wikidata.descriptions),
WikidataPreviewBox.QuickFacts(wikidata, options),
...(options?.extraItems ?? []),
diff --git a/src/UI/Wikipedia/WikidataSearchBox.ts b/src/UI/Wikipedia/WikidataSearchBox.ts
index 9790f7283..abb67b572 100644
--- a/src/UI/Wikipedia/WikidataSearchBox.ts
+++ b/src/UI/Wikipedia/WikidataSearchBox.ts
@@ -131,7 +131,7 @@ Another example is to search for species and trees:
const searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchField
.GetValue()
.bind((searchText) => {
- if (searchText.length < 3) {
+ if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) {
return tooShort
}
const lang = Locale.language.data
diff --git a/src/UI/Wikipedia/WikipediaArticle.svelte b/src/UI/Wikipedia/WikipediaArticle.svelte
index b2a35eec6..11775b563 100644
--- a/src/UI/Wikipedia/WikipediaArticle.svelte
+++ b/src/UI/Wikipedia/WikipediaArticle.svelte
@@ -11,7 +11,7 @@
import Translations from "../i18n/Translations";
/**
- * Small helper
+ * Shows a wikipedia-article + wikidata preview for the given item
*/
export let wikipediaDetails: Store;
@@ -23,9 +23,11 @@
{/if}
+
{#if $wikipediaDetails.wikidata}
{/if}
+
{#if $wikipediaDetails.articleUrl}
{#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined}
@@ -42,7 +44,7 @@
style={(open ? "transform: rotate(90deg); " : "") +
" transition: all .25s linear; width: 1.5rem; height: 1.5rem"}
/>
- Read the rest of the article
+
diff --git a/src/UI/Wikipedia/WikipediaPanel.svelte b/src/UI/Wikipedia/WikipediaPanel.svelte
index 8ca905fc7..20a9180d5 100644
--- a/src/UI/Wikipedia/WikipediaPanel.svelte
+++ b/src/UI/Wikipedia/WikipediaPanel.svelte
@@ -16,7 +16,7 @@
*/
export let wikiIds: Store;
let wikipediaStores: Store[]> = Locale.language.bind((language) =>
- wikiIds.map((wikiIds) => wikiIds.map((id) => Wikipedia.fetchArticleAndWikidata(id, language)))
+ wikiIds?.map((wikiIds) => wikiIds?.map((id) => Wikipedia.fetchArticleAndWikidata(id, language)))
);
let _wikipediaStores;
onDestroy(
diff --git a/src/index.css b/src/index.css
index d947f8ce5..028a66112 100644
--- a/src/index.css
+++ b/src/index.css
@@ -154,6 +154,11 @@ input[type=text] {
border-radius: 0.5rem;
}
+.border-region {
+ border: 2px dashed var(--interactive-background);
+ border-radius: 0.5rem;
+}
+
/******************* Styling of input elements **********************/