diff --git a/langs/en.json b/langs/en.json
index cc9974402e..7d75d0a7bd 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 fff429293f..534568b5de 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 91a0cd7e0e..e068f5f492 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 d22cae7632..dab705ad0b 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 40f4d1eeaa..5f48bf7e63 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 a546fd8c1c..6b4a64dd83 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 b7e503f6e0..0000000000
--- 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 0000000000..170c7fc182
--- /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 0000000000..5cb7341561
--- /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 0000000000..de7f873332
--- /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 198fc7a938..69397cabdf 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 213451e971..26a94ec7f3 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 4ad1430061..7d40c735f8 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 a9e6327fad..41f2afb2ca 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 9790f72831..abb67b572a 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 b2a35eec6c..11775b563b 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 8ca905fc77..20a9180d53 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 d947f8ce55..028a661122 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 **********************/