forked from MapComplete/MapComplete
		
	Add cyclestreet theme, various bugfixes
This commit is contained in:
		
							parent
							
								
									72a744f60d
								
							
						
					
					
						commit
						60c15e9c8d
					
				
					 23 changed files with 412 additions and 211 deletions
				
			
		|  | @ -11,12 +11,11 @@ export class LayerUpdater { | |||
|     public readonly sufficentlyZoomed: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     public readonly retries: UIEventSource<number> = new UIEventSource<number>(0); | ||||
|      /** | ||||
|     /** | ||||
|      * The previous bounds for which the query has been run | ||||
|      */ | ||||
|     private previousBounds: Bounds; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * The most important layer should go first, as that one gets first pick for the questions | ||||
|      * @param map | ||||
|  |  | |||
|  | @ -14,56 +14,75 @@ export class Changes { | |||
| 
 | ||||
|     private static _nextId = -1; // New assined ID's are negative
 | ||||
| 
 | ||||
|     addTag(elementId: string, tagsFilter : TagsFilter){ | ||||
|         if(tagsFilter instanceof  Tag){ | ||||
|             const tag = tagsFilter as Tag; | ||||
|             if(typeof tag.value !== "string"){ | ||||
|                 throw "Invalid value" | ||||
|             } | ||||
|             this.addChange(elementId, tag.key, tag.value); | ||||
|     addTag(elementId: string, tagsFilter: TagsFilter) { | ||||
|         const changes = this.tagToChange(tagsFilter); | ||||
| 
 | ||||
|         if (changes.length == 0) { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if(tagsFilter instanceof And){ | ||||
|         const eventSource = State.state.allElements.getElement(elementId); | ||||
|         const elementTags = eventSource.data; | ||||
|         const pending : {elementId:string, key: string, value: string}[] = []; | ||||
|         for (const change of changes) { | ||||
|             if (elementTags[change.k] !== change.v) { | ||||
|                 elementTags[change.k] = change.v; | ||||
|                 pending.push({elementId: elementTags.id, key: change.k, value: change.v}); | ||||
|             } | ||||
|         } | ||||
|         if(pending.length === 0){ | ||||
|             return; | ||||
|         } | ||||
|         eventSource.ping(); | ||||
|         this.uploadAll([], pending); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private tagToChange(tagsFilter: TagsFilter) { | ||||
|         let changes: { k: string, v: string }[] = []; | ||||
| 
 | ||||
|         if (tagsFilter instanceof Tag) { | ||||
|             const tag = tagsFilter as Tag; | ||||
|             if (typeof tag.value !== "string") { | ||||
|                 throw "Invalid value" | ||||
|             } | ||||
|             return [this.checkChange(tag.key, tag.value)]; | ||||
|         } | ||||
| 
 | ||||
|         if (tagsFilter instanceof And) { | ||||
|             const and = tagsFilter as And; | ||||
|             for (const tag of and.and) { | ||||
|                 this.addTag(elementId, tag); | ||||
|                 changes = changes.concat(this.tagToChange(tag)); | ||||
|             } | ||||
|             return; | ||||
|             return changes; | ||||
|         } | ||||
|         console.log("Unsupported tagsfilter element to addTag", tagsFilter); | ||||
|         throw "Unsupported tagsFilter element"; | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /** | ||||
|      * Adds a change to the pending changes | ||||
|      * @param elementId | ||||
|      * @param key | ||||
|      * @param value | ||||
|      */ | ||||
|     addChange(elementId: string, key: string, value: string) { | ||||
|     private checkChange(key: string, value: string): { k: string, v: string } { | ||||
|         if (key === undefined || key === null) { | ||||
|             console.log("Invalid key"); | ||||
|             return; | ||||
|             return undefined; | ||||
|         } | ||||
|         if (value === undefined || value === null) { | ||||
|             console.log("Invalid value for ",key); | ||||
|             return; | ||||
|             console.log("Invalid value for ", key); | ||||
|             return undefined; | ||||
|         } | ||||
|          | ||||
|         if(key.startsWith(" ") || value.startsWith(" ") || value.endsWith(" ") || key.endsWith(" ")){ | ||||
| 
 | ||||
|         if (key.startsWith(" ") || value.startsWith(" ") || value.endsWith(" ") || key.endsWith(" ")) { | ||||
|             console.warn("Tag starts with or ends with a space - trimming anyway") | ||||
|         } | ||||
| 
 | ||||
|         key = key.trim(); | ||||
|         value = value.trim(); | ||||
| 
 | ||||
|         const eventSource = State.state.allElements.getElement(elementId); | ||||
|         eventSource.data[key] = value; | ||||
|         eventSource.ping(); | ||||
| 
 | ||||
|         this.uploadAll([], [{elementId: eventSource.data.id, key: key, value: value}]); | ||||
| 
 | ||||
|         return {k: key, v: value}; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -109,37 +128,90 @@ export class Changes { | |||
|         return geojson; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private uploadChangesWithLatestVersions( | ||||
|         knownElements, newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { | ||||
|         // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements', which maps the ids onto the elements
 | ||||
|         // We apply the changes on them
 | ||||
|         for (const change of pending) { | ||||
|             if (parseInt(change.elementId.split("/")[1]) < 0) { | ||||
|                 // This is a new element - we should apply this on one of the new elements
 | ||||
|                 for (const newElement of newElements) { | ||||
|                     if (newElement.type + "/" + newElement.id === change.elementId) { | ||||
|                         newElement.addTag(change.key, change.value); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|             } else { | ||||
|                 knownElements[change.elementId].addTag(change.key, change.value); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Small sanity check for duplicate information
 | ||||
|         let changedElements = []; | ||||
|         for (const elementId in knownElements) { | ||||
|             const element = knownElements[elementId]; | ||||
|             if (element.changed) { | ||||
|                 changedElements.push(element); | ||||
|             } | ||||
|         } | ||||
|         if (changedElements.length == 0 && newElements.length == 0) { | ||||
|             console.log("No changes in any object"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         console.log("Beginning upload..."); | ||||
|         // At last, we build the changeset and upload
 | ||||
|         State.state.osmConnection.UploadChangeset( | ||||
|             function (csId) { | ||||
| 
 | ||||
|                 let modifications = ""; | ||||
|                 for (const element of changedElements) { | ||||
|                     if (!element.changed) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     modifications += element.ChangesetXML(csId) + "\n"; | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 let creations = ""; | ||||
|                 for (const newElement of newElements) { | ||||
|                     creations += newElement.ChangesetXML(csId); | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|                 let changes = `<osmChange version='0.6' generator='Mapcomplete ${State.vNumber}'>`; | ||||
| 
 | ||||
|                 if (creations.length > 0) { | ||||
|                     changes += | ||||
|                         "<create>" + | ||||
|                         creations + | ||||
|                         "</create>"; | ||||
|                 } | ||||
| 
 | ||||
|                 if (modifications.length > 0) { | ||||
| 
 | ||||
|                     changes += | ||||
|                         "<modify>" + | ||||
|                         modifications + | ||||
|                         "</modify>"; | ||||
|                 } | ||||
| 
 | ||||
|                 changes += "</osmChange>"; | ||||
| 
 | ||||
|                 return changes; | ||||
|             }); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     private uploadAll( | ||||
|         newElements: OsmObject[], | ||||
|         pending: { elementId: string; key: string; value: string }[] | ||||
|     ) { | ||||
|         const self = this; | ||||
| 
 | ||||
|         const knownElements = {}; // maps string --> OsmObject
 | ||||
|         function DownloadAndContinue(neededIds, continuation: (() => void)) { | ||||
|             // local function which downloads all the objects one by one
 | ||||
|             // this is one big loop, running one download, then rerunning the entire function
 | ||||
|             if (neededIds.length == 0) { | ||||
|                 continuation(); | ||||
|                 return; | ||||
|             } | ||||
|             const neededId = neededIds.pop(); | ||||
| 
 | ||||
|             if (neededId in knownElements) { | ||||
|                 DownloadAndContinue(neededIds, continuation); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             console.log("Downloading ", neededId); | ||||
|             OsmObject.DownloadObject(neededId, | ||||
|                 function (element) { | ||||
|                     knownElements[neededId] = element; // assign the element for later, continue downloading the next element
 | ||||
|                     DownloadAndContinue(neededIds, continuation); | ||||
|                 } | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         const neededIds = []; | ||||
|         let neededIds: string[] = []; | ||||
|         for (const change of pending) { | ||||
|             const id = change.elementId; | ||||
|             if (parseFloat(id.split("/")[1]) < 0) { | ||||
|  | @ -149,80 +221,9 @@ export class Changes { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         DownloadAndContinue(neededIds, function () { | ||||
|             // Here, inside the continuation, we know that all 'neededIds' are loaded in 'knownElements'
 | ||||
|             // We apply the changes on them
 | ||||
|             for (const change of pending) { | ||||
|                 if (parseInt(change.elementId.split("/")[1]) < 0) { | ||||
|                     // This is a new element - we should apply this on one of the new elements
 | ||||
|                     for (const newElement of newElements) { | ||||
|                         if (newElement.type + "/" + newElement.id === change.elementId) { | ||||
|                             newElement.addTag(change.key, change.value); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                 } else { | ||||
|                     knownElements[change.elementId].addTag(change.key, change.value); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Small sanity check for duplicate information
 | ||||
|             let changedElements = []; | ||||
|             for (const elementId in knownElements) { | ||||
|                 const element = knownElements[elementId]; | ||||
|                 if (element.changed) { | ||||
|                     changedElements.push(element); | ||||
|                 } | ||||
|             } | ||||
|             if (changedElements.length == 0 && newElements.length == 0) { | ||||
|                 console.log("No changes in any object"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             console.log("Beginning upload..."); | ||||
|             // At last, we build the changeset and upload
 | ||||
|             State.state.osmConnection.UploadChangeset( | ||||
|                 function (csId) { | ||||
| 
 | ||||
|                     let modifications = ""; | ||||
|                     for (const element of changedElements) { | ||||
|                         if (!element.changed) { | ||||
|                             continue; | ||||
|                         } | ||||
|                         modifications += element.ChangesetXML(csId) + "\n"; | ||||
|                     } | ||||
| 
 | ||||
| 
 | ||||
|                     let creations = ""; | ||||
|                     for (const newElement of newElements) { | ||||
|                         creations += newElement.ChangesetXML(csId); | ||||
|                     } | ||||
| 
 | ||||
| 
 | ||||
|                     let changes = `<osmChange version='0.6' generator='Mapcomplete ${State.vNumber}'>`; | ||||
| 
 | ||||
|                     if (creations.length > 0) { | ||||
|                         changes += | ||||
|                             "<create>" + | ||||
|                             creations + | ||||
|                             "</create>"; | ||||
|                     } | ||||
| 
 | ||||
|                     if (modifications.length > 0) { | ||||
| 
 | ||||
|                         changes += | ||||
|                             "<modify>" + | ||||
|                             modifications + | ||||
|                             "</modify>"; | ||||
|                     } | ||||
| 
 | ||||
|                     changes += "</osmChange>"; | ||||
| 
 | ||||
|                     return changes; | ||||
|                 }, | ||||
|                 () => { | ||||
|                 }); | ||||
|         neededIds = Utils.Dedup(neededIds); | ||||
|         OsmObject.DownloadAll(neededIds, {}, (knownElements) => { | ||||
|             self.uploadChangesWithLatestVersions(knownElements, newElements, pending) | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,11 +10,11 @@ export class ChangesetHandler { | |||
| 
 | ||||
|     public currentChangeset: UIEventSource<string>; | ||||
| 
 | ||||
|     constructor(dryRun: boolean, osmConnection: OsmConnection, auth) { | ||||
|     constructor(layoutName: string, dryRun: boolean, osmConnection: OsmConnection, auth) { | ||||
|         this._dryRun = dryRun; | ||||
|         this.userDetails = osmConnection.userDetails; | ||||
|         this.auth = auth; | ||||
|         this.currentChangeset = osmConnection.GetPreference("current-open-changeset"); | ||||
|         this.currentChangeset = osmConnection.GetPreference("current-open-changeset-" + layoutName); | ||||
| 
 | ||||
|         if (dryRun) { | ||||
|             console.log("DRYRUN ENABLED"); | ||||
|  | @ -26,7 +26,6 @@ export class ChangesetHandler { | |||
|                            continuation: () => void) { | ||||
| 
 | ||||
|         if (this._dryRun) { | ||||
|             console.log("NOT UPLOADING as dryrun is true"); | ||||
|             var changesetXML = generateChangeXML("123456"); | ||||
|             console.log(changesetXML); | ||||
|             continuation(); | ||||
|  | @ -40,7 +39,9 @@ export class ChangesetHandler { | |||
|             // We have to open a new changeset
 | ||||
|             this.OpenChangeset((csId) => { | ||||
|                 this.currentChangeset.setData(csId); | ||||
|                 self.AddChange(csId, generateChangeXML(csId), | ||||
|                 const changeset = generateChangeXML(csId); | ||||
|                 console.log(changeset); | ||||
|                 self.AddChange(csId, changeset, | ||||
|                     () => { | ||||
|                     }, | ||||
|                     (e) => { | ||||
|  | @ -67,23 +68,6 @@ export class ChangesetHandler { | |||
|             ) | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         /* | ||||
|                 this.OpenChangeset( | ||||
|                     function (csId) { | ||||
|                         var changesetXML = generateChangeXML(csId); | ||||
|                         self.AddChange(csId, changesetXML, | ||||
|                             function (csId, mapping) { | ||||
|                                 self.CloseChangeset(csId, continuation); | ||||
|                                 handleMapping(mapping); | ||||
|                             } | ||||
|                         ); | ||||
|          | ||||
|                     } | ||||
|                 );*/ | ||||
| 
 | ||||
|         this.userDetails.data.csCount++; | ||||
|         this.userDetails.ping(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,7 +29,11 @@ export class OsmConnection { | |||
|      | ||||
|     private _onLoggedIn : ((userDetails: UserDetails) => void)[] = []; | ||||
| 
 | ||||
|     constructor(dryRun: boolean, oauth_token: UIEventSource<string>, singlePage: boolean = true, useDevServer:boolean = false) { | ||||
|     constructor(dryRun: boolean, oauth_token: UIEventSource<string>, | ||||
|                 // Used to keep multiple changesets open and to write to the correct changeset
 | ||||
|                 layoutName: string, | ||||
|                 singlePage: boolean = true,  | ||||
|                 useDevServer:boolean = false) { | ||||
| 
 | ||||
|         let pwaStandAloneMode = false; | ||||
|         try { | ||||
|  | @ -72,7 +76,7 @@ export class OsmConnection { | |||
| 
 | ||||
|         this.preferencesHandler = new OsmPreferences(this.auth, this); | ||||
|          | ||||
|         this.changesetHandler = new ChangesetHandler(dryRun, this, this.auth); | ||||
|         this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, this.auth); | ||||
|         if (oauth_token.data !== undefined) { | ||||
|             console.log(oauth_token.data) | ||||
|             const self = this; | ||||
|  | @ -94,7 +98,7 @@ export class OsmConnection { | |||
| 
 | ||||
| 
 | ||||
|     public UploadChangeset(generateChangeXML: (csid: string) => string, | ||||
|                            continuation: () => void) { | ||||
|                            continuation: () => void = () => {}) { | ||||
|         this.changesetHandler.UploadChangeset(generateChangeXML, continuation); | ||||
|     } | ||||
| 
 | ||||
|  | @ -119,6 +123,7 @@ export class OsmConnection { | |||
| 
 | ||||
|     public AttemptLogin() { | ||||
|         const self = this; | ||||
|         console.log("Trying to log in..."); | ||||
|         this.auth.xhr({ | ||||
|             method: 'GET', | ||||
|             path: '/api/0.6/user/details' | ||||
|  |  | |||
|  | @ -60,6 +60,7 @@ export abstract class OsmObject { | |||
|         return tags; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     Download(continuation: ((element: OsmObject) => void)) { | ||||
|         const self = this; | ||||
|         $.getJSON("https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id, | ||||
|  | @ -96,6 +97,31 @@ export abstract class OsmObject { | |||
|         return 'version="'+this.version+'"'; | ||||
|     } | ||||
|     abstract ChangesetXML(changesetId: string): string; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public static DownloadAll(neededIds, knownElements: any = {}, continuation: ((knownObjects : any) => void)) { | ||||
|         // local function which downloads all the objects one by one
 | ||||
|         // this is one big loop, running one download, then rerunning the entire function
 | ||||
|         if (neededIds.length == 0) { | ||||
|             continuation(knownElements); | ||||
|             return; | ||||
|         } | ||||
|         const neededId = neededIds.pop(); | ||||
| 
 | ||||
|         if (neededId in knownElements) { | ||||
|             OsmObject.DownloadAll(neededIds, knownElements, continuation); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         console.log("Downloading ", neededId); | ||||
|         OsmObject.DownloadObject(neededId, | ||||
|             function (element) { | ||||
|                 knownElements[neededId] = element; // assign the element for later, continue downloading the next element
 | ||||
|                 OsmObject.DownloadAll(neededIds,knownElements, continuation); | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ export abstract class TagsFilter { | |||
|         return this.matches(TagUtils.proprtiesToKV(properties)); | ||||
|     } | ||||
| 
 | ||||
|     abstract asHumanString(linkToWiki: boolean); | ||||
|     abstract asHumanString(linkToWiki: boolean, shorten: boolean); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -90,7 +90,12 @@ export class Tag extends TagsFilter { | |||
|     } | ||||
| 
 | ||||
|     matches(tags: { k: string; v: string }[]): boolean { | ||||
|         if (this.value === "") { | ||||
|             return true | ||||
|         } | ||||
|          | ||||
|         for (const tag of tags) { | ||||
|              | ||||
|             if (Tag.regexOrStrMatches(this.key, tag.k)) { | ||||
| 
 | ||||
|                 if (tag.v === "") { | ||||
|  | @ -109,10 +114,6 @@ export class Tag extends TagsFilter { | |||
|                 return Tag.regexOrStrMatches(this.value, tag.v) !== this.invertValue | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.value === "") { | ||||
|             return true | ||||
|         } | ||||
|          | ||||
|         return this.invertValue | ||||
|     } | ||||
|  | @ -150,15 +151,17 @@ export class Tag extends TagsFilter { | |||
|         return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags)); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean) { | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         let v = "" | ||||
|         if (typeof (this.value) === "string") { | ||||
|            v = this.value; | ||||
|         }else{ | ||||
|             v = this.value; | ||||
|         } else { | ||||
|             // value is a regex
 | ||||
|             v = this.value.source; | ||||
|         } | ||||
|         v = Utils.EllipsesAfter(v, 25); | ||||
|         if (shorten) { | ||||
|             v = Utils.EllipsesAfter(v, 25); | ||||
|         } | ||||
|         if (linkToWiki) { | ||||
|             return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` + | ||||
|                 `=` + | ||||
|  | @ -221,8 +224,8 @@ export class Or extends TagsFilter { | |||
|         return new Or(newChoices); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean) { | ||||
|         return this.or.map(t => t.asHumanString(linkToWiki)).join("|"); | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.or.map(t => t.asHumanString(linkToWiki, shorten)).join("|"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -282,8 +285,8 @@ export class And extends TagsFilter { | |||
|         return new And(newChoices); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean) { | ||||
|         return this.and.map(t => t.asHumanString(linkToWiki)).join("&"); | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return this.and.map(t => t.asHumanString(linkToWiki, shorten)).join("&"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -308,8 +311,8 @@ export class Not extends TagsFilter{ | |||
|         return new Not(this.not.substituteValues(tags)); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean) { | ||||
|         return "!" + this.not.asHumanString(linkToWiki); | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean) { | ||||
|         return "!" + this.not.asHumanString(linkToWiki, shorten); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue