forked from MapComplete/MapComplete
		
	First version which caches changesets if not uploaded
This commit is contained in:
		
							parent
							
								
									d72dbc21df
								
							
						
					
					
						commit
						6732c12a0c
					
				
					 7 changed files with 84 additions and 41 deletions
				
			
		|  | @ -7,6 +7,7 @@ import FeatureSource from "../FeatureSource/FeatureSource"; | |||
| import {TagsFilter} from "../Tags/TagsFilter"; | ||||
| import {Tag} from "../Tags/Tag"; | ||||
| import {OsmConnection} from "./OsmConnection"; | ||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||
| 
 | ||||
| /** | ||||
|  * Handles all changes made to OSM. | ||||
|  | @ -24,8 +25,13 @@ export class Changes implements FeatureSource { | |||
|     /** | ||||
|      * All the pending changes | ||||
|      */ | ||||
|     public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = | ||||
|         new UIEventSource<{ elementId: string; key: string; value: string }[]>([]); | ||||
|     public readonly pending: UIEventSource<{ elementId: string, key: string, value: string }[]> = LocalStorageSource.GetParsed("pending-changes", []) | ||||
| 
 | ||||
|     /** | ||||
|      * All the pending new objects to upload | ||||
|      * @private | ||||
|      */ | ||||
|     private readonly newObjects: UIEventSource<{ id: number, lat: number, lon: number }[]> = LocalStorageSource.GetParsed("newObjects", []) | ||||
| 
 | ||||
|     /** | ||||
|      * Adds a change to the pending changes | ||||
|  | @ -82,8 +88,7 @@ export class Changes implements FeatureSource { | |||
|         if (flushreason !== undefined) { | ||||
|             console.log(flushreason) | ||||
|         } | ||||
|         this.uploadAll([], this.pending.data); | ||||
|         this.pending.setData([]); | ||||
|         this.uploadAll(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -93,12 +98,12 @@ export class Changes implements FeatureSource { | |||
|      */ | ||||
|     public createElement(basicTags: Tag[], lat: number, lon: number) { | ||||
|         console.log("Creating a new element with ", basicTags) | ||||
|         const osmNode = new OsmNode(Changes._nextId); | ||||
|         const newId = Changes._nextId; | ||||
|         Changes._nextId--; | ||||
| 
 | ||||
|         const id = "node/" + osmNode.id; | ||||
|         osmNode.lat = lat; | ||||
|         osmNode.lon = lon; | ||||
|         const id = "node/" + newId; | ||||
| 
 | ||||
| 
 | ||||
|         const properties = {id: id}; | ||||
| 
 | ||||
|         const geojson = { | ||||
|  | @ -135,22 +140,32 @@ export class Changes implements FeatureSource { | |||
|             properties["_backend"] = State.state.osmConnection.userDetails.data.backend | ||||
|         } | ||||
| 
 | ||||
|         // this.uploadAll([osmNode], changes);
 | ||||
| 
 | ||||
|         this.newObjects.data.push({id: newId, lat: lat, lon: lon}) | ||||
|         this.pending.data.push(...changes) | ||||
|         this.pending.ping(); | ||||
|         this.newObjects.ping(); | ||||
|         return geojson; | ||||
|     } | ||||
| 
 | ||||
|     private uploadChangesWithLatestVersions( | ||||
|         knownElements: OsmObject[], newElements: OsmObject[], pending: { elementId: string; key: string; value: string }[]) { | ||||
|         knownElements: OsmObject[]) { | ||||
|         const knownById = new Map<string, OsmObject>(); | ||||
| 
 | ||||
|         knownElements.forEach(knownElement => { | ||||
|             knownById.set(knownElement.type + "/" + knownElement.id, knownElement) | ||||
|         }) | ||||
| 
 | ||||
|         const newElements: OsmNode [] = this.newObjects.data.map(spec => { | ||||
|             const newElement = new OsmNode(spec.id); | ||||
|             newElement.lat = spec.lat; | ||||
|             newElement.lon = spec.lon; | ||||
|             return newElement | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|         // 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) { | ||||
|         for (const change of this.pending.data) { | ||||
|             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) { | ||||
|  | @ -217,17 +232,19 @@ export class Changes implements FeatureSource { | |||
|                 changes += "</osmChange>"; | ||||
| 
 | ||||
|                 return changes; | ||||
|             }, | ||||
|             () => { | ||||
|                 console.log("Upload successfull!") | ||||
|                 this.newObjects.setData([]) | ||||
|                 this.pending.setData([]); | ||||
|             }); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     private uploadAll( | ||||
|         newElements: OsmObject[], | ||||
|         pending: { elementId: string; key: string; value: string }[] | ||||
|     ) { | ||||
|     private uploadAll() { | ||||
|         const self = this; | ||||
| 
 | ||||
| 
 | ||||
|         const pending = this.pending.data; | ||||
|         let neededIds: string[] = []; | ||||
|         for (const change of pending) { | ||||
|             const id = change.elementId; | ||||
|  | @ -240,8 +257,7 @@ export class Changes implements FeatureSource { | |||
| 
 | ||||
|         neededIds = Utils.Dedup(neededIds); | ||||
|         OsmObject.DownloadAll(neededIds).addCallbackAndRunD(knownElements => { | ||||
|             console.log("KnownElements:", knownElements) | ||||
|             self.uploadChangesWithLatestVersions(knownElements, newElements, pending) | ||||
|             self.uploadChangesWithLatestVersions(knownElements) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export class ChangesetHandler { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) { | ||||
|     private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) : void{ | ||||
|         const nodes = response.getElementsByTagName("node"); | ||||
|         // @ts-ignore
 | ||||
|         for (const node of nodes) { | ||||
|  | @ -69,7 +69,8 @@ export class ChangesetHandler { | |||
|     public UploadChangeset( | ||||
|         layout: LayoutConfig, | ||||
|         allElements: ElementStorage, | ||||
|         generateChangeXML: (csid: string) => string) { | ||||
|         generateChangeXML: (csid: string) => string, | ||||
|         whenDone : (csId: string) => void) { | ||||
| 
 | ||||
|         if (this.userDetails.data.csCount == 0) { | ||||
|             // The user became a contributor!
 | ||||
|  | @ -80,6 +81,7 @@ export class ChangesetHandler { | |||
|         if (this._dryRun) { | ||||
|             const changesetXML = generateChangeXML("123456"); | ||||
|             console.log(changesetXML); | ||||
|             whenDone("123456") | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | @ -93,8 +95,7 @@ export class ChangesetHandler { | |||
|                 console.log(changeset); | ||||
|                 self.AddChange(csId, changeset, | ||||
|                     allElements, | ||||
|                     () => { | ||||
|                     }, | ||||
|                     whenDone, | ||||
|                     (e) => { | ||||
|                         console.error("UPLOADING FAILED!", e) | ||||
|                     } | ||||
|  | @ -107,14 +108,13 @@ export class ChangesetHandler { | |||
|                 csId, | ||||
|                 generateChangeXML(csId), | ||||
|                 allElements, | ||||
|                 () => { | ||||
|                 }, | ||||
|                 whenDone, | ||||
|                 (e) => { | ||||
|                     console.warn("Could not upload, changeset is probably closed: ", e); | ||||
|                     // Mark the CS as closed...
 | ||||
|                     this.currentChangeset.setData(""); | ||||
|                     // ... and try again. As the cs is closed, no recursive loop can exist  
 | ||||
|                     self.UploadChangeset(layout, allElements, generateChangeXML); | ||||
|                     self.UploadChangeset(layout, allElements, generateChangeXML, whenDone); | ||||
| 
 | ||||
|                 } | ||||
|             ) | ||||
|  | @ -244,7 +244,6 @@ export class ChangesetHandler { | |||
|         }, function (err, response) { | ||||
|             if (response === undefined) { | ||||
|                 console.log("err", err); | ||||
|                 alert("Could not upload change (opening failed). Please file a bug report") | ||||
|                 return; | ||||
|             } else { | ||||
|                 continuation(response); | ||||
|  | @ -265,7 +264,7 @@ export class ChangesetHandler { | |||
|     private AddChange(changesetId: string, | ||||
|                       changesetXML: string, | ||||
|                       allElements: ElementStorage, | ||||
|                       continuation: ((changesetId: string, idMapping: any) => void), | ||||
|                       continuation: ((changesetId: string) => void), | ||||
|                       onFail: ((changesetId: string, reason: string) => void) = undefined) { | ||||
|         this.auth.xhr({ | ||||
|             method: 'POST', | ||||
|  | @ -280,9 +279,9 @@ export class ChangesetHandler { | |||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|             const mapping = ChangesetHandler.parseUploadChangesetResponse(response, allElements); | ||||
|             ChangesetHandler.parseUploadChangesetResponse(response, allElements); | ||||
|             console.log("Uploaded changeset ", changesetId); | ||||
|             continuation(changesetId, mapping); | ||||
|             continuation(changesetId); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -110,8 +110,9 @@ export class OsmConnection { | |||
|     public UploadChangeset( | ||||
|         layout: LayoutConfig, | ||||
|         allElements: ElementStorage, | ||||
|         generateChangeXML: (csid: string) => string) { | ||||
|         this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML); | ||||
|         generateChangeXML: (csid: string) => string, | ||||
|         whenDone: (csId: string) => void) { | ||||
|         this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, whenDone); | ||||
|     } | ||||
| 
 | ||||
|     public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> { | ||||
|  |  | |||
|  | @ -5,7 +5,8 @@ import {UIEventSource} from "../UIEventSource"; | |||
| 
 | ||||
| export abstract class OsmObject { | ||||
| 
 | ||||
|     protected static backendURL = "https://www.openstreetmap.org/" | ||||
|     private static defaultBackend = "https://www.openstreetmap.org/" | ||||
|     protected static backendURL = OsmObject.defaultBackend; | ||||
|     private static polygonFeatures = OsmObject.constructPolygonFeatures() | ||||
|     private static objectCache = new Map<string, UIEventSource<OsmObject>>(); | ||||
|     private static referencingWaysCache = new Map<string, UIEventSource<OsmWay[]>>(); | ||||
|  | @ -37,15 +38,15 @@ export abstract class OsmObject { | |||
|     } | ||||
| 
 | ||||
|     static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource<OsmObject> { | ||||
|         let src : UIEventSource<OsmObject>; | ||||
|         let src: UIEventSource<OsmObject>; | ||||
|         if (OsmObject.objectCache.has(id)) { | ||||
|             src = OsmObject.objectCache.get(id) | ||||
|             if(forceRefresh){ | ||||
|             if (forceRefresh) { | ||||
|                 src.setData(undefined) | ||||
|             }else{ | ||||
|             } else { | ||||
|                 return src; | ||||
|             } | ||||
|         }else{ | ||||
|         } else { | ||||
|             src = new UIEventSource<OsmObject>(undefined) | ||||
|         } | ||||
|         const splitted = id.split("/"); | ||||
|  | @ -157,7 +158,7 @@ export abstract class OsmObject { | |||
|         const minlat = bounds[1][0] | ||||
|         const maxlat = bounds[0][0]; | ||||
|         const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}` | ||||
|         Utils.downloadJson(url).then( data => { | ||||
|         Utils.downloadJson(url).then(data => { | ||||
|             const elements: any[] = data.elements; | ||||
|             const objects = OsmObject.ParseObjects(elements) | ||||
|             callback(objects); | ||||
|  | @ -291,6 +292,7 @@ export abstract class OsmObject { | |||
| 
 | ||||
|                 self.LoadData(element) | ||||
|                 self.SaveExtraData(element, nodes); | ||||
| 
 | ||||
|                 const meta = { | ||||
|                     "_last_edit:contributor": element.user, | ||||
|                     "_last_edit:contributor:uid": element.uid, | ||||
|  | @ -299,6 +301,11 @@ export abstract class OsmObject { | |||
|                     "_version_number": element.version | ||||
|                 } | ||||
| 
 | ||||
|                 if (OsmObject.backendURL !== OsmObject.defaultBackend) { | ||||
|                     self.tags["_backend"] = OsmObject.backendURL | ||||
|                     meta["_backend"] = OsmObject.backendURL; | ||||
|                 } | ||||
| 
 | ||||
|                 continuation(self, meta); | ||||
|             } | ||||
|         ); | ||||
|  |  | |||
|  | @ -4,6 +4,22 @@ import {UIEventSource} from "../UIEventSource"; | |||
|  * UIEventsource-wrapper around localStorage | ||||
|  */ | ||||
| export class LocalStorageSource { | ||||
|      | ||||
|     static GetParsed(key: string, defaultValue : any){ | ||||
|         return LocalStorageSource.Get(key).map( | ||||
|             str => { | ||||
|                 if(str === undefined){ | ||||
|                     return defaultValue | ||||
|                 } | ||||
|                 try{ | ||||
|                     return JSON.parse(str) | ||||
|                 }catch{ | ||||
|                     return defaultValue | ||||
|                 } | ||||
|             }, [],  | ||||
|             value => JSON.stringify(value) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     static Get(key: string, defaultValue: string = undefined): UIEventSource<string> { | ||||
|         try { | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import { Utils } from "../Utils"; | |||
| 
 | ||||
| export default class Constants { | ||||
|      | ||||
|     public static vNumber = "0.8.3d"; | ||||
|     public static vNumber = "0.8.4"; | ||||
| 
 | ||||
|     // The user journey states thresholds when a new feature gets unlocked
 | ||||
|     public static userJourney = { | ||||
|  |  | |||
|  | @ -59,8 +59,12 @@ | |||
|     "render": "<a href='https://openstreetmap.org/{id}' target='_blank'><img src='./assets/svg/osm-logo-us.svg'/></a>", | ||||
|     "mappings": [ | ||||
|       { | ||||
|         "if": "id~=-", | ||||
|         "then": "<span class='alert'>Uploading...</alert>" | ||||
|         "if": "id~.*/-.*", | ||||
|         "then": "" | ||||
|       }, | ||||
|       { | ||||
|         "if": "_backend~*", | ||||
|         "then": "<a href='{_backend}/{id}' target='_blank'><img src='./assets/svg/osm-logo-us.svg'/></a>" | ||||
|       } | ||||
|     ], | ||||
|     "condition": "id~(node|way|relation)/[0-9]*" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue