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 downloadData: () => Promise<(ChangeSetData & OsmFeature)[]> = async () => {
 | 
			
		||||
    const results = []
 | 
			
		||||
    for (const p of paths) {
 | 
			
		||||
      const r = await Utils.downloadJson<FeatureCollection>(p)
 | 
			
		||||
      console.log("Downloaded", p)
 | 
			
		||||
      downloaded++
 | 
			
		||||
        return r
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
  ).mapD((list) => [].concat(...list.map((f) => f.features)))
 | 
			
		||||
      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,9 +77,15 @@
 | 
			
		|||
  <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>
 | 
			
		||||
  {#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">
 | 
			
		||||
| 
						 | 
				
			
			@ -86,3 +101,4 @@
 | 
			
		|||
      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>
 | 
			
		||||
<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={$indexFile.filter((p) => p.startsWith("stats")).map((p) => homeUrl + "/" + p)}
 | 
			
		||||
        paths={$filteredIndex.map((p) => homeUrl + "/" + p)}
 | 
			
		||||
      />
 | 
			
		||||
    {/if}
 | 
			
		||||
</div>
 | 
			
		||||
  </TitledPanel>
 | 
			
		||||
</main>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue