| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * The statistics-gui shows statistics from previous MapComplete-edits | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | import { UIEventSource } from "../Logic/UIEventSource" | 
					
						
							|  |  |  | import { VariableUiElement } from "./Base/VariableUIElement" | 
					
						
							|  |  |  | import Loading from "./Base/Loading" | 
					
						
							|  |  |  | import { Utils } from "../Utils" | 
					
						
							|  |  |  | import Combine from "./Base/Combine" | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  | import { StackedRenderingChart } from "./BigComponents/TagRenderingChart" | 
					
						
							|  |  |  | import { LayerFilterPanel } from "./BigComponents/FilterView" | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  | import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  | import MapState from "../Logic/State/MapState" | 
					
						
							|  |  |  | import BaseUIElement from "./BaseUIElement" | 
					
						
							|  |  |  | import Title from "./Base/Title" | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  | import { FixedUiElement } from "./Base/FixedUiElement" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  | class StatisticsForOverviewFile extends Combine { | 
					
						
							|  |  |  |     constructor(homeUrl: string, paths: string[]) { | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  |         const layer = AllKnownLayouts.allKnownLayouts.get("mapcomplete-changes").layers[0] | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         const filteredLayer = MapState.InitializeFilteredLayers( | 
					
						
							|  |  |  |             { id: "statistics-view", layers: [layer] }, | 
					
						
							|  |  |  |             undefined | 
					
						
							|  |  |  |         )[0] | 
					
						
							|  |  |  |         const filterPanel = new LayerFilterPanel(undefined, filteredLayer) | 
					
						
							|  |  |  |         const appliedFilters = filteredLayer.appliedFilters | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         const downloaded = new UIEventSource<{ features: ChangeSetData[] }[]>([]) | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         for (const filepath of paths) { | 
					
						
							|  |  |  |             Utils.downloadJson(homeUrl + filepath).then((data) => { | 
					
						
							|  |  |  |                 data?.features?.forEach((item) => { | 
					
						
							|  |  |  |                     item.properties = { ...item.properties, ...item.properties.metadata } | 
					
						
							|  |  |  |                     delete item.properties.metadata | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                 downloaded.data.push(data) | 
					
						
							|  |  |  |                 downloaded.ping() | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         const loading = new Loading( | 
					
						
							|  |  |  |             new VariableUiElement( | 
					
						
							|  |  |  |                 downloaded.map((dl) => "Downloaded " + dl.length + " items out of " + paths.length) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         super([ | 
					
						
							|  |  |  |             filterPanel, | 
					
						
							|  |  |  |             new VariableUiElement( | 
					
						
							|  |  |  |                 downloaded.map( | 
					
						
							|  |  |  |                     (downloaded) => { | 
					
						
							|  |  |  |                         if (downloaded.length !== paths.length) { | 
					
						
							|  |  |  |                             return loading | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         let overview = ChangesetsOverview.fromDirtyData( | 
					
						
							|  |  |  |                             [].concat(...downloaded.map((d) => d.features)) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                         if (appliedFilters.data.size > 0) { | 
					
						
							|  |  |  |                             appliedFilters.data.forEach((filterSpec) => { | 
					
						
							|  |  |  |                                 const tf = filterSpec?.currentFilter | 
					
						
							|  |  |  |                                 if (tf === undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                                     return | 
					
						
							|  |  |  |                                 } | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                                 overview = overview.filter((cs) => | 
					
						
							|  |  |  |                                     tf.matchesProperties(cs.properties) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                                 ) | 
					
						
							|  |  |  |                             }) | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                         if (overview._meta.length === 0) { | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                             return "No data matched the filter" | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                         const dateStrings = Utils.NoNull( | 
					
						
							|  |  |  |                             overview._meta.map((cs) => cs.properties.date) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                         const dates: number[] = dateStrings.map((d) => new Date(d).getTime()) | 
					
						
							|  |  |  |                         const mindate = Math.min(...dates) | 
					
						
							|  |  |  |                         const maxdate = Math.max(...dates) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                         const diffInDays = (maxdate - mindate) / (1000 * 60 * 60 * 24) | 
					
						
							|  |  |  |                         console.log("Diff in days is ", diffInDays, "got", overview._meta.length) | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                         const trs = layer.tagRenderings.filter( | 
					
						
							|  |  |  |                             (tr) => tr.mappings?.length > 0 || tr.freeform?.key !== undefined | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         ) | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                         const elements: BaseUIElement[] = [] | 
					
						
							|  |  |  |                         for (const tr of trs) { | 
					
						
							|  |  |  |                             let total = undefined | 
					
						
							|  |  |  |                             if (tr.freeform?.key !== undefined) { | 
					
						
							|  |  |  |                                 total = new Set( | 
					
						
							|  |  |  |                                     overview._meta.map((f) => f.properties[tr.freeform.key]) | 
					
						
							|  |  |  |                                 ).size | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  |                             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                             try { | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                                 elements.push( | 
					
						
							|  |  |  |                                     new Combine([ | 
					
						
							|  |  |  |                                         new Title(tr.question ?? tr.id).SetClass("p-2"), | 
					
						
							|  |  |  |                                         total > 1 ? total + " unique value" : undefined, | 
					
						
							|  |  |  |                                         new StackedRenderingChart(tr, <any>overview._meta, { | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                                             period: diffInDays <= 367 ? "day" : "month", | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                                             groupToOtherCutoff: | 
					
						
							|  |  |  |                                                 total > 50 ? 25 : total > 10 ? 3 : 0, | 
					
						
							|  |  |  |                                         }).SetStyle("width: 100%; height: 600px"), | 
					
						
							|  |  |  |                                     ]).SetClass("block border-2 border-subtle p-2 m-2 rounded-xl") | 
					
						
							|  |  |  |                                 ) | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                             } catch (e) { | 
					
						
							|  |  |  |                                 console.log("Could not generate a chart", e) | 
					
						
							|  |  |  |                                 elements.push( | 
					
						
							|  |  |  |                                     new FixedUiElement( | 
					
						
							|  |  |  |                                         "No relevant information for " + tr.question.txt | 
					
						
							|  |  |  |                                     ) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                                 ) | 
					
						
							| 
									
										
										
										
											2022-09-02 21:40:13 +02:00
										 |  |  |                             } | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                         return new Combine(elements) | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     [appliedFilters] | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ).SetClass("block w-full h-full"), | 
					
						
							|  |  |  |         ]) | 
					
						
							|  |  |  |         this.SetClass("block w-full h-full") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  | export default class StatisticsGUI extends VariableUiElement { | 
					
						
							| 
									
										
										
										
											2022-08-22 14:33:53 +02:00
										 |  |  |     private static readonly homeUrl = | 
					
						
							|  |  |  |         "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/Docs/Tools/stats/" | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |     private static readonly stats_files = "file-overview.json" | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |     constructor() { | 
					
						
							|  |  |  |         const index = UIEventSource.FromPromise( | 
					
						
							|  |  |  |             Utils.downloadJson(StatisticsGUI.homeUrl + StatisticsGUI.stats_files) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         super( | 
					
						
							|  |  |  |             index.map((paths) => { | 
					
						
							|  |  |  |                 if (paths === undefined) { | 
					
						
							|  |  |  |                     return new Loading("Loading overview...") | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |                 return new StatisticsForOverviewFile(StatisticsGUI.homeUrl, paths) | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-08-24 16:02:16 +02:00
										 |  |  |         this.SetClass("block w-full h-full") | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  | class ChangesetsOverview { | 
					
						
							|  |  |  |     private static readonly theme_remappings = { | 
					
						
							|  |  |  |         metamap: "maps", | 
					
						
							|  |  |  |         groen: "buurtnatuur", | 
					
						
							|  |  |  |         "updaten van metadata met mapcomplete": "buurtnatuur", | 
					
						
							|  |  |  |         "Toevoegen of dit natuurreservaat toegangkelijk is": "buurtnatuur", | 
					
						
							|  |  |  |         "wiki:mapcomplete/fritures": "fritures", | 
					
						
							|  |  |  |         "wiki:MapComplete/Fritures": "fritures", | 
					
						
							|  |  |  |         lits: "lit", | 
					
						
							|  |  |  |         pomp: "cyclofix", | 
					
						
							|  |  |  |         "wiki:user:joost_schouppe/campersite": "campersite", | 
					
						
							|  |  |  |         "wiki-user-joost_schouppe-geveltuintjes": "geveltuintjes", | 
					
						
							|  |  |  |         "wiki-user-joost_schouppe-campersite": "campersite", | 
					
						
							|  |  |  |         "wiki-User-joost_schouppe-campersite": "campersite", | 
					
						
							|  |  |  |         "wiki-User-joost_schouppe-geveltuintjes": "geveltuintjes", | 
					
						
							|  |  |  |         "wiki:User:joost_schouppe/campersite": "campersite", | 
					
						
							|  |  |  |         arbres: "arbres_llefia", | 
					
						
							|  |  |  |         aed_brugge: "aed", | 
					
						
							|  |  |  |         "https://llefia.org/arbres/mapcomplete.json": "arbres_llefia", | 
					
						
							|  |  |  |         "https://llefia.org/arbres/mapcomplete1.json": "arbres_llefia", | 
					
						
							|  |  |  |         "toevoegen of dit natuurreservaat toegangkelijk is": "buurtnatuur", | 
					
						
							|  |  |  |         "testing mapcomplete 0.0.0": "buurtnatuur", | 
					
						
							|  |  |  |         entrances: "indoor", | 
					
						
							|  |  |  |         "https://raw.githubusercontent.com/osmbe/play/master/mapcomplete/geveltuinen/geveltuinen.json": | 
					
						
							|  |  |  |             "geveltuintjes", | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  |     public readonly _meta: ChangeSetData[] | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-20 12:46:33 +02:00
										 |  |  |     public static fromDirtyData(meta: ChangeSetData[]) { | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         return new ChangesetsOverview(meta?.map((cs) => ChangesetsOverview.cleanChangesetData(cs))) | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private constructor(meta: ChangeSetData[]) { | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         this._meta = Utils.NoNull(meta) | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public filter(predicate: (cs: ChangeSetData) => boolean) { | 
					
						
							|  |  |  |         return new ChangesetsOverview(this._meta.filter(predicate)) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static cleanChangesetData(cs: ChangeSetData): ChangeSetData { | 
					
						
							| 
									
										
										
										
											2022-08-22 13:34:47 +02:00
										 |  |  |         if (cs === undefined) { | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (cs.properties.editor?.startsWith("iD")) { | 
					
						
							|  |  |  |             // We also fetch based on hashtag, so some edits with iD show up as well
 | 
					
						
							|  |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  |         if (cs.properties.theme === undefined) { | 
					
						
							|  |  |  |             cs.properties.theme = cs.properties.comment.substr( | 
					
						
							|  |  |  |                 cs.properties.comment.lastIndexOf("#") + 1 | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         cs.properties.theme = cs.properties.theme.toLowerCase() | 
					
						
							|  |  |  |         const remapped = ChangesetsOverview.theme_remappings[cs.properties.theme] | 
					
						
							|  |  |  |         cs.properties.theme = remapped ?? cs.properties.theme | 
					
						
							|  |  |  |         if (cs.properties.theme.startsWith("https://raw.githubusercontent.com/")) { | 
					
						
							|  |  |  |             cs.properties.theme = | 
					
						
							|  |  |  |                 "gh://" + cs.properties.theme.substr("https://raw.githubusercontent.com/".length) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (cs.properties.modify + cs.properties.delete + cs.properties.create == 0) { | 
					
						
							|  |  |  |             cs.properties.theme = "EMPTY CS" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             cs.properties.host = new URL(cs.properties.host).host | 
					
						
							|  |  |  |         } catch (e) {} | 
					
						
							|  |  |  |         return cs | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface ChangeSetData { | 
					
						
							|  |  |  |     id: number | 
					
						
							|  |  |  |     type: "Feature" | 
					
						
							|  |  |  |     geometry: { | 
					
						
							|  |  |  |         type: "Polygon" | 
					
						
							|  |  |  |         coordinates: [number, number][][] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     properties: { | 
					
						
							|  |  |  |         check_user: null | 
					
						
							|  |  |  |         reasons: [] | 
					
						
							|  |  |  |         tags: [] | 
					
						
							|  |  |  |         features: [] | 
					
						
							|  |  |  |         user: string | 
					
						
							|  |  |  |         uid: string | 
					
						
							|  |  |  |         editor: string | 
					
						
							|  |  |  |         comment: string | 
					
						
							|  |  |  |         comments_count: number | 
					
						
							|  |  |  |         source: string | 
					
						
							|  |  |  |         imagery_used: string | 
					
						
							|  |  |  |         date: string | 
					
						
							|  |  |  |         reviewed_features: [] | 
					
						
							|  |  |  |         create: number | 
					
						
							|  |  |  |         modify: number | 
					
						
							|  |  |  |         delete: number | 
					
						
							|  |  |  |         area: number | 
					
						
							|  |  |  |         is_suspect: boolean | 
					
						
							|  |  |  |         harmful: any | 
					
						
							|  |  |  |         checked: boolean | 
					
						
							|  |  |  |         check_date: any | 
					
						
							| 
									
										
										
										
											2022-08-18 23:37:44 +02:00
										 |  |  |         host: string | 
					
						
							|  |  |  |         theme: string | 
					
						
							|  |  |  |         imagery: string | 
					
						
							|  |  |  |         language: string | 
					
						
							| 
									
										
										
										
											2022-08-02 19:50:17 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } |