forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
c9697b7ffb
7 changed files with 130 additions and 115 deletions
|
@ -1,9 +1,11 @@
|
|||
# Integrating MapRoulette
|
||||
|
||||
[MapRoulette](https://www.maproulette.org/) is a website which has challenges. A challenge is a collection of _microtasks_, i.e. mapping
|
||||
tasks which can be solved in a few minutes.
|
||||
[MapRoulette](https://www.maproulette.org/) is a website which has challenges. A challenge is a collection of _microtasks_, i.e. mapping tasks which can be solved in a few minutes.
|
||||
|
||||
A perfect example of this is to setup such a challenge to e.g. import new points.
|
||||
|
||||
> [Important: always follow the import guidelines if you want to import data.](https://wiki.openstreetmap.org/wiki/Import/Guidelines)
|
||||
|
||||
A perfect example of this is to setup such a challenge to e.g. import new points. [Important: always follow the import guidelines if you want to import data.](https://wiki.openstreetmap.org/wiki/Import/Guidelines)
|
||||
(Another approach to set up a guided import is to create a map note for every point with the [import helper](https://mapcomplete.osm.be/import_helper). This however litters the map notes and will upset mappers if used with to much points. However, this flow is easier to setup as no changes to theme files are needed, nor is a maproulette-account needed)
|
||||
|
||||
## The API
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import Svg from "../../Svg"
|
||||
import Combine from "../Base/Combine"
|
||||
import { SubtleButton } from "../Base/SubtleButton"
|
||||
import {SubtleButton} from "../Base/SubtleButton"
|
||||
import Translations from "../i18n/Translations"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import LayoutConfig, { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import LayoutConfig, {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource"
|
||||
import Loc from "../../Models/Loc"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
import { Utils } from "../../Utils"
|
||||
import {Utils} from "../../Utils"
|
||||
import Title from "../Base/Title"
|
||||
import themeOverview from "../../assets/generated/theme_overview.json"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { TextField } from "../Input/TextField"
|
||||
import {Translation} from "../i18n/Translation"
|
||||
import {TextField} from "../Input/TextField"
|
||||
import Locale from "../i18n/Locale"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import ThemesList from "./ThemesList.svelte"
|
||||
|
@ -37,7 +37,7 @@ export default class MoreScreen extends Combine {
|
|||
searchTerm = searchTerm.toLowerCase()
|
||||
if (searchTerm === "personal") {
|
||||
window.location.href = MoreScreen.createUrlFor(
|
||||
{ id: "personal" },
|
||||
{id: "personal"},
|
||||
false,
|
||||
state
|
||||
).data
|
||||
|
@ -59,13 +59,13 @@ export default class MoreScreen extends Combine {
|
|||
(th) =>
|
||||
th.hideFromOverview == false &&
|
||||
th.id !== "personal" &&
|
||||
MoreScreen.MatchesLayoutFunc(th)(searchTerm)
|
||||
MoreScreen.MatchesLayout(th, searchTerm)
|
||||
)
|
||||
if (publicTheme !== undefined) {
|
||||
window.location.href = MoreScreen.createUrlFor(publicTheme, false, state).data
|
||||
}
|
||||
const hiddenTheme = MoreScreen.officialThemes.find(
|
||||
(th) => th.id !== "personal" && MoreScreen.MatchesLayoutFunc(th)(searchTerm)
|
||||
(th) => th.id !== "personal" && MoreScreen.MatchesLayout(th, searchTerm)
|
||||
)
|
||||
if (hiddenTheme !== undefined) {
|
||||
window.location.href = MoreScreen.createUrlFor(hiddenTheme, false, state).data
|
||||
|
@ -109,6 +109,85 @@ export default class MoreScreen extends Combine {
|
|||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button linking to the given theme
|
||||
* @private
|
||||
*/
|
||||
public static createLinkButton(
|
||||
state: {
|
||||
locationControl?: UIEventSource<Loc>
|
||||
layoutToUse?: LayoutConfig
|
||||
},
|
||||
layout: {
|
||||
id: string
|
||||
icon: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
definition?: any
|
||||
mustHaveLanguage?: boolean
|
||||
},
|
||||
isCustom: boolean = false
|
||||
): BaseUIElement {
|
||||
const url = MoreScreen.createUrlFor(layout, isCustom, state)
|
||||
let content = new Combine([
|
||||
new Translation(
|
||||
layout.title,
|
||||
!isCustom && !layout.mustHaveLanguage ? "themes:" + layout.id + ".title" : undefined
|
||||
),
|
||||
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
|
||||
]).SetClass("overflow-hidden flex flex-col")
|
||||
|
||||
if (state.layoutToUse === undefined) {
|
||||
// Currently on the index screen: we style the buttons equally large
|
||||
content = new Combine([content]).SetClass("flex flex-col justify-center h-24")
|
||||
}
|
||||
|
||||
return new SubtleButton(layout.icon, content, {url, newTab: false})
|
||||
}
|
||||
|
||||
public static CreateProffessionalSerivesButton() {
|
||||
const t = Translations.t.professional.indexPage
|
||||
return new Combine([
|
||||
new Title(t.hook, 4),
|
||||
t.hookMore,
|
||||
new SubtleButton(undefined, t.button, {url: "./professional.html"}),
|
||||
]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg")
|
||||
}
|
||||
|
||||
public static MatchesLayout(layout: {
|
||||
id: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
keywords?: any[]
|
||||
}, search: string): boolean {
|
||||
if(search === undefined){
|
||||
return true
|
||||
}
|
||||
search = search.toLocaleLowerCase()
|
||||
if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
|
||||
return true
|
||||
}
|
||||
if(layout.id === "personal"){
|
||||
return false
|
||||
}
|
||||
const entitiesToSearch = [
|
||||
layout.shortDescription,
|
||||
layout.title,
|
||||
...(layout.keywords ?? []),
|
||||
]
|
||||
for (const entity of entitiesToSearch) {
|
||||
if (entity === undefined) {
|
||||
continue
|
||||
}
|
||||
const term = entity["*"] ?? entity[Locale.language.data]
|
||||
if (term?.toLowerCase()?.indexOf(search) >= 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private static createUrlFor(
|
||||
layout: { id: string; definition?: string },
|
||||
isCustom: boolean,
|
||||
|
@ -164,79 +243,4 @@ export default class MoreScreen extends Combine {
|
|||
}) ?? new ImmutableStore<string>(`${linkPrefix}`)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button linking to the given theme
|
||||
* @private
|
||||
*/
|
||||
public static createLinkButton(
|
||||
state: {
|
||||
locationControl?: UIEventSource<Loc>
|
||||
layoutToUse?: LayoutConfig
|
||||
},
|
||||
layout: {
|
||||
id: string
|
||||
icon: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
definition?: any
|
||||
mustHaveLanguage?: boolean
|
||||
},
|
||||
isCustom: boolean = false
|
||||
): BaseUIElement {
|
||||
const url = MoreScreen.createUrlFor(layout, isCustom, state)
|
||||
let content = new Combine([
|
||||
new Translation(
|
||||
layout.title,
|
||||
!isCustom && !layout.mustHaveLanguage ? "themes:" + layout.id + ".title" : undefined
|
||||
),
|
||||
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
|
||||
]).SetClass("overflow-hidden flex flex-col")
|
||||
|
||||
if (state.layoutToUse === undefined) {
|
||||
// Currently on the index screen: we style the buttons equally large
|
||||
content = new Combine([content]).SetClass("flex flex-col justify-center h-24")
|
||||
}
|
||||
|
||||
return new SubtleButton(layout.icon, content, { url, newTab: false })
|
||||
}
|
||||
|
||||
public static CreateProffessionalSerivesButton() {
|
||||
const t = Translations.t.professional.indexPage
|
||||
return new Combine([
|
||||
new Title(t.hook, 4),
|
||||
t.hookMore,
|
||||
new SubtleButton(undefined, t.button, { url: "./professional.html" }),
|
||||
]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg")
|
||||
}
|
||||
|
||||
private static MatchesLayoutFunc(layout: {
|
||||
id: string
|
||||
title: any
|
||||
shortDescription: any
|
||||
keywords?: any[]
|
||||
}): (search: string) => boolean {
|
||||
return (search: string) => {
|
||||
search = search.toLocaleLowerCase()
|
||||
if (layout.id.toLowerCase().indexOf(search) >= 0) {
|
||||
return true
|
||||
}
|
||||
const entitiesToSearch = [
|
||||
layout.shortDescription,
|
||||
layout.title,
|
||||
...(layout.keywords ?? []),
|
||||
]
|
||||
for (const entity of entitiesToSearch) {
|
||||
if (entity === undefined) {
|
||||
continue
|
||||
}
|
||||
const term = entity["*"] ?? entity[Locale.language.data]
|
||||
if (term?.toLowerCase()?.indexOf(search) >= 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import ProfessionalServicesButton from "./ProfessionalServicesButton.svelte"
|
||||
import ThemeButton from "./ThemeButton.svelte"
|
||||
import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import MoreScreen from "./MoreScreen";
|
||||
|
||||
export let search: UIEventSource<string>
|
||||
export let themes: LayoutInformation[]
|
||||
|
@ -18,26 +19,7 @@
|
|||
export let hideThemes: boolean = true
|
||||
|
||||
// Filter theme based on search value
|
||||
$: filteredThemes = themes.filter((theme) => {
|
||||
if ($search === undefined || $search === "") return true
|
||||
|
||||
const srch = $search.toLocaleLowerCase()
|
||||
if (theme.id.toLowerCase().indexOf(srch) >= 0) {
|
||||
return true
|
||||
}
|
||||
const entitiesToSearch = [theme.shortDescription, theme.title, ...(theme.keywords ?? [])]
|
||||
for (const entity of entitiesToSearch) {
|
||||
if (entity === undefined) {
|
||||
continue
|
||||
}
|
||||
const term = entity["*"] ?? entity[Locale.language.data]
|
||||
if (term?.toLowerCase()?.indexOf(search) >= 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
$: filteredThemes = themes.filter((theme) => MoreScreen.MatchesLayout(theme, $search))
|
||||
</script>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -836,5 +836,8 @@
|
|||
"render": "1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filter": [
|
||||
"open_now"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "maproulette_challenge",
|
||||
"name": null,
|
||||
"description": {
|
||||
"en": "Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to the documentation on how to do this.",
|
||||
"en": "Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this.",
|
||||
"de": "Ebene mit Aufgaben einer einzelnen MapRoulette-Herausforderung. Diese Ebene soll in Themen wiederverwendet und erweitert werden; Informationen dazu finden Sie in der Dokumentation.",
|
||||
"nl": "Laag met taken van een MapRoulette uitdaging"
|
||||
},
|
||||
|
@ -224,4 +224,4 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
24
scripts/downloadFromOverpass.ts
Normal file
24
scripts/downloadFromOverpass.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Script from "./Script";
|
||||
import {TagUtils} from "../Logic/Tags/TagUtils";
|
||||
import {And} from "../Logic/Tags/And";
|
||||
import Constants from "../Models/Constants";
|
||||
import {ImmutableStore} from "../Logic/UIEventSource";
|
||||
import {BBox} from "../Logic/BBox";
|
||||
import {Overpass} from "../Logic/Osm/Overpass";
|
||||
const fs = require("fs")
|
||||
class DownloadFromOverpass extends Script {
|
||||
|
||||
constructor() {
|
||||
super("Downloads data from openstreetmap, will save this as 'export.geojson'. All arguments will be interpreted as key=value pairs");
|
||||
}
|
||||
async main(args: string[]): Promise<void> {
|
||||
const tags = new And(args.map(k => TagUtils.Tag(k)))
|
||||
const overpass = new Overpass(tags,[], Constants.defaultOverpassUrls[0], new ImmutableStore(500))
|
||||
const [data, _] = await overpass.queryGeoJson(BBox.global)
|
||||
fs.writeFileSync("export.geojson", JSON.stringify(data), "utf8")
|
||||
console.log("Written", data.features.length,"entries")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
new DownloadFromOverpass().run()
|
|
@ -28,7 +28,7 @@ export default class GenerateImageAnalysis extends Script {
|
|||
tag,
|
||||
[],
|
||||
"https://overpass.kumi.systems/api/interpreter",
|
||||
new ImmutableStore(180),
|
||||
new ImmutableStore(500),
|
||||
undefined,
|
||||
false
|
||||
)
|
||||
|
@ -110,10 +110,10 @@ export default class GenerateImageAnalysis extends Script {
|
|||
|
||||
const msg = `${i}/${
|
||||
allImages.size
|
||||
} downloaded: ${d},skipped: ${s}, failed: ${f}, running: ${runningSecs}sec, ETA: ${estimatedActualMinutes}:${
|
||||
} downloaded: ${d},skipped: ${s}, failed: ${f}, running: ${Math.floor(runningSecs)}sec, ETA: ${estimatedActualMinutes}:${
|
||||
estimatedActualSeconds % 60
|
||||
}`
|
||||
ScriptUtils.erasableLog(msg)
|
||||
ScriptUtils.erasableLog( " ", msg)
|
||||
if (downloaded) {
|
||||
d++
|
||||
} else {
|
||||
|
@ -227,10 +227,10 @@ export default class GenerateImageAnalysis extends Script {
|
|||
}
|
||||
|
||||
async main(args: string[]): Promise<void> {
|
||||
const datapath = args[0] ?? "../MapComplete-data/ImageLicenseInfo"
|
||||
const datapath = args[0] ?? "../../git/MapComplete-data/ImageLicenseInfo"
|
||||
await this.downloadData(datapath)
|
||||
|
||||
//await this.downloadMetadata(datapath)
|
||||
await this.downloadMetadata(datapath)
|
||||
this.analyze(datapath)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue