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