forked from MapComplete/MapComplete
		
	Refactoring: remove scripts and leaderboard which depend on imgur
This commit is contained in:
		
							parent
							
								
									e3a984a2dd
								
							
						
					
					
						commit
						d920afa09e
					
				
					 5 changed files with 0 additions and 566 deletions
				
			
		|  | @ -1,75 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"> | ||||
|     <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://gc.zgo.at/; img-src *; connect-src 'self' https://raw.githubusercontent.com/ https://www.openstreetmap.org/ https://api.openstreetmap.org/;"> | ||||
|     <link href="./css/mobile.css" rel="stylesheet"/> | ||||
|     <link href="./css/openinghourstable.css" rel="stylesheet"/> | ||||
|     <link href="./css/tagrendering.css" rel="stylesheet"/> | ||||
|     <link href="css/ReviewElement.css" rel="stylesheet"/> | ||||
|     <link href="./css/index-tailwind-output.css" rel="stylesheet"/> | ||||
|     <link href="./css/wikipedia.css" rel="stylesheet"/> | ||||
|     <meta content="website" property="og:type"> | ||||
| 
 | ||||
|     <!-- THEME-SPECIFIC --> | ||||
|     <!-- Every theme gets their own html page, this is created by a script; this part will be removed except for the index --> | ||||
|     <title>MapComplete</title> | ||||
|     <link href="./index.webmanifest" rel="manifest"> | ||||
|      | ||||
|     <link href="./assets/svg/add.svg" rel="icon" sizes="any" type="image/svg+xml"> | ||||
|     <meta content="./assets/SocialImage.png" property="og:image"> | ||||
|     <meta content="MapComplete - editable, thematic maps with OpenStreetMap" property="og:title"> | ||||
|     <meta content="MapComplete is a platform to visualize OpenStreetMap on a specific topic and to easily contribute data back to it." | ||||
|           property="og:description"> | ||||
| 
 | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo512.png" rel="apple-touch-icon" sizes="512x512"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo384.png" rel="apple-touch-icon" sizes="384x384"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo192.png" rel="apple-touch-icon" sizes="192x192"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo180.png" rel="apple-touch-icon" sizes="180x180"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo152.png" rel="apple-touch-icon" sizes="152x152"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo144.png" rel="apple-touch-icon" sizes="144x144"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo128.png" rel="apple-touch-icon" sizes="128x128"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo120.png" rel="apple-touch-icon" sizes="120x120"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo96.png" rel="apple-touch-icon" sizes="96x96"> | ||||
|     <link href="./assets/generated/images/assets_svg_mapcomplete_logo72.png" rel="apple-touch-icon" sizes="72x72"> | ||||
| 
 | ||||
| 
 | ||||
|     <!-- THEME-SPECIFIC-END--> | ||||
| 
 | ||||
|     <style> | ||||
|         #decoration-desktop img { | ||||
|             width: 100%; | ||||
|             height: 100%; | ||||
|         } | ||||
|     </style> | ||||
| 
 | ||||
| </head> | ||||
| <body> | ||||
| 
 | ||||
| <!-- Mastodon link verification: https://docs.joinmastodon.org/user/profile/#Link%20verification --> | ||||
| <a rel="me" href="https://en.osm.town/@MapComplete" class="hidden">Mastodon</a> | ||||
| 
 | ||||
| <div id="main"></div> | ||||
| <script type="module" src="./src/leaderboard.ts"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" | ||||
|         integrity="sha384-QfJMxHNngbaF6IXH2kBwubsLYh7GVSFmJOX1O1YKJBq+zv1VVypB9BysTzyG1D1U"></script> | ||||
| 
 | ||||
| <script> | ||||
|     window.addEventListener('load', () => { | ||||
|         if ('serviceWorker' in navigator) { | ||||
|             navigator.serviceWorker.register('/service-worker.js').then( | ||||
|                 () => { | ||||
|                     console.log('Service worker registration successful'); | ||||
|                 }, | ||||
|                 err => { | ||||
|                     console.error('Service worker registration failed', err) | ||||
|                 }); | ||||
|         } else { | ||||
|             console.log("Service workers are not supported") | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  | @ -121,7 +121,6 @@ | |||
|     "download:nsi": "npm i name-suggestion-index && vite-node scripts/generateStats.ts && npm run download:nsi-logos && vite-node scripts/nsiLogos.ts -- all", | ||||
|     "download:editor-layer-index": "vite-node scripts/downloadEli.ts", | ||||
|     "download:stats": "vite-node scripts/GenerateSeries.ts", | ||||
|     "download:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/", | ||||
|     "download:community-index": "vite-node scripts/downloadCommunityIndex.ts ", | ||||
|     "weblate:add-upstream": "git remote add weblate https://translate.mapcomplete.org/git/mapcomplete/core/ ; git remote update weblate", | ||||
|     "weblate:fix": "npm run weblate:add-upstream && git merge weblate/master && git rebase origin/master && git push origin master", | ||||
|  |  | |||
|  | @ -1,414 +0,0 @@ | |||
| import Script from "./Script" | ||||
| import { Overpass } from "../src/Logic/Osm/Overpass" | ||||
| import { RegexTag } from "../src/Logic/Tags/RegexTag" | ||||
| import { ImmutableStore } from "../src/Logic/UIEventSource" | ||||
| import { BBox } from "../src/Logic/BBox" | ||||
| import * as fs from "fs" | ||||
| import { writeFileSync } from "fs" | ||||
| import { Feature } from "geojson" | ||||
| import ScriptUtils from "./ScriptUtils" | ||||
| import { Imgur } from "../src/Logic/ImageProviders/Imgur" | ||||
| import { LicenseInfo } from "../src/Logic/ImageProviders/LicenseInfo" | ||||
| import { Utils } from "../src/Utils" | ||||
| import Constants from "../src/Models/Constants" | ||||
| 
 | ||||
| export default class GenerateImageAnalysis extends Script { | ||||
|     /** | ||||
|      * Max N in `image:N`-keys and `imageN` keys | ||||
|      * @private | ||||
|      */ | ||||
|     private static readonly maxImageIndex = 31 | ||||
|     constructor() { | ||||
|         super( | ||||
|             [ | ||||
|                 "Downloads (from overpass) all tags which have an imgur-image; then analyses the licenses and downloads all the images", | ||||
|                 "", | ||||
|                 "Arguments:", | ||||
|                 "Path to download the images to", | ||||
|                 "Path to save the overview to", | ||||
|             ].join("\n") | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     async fetchImages(key: string, datapath: string, refresh: boolean): Promise<void> { | ||||
|         const targetPath = `${datapath}/features_with_${key.replace(/[:\/]/, "_")}.geojson` | ||||
|         if (fs.existsSync(targetPath) && !refresh) { | ||||
|             console.log("Skipping", key) | ||||
|             return | ||||
|         } | ||||
|         const tag = new RegexTag(key, /^https:\/\/i.imgur.com\/.*$/i) | ||||
|         const overpass = new Overpass( | ||||
|             tag, | ||||
|             [], | ||||
|             Constants.defaultOverpassUrls[0], //"https://overpass.kumi.systems/api/interpreter",
 | ||||
|             new ImmutableStore(500), | ||||
|             false | ||||
|         ) | ||||
|         console.log("Starting query...") | ||||
|         const data = await overpass.queryGeoJson(BBox.global) | ||||
|         console.log( | ||||
|             "Got data:", | ||||
|             data[0].features.length, | ||||
|             "items; timestamp:", | ||||
|             data[1].toISOString() | ||||
|         ) | ||||
|         fs.writeFileSync(targetPath, JSON.stringify(data[0]), "utf8") | ||||
|         console.log("Written", targetPath) | ||||
|     } | ||||
| 
 | ||||
|     async downloadData(datapath: string, refresh: boolean): Promise<void> { | ||||
|         if (!fs.existsSync(datapath)) { | ||||
|             fs.mkdirSync(datapath) | ||||
|         } | ||||
|         await this.fetchImages("image", datapath, refresh) | ||||
|         await this.fetchImages("image:streetsign", datapath, refresh) | ||||
|         await this.fetchImages("image:menu", datapath, refresh) | ||||
|         for (let i = 0; i < GenerateImageAnalysis.maxImageIndex; i++) { | ||||
|             await this.fetchImages("image:" + i, datapath, refresh) | ||||
|             await this.fetchImages("image" + i, datapath, refresh) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     loadData(datapath: string): Feature[] { | ||||
|         const allFeatures: Feature[] = [] | ||||
| 
 | ||||
|         const files = ScriptUtils.readDirRecSync(datapath) | ||||
|         for (const file of files) { | ||||
|             if (!file.endsWith(".geojson")) { | ||||
|                 continue | ||||
|             } | ||||
|             const contents = JSON.parse(fs.readFileSync(file, "utf8")) | ||||
|             allFeatures.push(...contents.features) | ||||
|         } | ||||
| 
 | ||||
|         return allFeatures | ||||
|     } | ||||
| 
 | ||||
|     async fetchImageMetadata(datapath: string, image: string): Promise<boolean> { | ||||
|         if (image === undefined) { | ||||
|             return false | ||||
|         } | ||||
|         if (!image.match(/https:\/\/i\.imgur\.com\/[a-zA-Z0-9]+(\.jpe?g)|(\.png)/)) { | ||||
|             return false | ||||
|         } | ||||
|         const filename = image.replace(/[\/:.\-%]/g, "_") + ".json" | ||||
|         const targetPath = datapath + "/" + filename | ||||
|         if (fs.existsSync(targetPath)) { | ||||
|             return false | ||||
|         } | ||||
|         const attribution = await Imgur.singleton.DownloadAttribution({ url: image }) | ||||
| 
 | ||||
|         if ((attribution.artist ?? "") === "") { | ||||
|             // This is an invalid attribution. We save the raw response as well
 | ||||
|             const hash = image.substr("https://i.imgur.com/".length).split(".jpg")[0] | ||||
| 
 | ||||
|             const apiUrl = "https://api.imgur.com/3/image/" + hash | ||||
|             const response = await Utils.downloadJsonCached(apiUrl, 365 * 24 * 60 * 60, { | ||||
|                 Authorization: "Client-ID " + Constants.ImgurApiKey, | ||||
|             }) | ||||
|             const rawTarget = datapath + "/raw/" + filename | ||||
|             console.log("Also storing the raw response to", rawTarget) | ||||
|             await fs.writeFileSync(rawTarget, JSON.stringify(response, null, "    ")) | ||||
|         } | ||||
| 
 | ||||
|         await fs.writeFileSync(targetPath, JSON.stringify(attribution, null, "    ")) | ||||
|         return true | ||||
|     } | ||||
| 
 | ||||
|     loadImageUrls(datapath: string): { allImages: Set<string>; imageSource: Map<string, string> } { | ||||
|         let allImages = new Set<string>() | ||||
|         const features = this.loadData(datapath) | ||||
|         let imageSource: Map<string, string> = new Map<string, string>() | ||||
| 
 | ||||
|         for (const feature of features) { | ||||
|             allImages.add(feature.properties["image"]) | ||||
|             imageSource[feature.properties["image"]] = feature.properties.id | ||||
|             allImages.add(feature.properties["image:streetsign"]) | ||||
|             imageSource[feature.properties["image:streetsign"]] = | ||||
|                 feature.properties.id + " (streetsign)" | ||||
| 
 | ||||
|             for (let i = 0; i < GenerateImageAnalysis.maxImageIndex; i++) { | ||||
|                 allImages.add(feature.properties["image:" + i]) | ||||
|                 imageSource[ | ||||
|                     feature.properties["image:" + i] | ||||
|                 ] = `${feature.properties.id} (image:${i})` | ||||
| 
 | ||||
|                 allImages.add(feature.properties["image" + i]) | ||||
|                 imageSource[ | ||||
|                     feature.properties["image" + i] | ||||
|                 ] = `${feature.properties.id} (image${i})` | ||||
|             } | ||||
|         } | ||||
|         allImages.delete(undefined) | ||||
|         allImages.delete(null) | ||||
|         imageSource.delete(undefined) | ||||
|         imageSource.delete(null) | ||||
|         return { allImages, imageSource } | ||||
|     } | ||||
| 
 | ||||
|     async downloadMetadata(datapath: string): Promise<void> { | ||||
|         const { allImages, imageSource } = this.loadImageUrls(datapath) | ||||
|         console.log("Detected", allImages.size, "images") | ||||
|         let i = 0 | ||||
|         let d = 0 | ||||
|         let s = 0 | ||||
|         let f = 0 | ||||
|         let start = Date.now() | ||||
|         for (const image of Array.from(allImages)) { | ||||
|             i++ | ||||
|             try { | ||||
|                 const downloaded = await this.fetchImageMetadata(datapath, image) | ||||
|                 const runningSecs = (Date.now() - start) / 1000 | ||||
|                 const left = allImages.size - i | ||||
| 
 | ||||
|                 const estimatedActualSeconds = Math.floor((left * runningSecs) / (f + d)) | ||||
|                 const estimatedActualMinutes = Math.floor(estimatedActualSeconds / 60) | ||||
| 
 | ||||
|                 const msg = `${i}/${ | ||||
|                     allImages.size | ||||
|                 } downloaded: ${d},skipped: ${s}, failed: ${f}, running: ${Math.floor( | ||||
|                     runningSecs | ||||
|                 )}sec, ETA: ${estimatedActualMinutes}:${estimatedActualSeconds % 60}` | ||||
|                 if (d + (f % 1000) === 1 || downloaded) { | ||||
|                     ScriptUtils.erasableLog(msg) | ||||
|                 } | ||||
|                 if (downloaded) { | ||||
|                     d++ | ||||
|                 } else { | ||||
|                     s++ | ||||
|                 } | ||||
|                 if (d + f == 75000) { | ||||
|                     console.log("Used 75000 API calls, leaving 5000 for the rest of the day...") | ||||
|                     break | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 // console.log(e)
 | ||||
|                 console.log( | ||||
|                     "Offending image hash is", | ||||
|                     image, | ||||
|                     "from https://openstreetmap.org/" + imageSource[image] | ||||
|                 ) | ||||
|                 f++ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     async downloadImage(url: string, imagePath: string): Promise<boolean> { | ||||
|         const filenameLong = url.replace(/[\/:.\-%]/g, "_") + ".jpg" | ||||
|         const targetPathLong = imagePath + "/" + filenameLong | ||||
| 
 | ||||
|         const filename = url.substring("https://i.imgur.com/".length) | ||||
|         const targetPath = imagePath + "/" + filename | ||||
|         if (fs.existsSync(targetPathLong)) { | ||||
|             if (fs.existsSync(targetPath)) { | ||||
|                 fs.unlinkSync(targetPathLong) | ||||
|                 console.log("Unlinking duplicate") | ||||
|                 return false | ||||
|             } | ||||
|             console.log("Renaming...") | ||||
|             fs.renameSync(targetPathLong, targetPath) | ||||
|             return false | ||||
|         } | ||||
|         if (fs.existsSync(targetPath)) { | ||||
|             return false | ||||
|         } | ||||
|         await ScriptUtils.DownloadFileTo(url, targetPath) | ||||
|         return true | ||||
|     } | ||||
| 
 | ||||
|     async downloadAllImages(datapath: string, imagePath: string): Promise<void> { | ||||
|         const { allImages } = this.loadImageUrls(datapath) | ||||
|         let skipped = 0 | ||||
|         let failed = 0 | ||||
|         let downloaded = 0 | ||||
|         let invalid = 0 | ||||
|         const startTime = Date.now() | ||||
|         const urls = Array.from(allImages).filter((url) => url.startsWith("https://i.imgur.com")) | ||||
|         for (const url of urls) { | ||||
|             const runningTime = (Date.now() - startTime) / 1000 | ||||
|             const handled = skipped + downloaded + failed | ||||
|             const itemsLeft = allImages.size - handled | ||||
|             const speed = handled / runningTime | ||||
|             const timeLeft = Math.round(itemsLeft * speed) | ||||
|             try { | ||||
|                 const urls = url.split(/[;,]/) | ||||
|                 const downloadedStatus = await Promise.all( | ||||
|                     urls.map((url) => this.downloadImage(url.trim(), imagePath)) | ||||
|                 ) | ||||
| 
 | ||||
|                 for (const b of downloadedStatus) { | ||||
|                     if (b) { | ||||
|                         downloaded += 1 | ||||
|                     } else { | ||||
|                         skipped += 1 | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (downloadedStatus.some((i) => i) || skipped % 10000 === 0) { | ||||
|                     console.log( | ||||
|                         "Handled", | ||||
|                         url, | ||||
|                         JSON.stringify({ | ||||
|                             skipped, | ||||
|                             failed, | ||||
|                             downloaded, | ||||
|                             invalid, | ||||
|                             total: allImages.size, | ||||
|                             eta: timeLeft + "s", | ||||
|                         }) | ||||
|                     ) | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.log(e) | ||||
|                 failed++ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     analyze(datapath: string) { | ||||
|         const files = ScriptUtils.readDirRecSync(datapath) | ||||
|         const byAuthor = new Map<string, string[]>() | ||||
|         const byLicense = new Map<string, string[]>() | ||||
|         const licenseByAuthor = new Map<string, Set<string>>() | ||||
|         for (const file of files) { | ||||
|             if (!file.endsWith(".json")) { | ||||
|                 continue | ||||
|             } | ||||
|             const attr = <LicenseInfo>JSON.parse(fs.readFileSync(file, { encoding: "utf8" })) | ||||
|             const license = attr.licenseShortName | ||||
| 
 | ||||
|             if (license === undefined || attr.artist === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             if (byAuthor.get(attr.artist) === undefined) { | ||||
|                 byAuthor.set(attr.artist, []) | ||||
|             } | ||||
|             byAuthor.get(attr.artist).push(file) | ||||
| 
 | ||||
|             if (byLicense.get(license) === undefined) { | ||||
|                 byLicense.set(license, []) | ||||
|             } | ||||
|             byLicense.get(license).push(file) | ||||
| 
 | ||||
|             if (licenseByAuthor.get(license) === undefined) { | ||||
|                 licenseByAuthor.set(license, new Set<string>()) | ||||
|             } | ||||
|             licenseByAuthor.get(license).add(attr.artist) | ||||
|         } | ||||
|         byAuthor.delete(undefined) | ||||
|         byLicense.delete(undefined) | ||||
|         licenseByAuthor.delete(undefined) | ||||
| 
 | ||||
|         const byLicenseCount = Utils.MapToObj(byLicense, (a) => a.length) | ||||
|         const byAuthorCount = Utils.MapToObj(byAuthor, (a) => a.length) | ||||
|         const licenseByAuthorCount = Utils.MapToObj(licenseByAuthor, (a) => a.size) | ||||
| 
 | ||||
|         const countsPerAuthor: number[] = Array.from(Object.keys(byAuthorCount)).map( | ||||
|             (k) => byAuthorCount[k] | ||||
|         ) | ||||
|         console.log(countsPerAuthor) | ||||
|         countsPerAuthor.sort() | ||||
|         const median = countsPerAuthor[Math.floor(countsPerAuthor.length / 2)] | ||||
|         const json: { | ||||
|             leaderboard: { rank: number; account: string; name: string; nrOfImages: number }[] | ||||
|         } = { | ||||
|             leaderboard: [], | ||||
|         } | ||||
|         for (let i = 0; i < 100; i++) { | ||||
|             let maxAuthor: string = undefined | ||||
|             let maxCount = 0 | ||||
|             for (const author in byAuthorCount) { | ||||
|                 const count = byAuthorCount[author] | ||||
|                 if (maxAuthor === undefined || count > maxCount) { | ||||
|                     maxAuthor = author | ||||
|                     maxCount = count | ||||
|                 } | ||||
|             } | ||||
|             json.leaderboard.push({ | ||||
|                 rank: i + 1, | ||||
|                 name: maxAuthor, | ||||
|                 account: "https://openstreetmap.org/user/" + maxAuthor.replace(/ /g, "%20"), | ||||
|                 nrOfImages: maxCount, | ||||
|             }) | ||||
|             console.log( | ||||
|                 "|", | ||||
|                 i + 1, | ||||
|                 "|", | ||||
|                 `[${maxAuthor}](https://openstreetmap.org/user/${maxAuthor.replace(/ /g, "%20")})`, | ||||
|                 "|", | ||||
|                 maxCount, | ||||
|                 "|" | ||||
|             ) | ||||
|             delete byAuthorCount[maxAuthor] | ||||
|         } | ||||
| 
 | ||||
|         const totalAuthors = byAuthor.size | ||||
|         let totalLicensedImages = 0 | ||||
|         json["totalAuthors"] = totalAuthors | ||||
|         for (const license in byLicenseCount) { | ||||
|             totalLicensedImages += byLicenseCount[license] | ||||
|         } | ||||
|         json["byLicense"] = {} | ||||
|         for (const license in byLicenseCount) { | ||||
|             const total = byLicenseCount[license] | ||||
|             const authors = licenseByAuthorCount[license] | ||||
|             console.log( | ||||
|                 `License ${license}: ${total} total pictures (${ | ||||
|                     Math.floor((1000 * total) / totalLicensedImages) / 10 | ||||
|                 }%), ${authors} authors (${ | ||||
|                     Math.floor((1000 * authors) / totalAuthors) / 10 | ||||
|                 }%), ${Math.floor(total / authors)} images/author` | ||||
|             ) | ||||
|             json["byLicense"] = { | ||||
|                 license, | ||||
|                 total, | ||||
|                 authors, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const nonDefaultAuthors = [ | ||||
|             ...Array.from(licenseByAuthor.get("CC-BY 4.0").values()), | ||||
|             ...Array.from(licenseByAuthor.get("CC-BY-SA 4.0").values()), | ||||
|         ] | ||||
| 
 | ||||
|         console.log( | ||||
|             "Total number of correctly licenses pictures: ", | ||||
|             totalLicensedImages, | ||||
|             "(out of ", | ||||
|             files.length, | ||||
|             " images)" | ||||
|         ) | ||||
|         console.log("Total number of authors:", byAuthor.size) | ||||
|         console.log( | ||||
|             "Total number of authors which used a valid, non CC0 license at one point in time", | ||||
|             nonDefaultAuthors.length | ||||
|         ) | ||||
|         console.log("Median contributions per author:", median) | ||||
|         json["median"] = median | ||||
|         json["date"] = new Date().toISOString() | ||||
|         writeFileSync( | ||||
|             "../../git/MapComplete-data/picture-leaderboard.json", | ||||
|             JSON.stringify(json), | ||||
|             "utf8" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     async main(args: string[]): Promise<void> { | ||||
|         console.log("Usage: [--cached] to use the cached osm data") | ||||
|         console.log("Args are", args) | ||||
|         const cached = args.indexOf("--cached") < 0 | ||||
|         args = args.filter((a) => a !== "--cached") | ||||
|         const datapath = args[1] ?? "../../git/MapComplete-data/ImageLicenseInfo" | ||||
|         const imageBackupPath = args[0] | ||||
|         if (imageBackupPath === "" || imageBackupPath === undefined) { | ||||
|             throw "No imageBackup path specified" | ||||
|         } | ||||
|         await this.downloadData(datapath, cached) | ||||
| 
 | ||||
|         // await this.downloadViews(datapath)
 | ||||
|         await this.downloadMetadata(datapath) | ||||
|         await this.downloadAllImages(datapath, imageBackupPath) | ||||
|         this.analyze(datapath) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| new GenerateImageAnalysis().run() | ||||
|  | @ -1,71 +0,0 @@ | |||
| <script lang="ts"> | ||||
|   import { Utils } from "../Utils" | ||||
|   import { Store, UIEventSource } from "../Logic/UIEventSource" | ||||
|   import Loading from "./Base/Loading.svelte" | ||||
|   import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||
| 
 | ||||
|   const osmConnection = new OsmConnection({ | ||||
|     attemptLogin: true, | ||||
|   }) | ||||
|   let loggedInContributor: Store<string> = osmConnection.userDetails.map((ud) => ud.name) | ||||
|   export let source = | ||||
|     "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/picture-leaderboard.json" | ||||
|   let data: Store< | ||||
|     | undefined | ||||
|     | { | ||||
|         leaderboard: { | ||||
|           rank: number | ||||
|           name: string | ||||
|           account: string | ||||
|           nrOfImages: number | ||||
|         }[] | ||||
|         median: number | ||||
|         totalAuthors: number | ||||
|         byLicense: { | ||||
|           license: string | ||||
|           total: number | ||||
|           authors: string[] | ||||
|         } | ||||
|         date: string | ||||
|       } | ||||
|   > = UIEventSource.FromPromise(Utils.downloadJsonCached(source)) | ||||
| </script> | ||||
| 
 | ||||
| <main> | ||||
|   <h1>Contributed images with MapComplete: leaderboard</h1> | ||||
| 
 | ||||
|   {#if $data} | ||||
|     <table> | ||||
|       <tr> | ||||
|         <th>Rank</th> | ||||
|         <th>Contributor</th> | ||||
|         <th>Number of images contributed</th> | ||||
|       </tr> | ||||
|       {#each $data.leaderboard as contributor} | ||||
|         <tr> | ||||
|           <td> | ||||
|             {contributor.rank} | ||||
|           </td> | ||||
|           <td> | ||||
|             {#if $loggedInContributor === contributor.name} | ||||
|               <a class="thanks" href={contributor.account}>{contributor.name}</a> | ||||
|             {:else} | ||||
|               <a href={contributor.account}>{contributor.name}</a> | ||||
|             {/if} | ||||
|           </td> | ||||
|           <td> | ||||
|             <b>{contributor.nrOfImages}</b> | ||||
|             total images | ||||
|           </td> | ||||
|         </tr> | ||||
|       {/each} | ||||
|     </table> | ||||
|     Statistics generated on {$data.date} | ||||
|   {:else} | ||||
|     <Loading /> | ||||
|   {/if} | ||||
| 
 | ||||
|   <div> | ||||
|     Logged in as {$loggedInContributor} | ||||
|   </div> | ||||
| </main> | ||||
|  | @ -1,5 +0,0 @@ | |||
| import Leaderboard from "./UI/Leaderboard.svelte" | ||||
| 
 | ||||
| new Leaderboard({ | ||||
|     target: document.getElementById("main"), | ||||
| }) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue