Finish conflate script
This commit is contained in:
parent
dbf47ec121
commit
d4cef78325
3 changed files with 88 additions and 40 deletions
|
@ -1,10 +1,10 @@
|
||||||
import { Utils } from "../../Utils"
|
import {Utils} from "../../Utils"
|
||||||
import polygon_features from "../../assets/polygon-features.json"
|
import polygon_features from "../../assets/polygon-features.json"
|
||||||
import { Store, UIEventSource } from "../UIEventSource"
|
import {Store, UIEventSource} from "../UIEventSource"
|
||||||
import { BBox } from "../BBox"
|
import {BBox} from "../BBox"
|
||||||
import OsmToGeoJson from "osmtogeojson"
|
import OsmToGeoJson from "osmtogeojson"
|
||||||
import { NodeId, OsmFeature, OsmId, OsmTags, RelationId, WayId } from "../../Models/OsmFeature"
|
import {NodeId, OsmFeature, OsmId, OsmTags, RelationId, WayId} from "../../Models/OsmFeature"
|
||||||
import { Feature, LineString, Polygon } from "geojson"
|
import {Feature, LineString, Polygon} from "geojson"
|
||||||
|
|
||||||
export abstract class OsmObject {
|
export abstract class OsmObject {
|
||||||
private static defaultBackend = "https://www.openstreetmap.org/"
|
private static defaultBackend = "https://www.openstreetmap.org/"
|
||||||
|
@ -346,8 +346,7 @@ export abstract class OsmObject {
|
||||||
}
|
}
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
abstract ChangesetXML(changesetId: string, header?: string): string
|
||||||
abstract ChangesetXML(changesetId: string): string
|
|
||||||
|
|
||||||
protected VersionXML() {
|
protected VersionXML() {
|
||||||
if (this.version === undefined) {
|
if (this.version === undefined) {
|
||||||
|
@ -382,15 +381,15 @@ export class OsmNode extends OsmObject {
|
||||||
super("node", id)
|
super("node", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string, header?: string): string {
|
||||||
let tags = this.TagsXML()
|
let tags = this.TagsXML()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
' <node id="' +
|
' <node id="' +
|
||||||
this.id +
|
this.id +
|
||||||
'" changeset="' +
|
|
||||||
changesetId +
|
|
||||||
'" ' +
|
'" ' +
|
||||||
|
(header ?? "") +
|
||||||
|
(changesetId ? ('" changeset="' + changesetId) : "" ) +
|
||||||
this.VersionXML() +
|
this.VersionXML() +
|
||||||
' lat="' +
|
' lat="' +
|
||||||
this.lat +
|
this.lat +
|
||||||
|
@ -438,7 +437,7 @@ export class OsmWay extends OsmObject {
|
||||||
return [this.lat, this.lon]
|
return [this.lat, this.lon]
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string, header?: string): string {
|
||||||
let tags = this.TagsXML()
|
let tags = this.TagsXML()
|
||||||
let nds = ""
|
let nds = ""
|
||||||
for (const node in this.nodes) {
|
for (const node in this.nodes) {
|
||||||
|
@ -448,8 +447,8 @@ export class OsmWay extends OsmObject {
|
||||||
return (
|
return (
|
||||||
' <way id="' +
|
' <way id="' +
|
||||||
this.id +
|
this.id +
|
||||||
'" changeset="' +
|
(header ?? "")+
|
||||||
changesetId +
|
(changesetId ? ('" changeset="' + changesetId) : "" ) +
|
||||||
'" ' +
|
'" ' +
|
||||||
this.VersionXML() +
|
this.VersionXML() +
|
||||||
">\n" +
|
">\n" +
|
||||||
|
@ -542,7 +541,7 @@ export class OsmRelation extends OsmObject {
|
||||||
return [0, 0] // TODO
|
return [0, 0] // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string, header?: string): string {
|
||||||
let members = ""
|
let members = ""
|
||||||
for (const member of this.members) {
|
for (const member of this.members) {
|
||||||
members +=
|
members +=
|
||||||
|
@ -560,7 +559,7 @@ export class OsmRelation extends OsmObject {
|
||||||
if (changesetId !== undefined) {
|
if (changesetId !== undefined) {
|
||||||
cs = `changeset="${changesetId}"`
|
cs = `changeset="${changesetId}"`
|
||||||
}
|
}
|
||||||
return ` <relation id="${this.id}" ${cs} ${this.VersionXML()}>
|
return ` <relation id="${this.id}" ${header ?? ""} ${cs} ${this.VersionXML()}>
|
||||||
${members}${tags} </relation>
|
${members}${tags} </relation>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,8 @@ export default abstract class Script {
|
||||||
args.splice(0, 2)
|
args.splice(0, 2)
|
||||||
this.main(args).then((_) => console.log("All done"))
|
this.main(args).then((_) => console.log("All done"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public printHelp(){
|
||||||
|
console.log(this._docs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import Script from "./Script"
|
import Script from "./Script"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { Feature } from "geojson"
|
import {Feature} from "geojson"
|
||||||
import { GeoOperations } from "../Logic/GeoOperations"
|
import {GeoOperations} from "../Logic/GeoOperations"
|
||||||
import { Utils } from "../Utils"
|
import {Utils} from "../Utils"
|
||||||
import { OsmObject } from "../Logic/Osm/OsmObject"
|
import {OsmObject} from "../Logic/Osm/OsmObject"
|
||||||
import { PhoneTextField, UrlTextfieldDef } from "../UI/Input/ValidatedTextField"
|
import {PhoneTextField, UrlTextfieldDef} from "../UI/Input/ValidatedTextField"
|
||||||
import { OsmId } from "../Models/OsmFeature"
|
import {OsmId} from "../Models/OsmFeature"
|
||||||
import ScriptUtils from "./ScriptUtils"
|
import ScriptUtils from "./ScriptUtils"
|
||||||
|
|
||||||
interface PossibleMatch {
|
interface PossibleMatch {
|
||||||
|
@ -45,9 +45,25 @@ export class Conflate extends Script {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static toXml(changedObjects: OsmObject[]): string {
|
||||||
|
|
||||||
|
return [
|
||||||
|
"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
|
"<osm version=\"0.6\" generator='mapcomplete-conflate-script'>",
|
||||||
|
...changedObjects.map(obj =>
|
||||||
|
obj.ChangesetXML(undefined, ' action="modify" ')
|
||||||
|
),
|
||||||
|
"</osm>"
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
async main(args: string[]): Promise<void> {
|
async main(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
super.printHelp()
|
||||||
|
return
|
||||||
|
}
|
||||||
const [osm_file_path, external_file_path] = args
|
const [osm_file_path, external_file_path] = args
|
||||||
let max_range = 50
|
let max_range = 25
|
||||||
if (args.length === 3) {
|
if (args.length === 3) {
|
||||||
max_range = Number(args[2])
|
max_range = Number(args[2])
|
||||||
}
|
}
|
||||||
|
@ -66,10 +82,10 @@ export class Conflate extends Script {
|
||||||
}
|
}
|
||||||
|
|
||||||
const external_features: Feature[] = JSON.parse(
|
const external_features: Feature[] = JSON.parse(
|
||||||
fs.readFileSync(external_file_path, { encoding: "utf-8" })
|
fs.readFileSync(external_file_path, {encoding: "utf-8"})
|
||||||
).features
|
).features
|
||||||
const osm_features: Feature[] = JSON.parse(
|
const osm_features: Feature[] = JSON.parse(
|
||||||
fs.readFileSync(osm_file_path, { encoding: "utf-8" })
|
fs.readFileSync(osm_file_path, {encoding: "utf-8"})
|
||||||
).features
|
).features
|
||||||
|
|
||||||
const bestMatches = await this.calculateMatches(external_features, osm_features, max_range)
|
const bestMatches = await this.calculateMatches(external_features, osm_features, max_range)
|
||||||
|
@ -86,31 +102,48 @@ export class Conflate extends Script {
|
||||||
"...properties_differences",
|
"...properties_differences",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
for (const { match, replayed } of bestMatches) {
|
|
||||||
const { external_feature, d, osm_feature } = match
|
const changedObjects: OsmObject[] = []
|
||||||
const { possibly_imported, certainly_imported, resting_properties } = replayed
|
for (const {match, replayed} of bestMatches) {
|
||||||
|
const {external_feature, d, osm_feature} = match
|
||||||
|
const {possibly_imported, certainly_imported, resting_properties} = replayed
|
||||||
const status = resting_properties["status"]
|
const status = resting_properties["status"]
|
||||||
delete resting_properties["status"]
|
delete resting_properties["status"]
|
||||||
if (Object.keys(resting_properties).length === 0) {
|
if (Object.keys(resting_properties).length === 0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const id = osm_feature.properties["@id"]
|
||||||
match_lengths.push([
|
match_lengths.push([
|
||||||
osm_feature.properties["@id"],
|
id,
|
||||||
d,
|
d,
|
||||||
osm_feature.properties.name,
|
osm_feature.properties.name,
|
||||||
certainly_imported ? "import" : possibly_imported ? "prob import" : "new",
|
certainly_imported ? "import" : possibly_imported ? "prob import" : "new",
|
||||||
status,
|
status,
|
||||||
JSON.stringify(resting_properties),
|
JSON.stringify(resting_properties),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const osmObj = await OsmObject.DownloadObjectAsync(id)
|
||||||
|
for (const key in resting_properties) {
|
||||||
|
osmObj.tags[key] = resting_properties[key]
|
||||||
|
}
|
||||||
|
changedObjects.push(osmObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetDir = "../onwheels-data-prep/output"
|
||||||
|
console.log("Writing results to directory", targetDir)
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
"../onwheels-data-prep/matches.tsv",
|
targetDir + "/matches.tsv",
|
||||||
match_lengths.map((l) => l.join("\t")).join("\n")
|
match_lengths.map((l) => l.join("\t")).join("\n")
|
||||||
)
|
)
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(targetDir + "/changeset.xml",
|
||||||
"../onwheels-data-prep/unmatched.geojson",
|
Conflate.toXml(changedObjects)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fs.writeFileSync(targetDir +
|
||||||
|
"/unmatched.geojson",
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
|
@ -158,7 +191,7 @@ export class Conflate extends Script {
|
||||||
this.latestDate = latest
|
this.latestDate = latest
|
||||||
}
|
}
|
||||||
|
|
||||||
return { earliestDateOfImport: earliest, latestDateOfImport: latest }
|
return {earliestDateOfImport: earliest, latestDateOfImport: latest}
|
||||||
}
|
}
|
||||||
|
|
||||||
private findPossibleMatchesFor(
|
private findPossibleMatchesFor(
|
||||||
|
@ -189,10 +222,12 @@ export class Conflate extends Script {
|
||||||
if (url.indexOf("facebook.com") > 0) {
|
if (url.indexOf("facebook.com") > 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if (!fs.existsSync(this.historyCacheDir + "urls/")) {
|
||||||
|
fs.mkdirSync(this.historyCacheDir + "urls/")
|
||||||
|
}
|
||||||
const cachePath = this.historyCacheDir + "/urls/ " + url.replace(/[/\\:]/g, "_")
|
const cachePath = this.historyCacheDir + "/urls/ " + url.replace(/[/\\:]/g, "_")
|
||||||
if (fs.existsSync(cachePath)) {
|
if (fs.existsSync(cachePath)) {
|
||||||
const online = JSON.parse(fs.readFileSync(cachePath, { encoding: "utf-8" }))
|
return JSON.parse(fs.readFileSync(cachePath, {encoding: "utf-8"}))
|
||||||
return online
|
|
||||||
}
|
}
|
||||||
let online: boolean | string = false
|
let online: boolean | string = false
|
||||||
try {
|
try {
|
||||||
|
@ -204,7 +239,7 @@ export class Conflate extends Script {
|
||||||
console.log("Maybe trying the homepage will help?")
|
console.log("Maybe trying the homepage will help?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fs.writeFileSync(cachePath, JSON.stringify(online, null, " "), { encoding: "utf-8" })
|
fs.writeFileSync(cachePath, JSON.stringify(online, null, " "), {encoding: "utf-8"})
|
||||||
return online
|
return online
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +249,9 @@ export class Conflate extends Script {
|
||||||
}
|
}
|
||||||
url = url.replace("http://", "https://")
|
url = url.replace("http://", "https://")
|
||||||
try {
|
try {
|
||||||
const result = await ScriptUtils.Download(url)
|
const result = await ScriptUtils.Download(url, {
|
||||||
|
"User-agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0"
|
||||||
|
})
|
||||||
if (result["redirect"]) {
|
if (result["redirect"]) {
|
||||||
if (result["redirect"].startsWith("/")) {
|
if (result["redirect"].startsWith("/")) {
|
||||||
return true
|
return true
|
||||||
|
@ -225,6 +262,7 @@ export class Conflate extends Script {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
console.error("Got a result, but no content?", url, result)
|
console.error("Got a result, but no content?", url, result)
|
||||||
|
return false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Offline (error):", url, e.message)
|
console.log("Offline (error):", url, e.message)
|
||||||
return false
|
return false
|
||||||
|
@ -232,12 +270,15 @@ export class Conflate extends Script {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async historyCached(id): Promise<OsmObject[]> {
|
private async historyCached(id): Promise<OsmObject[]> {
|
||||||
const cachePath = this.historyCacheDir + "/" + id.replace("/", "_")
|
const cachePath = this.historyCacheDir + id.replace("/", "_")
|
||||||
|
if (!fs.existsSync(this.historyCacheDir)) {
|
||||||
|
fs.mkdirSync(this.historyCacheDir)
|
||||||
|
}
|
||||||
if (fs.existsSync(cachePath)) {
|
if (fs.existsSync(cachePath)) {
|
||||||
return JSON.parse(fs.readFileSync(cachePath, { encoding: "utf-8" }))
|
return JSON.parse(fs.readFileSync(cachePath, {encoding: "utf-8"}))
|
||||||
}
|
}
|
||||||
const history = await OsmObject.DownloadHistory(id).AsPromise((l) => l.length > 0)
|
const history = await OsmObject.DownloadHistory(id).AsPromise((l) => l.length > 0)
|
||||||
fs.writeFileSync(cachePath, JSON.stringify(history, null, " "), { encoding: "utf-8" })
|
fs.writeFileSync(cachePath, JSON.stringify(history, null, " "), {encoding: "utf-8"})
|
||||||
return history
|
return history
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,8 +290,12 @@ export class Conflate extends Script {
|
||||||
let website = properties.website.toLowerCase()
|
let website = properties.website.toLowerCase()
|
||||||
website
|
website
|
||||||
.replace("http://http://", "http://")
|
.replace("http://http://", "http://")
|
||||||
|
.replace("https://https://", "https://")
|
||||||
.replace("https//", "https://")
|
.replace("https//", "https://")
|
||||||
.replace("http://", "https://")
|
.replace("http://", "https://")
|
||||||
|
if (website.startsWith("https://")) {
|
||||||
|
website = "https://" + website
|
||||||
|
}
|
||||||
const validator = new UrlTextfieldDef()
|
const validator = new UrlTextfieldDef()
|
||||||
if (validator.isValid(website)) {
|
if (validator.isValid(website)) {
|
||||||
properties.website = new UrlTextfieldDef().reformat(website)
|
properties.website = new UrlTextfieldDef().reformat(website)
|
||||||
|
@ -278,7 +323,7 @@ export class Conflate extends Script {
|
||||||
let certainly_imported = match.d < 0.0001
|
let certainly_imported = match.d < 0.0001
|
||||||
let possibly_imported = false
|
let possibly_imported = false
|
||||||
|
|
||||||
const resting_properties = { ...match.external_feature.properties }
|
const resting_properties = {...match.external_feature.properties}
|
||||||
await this.normalize(resting_properties)
|
await this.normalize(resting_properties)
|
||||||
|
|
||||||
for (const historyElement of history) {
|
for (const historyElement of history) {
|
||||||
|
|
Loading…
Reference in a new issue