Restructuring
This commit is contained in:
parent
1af27106f9
commit
5d5cf67820
27 changed files with 220 additions and 247 deletions
264
Logic/Osm/Changes.ts
Normal file
264
Logic/Osm/Changes.ts
Normal file
|
@ -0,0 +1,264 @@
|
|||
/**
|
||||
* Handles all changes made to OSM.
|
||||
* Needs an authenticator via OsmConnection
|
||||
*/
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {OsmConnection} from "./OsmConnection";
|
||||
import {OsmNode, OsmObject} from "./OsmObject";
|
||||
import {And, Tag, TagsFilter} from "../TagsFilter";
|
||||
import {ElementStorage} from "../ElementStorage";
|
||||
|
||||
export class Changes {
|
||||
|
||||
private static _nextId = -1; // New assined ID's are negative
|
||||
|
||||
public readonly login: OsmConnection;
|
||||
public readonly _allElements: ElementStorage;
|
||||
|
||||
private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll
|
||||
private newElements: OsmObject[] = []; // Gets reset on uploadAll
|
||||
|
||||
public readonly pendingChangesES = new UIEventSource<number>(this._pendingChanges.length);
|
||||
public readonly isSaving = new UIEventSource(false);
|
||||
private readonly _changesetComment: string;
|
||||
|
||||
constructor(
|
||||
changesetComment: string,
|
||||
login: OsmConnection,
|
||||
allElements: ElementStorage) {
|
||||
this._changesetComment = changesetComment;
|
||||
this.login = login;
|
||||
this._allElements = allElements;
|
||||
}
|
||||
|
||||
addTag(elementId: string, tagsFilter : TagsFilter){
|
||||
if(tagsFilter instanceof Tag){
|
||||
const tag = tagsFilter as Tag;
|
||||
this.addChange(elementId, tag.key, tag.value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(tagsFilter instanceof And){
|
||||
const and = tagsFilter as And;
|
||||
for (const tag of and.and) {
|
||||
this.addTag(elementId, tag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
console.log("Received change",key, value)
|
||||
if (key === undefined || key === null) {
|
||||
console.log("Invalid key");
|
||||
return;
|
||||
}
|
||||
if (value === undefined || value === null) {
|
||||
console.log("Invalid value for ",key);
|
||||
return;
|
||||
}
|
||||
|
||||
const eventSource = this._allElements.getElement(elementId);
|
||||
|
||||
eventSource.data[key] = value;
|
||||
eventSource.ping();
|
||||
// We get the id from the event source, as that ID might be rewritten
|
||||
this._pendingChanges.push({elementId: eventSource.data.id, key: key, value: value});
|
||||
this.pendingChangesES.setData(this._pendingChanges.length);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new node element at the given lat/long.
|
||||
* An internal OsmObject is created to upload later on, a geojson represention is returned.
|
||||
* Note that the geojson version shares the tags (properties) by pointer, but has _no_ id in properties
|
||||
*/
|
||||
createElement(basicTags:Tag[], lat: number, lon: number) {
|
||||
const osmNode = new OsmNode(Changes._nextId);
|
||||
this.newElements.push(osmNode);
|
||||
Changes._nextId--;
|
||||
|
||||
const id = "node/" + osmNode.id;
|
||||
osmNode.lat = lat;
|
||||
osmNode.lon = lon;
|
||||
const properties = {id: id};
|
||||
|
||||
const geojson = {
|
||||
"type": "Feature",
|
||||
"properties": properties,
|
||||
"id": id,
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
lon,
|
||||
lat
|
||||
]
|
||||
}
|
||||
}
|
||||
this._allElements.addOrGetElement(geojson);
|
||||
|
||||
// The basictags are COPIED, the id is included in the properties
|
||||
// The tags are not yet written into the OsmObject, but this is applied onto a
|
||||
for (const kv of basicTags) {
|
||||
this.addChange(id, kv.key, kv.value); // We use the call, to trigger all the other machinery (including updating the geojson itsel
|
||||
properties[kv.key] = kv.value;
|
||||
}
|
||||
|
||||
return geojson;
|
||||
}
|
||||
|
||||
public uploadAll(optionalContinuation: (() => void) = undefined) {
|
||||
const self = this;
|
||||
|
||||
this.isSaving.setData(true);
|
||||
const optionalContinuationWrapped = function () {
|
||||
self.isSaving.setData(false);
|
||||
if (optionalContinuation) {
|
||||
optionalContinuation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const pending: { elementId: string; key: string; value: string }[] = this._pendingChanges;
|
||||
this._pendingChanges = [];
|
||||
this.pendingChangesES.setData(this._pendingChanges.length);
|
||||
|
||||
const newElements = this.newElements;
|
||||
this.newElements = [];
|
||||
|
||||
|
||||
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 = [];
|
||||
for (const change of pending) {
|
||||
const id = change.elementId;
|
||||
if (parseFloat(id.split("/")[1]) < 0) {
|
||||
console.log("Detected a new element! Exciting!")
|
||||
} else {
|
||||
neededIds.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
console.log(knownElements, change.elementId);
|
||||
knownElements[change.elementId].addTag(change.key, change.value);
|
||||
// note: addTag will flag changes with 'element.changed' internally
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
const handleMapping = function (idMapping) {
|
||||
for (const oldId in idMapping) {
|
||||
const newId = idMapping[oldId];
|
||||
|
||||
const element = self._allElements.getElement(oldId);
|
||||
element.data.id = newId;
|
||||
self._allElements.addElementById(newId, element);
|
||||
element.ping();
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Beginning upload...");
|
||||
// At last, we build the changeset and upload
|
||||
self.login.UploadChangeset(self._changesetComment,
|
||||
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 0.0.1'>";
|
||||
|
||||
if (creations.length > 0) {
|
||||
changes +=
|
||||
"<create>" +
|
||||
creations +
|
||||
"</create>";
|
||||
}
|
||||
|
||||
if (modifications.length > 0) {
|
||||
|
||||
changes +=
|
||||
"<modify>" +
|
||||
modifications +
|
||||
"</modify>";
|
||||
}
|
||||
|
||||
changes += "</osmChange>";
|
||||
|
||||
return changes;
|
||||
},
|
||||
handleMapping,
|
||||
optionalContinuationWrapped);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
25
Logic/Osm/Geocoding.ts
Normal file
25
Logic/Osm/Geocoding.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import {Basemap} from "../Leaflet/Basemap";
|
||||
import $ from "jquery"
|
||||
export class Geocoding {
|
||||
|
||||
private static readonly host = "https://nominatim.openstreetmap.org/search?";
|
||||
|
||||
static Search(query: string,
|
||||
basemap: Basemap,
|
||||
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
|
||||
onFail: (() => void)) {
|
||||
const b = basemap.map.getBounds();
|
||||
console.log(b);
|
||||
$.getJSON(
|
||||
Geocoding.host + "format=json&limit=1&viewbox=" +
|
||||
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}`+
|
||||
"&accept-language=nl&q=" + query,
|
||||
function (data) {
|
||||
handleResult(data);
|
||||
}).fail(() => {
|
||||
onFail();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
9
Logic/Osm/Notes.ts
Normal file
9
Logic/Osm/Notes.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import {Bounds} from "../Bounds";
|
||||
|
||||
export class Notes {
|
||||
|
||||
|
||||
queryGeoJson(bounds: Bounds, continuation: ((any) => void), onFail: ((reason) => void)): void {
|
||||
|
||||
}
|
||||
}
|
324
Logic/Osm/OsmConnection.ts
Normal file
324
Logic/Osm/OsmConnection.ts
Normal file
|
@ -0,0 +1,324 @@
|
|||
// @ts-ignore
|
||||
import osmAuth from "osm-auth";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
|
||||
export class UserDetails {
|
||||
|
||||
public loggedIn = false;
|
||||
public name = "Not logged in";
|
||||
public csCount = 0;
|
||||
public img: string;
|
||||
public unreadMessages = 0;
|
||||
public totalMessages = 0;
|
||||
public osmConnection: OsmConnection;
|
||||
public dryRun: boolean;
|
||||
home: { lon: number; lat: number };
|
||||
}
|
||||
|
||||
export class OsmConnection {
|
||||
|
||||
public auth;
|
||||
public userDetails: UIEventSource<UserDetails>;
|
||||
private _dryRun: boolean;
|
||||
|
||||
constructor(dryRun: boolean, oauth_token: UIEventSource<string>) {
|
||||
|
||||
this.auth = new osmAuth({
|
||||
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
|
||||
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
|
||||
singlepage: true,
|
||||
landing: window.location.href,
|
||||
auto: true // show a login form if the user is not authenticated and
|
||||
// you try to do a call
|
||||
|
||||
});
|
||||
|
||||
|
||||
this.userDetails = new UIEventSource<UserDetails>(new UserDetails());
|
||||
this.userDetails.data.osmConnection = this;
|
||||
this.userDetails.data.dryRun = dryRun;
|
||||
this._dryRun = dryRun;
|
||||
|
||||
|
||||
if(oauth_token.data !== undefined){
|
||||
console.log(oauth_token.data)
|
||||
const self = this;
|
||||
this.auth.bootstrapToken(oauth_token.data,
|
||||
(x) => {
|
||||
console.log("Called back: ", x)
|
||||
self.AttemptLogin();
|
||||
}, this.auth);
|
||||
|
||||
oauth_token.setData(undefined);
|
||||
|
||||
}
|
||||
if (this.auth.authenticated()) {
|
||||
this.AttemptLogin(); // Also updates the user badge
|
||||
} else {
|
||||
console.log("Not authenticated");
|
||||
}
|
||||
|
||||
|
||||
if (dryRun) {
|
||||
console.log("DRYRUN ENABLED");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public LogOut() {
|
||||
this.auth.logout();
|
||||
this.userDetails.data.loggedIn = false;
|
||||
this.userDetails.ping();
|
||||
console.log("Logged out")
|
||||
}
|
||||
|
||||
public AttemptLogin() {
|
||||
const self = this;
|
||||
this.auth.xhr({
|
||||
method: 'GET',
|
||||
path: '/api/0.6/user/details'
|
||||
}, function (err, details) {
|
||||
if(err != null){
|
||||
console.log(err);
|
||||
self.auth.logout();
|
||||
self.userDetails.data.loggedIn = false;
|
||||
self.userDetails.ping();
|
||||
}
|
||||
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.UpdatePreferences();
|
||||
// details is an XML DOM of user details
|
||||
let userInfo = details.getElementsByTagName("user")[0];
|
||||
|
||||
// let moreDetails = new DOMParser().parseFromString(userInfo.innerHTML, "text/xml");
|
||||
|
||||
let data = self.userDetails.data;
|
||||
data.loggedIn = true;
|
||||
console.log("Login completed, userinfo is ", userInfo);
|
||||
data.name = userInfo.getAttribute('display_name');
|
||||
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
|
||||
|
||||
data.img = undefined;
|
||||
const imgEl = userInfo.getElementsByTagName("img");
|
||||
if (imgEl !== undefined && imgEl[0] !== undefined) {
|
||||
data.img = imgEl[0].getAttribute("href");
|
||||
}
|
||||
data.img = data.img ?? "./assets/osm-logo.svg";
|
||||
|
||||
const homeEl = userInfo.getElementsByTagName("home");
|
||||
if (homeEl !== undefined && homeEl[0] !== undefined) {
|
||||
const lat = parseFloat(homeEl[0].getAttribute("lat"));
|
||||
const lon = parseFloat(homeEl[0].getAttribute("lon"));
|
||||
data.home = {lat: lat, lon: lon};
|
||||
}
|
||||
|
||||
const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0];
|
||||
data.unreadMessages = parseInt(messages.getAttribute("unread"));
|
||||
data.totalMessages = parseInt(messages.getAttribute("count"));
|
||||
self.userDetails.ping();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* All elements with class 'activate-osm-authentication' are loaded and get an 'onclick' to authenticate
|
||||
*/
|
||||
registerActivateOsmAUthenticationClass() {
|
||||
|
||||
const self = this;
|
||||
const authElements = document.getElementsByClassName("activate-osm-authentication");
|
||||
for (let i = 0; i < authElements.length; i++) {
|
||||
let element = authElements.item(i);
|
||||
// @ts-ignore
|
||||
element.onclick = function () {
|
||||
self.AttemptLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public preferences = new UIEventSource<any>({});
|
||||
public preferenceSources : any = {}
|
||||
|
||||
public GetPreference(key: string) : UIEventSource<string>{
|
||||
key = "mapcomplete-"+key;
|
||||
if (this.preferenceSources[key] !== undefined) {
|
||||
return this.preferenceSources[key];
|
||||
}
|
||||
if (this.userDetails.data.loggedIn) {
|
||||
this.UpdatePreferences();
|
||||
}
|
||||
const pref = new UIEventSource<string>(this.preferences.data[key]);
|
||||
pref.addCallback((v) => {
|
||||
this.SetPreference(key, v);
|
||||
});
|
||||
|
||||
this.preferences.addCallback((prefs) => {
|
||||
if (prefs[key] !== undefined) {
|
||||
pref.setData(prefs[key]);
|
||||
}
|
||||
});
|
||||
|
||||
this.preferenceSources[key] = pref;
|
||||
return pref;
|
||||
}
|
||||
|
||||
private UpdatePreferences() {
|
||||
const self = this;
|
||||
this.auth.xhr({
|
||||
method: 'GET',
|
||||
path: '/api/0.6/user/preferences'
|
||||
}, function (error, value: XMLDocument) {
|
||||
if(error){
|
||||
console.log("Could not load preferences", error);
|
||||
return;
|
||||
}
|
||||
const prefs = value.getElementsByTagName("preference");
|
||||
for (let i = 0; i < prefs.length; i++) {
|
||||
const pref = prefs[i];
|
||||
const k = pref.getAttribute("k");
|
||||
const v = pref.getAttribute("v");
|
||||
self.preferences.data[k] = v;
|
||||
}
|
||||
self.preferences.ping();
|
||||
});
|
||||
}
|
||||
|
||||
private SetPreference(k:string, v:string) {
|
||||
if(!this.userDetails.data.loggedIn){
|
||||
console.log("Not saving preference: user not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.preferences.data[k] === v) {
|
||||
console.log("Not updating preference", k, " to ", v, "not changed");
|
||||
return;
|
||||
}
|
||||
console.log("Updating preference", k, " to ", v);
|
||||
|
||||
this.preferences.data[k] = v;
|
||||
this.preferences.ping();
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/user/preferences/' + k,
|
||||
options: {header: {'Content-Type': 'text/plain'}},
|
||||
content: v
|
||||
}, function (error, result) {
|
||||
if (error) {
|
||||
console.log("Could not set preference", error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Preference written!", result == "" ? "OK" : result);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private static parseUploadChangesetResponse(response: XMLDocument) {
|
||||
const nodes = response.getElementsByTagName("node");
|
||||
const mapping = {};
|
||||
// @ts-ignore
|
||||
for (const node of nodes) {
|
||||
const oldId = parseInt(node.attributes.old_id.value);
|
||||
const newId = parseInt(node.attributes.new_id.value);
|
||||
if (oldId !== undefined && newId !== undefined &&
|
||||
!isNaN(oldId) && !isNaN(newId)) {
|
||||
mapping["node/"+oldId] = "node/"+newId;
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
|
||||
public UploadChangeset(comment: string, generateChangeXML: ((csid: string) => string),
|
||||
handleMapping: ((idMapping: any) => void),
|
||||
continuation: (() => void)) {
|
||||
|
||||
if (this._dryRun) {
|
||||
console.log("NOT UPLOADING as dryrun is true");
|
||||
var changesetXML = generateChangeXML("123456");
|
||||
console.log(changesetXML);
|
||||
continuation();
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this.OpenChangeset(comment,
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
private OpenChangeset(comment: string, continuation: ((changesetId: string) => void)) {
|
||||
|
||||
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/create',
|
||||
options: { header: { 'Content-Type': 'text/xml' } },
|
||||
content: '<osm><changeset>' +
|
||||
'<tag k="created_by" v="MapComplete 0.0.0" />' +
|
||||
'<tag k="comment" v="' + comment + '"/>' +
|
||||
'</changeset></osm>'
|
||||
}, function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.log("err", err);
|
||||
return;
|
||||
} else {
|
||||
continuation(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private AddChange(changesetId: string,
|
||||
changesetXML: string,
|
||||
continuation: ((changesetId: string, idMapping: any) => void)){
|
||||
this.auth.xhr({
|
||||
method: 'POST',
|
||||
options: { header: { 'Content-Type': 'text/xml' } },
|
||||
path: '/api/0.6/changeset/'+changesetId+'/upload',
|
||||
content: changesetXML
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
console.log("err", err);
|
||||
return;
|
||||
}
|
||||
const mapping = OsmConnection.parseUploadChangesetResponse(response);
|
||||
console.log("Uplaoded changeset ", changesetId);
|
||||
continuation(changesetId, mapping);
|
||||
});
|
||||
}
|
||||
|
||||
private CloseChangeset(changesetId: string, continuation : (() => void)) {
|
||||
console.log("closing");
|
||||
this.auth.xhr({
|
||||
method: 'PUT',
|
||||
path: '/api/0.6/changeset/'+changesetId+'/close',
|
||||
}, function (err, response) {
|
||||
if (response == null) {
|
||||
|
||||
console.log("err", err);
|
||||
}
|
||||
console.log("Closed changeset ", changesetId);
|
||||
|
||||
if(continuation !== undefined){
|
||||
continuation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
85
Logic/Osm/OsmImageUploadHandler.ts
Normal file
85
Logic/Osm/OsmImageUploadHandler.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Helps in uplaoding, by generating the rigth title, decription and by adding the tag to the changeset
|
||||
*/
|
||||
import {Changes} from "./Changes";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
||||
import {UserDetails} from "./OsmConnection";
|
||||
import {SlideShow} from "../../UI/SlideShow";
|
||||
|
||||
export class OsmImageUploadHandler {
|
||||
private _tags: UIEventSource<any>;
|
||||
private _changeHandler: Changes;
|
||||
private _userdetails: UIEventSource<UserDetails>;
|
||||
private _slideShow: SlideShow;
|
||||
private _preferedLicense: UIEventSource<string>;
|
||||
|
||||
constructor(tags: UIEventSource<any>,
|
||||
userdetails: UIEventSource<UserDetails>,
|
||||
preferedLicense: UIEventSource<string>,
|
||||
changeHandler: Changes,
|
||||
slideShow : SlideShow
|
||||
) {
|
||||
this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element
|
||||
if (tags === undefined || userdetails === undefined || changeHandler === undefined) {
|
||||
throw "Something is undefined"
|
||||
}
|
||||
this._tags = tags;
|
||||
this._changeHandler = changeHandler;
|
||||
this._userdetails = userdetails;
|
||||
this._preferedLicense = preferedLicense;
|
||||
}
|
||||
|
||||
private generateOptions(license: string) {
|
||||
const tags = this._tags.data;
|
||||
const self = this;
|
||||
|
||||
const title = tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + this._userdetails.data.name,
|
||||
"license:" + license,
|
||||
"wikidata:" + tags.wikidata,
|
||||
"osmid:" + tags.id,
|
||||
"name:" + tags.name
|
||||
].join("\n");
|
||||
|
||||
const changes = this._changeHandler;
|
||||
return {
|
||||
title: title,
|
||||
description: description,
|
||||
handleURL: function (url) {
|
||||
|
||||
let key = "image";
|
||||
if (tags["image"] !== undefined) {
|
||||
|
||||
let freeIndex = 0;
|
||||
while (tags["image:" + freeIndex] !== undefined) {
|
||||
freeIndex++;
|
||||
}
|
||||
key = "image:" + freeIndex;
|
||||
}
|
||||
console.log("Adding image:" + key, url);
|
||||
changes.addChange(tags.id, key, url);
|
||||
self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view
|
||||
},
|
||||
allDone: function () {
|
||||
changes.uploadAll(function () {
|
||||
console.log("Writing changes...")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getUI(): ImageUploadFlow {
|
||||
const self = this;
|
||||
return new ImageUploadFlow(
|
||||
this._userdetails,
|
||||
this._preferedLicense,
|
||||
function (license) {
|
||||
return self.generateOptions(license)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
185
Logic/Osm/OsmObject.ts
Normal file
185
Logic/Osm/OsmObject.ts
Normal file
|
@ -0,0 +1,185 @@
|
|||
import * as $ from "jquery"
|
||||
|
||||
|
||||
export abstract class OsmObject {
|
||||
|
||||
type: string;
|
||||
id: number;
|
||||
tags: {} = {};
|
||||
version: number;
|
||||
public changed: boolean = false;
|
||||
|
||||
protected constructor(type: string, id: number) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
static DownloadObject(id, continuation: ((element: OsmObject) => void)) {
|
||||
const splitted = id.split("/");
|
||||
const type = splitted[0];
|
||||
const idN = splitted[1];
|
||||
switch (type) {
|
||||
case("node"):
|
||||
return new OsmNode(idN).Download(continuation);
|
||||
case("way"):
|
||||
return new OsmWay(idN).Download(continuation);
|
||||
case("relation"):
|
||||
return new OsmRelation(idN).Download(continuation);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract SaveExtraData(element);
|
||||
|
||||
/**
|
||||
* Replaces all '"' (double quotes) by '"'
|
||||
* Bugfix where names containing '"' were not uploaded, such as '"Het Zwin" nature reserve'
|
||||
* @param string
|
||||
* @constructor
|
||||
*/
|
||||
private Escape(string: string) {
|
||||
while (string.indexOf('"') >= 0) {
|
||||
string = string.replace('"', '"');
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the changeset-XML for tags
|
||||
* @constructor
|
||||
*/
|
||||
TagsXML(): string {
|
||||
let tags = "";
|
||||
for (const key in this.tags) {
|
||||
const v = this.tags[key];
|
||||
if (v !== "") {
|
||||
tags += ' <tag k="' + this.Escape(key) + '" v="' + this.Escape(this.tags[key]) + '"/>\n'
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
Download(continuation: ((element: OsmObject) => void)) {
|
||||
const self = this;
|
||||
$.getJSON("https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id,
|
||||
function (data) {
|
||||
const element = data.elements[0];
|
||||
self.tags = element.tags;
|
||||
self.version = element.version;
|
||||
self.SaveExtraData(element);
|
||||
continuation(self);
|
||||
}
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addTag(k: string, v: string): void {
|
||||
if (k in this.tags) {
|
||||
const oldV = this.tags[k];
|
||||
if (oldV == v) {
|
||||
return;
|
||||
}
|
||||
console.log("WARNING: overwriting ",oldV, " with ", v," for key ",k)
|
||||
}
|
||||
this.tags[k] = v;
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
protected VersionXML(){
|
||||
if(this.version === undefined){
|
||||
return "";
|
||||
}
|
||||
return 'version="'+this.version+'"';
|
||||
}
|
||||
abstract ChangesetXML(changesetId: string): string;
|
||||
}
|
||||
|
||||
|
||||
export class OsmNode extends OsmObject {
|
||||
|
||||
lat: number;
|
||||
lon: number;
|
||||
|
||||
constructor(id) {
|
||||
super("node", id);
|
||||
|
||||
}
|
||||
|
||||
ChangesetXML(changesetId: string): string {
|
||||
let tags = this.TagsXML();
|
||||
|
||||
let change =
|
||||
' <node id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + ' lat="' + this.lat + '" lon="' + this.lon + '">\n' +
|
||||
tags +
|
||||
' </node>\n';
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
SaveExtraData(element) {
|
||||
this.lat = element.lat;
|
||||
this.lon = element.lon;
|
||||
}
|
||||
}
|
||||
|
||||
export class OsmWay extends OsmObject {
|
||||
|
||||
nodes: number[];
|
||||
|
||||
constructor(id) {
|
||||
super("way", id);
|
||||
|
||||
}
|
||||
|
||||
ChangesetXML(changesetId: string): string {
|
||||
let tags = this.TagsXML();
|
||||
let nds = "";
|
||||
for (const node in this.nodes) {
|
||||
nds += ' <nd ref="' + this.nodes[node] + '"/>\n';
|
||||
}
|
||||
|
||||
let change =
|
||||
' <way id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
||||
nds +
|
||||
tags +
|
||||
' </way>\n';
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
SaveExtraData(element) {
|
||||
this.nodes = element.nodes;
|
||||
}
|
||||
}
|
||||
|
||||
export class OsmRelation extends OsmObject {
|
||||
|
||||
members;
|
||||
|
||||
constructor(id) {
|
||||
super("relation", id);
|
||||
|
||||
}
|
||||
|
||||
ChangesetXML(changesetId: string): string {
|
||||
let members = "";
|
||||
for (const memberI in this.members) {
|
||||
const member = this.members[memberI];
|
||||
members += ' <member type="' + member.type + '" ref="' + member.ref + '" role="' + member.role + '"/>\n';
|
||||
}
|
||||
|
||||
let tags = this.TagsXML();
|
||||
let change =
|
||||
' <relation id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
||||
members +
|
||||
tags +
|
||||
' </relation>\n';
|
||||
return change;
|
||||
|
||||
}
|
||||
|
||||
SaveExtraData(element) {
|
||||
this.members = element.members;
|
||||
}
|
||||
}
|
54
Logic/Osm/Overpass.ts
Normal file
54
Logic/Osm/Overpass.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Interfaces overpass to get all the latest data
|
||||
*/
|
||||
import {Bounds} from "../Bounds";
|
||||
import {TagsFilter} from "../TagsFilter";
|
||||
import $ from "jquery"
|
||||
import * as OsmToGeoJson from "osmtogeojson";
|
||||
|
||||
export class Overpass {
|
||||
private _filter: TagsFilter
|
||||
public static testUrl: string = null
|
||||
|
||||
constructor(filter: TagsFilter) {
|
||||
this._filter = filter
|
||||
}
|
||||
|
||||
|
||||
private buildQuery(bbox: string): string {
|
||||
const filters = this._filter.asOverpass()
|
||||
let filter = ""
|
||||
for (const filterOr of filters) {
|
||||
filter += 'nwr' + filterOr + ';'
|
||||
}
|
||||
const query =
|
||||
'[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;'
|
||||
return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query)
|
||||
}
|
||||
|
||||
queryGeoJson(bounds: Bounds, continuation: ((any) => void), onFail: ((reason) => void)): void {
|
||||
|
||||
let query = this.buildQuery( "[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
|
||||
|
||||
if(Overpass.testUrl !== null){
|
||||
console.log("Using testing URL")
|
||||
query = Overpass.testUrl;
|
||||
}
|
||||
|
||||
$.getJSON(query,
|
||||
function (json, status) {
|
||||
if (status !== "success") {
|
||||
console.log("Query failed")
|
||||
onFail(status);
|
||||
}
|
||||
|
||||
if(json.elements === [] && json.remarks.indexOf("runtime error") > 0){
|
||||
console.log("Timeout or other runtime error");
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
const geojson = OsmToGeoJson.default(json);
|
||||
continuation(geojson);
|
||||
}).fail(onFail)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue