forked from MapComplete/MapComplete
Misc: improve statistics page
This commit is contained in:
parent
d5ccd42cfd
commit
9021c372ad
4 changed files with 75 additions and 43 deletions
|
@ -105,8 +105,8 @@ class StatsDownloader {
|
|||
): Promise<ChangeSetData[]> {
|
||||
let page = 1
|
||||
let allFeatures: ChangeSetData[] = []
|
||||
let endDay = new Date(year, month - 1 /* Zero-indexed: 0 = january*/, day + 1)
|
||||
let endDate = `${endDay.getFullYear()}-${Utils.TwoDigits(
|
||||
const endDay = new Date(year, month - 1 /* Zero-indexed: 0 = january*/, day + 1)
|
||||
const endDate = `${endDay.getFullYear()}-${Utils.TwoDigits(
|
||||
endDay.getMonth() + 1
|
||||
)}-${Utils.TwoDigits(endDay.getDate())}`
|
||||
let url = this.urlTemplate
|
||||
|
@ -117,7 +117,7 @@ class StatsDownloader {
|
|||
.replace("{end_date}", endDate)
|
||||
.replace("{page}", "" + page)
|
||||
|
||||
let headers = {
|
||||
const headers = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
|
@ -148,6 +148,9 @@ class StatsDownloader {
|
|||
allFeatures = Utils.NoNull(allFeatures)
|
||||
allFeatures.forEach((f) => {
|
||||
f.properties = { ...f.properties, ...f.properties.metadata }
|
||||
if (f.properties.editor.toLowerCase().indexOf("android") >= 0) {
|
||||
f.properties["android"] = "yes"
|
||||
}
|
||||
delete f.properties.metadata
|
||||
f.properties["id"] = f.id
|
||||
})
|
||||
|
@ -212,8 +215,8 @@ class GenerateSeries extends Script {
|
|||
}
|
||||
|
||||
private async downloadStatistics(targetDir: string) {
|
||||
let year = 2020
|
||||
let month = 5
|
||||
let year = 2025
|
||||
let month = 1
|
||||
let day = 1
|
||||
if (!isNaN(Number(process.argv[2]))) {
|
||||
year = Number(process.argv[2])
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import type { FeatureCollection } from "geojson"
|
||||
|
@ -17,6 +17,7 @@
|
|||
import SingleStat from "./SingleStat.svelte"
|
||||
import { DownloadIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import Filter from "../../assets/svg/Filter.svelte"
|
||||
|
||||
export let paths: string[]
|
||||
|
||||
|
@ -24,23 +25,31 @@
|
|||
const layer = new ThemeConfig(<ThemeConfigJson>mcChanges, true).layers[0]
|
||||
const filteredLayer = new FilteredLayer(layer)
|
||||
|
||||
let allData = <UIEventSource<(ChangeSetData & OsmFeature)[]>>UIEventSource.FromPromise(
|
||||
Promise.all(
|
||||
paths.map(async (p) => {
|
||||
const r = await Utils.downloadJson<FeatureCollection>(p)
|
||||
downloaded++
|
||||
return r
|
||||
})
|
||||
)
|
||||
).mapD((list) => [].concat(...list.map((f) => f.features)))
|
||||
const downloadData: () => Promise<(ChangeSetData & OsmFeature)[]> = async () => {
|
||||
const results = []
|
||||
for (const p of paths) {
|
||||
const r = await Utils.downloadJson<FeatureCollection>(p)
|
||||
console.log("Downloaded", p)
|
||||
downloaded++
|
||||
if (Array.isArray(r)) {
|
||||
results.push(...r)
|
||||
} else {
|
||||
results.push(...r.features ?? [])
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
let overview = allData.mapD(
|
||||
let allData = <UIEventSource<(ChangeSetData & OsmFeature)[]>>UIEventSource.FromPromise(downloadData())
|
||||
|
||||
let overview: Store<ChangesetsOverview | undefined> = allData.mapD(
|
||||
(data) =>
|
||||
ChangesetsOverview.fromDirtyData(data).filter((cs) =>
|
||||
filteredLayer.isShown(<any>cs.properties)
|
||||
),
|
||||
[filteredLayer.currentFilter]
|
||||
)
|
||||
overview.addCallbackAndRunD(d => console.log(d))
|
||||
|
||||
const trs = layer.tagRenderings
|
||||
.filter((tr) => tr.mappings?.length > 0 || tr.freeform?.key !== undefined)
|
||||
|
@ -56,10 +65,10 @@
|
|||
|
||||
function offerAsDownload() {
|
||||
const data = GeoOperations.toCSV($overview._meta, {
|
||||
ignoreTags: /^((deletion:node)|(import:node)|(move:node)|(soft-delete:))/,
|
||||
ignoreTags: /^((deletion:node)|(import:node)|(move:node)|(soft-delete:))/
|
||||
})
|
||||
Utils.offerContentsAsDownloadableFile(data, "statistics.csv", {
|
||||
mimetype: "text/csv",
|
||||
mimetype: "text/csv"
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -68,21 +77,28 @@
|
|||
<Loading>Loaded {downloaded} out of {paths.length}</Loading>
|
||||
{:else}
|
||||
<AccordionSingle>
|
||||
<span slot="header">Filters</span>
|
||||
<div slot="header" class="flex items-center">
|
||||
<Filter class="h-6 w-6 pr-2" />
|
||||
Filters
|
||||
</div>
|
||||
<Filterview {filteredLayer} state={undefined} showLayerTitle={false} />
|
||||
</AccordionSingle>
|
||||
<Accordion>
|
||||
{#each trs as tr}
|
||||
<AccordionItem paddingDefault="p-0" inactiveClass="text-black">
|
||||
{#if !$overview || $overview._meta.length === 0}
|
||||
<div class="alert">Filter matches no items</div>
|
||||
{:else}
|
||||
<Accordion>
|
||||
{#each trs as tr}
|
||||
<AccordionItem paddingDefault="p-0" inactiveClass="text-black">
|
||||
<span slot="header" class={"w-full p-2 text-base"}>
|
||||
{tr.question ?? tr.id}
|
||||
</span>
|
||||
<SingleStat {tr} overview={$overview} diffInDays={$diffInDays} />
|
||||
</AccordionItem>
|
||||
{/each}
|
||||
</Accordion>
|
||||
<button on:click={() => offerAsDownload()}>
|
||||
<DownloadIcon class="h-6 w-6" />
|
||||
Download as CSV
|
||||
</button>
|
||||
<SingleStat {tr} overview={$overview} diffInDays={$diffInDays} />
|
||||
</AccordionItem>
|
||||
{/each}
|
||||
</Accordion>
|
||||
<button on:click={() => offerAsDownload()}>
|
||||
<DownloadIcon class="h-6 w-6" />
|
||||
Download as CSV
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import { Feature, Polygon } from "geojson"
|
||||
import { OsmFeature } from "../../Models/OsmFeature"
|
||||
|
||||
export interface ChangeSetData extends Feature<Polygon> {
|
||||
id: number
|
||||
type: "Feature"
|
||||
|
@ -92,8 +93,12 @@ export class ChangesetsOverview {
|
|||
if (cs === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (cs.properties.android) {
|
||||
console.log("Found an ANDROID:", cs.properties)
|
||||
}
|
||||
if (cs.properties.editor?.startsWith("iD")) {
|
||||
// We also fetch based on hashtag, so some edits with iD show up as well
|
||||
// Sometimes, iD reuses a previous changeset, mimicking (!) mapcomplete accidentally
|
||||
return undefined
|
||||
}
|
||||
if (cs.properties.theme === undefined) {
|
||||
|
|
|
@ -3,24 +3,32 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import AllStats from "./AllStats.svelte"
|
||||
import TitledPanel from "../Base/TitledPanel.svelte"
|
||||
|
||||
let homeUrl =
|
||||
"https://data.mapcomplete.org/changeset-metadata/"
|
||||
let stats_files = "file-overview.json"
|
||||
|
||||
let indexFile = UIEventSource.FromPromise(Utils.downloadJson<string[]>(homeUrl + stats_files))
|
||||
let prefix = /^stats.202[45]/
|
||||
let filteredIndex = indexFile.mapD(index => index.filter(path => path.match(prefix)))
|
||||
filteredIndex.addCallbackAndRunD(filtered => console.log("Filtered items are", filtered, indexFile.data))
|
||||
</script>
|
||||
|
||||
<div class="m-4">
|
||||
<div class="flex justify-between">
|
||||
<h2>Statistics of changes made with MapComplete</h2>
|
||||
<a href="/" class="button">Back to index</a>
|
||||
</div>
|
||||
{#if $indexFile === undefined}
|
||||
<Loading>Loading index file...</Loading>
|
||||
{:else}
|
||||
<AllStats
|
||||
paths={$indexFile.filter((p) => p.startsWith("stats")).map((p) => homeUrl + "/" + p)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<main class="h-screen">
|
||||
|
||||
<TitledPanel>
|
||||
<div slot="title" class="flex w-full justify-between">
|
||||
<span>Statistics of changes made with MapComplete</span>
|
||||
</div>
|
||||
<a slot="title-end" href="/" class="button">Back to index</a>
|
||||
|
||||
{#if $indexFile === undefined}
|
||||
<Loading>Loading index file...</Loading>
|
||||
{:else}
|
||||
<AllStats
|
||||
paths={$filteredIndex.map((p) => homeUrl + "/" + p)}
|
||||
/>
|
||||
{/if}
|
||||
</TitledPanel>
|
||||
</main>
|
||||
|
|
Loading…
Add table
Reference in a new issue