@osm-conflation-engine/cli 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3,4 +3,4 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.
|
|
|
3
3
|
[out:json][timeout:480];
|
|
4
4
|
nwr["${e.config.merge.osm_key}"];
|
|
5
5
|
out meta center;
|
|
6
|
-
`,i=t?await u.promises.readFile(t,`utf8`):r;console.info(`fetching OSM data via overpass…`);let a=await fetch(`${n||`https://overpass-api.de/api/interpreter`}?data=${encodeURIComponent(i)}`,{headers:C(e.config)});if(!a.ok){let e=await a.text()||a.statusText;throw Error(`HTTP Error ${a.status} ${e}`)}let o=await a.json(),{pickTags:s}=w(e),c={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0};for(let t of o.elements)O(e,c,{id:`${t.type[0]}${t.id}`,centroid:t.type===`node`?[t.lon,t.lat]:t.center?[t.center.lon,t.center.lat]:[0,0],tags:s(t.tags),metadata:{changeset:t.changeset,timestamp:t.timestamp,user:t.user,version:t.version},sectors:[],flags:0});await k(e,c)}const j=`https://osm-internal.download.geofabrik.de`,M=`https://www.openstreetmap.org`,N=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function ie(e,t){if(t.startsWith(j)||e.warnings.push(`(!) Planet PBF url is not from ${j}, so metadata may not be available.`),!process.env.OSM_AUTH)return e.warnings.push(`(!) No OSM credentials, so the planet extract will not include metadata`),{url:t.replace(j,`https://download.geofabrik.de`).replace(`-internal.osm.pbf`,`.osm.pbf`),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=C(e.config),i=await fetch(`${j}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${M}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${M}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:N(await a.text())}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}});if(s.status!==200)throw Error(`Login failed`);o+=`; ${s.headers.get(`Set-Cookie`)}`;let c=await fetch(i.authorization_url,{headers:{...r,Cookie:o}}),l=c.url.startsWith(M)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:N(await c.text()),state:i.state,response_type:`code`,scope:`read_prefs`,nonce:``,code_challenge:``,code_challenge_method:``,commit:`Authorize`}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}}).then(e=>e.text()):await c.text();if(!l.includes(`gf_download_oauth`))throw Error(`No token`);return{url:t,cookies:l}}async function ae(e,t){let n=e.tempFileNames.pbf,{cookies:r,url:i}=await ie(e,t);if(await u.promises.mkdir((0,c.dirname)(n),{recursive:!0}),await u.promises.rm(n,{recursive:!0,force:!0}),/^https?:\/\//.test(i)){console.info(`downloading from`,i,`to`,n);let e=(0,h.spawn)(`curl`,[`-L`,i,`-H`,`Cookie: ${r}`,`--output`,(0,c.basename)(n)],{cwd:(0,c.dirname)(n)});e.stdout.pipe(process.stdout),e.stderr.pipe(process.stderr),await new Promise((t,n)=>{e.on(`close`,e=>e?n(e):t())}),await(0,m.setTimeout)(5e3)}else console.info(`copying file from`,i,`to`,n),await u.promises.copyFile(i,n);console.info(n,await u.promises.stat(n))}async function oe(e){let t=e.config.merge.osm_key,n=e.config.e2e_tests?.ignore_list_file_path;if(b&&!n)throw Error(`When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath`);let r=b&&n?JSON.parse(await u.promises.readFile(n,`utf8`)):await fetch(`${S}/api/ignore_list/${encodeURIComponent(t)}?return_ids_only=true`).then(e=>e.json());await u.promises.writeFile(e.tempFileNames.ignore_list,JSON.stringify(r.rows,null,2))}async function se(e,t){if(e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}let{pickTags:n}=w(e);console.info(`Starting preprocess of OSM data...`),await k(e,await new Promise((r,i)=>{let a={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},o=!1,s=!1;g.default.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{s||=!0;let r=t.type===`node`?t:t.centroid,i=t.meta;i&&(o=!0);let c=n(t.tags);O(e,a,{id:`${t.type[0]}${t.id}`,centroid:[r.lon,r.lat],metadata:i?{changeset:i.changeset,timestamp:i.timestamp?new Date(1e3*i.timestamp).toISOString():void 0,user:i.user,version:i.version}:void 0,tags:c,sectors:[],flags:0}),a.count%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{if(o||e.warnings.push(`No metadata extracted from the planet file!`),!s)throw Error(`Nothing extracted from the pbf file, presumably it’s invalid.`);r(a)}).on(`error`,i)}))}async function ce(e){if(await oe(e),e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached OSM data`);return}switch(e.config.o_data.source.type){case`pbf`:await ae(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:await A(e,e.config.o_data.source.overpass_query_file,e.config.o_data.source.overpass_server_url);break;case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function le(e,t,n){let r=[];for(let e in t.withRef){let i=e,a=t.withRef[i];!(i in n)&&!(a.flags&8)&&r.push(i)}let i={};for(let t in n){let r=t,a=n[r];for(let t of a.sectors){i[t]||={};let n=e.callbacks.getLocalKeyForSource(a);i[t][n]=r}}let a={},o=[];for(let n of r){let r=t.withRef[n],s=e.callbacks.getLocalKeyForOsm(r),c=new Set(r.sectors.map(e=>i[e]?.[s]).filter(Boolean));if(c.size>1&&console.warn(`${r.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`),c.size===1){let e=[...c][0];t.withRef[e]?o.push(n):a[e]=n}else o.push(n)}return{refsThatChanged:a,toDelete:o}}const ue=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`;function P(e,t){let n=Math.max(0,t.minLat-e.maxLat,e.minLat-t.maxLat),r=Math.max(0,t.minLng-e.maxLng,e.minLng-t.maxLng);return Math.hypot(n,r)}function F(e){return Math.max(Math.abs(e.maxLat-e.minLat),Math.abs(e.maxLng-e.minLng))}const I=new Map,L=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=I.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),I.set(t,n)),n===r}).map(e=>{let n=6371008.8*(0,_.geoDistance)(e.centroid,t.centroid);return Object.assign(e,{offset:n})}).filter(e=>e.offset<200).toSorted((e,t)=>e.offset-t.offset)};async function R(e,t,n){let r=Object.keys(t).length,i=new Set(JSON.parse(await u.promises.readFile(e.tempFileNames.ignore_list,`utf8`)));console.info(`processing deleted data...`),console.time(`matchDeletions`);let{refsThatChanged:a,toDelete:o}=le(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let s=0;console.time(`matchLoop`);let c={1:[],2:[],3:{},4:[],5:o,6:[]},l=Object.values(n.noRef),d={};for(let e in n.semi){let t=e.split(`;`);for(let[n,r]of t.entries())d[r]||=[],d[r].push({value:e,isFirst:!n})}for(let o in t){let u=o,f=a[u]||u;if(i.has(f)||f in a)continue;let p=n.withRef[f],m=t[u],h=n.duplicateRefs[f];if(d[f])if(d[f][0].isFirst)f=d[f][0].value;else continue;let g=n.semi[f];if(p&&g){let e=f.split(`;`);c[4].push({osm:[p.id,g.id],source:e})}else if(p)c[1].push({osm:p.id,source:u});else if(h)c[2].push({osm:h.map(e=>e.id),source:f});else if(g){let e=f.split(`;`);c[3][g.id]||=[],c[3][g.id]?.push(...e)}else{let t=L(e,m,l);t.length===1?c[1].push({source:f,osm:t[0].id}):c[6].push({source:f,osmCandidates:t.map(e=>e.id)})}s+=1,s%1e3||process.stdout.write(`${(s/r*100).toFixed(1)}% `)}return console.timeEnd(`matchLoop`),await u.promises.writeFile(e.tempFileNames.matches,JSON.stringify(c,null,void 0)),{output:c}}const z=2e-4;function B({lat:e,lng:t}){let n=[[t,e+z],[t+z,e],[t,e-z],[t-z,e]];return n.push(n[0]),[n]}function V({lat:e,lng:t}){let n=[[t+z/2,e+z/2],[t+z/2,e-z/2],[t-z/2,e-z/2],[t-z/2,e+z/2]];return n.push(n[0]),[n]}function H(e){let t={minLat:1/0,minLng:1/0,maxLat:-1/0,maxLng:-1/0};function n([e,n]){let r=+n,i=+e;r<t.minLat&&(t.minLat=r),i<t.minLng&&(t.minLng=i),r>t.maxLat&&(t.maxLat=r),i>t.maxLng&&(t.maxLng=i)}for(let t of e)switch(t.geometry.type){case`Point`:n(t.geometry.coordinates);break;case`LineString`:t.geometry.coordinates.forEach(n);break;case`Polygon`:for(let e of t.geometry.coordinates)e.forEach(n);break;case`MultiPolygon`:for(let e of t.geometry.coordinates)for(let t of e)t.forEach(n);break;default:}return t}function U({minLng:e,minLat:t,maxLng:n,maxLat:r}){return{type:`Polygon`,coordinates:[[[e,t],[n,t],[n,r],[e,r],[e,t]]]}}function W(e,t,n,r,i=0){if(!r.length)return{};let a=H(r),o=D(U(a),e.config.merge.sector_resolution),s=i>10;if(s&&console.info(`(!) Couldn't split ${t} (${r.length} left)`),r.length<=110||s)return{[t]:{features:r,bbox:a,sectorIds:o,...n}};if(a.maxLat-a.minLat>a.maxLng-a.minLng){let o=a.minLat+(a.maxLat-a.minLat)/2,s=[[],[]];for(let e of r)s[+((0,_.geoCentroid)(e.geometry)[1]<o)].push(e);let[c,l]=s;return{...W(e,`${t}^N`,n,c,i+1),...W(e,`${t}^S`,n,l,i+1)}}let c=a.minLng+(a.maxLng-a.minLng)/2,l=[[],[]];for(let e of r)l[+((0,_.geoCentroid)(e.geometry)[0]<c)].push(e);let[u,d]=l;return{...W(e,`${t}^E`,n,u,i+1),...W(e,`${t}^W`,n,d,i+1)}}const{sin:de,cos:fe,floor:pe,sqrt:me,PI:he}=Math;function ge(e){let t=pe(me(e)),n=(e-t**2)*he/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*fe(n),t*de(n)];return[+r.toFixed(2),+i.toFixed(2)]}function _e(e){for(let t in e){let n=e[t],r={};for(let e of n.features)if(e.geometry.type===`Point`){let[t,n]=e.geometry.coordinates,i=ue(n,t,4);r[i]||=[],r[i].push(e)}for(let e of Object.values(r))if(e.length>1)for(let t=0;t<e.length;t+=1){let[n,r]=ge(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function G(e){return(0,v.createHash)(`sha256`).update(e,`utf8`).digest(`hex`)}function ve(e){let t=0,n=0,r=0,i=0;for(let a of e)switch(a.properties.__action){case`delete`:n+=1;break;case`move`:r+=1;break;case`edit`:i+=1;break;default:t+=1}return{count:[t&&`add ${t}`,n&&`delete ${n}`,r&&`move ${r}`,i&&`edit ${i}`].filter(e=>!!e).join(`, `),totalCount:t+n+r+i}}function K(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``).slice(0,100)}_${G(e).slice(0,6)}`}async function ye(e,t,n){let r=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!r)throw Error(`git_repository is not a valid github url`);let i=e.config.output?.folder||(0,c.join)(process.cwd(),`output`),a=`suburbs`,o=Object.entries(n).flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({category:e,group:t,title:[e,t].filter(Boolean).join(` - `),bbox:n.bbox,instructions:n.instructions,...ve(n.features)}))),s={fields:[],results:o.map(e=>({id:K(e.title),url:`https://${r[1]}.github.io/${r[2]}/${a}/${K(e.title)}.osmPatch.geo.json`,name:e.title,title:e.title,totalCount:e.totalCount,source:``,snippet:e.count,extent:[[e.bbox.minLng,e.bbox.minLat],[e.bbox.maxLng,e.bbox.maxLat]],instructions:e.instructions,groupCategories:[e.category,`/Categories/Addresses`]})).toSorted((e,t)=>e.name.localeCompare(t.name))},l={type:`FeatureCollection`,features:o.map(({bbox:e,...t})=>({type:`Feature`,geometry:U(e),properties:t})).toSorted((e,t)=>e.properties.title.localeCompare(t.properties.title))};await u.promises.mkdir((0,c.join)(i,a),{recursive:!0}),await u.promises.writeFile((0,c.join)(i,`index.json`),JSON.stringify(s,null,b?2:void 0)),await u.promises.writeFile((0,c.join)(i,`index.geo.json`),JSON.stringify(l,null,b?2:void 0)),await u.promises.writeFile((0,c.join)(i,`metrics.json`),JSON.stringify(t,null,2));for(let e of o){let t={type:`FeatureCollection`,...n[e.category][e.group]};await u.promises.writeFile((0,c.join)(i,a,`${K(e.title)}.osmPatch.geo.json`),JSON.stringify(t,null,b?2:void 0))}}function q(e,t){let n={...e};for(let e in t)n[e]?n[e]!==t[e]&&(n[e]+=`;${t[e]}`):n[e]=t[e];return n}function be(e){let t=!0;for(;t;){t=!1;for(let n in e){let r=e[n],i=r.sectorIds[0];if(r.features.length>40)continue;let a=Object.entries(e).filter(([e,t])=>t.sectorIds.includes(i)&&e!==n&&t.features.length<=40&&r.features.length+t.features.length<110).toSorted(([,e],[,t])=>P(r.bbox,e.bbox)-P(r.bbox,t.bbox))[0];if(!a)continue;let[o,s]=a,c=H([{type:`Feature`,geometry:U(r.bbox),properties:{}},{type:`Feature`,geometry:U(s.bbox),properties:{}}]);if(F(c)>4&&(F(r.bbox)<4||F(s.bbox)<4))continue;r.bbox=c,r.sectorIds=[...new Set([...r.sectorIds,...s.sectorIds])],r.changesetTags=q(r.changesetTags||{},s.changesetTags||{}),s.instructions&&(r.instructions||=``,r.instructions+=`\n\n${s.instructions}`,r.instructions.trim()),r.features.push(...s.features),delete e[n],delete e[o];let{name:l}=q({name:n},{name:o});e[l]=r,t=!0}}return e}async function xe(e,t,n,r,i){let a=new Set(JSON.parse(await u.promises.readFile(e.tempFileNames.ignore_list,`utf8`))),o=0,s=0,c=0;function l(e){e.flags&1&&o++,e.flags&2&&s++,e.flags&8&&c++}for(let e of Object.values(n.withRef))l(e);for(let e of Object.values(n.noRef))l(e);for(let e of Object.values(n.semi))l(e);for(let e of Object.values(n.duplicateRefs))for(let t of e)l(t);let d={config:e.config,warnings:e.warnings,countsByPhase:{init:{sourceDataset:Object.keys(t).length,ignored:a.size,osm:{withRef:Object.keys(n.withRef).length,noRef:Object.keys(n.noRef).length,semi:Object.keys(n.semi).length,duplicateRefs:Object.values(n.duplicateRefs).flat().length,lastEditedByImporter:o,recentlyChanged:s,recentlyChecked:c}},matched:{1:r[1].length,2:r[2].length,3:Object.keys(r[3]).length,4:r[4].length,5:r[5].length,6:r[6].length},conflated:{create:0,edit:0,delete:0,perfect:0}}};for(let e of Object.values(i).flatMap(e=>Object.values(e)))for(let t of e.features)t.properties.__action===`edit`?d.countsByPhase.conflated.edit++:t.properties.__action===`delete`?d.countsByPhase.conflated.delete++:d.countsByPhase.conflated.create++;return d.countsByPhase.conflated.perfect=n.count-d.countsByPhase.conflated.edit-d.countsByPhase.conflated.delete,d}async function Se(e){if(!process.env.GITHUB_ACTIONS){console.info(`Skipping run history update, because not running in GitHub Actions`);return}let t=await(0,y.getIDToken)(`osm-conflation-engine`),n=await fetch(`${S}/api/run_history/${e}`,{method:`PUT`,headers:{Authorization:`Bearer ${t}`}});if(n.status!==200)throw Error(await n.text())}const J=`invalid value returned by your callback function`;function Ce(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function Y(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Ce(e.tags)}function X(e,t,n){if(!t&&!n)throw Error(`requires either o or s`);t&&(e.tags.__action||=`edit`);let[r,i]=t?.centroid||n?.centroid;return{type:`Feature`,id:t?.id||n?.id,geometry:e.tags.__action===`delete`?{type:`Polygon`,coordinates:V({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:B({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function we(e,t,n){let r={...n.noRef};for(let e of[`withRef`,`semi`,`duplicateRefs`])for(let t in n[e]){let i=n[e][t];if(Array.isArray(i))for(let e of i)r[e.id]=e;else r[i.id]=i}let i={};function a(t,n,r){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||={},i[n][r]||=[],i[n][r].push(...t.createFeatures)))}let o=JSON.parse(await u.promises.readFile(e.tempFileNames.matches,`utf8`));for(let{source:n,osm:s}of o[1]){let o=r[s],c=t[n];if(o.flags&8)continue;let l=await e.callbacks.mergeOneToOne({osm:o,source:c});if(typeof l!=`object`)throw TypeError(J);if(!Y(l.diff))continue;let u=l.category||``,d=l.group||o.sectors[0];a(l.extra,u,d),i[u]||={},i[u][d]||=[],i[u][d].push(X(l.diff,o,c))}for(let{source:n,osm:s}of o[2]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=t[n],l=await e.callbacks.mergeOneToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,u=l.group||n.sectors[0];a(l.extra,s,u),i[s]||={},i[s][u]||=[],i[s][u].push(X(r,n,c))}}}for(let n in o[3]){let s=n,c=r[s],l=o[3][s].map(e=>t[e]);if(c.flags&8)continue;let u=await e.callbacks.mergeManyToOne?.({osm:c,source:l});if(!u)continue;if(typeof u!=`object`||typeof u.diff!=`object`)throw TypeError(J);if(!Y(u.diff))continue;let d=u.category||``,f=u.group||c.sectors[0];a(u.extra,d,f),i[d]||={},i[d][f]||=[],i[d][f].push(X(u.diff,c))}for(let{source:n,osm:s}of o[4]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=n.map(e=>t[e]),l=await e.callbacks.mergeManyToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,c=l.group||n.sectors[0];a(l.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(r,n))}}}for(let t of o[5]){let r=n.withRef[t];if(r.flags&8)continue;let o=await e.callbacks.deleteFeature?.({osm:r});if(!o)continue;if(typeof o!=`object`||typeof o.diff!=`object`)throw TypeError(J);if(!Y(o.diff))continue;let s=o.category||``,c=o.group||r.sectors[0];a(o.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(o.diff,r))}for(let r of o[6]){let o=t[r.source],s=await e.callbacks.create({source:o,osmCandidates:r.osmCandidates.map(e=>n.noRef[e])});if(!s)continue;if(typeof s!=`object`||typeof s.diff!=`object`)throw TypeError(J);if(!Y(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.category||``,u=s.group||c?.sectors[0]||`unknown`;a(s.extra,l,u),i[l]||={},i[l][u]||=[],i[l][u].push(X(s.diff,c,o))}let s={};for(let n in i){s[n]||={};for(let a in i[n]){let{changesetTags:o={},instructions:c}=await e.callbacks.getChangesetTags?.({category:n,group:a})||{};o.created_by||=`osm-conflation-engine`,o.import||=`yes`,o.source||=e.config.metadata.wiki_page;let l=i[n][a];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({category:n,group:a,osmData:r,sourceData:t,features:l});let u=W(e,a,{changesetTags:o,instructions:c},l);Object.assign(s[n],u)}_e(s[n]),be(s[n])}let c=await xe(e,t,n,o,s);return await ye(e,c,s),await Se(e.config.merge.osm_key),c}async function Z(e){console.info(`Reading source data into memory...`);let t=0,n={},r=(0,f.createInterface)({input:(0,u.createReadStream)(e.config.source_data.file),crlfDelay:1/0});for await(let i of r){let r=JSON.parse(i),a=r.properties[e.config.merge.dataset_column];n[a]=Object.assign(r,{sectors:D(r.geometry,e.config.merge.sector_resolution),centroid:r.geometry.type===`Point`?r.geometry.coordinates:(0,_.geoCentroid)(r.geometry)}),t++}console.info(`\tread ${t} rows.`),console.info(`Reading osm data into memory (withRef)...`);let i=await T(e.tempFileNames.osm_processed_with_ref,t=>t.tags[e.config.merge.osm_key]);console.info(`Reading osm data into memory (noRef)...`);let a=await T(e.tempFileNames.osm_processed_no_ref,e=>e.id);console.info(`Reading osm data into memory (other)...`);let o={withRef:i,noRef:a,...JSON.parse(await u.promises.readFile(e.tempFileNames.osm_processed_other,`utf8`))};return console.info(` read data.`),{sourceData:n,osmData:o}}function Q(e){for(let t of e)if(!x.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${x.join(`, `)}`)}async function $(e){let t=G(e.metadata.name).slice(0,12),n=b?`-mock`:``,r=(0,c.join)((0,l.tmpdir)(),`oce-${n}`,t);return await u.promises.mkdir(r,{recursive:!0}),{id:t,tempFolder:r,tempFileNames:Object.fromEntries(Object.entries(ee).map(([e,t])=>[e.toLowerCase(),(0,c.join)(r,t)]))}}async function Te(e,t,n={}){let r=JSON.parse(await u.promises.readFile(new URL(`config.schema.json`,require("url").pathToFileURL(__filename).href),`utf8`)),i=new d.Ajv().compile(r);if(!i(e))throw AggregateError(i.errors.map(e=>TypeError(e.message+JSON.stringify(e,null,2),{cause:e})),`Config file does not conform to the schema`);let{id:a,tempFolder:o,tempFileNames:s}=await $(e),c={use_cache:!!n.use_cache,id:a,tempFolder:o,tempFileNames:s,config:e,callbacks:t,warnings:[]};console.info(`temp folder:`,o);let{steps:l=x}=n;Q(l),l.includes(`download`)&&(console.time(`download`),await ce(c),console.timeEnd(`download`));let f,p;l.includes(`match`)&&({sourceData:f,osmData:p}=await Z(c),console.time(`match`),await R(c,f,p),console.timeEnd(`match`));let m;return l.includes(`conflate`)&&((!f||!p)&&({sourceData:f,osmData:p}=await Z(c)),console.time(`conflate`),m=await we(c,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}exports.OsmFlags=te,exports.getTempFileNames=$,exports.readJsonL=T,exports.run=Te,exports.writeJsonL=E;
|
|
6
|
+
`,i=t?await u.promises.readFile(t,`utf8`):r;console.info(`fetching OSM data via overpass…`);let a=await fetch(`${n||`https://overpass-api.de/api/interpreter`}?data=${encodeURIComponent(i)}`,{headers:C(e.config)});if(!a.ok){let e=await a.text()||a.statusText;throw Error(`HTTP Error ${a.status} ${e}`)}let o=await a.json(),{pickTags:s}=w(e),c={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0};for(let t of o.elements)O(e,c,{id:`${t.type[0]}${t.id}`,centroid:t.type===`node`?[t.lon,t.lat]:t.center?[t.center.lon,t.center.lat]:[0,0],tags:s(t.tags),metadata:{changeset:t.changeset,timestamp:t.timestamp,user:t.user,version:t.version},sectors:[],flags:0});await k(e,c)}const j=`https://osm-internal.download.geofabrik.de`,M=`https://www.openstreetmap.org`,N=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function ie(e,t){if(t.startsWith(j)||e.warnings.push(`(!) Planet PBF url is not from ${j}, so metadata may not be available.`),!process.env.OSM_AUTH)return e.warnings.push(`(!) No OSM credentials, so the planet extract will not include metadata`),{url:t.replace(j,`https://download.geofabrik.de`).replace(`-internal.osm.pbf`,`.osm.pbf`),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=C(e.config),i=await fetch(`${j}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${M}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${M}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:N(await a.text())}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}});if(s.status!==200)throw Error(`Login failed`);o+=`; ${s.headers.get(`Set-Cookie`)}`;let c=await fetch(i.authorization_url,{headers:{...r,Cookie:o}}),l=c.url.startsWith(M)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:N(await c.text()),state:i.state,response_type:`code`,scope:`read_prefs`,nonce:``,code_challenge:``,code_challenge_method:``,commit:`Authorize`}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}}).then(e=>e.text()):await c.text();if(!l.includes(`gf_download_oauth`))throw Error(`No token`);return{url:t,cookies:l}}async function ae(e,t){let n=e.tempFileNames.pbf,{cookies:r,url:i}=await ie(e,t);if(await u.promises.mkdir((0,c.dirname)(n),{recursive:!0}),await u.promises.rm(n,{recursive:!0,force:!0}),/^https?:\/\//.test(i)){console.info(`downloading from`,i,`to`,n);let e=(0,h.spawn)(`curl`,[`-L`,i,`-H`,`Cookie: ${r}`,`--output`,(0,c.basename)(n)],{cwd:(0,c.dirname)(n)});e.stdout.pipe(process.stdout),e.stderr.pipe(process.stderr),await new Promise((t,n)=>{e.on(`close`,e=>e?n(e):t())}),await(0,m.setTimeout)(5e3)}else console.info(`copying file from`,i,`to`,n),await u.promises.copyFile(i,n);console.info(n,await u.promises.stat(n))}async function oe(e){let t=e.config.merge.osm_key,n=e.config.e2e_tests?.ignore_list_file_path;if(b&&!n)throw Error(`When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath`);let r=b&&n?JSON.parse(await u.promises.readFile(n,`utf8`)):await fetch(`${S}/api/ignore_list/${encodeURIComponent(t)}?return_ids_only=true`).then(e=>e.json());await u.promises.writeFile(e.tempFileNames.ignore_list,JSON.stringify(r.rows,null,2))}async function se(e,t){if(e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}let{pickTags:n}=w(e);console.info(`Starting preprocess of OSM data...`),await k(e,await new Promise((r,i)=>{let a={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},o=!1,s=!1;g.default.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{s||=!0;let r=t.type===`node`?t:t.centroid,i=t.meta;i&&(o=!0);let c=n(t.tags);O(e,a,{id:`${t.type[0]}${t.id}`,centroid:[r.lon,r.lat],metadata:i?{changeset:i.changeset,timestamp:i.timestamp?new Date(1e3*i.timestamp).toISOString():void 0,user:i.user,version:i.version}:void 0,tags:c,sectors:[],flags:0}),a.count%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{if(o||e.warnings.push(`No metadata extracted from the planet file!`),!s)throw Error(`Nothing extracted from the pbf file, presumably it’s invalid.`);r(a)}).on(`error`,i)}))}async function ce(e){if(await oe(e),e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached OSM data`);return}switch(e.config.o_data.source.type){case`pbf`:await ae(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:await A(e,e.config.o_data.source.overpass_query_file,e.config.o_data.source.overpass_server_url);break;case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function le(e,t,n){let r=[];for(let e in t.withRef){let i=e,a=t.withRef[i];!(i in n)&&!(a.flags&8)&&r.push(i)}let i={};for(let t in n){let r=t,a=n[r];for(let t of a.sectors){i[t]||={};let n=e.callbacks.getLocalKeyForSource(a);i[t][n]=r}}let a={},o=[];for(let n of r){let r=t.withRef[n],s=e.callbacks.getLocalKeyForOsm(r),c=new Set(r.sectors.map(e=>i[e]?.[s]).filter(Boolean));if(c.size>1&&console.warn(`${r.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`),c.size===1){let e=[...c][0];t.withRef[e]?o.push(n):a[e]=n}else o.push(n)}return{refsThatChanged:a,toDelete:o}}const ue=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`;function P(e,t){let n=Math.max(0,t.minLat-e.maxLat,e.minLat-t.maxLat),r=Math.max(0,t.minLng-e.maxLng,e.minLng-t.maxLng);return Math.hypot(n,r)}function F(e){return Math.max(Math.abs(e.maxLat-e.minLat),Math.abs(e.maxLng-e.minLng))}const I=new Map,L=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=I.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),I.set(t,n)),n===r}).map(e=>{let n=6371008.8*(0,_.geoDistance)(e.centroid,t.centroid);return Object.assign(e,{offset:n})}).filter(e=>e.offset<200).toSorted((e,t)=>e.offset-t.offset)};async function R(e,t,n){let r=Object.keys(t).length,i=new Set(JSON.parse(await u.promises.readFile(e.tempFileNames.ignore_list,`utf8`)));console.info(`processing deleted data...`),console.time(`matchDeletions`);let{refsThatChanged:a,toDelete:o}=le(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let s=0;console.time(`matchLoop`);let c={1:[],2:[],3:{},4:[],5:o,6:[]},l=Object.values(n.noRef),d={};for(let e in n.semi){let t=e.split(`;`);for(let[n,r]of t.entries())d[r]||=[],d[r].push({value:e,isFirst:!n})}for(let o in t){let u=o,f=a[u]||u;if(i.has(f)||f in a)continue;let p=n.withRef[f],m=t[u],h=n.duplicateRefs[f];if(d[f])if(d[f][0].isFirst)f=d[f][0].value;else continue;let g=n.semi[f];if(p&&g){let e=f.split(`;`);c[4].push({osm:[p.id,g.id],source:e})}else if(p)c[1].push({osm:p.id,source:u});else if(h)c[2].push({osm:h.map(e=>e.id),source:f});else if(g){let e=f.split(`;`);c[3][g.id]||=[],c[3][g.id]?.push(...e)}else{let t=L(e,m,l);t.length===1?c[1].push({source:f,osm:t[0].id}):c[6].push({source:f,osmCandidates:t.map(e=>e.id)})}s+=1,s%1e3||process.stdout.write(`${(s/r*100).toFixed(1)}% `)}return console.timeEnd(`matchLoop`),await u.promises.writeFile(e.tempFileNames.matches,JSON.stringify(c,null,void 0)),{output:c}}const z=2e-4;function B({lat:e,lng:t}){let n=[[t,e+z],[t+z,e],[t,e-z],[t-z,e]];return n.push(n[0]),[n]}function V({lat:e,lng:t}){let n=[[t+z/2,e+z/2],[t+z/2,e-z/2],[t-z/2,e-z/2],[t-z/2,e+z/2]];return n.push(n[0]),[n]}function H(e){let t={minLat:1/0,minLng:1/0,maxLat:-1/0,maxLng:-1/0};function n([e,n]){let r=+n,i=+e;r<t.minLat&&(t.minLat=r),i<t.minLng&&(t.minLng=i),r>t.maxLat&&(t.maxLat=r),i>t.maxLng&&(t.maxLng=i)}for(let t of e)switch(t.geometry.type){case`Point`:n(t.geometry.coordinates);break;case`LineString`:t.geometry.coordinates.forEach(n);break;case`Polygon`:for(let e of t.geometry.coordinates)e.forEach(n);break;case`MultiPolygon`:for(let e of t.geometry.coordinates)for(let t of e)t.forEach(n);break;default:}return t}function U({minLng:e,minLat:t,maxLng:n,maxLat:r}){return{type:`Polygon`,coordinates:[[[e,t],[n,t],[n,r],[e,r],[e,t]]]}}function W(e,t,n,r,i=0){if(!r.length)return{};let a=H(r),o=D(U(a),e.config.merge.sector_resolution),s=i>10;if(s&&console.info(`(!) Couldn't split ${t} (${r.length} left)`),r.length<=110||s)return{[t]:{features:r,bbox:a,sectorIds:o,...n}};if(a.maxLat-a.minLat>a.maxLng-a.minLng){let o=a.minLat+(a.maxLat-a.minLat)/2,s=[[],[]];for(let e of r)s[+((0,_.geoCentroid)(e.geometry)[1]<o)].push(e);let[c,l]=s;return{...W(e,`${t}^N`,n,c,i+1),...W(e,`${t}^S`,n,l,i+1)}}let c=a.minLng+(a.maxLng-a.minLng)/2,l=[[],[]];for(let e of r)l[+((0,_.geoCentroid)(e.geometry)[0]<c)].push(e);let[u,d]=l;return{...W(e,`${t}^E`,n,u,i+1),...W(e,`${t}^W`,n,d,i+1)}}const{sin:de,cos:fe,floor:pe,sqrt:me,PI:he}=Math;function ge(e){let t=pe(me(e)),n=(e-t**2)*he/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*fe(n),t*de(n)];return[+r.toFixed(2),+i.toFixed(2)]}function _e(e){for(let t in e){let n=e[t],r={};for(let e of n.features)if(e.geometry.type===`Point`){let[t,n]=e.geometry.coordinates,i=ue(n,t,4);r[i]||=[],r[i].push(e)}for(let e of Object.values(r))if(e.length>1)for(let t=0;t<e.length;t+=1){let[n,r]=ge(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function G(e){return(0,v.createHash)(`sha256`).update(e,`utf8`).digest(`hex`)}function ve(e){let t=0,n=0,r=0,i=0;for(let a of e)switch(a.properties.__action){case`delete`:n+=1;break;case`move`:r+=1;break;case`edit`:i+=1;break;default:t+=1}return{count:[t&&`add ${t}`,n&&`delete ${n}`,r&&`move ${r}`,i&&`edit ${i}`].filter(e=>!!e).join(`, `),totalCount:t+n+r+i}}function K(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``).slice(0,100)}_${G(e).slice(0,6)}`}async function ye(e,t,n){let r=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!r)throw Error(`git_repository is not a valid github url`);let i=e.config.output?.folder||(0,c.join)(process.cwd(),`output`),a=`suburbs`,o=Object.entries(n).flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({category:e,group:t,title:[e,t].filter(Boolean).join(` - `),bbox:n.bbox,instructions:n.instructions,...ve(n.features)}))),s={fields:[],results:o.map(e=>({id:K(e.title),url:`https://${r[1]}.github.io/${r[2]}/${a}/${K(e.title)}.osmPatch.geo.json`,name:e.title,title:e.title,totalCount:e.totalCount,source:``,snippet:e.count,extent:[[e.bbox.minLng,e.bbox.minLat],[e.bbox.maxLng,e.bbox.maxLat]],instructions:e.instructions,groupCategories:[e.category,`/Categories/Addresses`]})).toSorted((e,t)=>e.name.localeCompare(t.name))},l={type:`FeatureCollection`,features:o.map(({bbox:e,...t})=>({type:`Feature`,geometry:U(e),properties:t})).toSorted((e,t)=>e.properties.title.localeCompare(t.properties.title))};await u.promises.mkdir((0,c.join)(i,a),{recursive:!0}),await u.promises.writeFile((0,c.join)(i,`index.json`),JSON.stringify(s,null,b?2:void 0)),await u.promises.writeFile((0,c.join)(i,`index.geo.json`),JSON.stringify(l,null,b?2:void 0)),await u.promises.writeFile((0,c.join)(i,`metrics.json`),JSON.stringify(t,null,2));for(let e of o){let t={type:`FeatureCollection`,...n[e.category][e.group]};await u.promises.writeFile((0,c.join)(i,a,`${K(e.title)}.osmPatch.geo.json`),JSON.stringify(t,null,b?2:void 0))}}function q(e,t){let n={...e};for(let e in t)n[e]?n[e]!==t[e]&&(n[e]+=`;${t[e]}`):n[e]=t[e];return n}function be(e){let t=!0;for(;t;){t=!1;for(let n in e){let r=e[n],i=r.sectorIds[0];if(r.features.length>40)continue;let a=Object.entries(e).filter(([e,t])=>t.sectorIds.includes(i)&&e!==n&&t.features.length<=40&&r.features.length+t.features.length<110).toSorted(([,e],[,t])=>P(r.bbox,e.bbox)-P(r.bbox,t.bbox))[0];if(!a)continue;let[o,s]=a,c=H([{type:`Feature`,geometry:U(r.bbox),properties:{}},{type:`Feature`,geometry:U(s.bbox),properties:{}}]);if(F(c)>4&&(F(r.bbox)<4||F(s.bbox)<4))continue;r.bbox=c,r.sectorIds=[...new Set([...r.sectorIds,...s.sectorIds])],r.changesetTags=q(r.changesetTags||{},s.changesetTags||{}),s.instructions&&(r.instructions||=``,r.instructions+=`\n\n${s.instructions}`,r.instructions.trim()),r.features.push(...s.features),delete e[n],delete e[o];let{name:l}=q({name:n},{name:o});e[l]=r,t=!0}}return e}async function xe(e,t,n,r,i){let a=new Set(JSON.parse(await u.promises.readFile(e.tempFileNames.ignore_list,`utf8`))),o=0,s=0,c=0;function l(e){e.flags&1&&o++,e.flags&2&&s++,e.flags&8&&c++}for(let e of Object.values(n.withRef))l(e);for(let e of Object.values(n.noRef))l(e);for(let e of Object.values(n.semi))l(e);for(let e of Object.values(n.duplicateRefs))for(let t of e)l(t);let d={config:e.config,warnings:e.warnings,countsByPhase:{init:{sourceDataset:Object.keys(t).length,ignored:a.size,osm:{withRef:Object.keys(n.withRef).length,noRef:Object.keys(n.noRef).length,semi:Object.keys(n.semi).length,duplicateRefs:Object.values(n.duplicateRefs).flat().length,lastEditedByImporter:o,recentlyChanged:s,recentlyChecked:c}},matched:{1:r[1].length,2:r[2].length,3:Object.keys(r[3]).length,4:r[4].length,5:r[5].length,6:r[6].length},conflated:{create:0,edit:0,delete:0,perfect:0}}};for(let e of Object.values(i).flatMap(e=>Object.values(e)))for(let t of e.features)t.properties.__action===`edit`?d.countsByPhase.conflated.edit++:t.properties.__action===`delete`?d.countsByPhase.conflated.delete++:d.countsByPhase.conflated.create++;return d.countsByPhase.conflated.perfect=n.count-d.countsByPhase.conflated.edit-d.countsByPhase.conflated.delete,d}async function Se(e){if(b)return;if(!process.env.GITHUB_ACTIONS){console.info(`Skipping run history update, because not running in GitHub Actions`);return}let t=await(0,y.getIDToken)(`osm-conflation-engine`),n=e.config.merge.osm_key,r=await fetch(`${S}/api/run_history/${n}`,{method:`PUT`,headers:{Authorization:`Bearer ${t}`},body:JSON.stringify(e)});if(r.status!==200)throw Error(await r.text())}const J=`invalid value returned by your callback function`;function Ce(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function Y(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Ce(e.tags)}function X(e,t,n){if(!t&&!n)throw Error(`requires either o or s`);t&&(e.tags.__action||=`edit`);let[r,i]=t?.centroid||n?.centroid;return{type:`Feature`,id:t?.id||n?.id,geometry:e.tags.__action===`delete`?{type:`Polygon`,coordinates:V({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:B({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function we(e,t,n){let r={...n.noRef};for(let e of[`withRef`,`semi`,`duplicateRefs`])for(let t in n[e]){let i=n[e][t];if(Array.isArray(i))for(let e of i)r[e.id]=e;else r[i.id]=i}let i={};function a(t,n,r){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||={},i[n][r]||=[],i[n][r].push(...t.createFeatures)))}let o=JSON.parse(await u.promises.readFile(e.tempFileNames.matches,`utf8`));for(let{source:n,osm:s}of o[1]){let o=r[s],c=t[n];if(o.flags&8)continue;let l=await e.callbacks.mergeOneToOne({osm:o,source:c});if(typeof l!=`object`)throw TypeError(J);if(!Y(l.diff))continue;let u=l.category||``,d=l.group||o.sectors[0];a(l.extra,u,d),i[u]||={},i[u][d]||=[],i[u][d].push(X(l.diff,o,c))}for(let{source:n,osm:s}of o[2]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=t[n],l=await e.callbacks.mergeOneToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,u=l.group||n.sectors[0];a(l.extra,s,u),i[s]||={},i[s][u]||=[],i[s][u].push(X(r,n,c))}}}for(let n in o[3]){let s=n,c=r[s],l=o[3][s].map(e=>t[e]);if(c.flags&8)continue;let u=await e.callbacks.mergeManyToOne?.({osm:c,source:l});if(!u)continue;if(typeof u!=`object`||typeof u.diff!=`object`)throw TypeError(J);if(!Y(u.diff))continue;let d=u.category||``,f=u.group||c.sectors[0];a(u.extra,d,f),i[d]||={},i[d][f]||=[],i[d][f].push(X(u.diff,c))}for(let{source:n,osm:s}of o[4]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=n.map(e=>t[e]),l=await e.callbacks.mergeManyToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,c=l.group||n.sectors[0];a(l.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(r,n))}}}for(let t of o[5]){let r=n.withRef[t];if(r.flags&8)continue;let o=await e.callbacks.deleteFeature?.({osm:r});if(!o)continue;if(typeof o!=`object`||typeof o.diff!=`object`)throw TypeError(J);if(!Y(o.diff))continue;let s=o.category||``,c=o.group||r.sectors[0];a(o.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(o.diff,r))}for(let r of o[6]){let o=t[r.source],s=await e.callbacks.create({source:o,osmCandidates:r.osmCandidates.map(e=>n.noRef[e])});if(!s)continue;if(typeof s!=`object`||typeof s.diff!=`object`)throw TypeError(J);if(!Y(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.category||``,u=s.group||c?.sectors[0]||`unknown`;a(s.extra,l,u),i[l]||={},i[l][u]||=[],i[l][u].push(X(s.diff,c,o))}let s={};for(let n in i){s[n]||={};for(let a in i[n]){let{changesetTags:o={},instructions:c}=await e.callbacks.getChangesetTags?.({category:n,group:a})||{};o.created_by||=`osm-conflation-engine`,o.import||=`yes`,o.source||=e.config.metadata.wiki_page;let l=i[n][a];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({category:n,group:a,osmData:r,sourceData:t,features:l});let u=W(e,a,{changesetTags:o,instructions:c},l);Object.assign(s[n],u)}_e(s[n]),be(s[n])}let c=await xe(e,t,n,o,s);return await ye(e,c,s),await Se(c),c}async function Z(e){console.info(`Reading source data into memory...`);let t=0,n={},r=(0,f.createInterface)({input:(0,u.createReadStream)(e.config.source_data.file),crlfDelay:1/0});for await(let i of r){let r=JSON.parse(i),a=r.properties[e.config.merge.dataset_column];n[a]=Object.assign(r,{sectors:D(r.geometry,e.config.merge.sector_resolution),centroid:r.geometry.type===`Point`?r.geometry.coordinates:(0,_.geoCentroid)(r.geometry)}),t++}console.info(`\tread ${t} rows.`),console.info(`Reading osm data into memory (withRef)...`);let i=await T(e.tempFileNames.osm_processed_with_ref,t=>t.tags[e.config.merge.osm_key]);console.info(`Reading osm data into memory (noRef)...`);let a=await T(e.tempFileNames.osm_processed_no_ref,e=>e.id);console.info(`Reading osm data into memory (other)...`);let o={withRef:i,noRef:a,...JSON.parse(await u.promises.readFile(e.tempFileNames.osm_processed_other,`utf8`))};return console.info(` read data.`),{sourceData:n,osmData:o}}function Q(e){for(let t of e)if(!x.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${x.join(`, `)}`)}async function $(e){let t=G(e.metadata.name).slice(0,12),n=b?`-mock`:``,r=(0,c.join)((0,l.tmpdir)(),`oce-${n}`,t);return await u.promises.mkdir(r,{recursive:!0}),{id:t,tempFolder:r,tempFileNames:Object.fromEntries(Object.entries(ee).map(([e,t])=>[e.toLowerCase(),(0,c.join)(r,t)]))}}async function Te(e,t,n={}){let r=JSON.parse(await u.promises.readFile(new URL(`config.schema.json`,require("url").pathToFileURL(__filename).href),`utf8`)),i=new d.Ajv().compile(r);if(!i(e))throw AggregateError(i.errors.map(e=>TypeError(e.message+JSON.stringify(e,null,2),{cause:e})),`Config file does not conform to the schema`);let{id:a,tempFolder:o,tempFileNames:s}=await $(e),c={use_cache:!!n.use_cache,id:a,tempFolder:o,tempFileNames:s,config:e,callbacks:t,warnings:[]};console.info(`temp folder:`,o);let{steps:l=x}=n;Q(l),l.includes(`download`)&&(console.time(`download`),await ce(c),console.timeEnd(`download`));let f,p;l.includes(`match`)&&({sourceData:f,osmData:p}=await Z(c),console.time(`match`),await R(c,f,p),console.timeEnd(`match`));let m;return l.includes(`conflate`)&&((!f||!p)&&({sourceData:f,osmData:p}=await Z(c)),console.time(`conflate`),m=await we(c,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}exports.OsmFlags=te,exports.getTempFileNames=$,exports.readJsonL=T,exports.run=Te,exports.writeJsonL=E;
|
package/dist/index.d.cts
CHANGED
|
@@ -469,6 +469,15 @@ interface ConflateResult {
|
|
|
469
469
|
interface RunResult {
|
|
470
470
|
conflate?: ConflateResult;
|
|
471
471
|
}
|
|
472
|
+
interface IndexFileProperties {
|
|
473
|
+
category: string;
|
|
474
|
+
group: string;
|
|
475
|
+
title: string;
|
|
476
|
+
instructions: string | undefined;
|
|
477
|
+
totalCount: number;
|
|
478
|
+
}
|
|
479
|
+
/** the shape of `index.geo.json` */
|
|
480
|
+
type IndexFile = FeatureCollection<Polygon, IndexFileProperties>;
|
|
472
481
|
//#endregion
|
|
473
482
|
//#region src/main.d.ts
|
|
474
483
|
declare function getTempFileNames(config: Config): Promise<Pick<Ctx, 'id' | 'tempFolder' | 'tempFileNames'>>;
|
|
@@ -480,5 +489,5 @@ declare function readJsonL<T, U extends string>(filePath: string, getKey: (row:
|
|
|
480
489
|
/** writes JSONL to a file by stringifying each line at a time */
|
|
481
490
|
declare function writeJsonL(filePath: string, rows: unknown[]): Promise<void>;
|
|
482
491
|
//#endregion
|
|
483
|
-
export { Callbacks, Config, ConflateResult, ConflationDiff, ConflationResultExtra, DatasetId, Identity, MutliFeatureConflationResult, OsmFeature, OsmFeatureTypeShort, OsmFlags, OsmId, RunOptions, RunResult, SingleFeatureConflationResult, SourceDataFeature, TagDiff, Vec2, getTempFileNames, readJsonL, run, writeJsonL };
|
|
492
|
+
export { Callbacks, Config, ConflateResult, ConflationDiff, ConflationResultExtra, DatasetId, Identity, IndexFile, IndexFileProperties, MutliFeatureConflationResult, OsmFeature, OsmFeatureTypeShort, OsmFlags, OsmId, RunOptions, RunResult, SingleFeatureConflationResult, SourceDataFeature, TagDiff, Vec2, getTempFileNames, readJsonL, run, writeJsonL };
|
|
484
493
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":["Geometry","GeoJSON","type","GeoJsonTypes","bbox","BBox","G","P","GeoJsonProperties","Feature","FeatureCollection","Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection","GeoJsonObject","coordinates","Position","geometries","name","geometry","id","properties","features","Array"],"sources":["../../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts","../src/types/config.def.ts","../src/constants/defaults.ts","../src/types/internal.def.ts","../src/types/callbacks.def.ts","../src/main.ts","../src/common/jsonl.ts"],"x_google_ignoreList":[0],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":["Geometry","GeoJSON","type","GeoJsonTypes","bbox","BBox","G","P","GeoJsonProperties","Feature","FeatureCollection","Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection","GeoJsonObject","coordinates","Position","geometries","name","geometry","id","properties","features","Array"],"sources":["../../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts","../src/types/config.def.ts","../src/constants/defaults.ts","../src/types/internal.def.ts","../src/types/callbacks.def.ts","../src/main.ts","../src/common/jsonl.ts"],"x_google_ignoreList":[0],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -469,6 +469,15 @@ interface ConflateResult {
|
|
|
469
469
|
interface RunResult {
|
|
470
470
|
conflate?: ConflateResult;
|
|
471
471
|
}
|
|
472
|
+
interface IndexFileProperties {
|
|
473
|
+
category: string;
|
|
474
|
+
group: string;
|
|
475
|
+
title: string;
|
|
476
|
+
instructions: string | undefined;
|
|
477
|
+
totalCount: number;
|
|
478
|
+
}
|
|
479
|
+
/** the shape of `index.geo.json` */
|
|
480
|
+
type IndexFile = FeatureCollection<Polygon, IndexFileProperties>;
|
|
472
481
|
//#endregion
|
|
473
482
|
//#region src/main.d.ts
|
|
474
483
|
declare function getTempFileNames(config: Config): Promise<Pick<Ctx, 'id' | 'tempFolder' | 'tempFileNames'>>;
|
|
@@ -480,5 +489,5 @@ declare function readJsonL<T, U extends string>(filePath: string, getKey: (row:
|
|
|
480
489
|
/** writes JSONL to a file by stringifying each line at a time */
|
|
481
490
|
declare function writeJsonL(filePath: string, rows: unknown[]): Promise<void>;
|
|
482
491
|
//#endregion
|
|
483
|
-
export { Callbacks, Config, ConflateResult, ConflationDiff, ConflationResultExtra, DatasetId, Identity, MutliFeatureConflationResult, OsmFeature, OsmFeatureTypeShort, OsmFlags, OsmId, RunOptions, RunResult, SingleFeatureConflationResult, SourceDataFeature, TagDiff, Vec2, getTempFileNames, readJsonL, run, writeJsonL };
|
|
492
|
+
export { Callbacks, Config, ConflateResult, ConflationDiff, ConflationResultExtra, DatasetId, Identity, IndexFile, IndexFileProperties, MutliFeatureConflationResult, OsmFeature, OsmFeatureTypeShort, OsmFlags, OsmId, RunOptions, RunResult, SingleFeatureConflationResult, SourceDataFeature, TagDiff, Vec2, getTempFileNames, readJsonL, run, writeJsonL };
|
|
484
493
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":["Geometry","GeoJSON","type","GeoJsonTypes","bbox","BBox","G","P","GeoJsonProperties","Feature","FeatureCollection","Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection","GeoJsonObject","coordinates","Position","geometries","name","geometry","id","properties","features","Array"],"sources":["../../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts","../src/types/config.def.ts","../src/constants/defaults.ts","../src/types/internal.def.ts","../src/types/callbacks.def.ts","../src/main.ts","../src/common/jsonl.ts"],"x_google_ignoreList":[0],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["Geometry","GeoJSON","type","GeoJsonTypes","bbox","BBox","G","P","GeoJsonProperties","Feature","FeatureCollection","Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection","GeoJsonObject","coordinates","Position","geometries","name","geometry","id","properties","features","Array"],"sources":["../../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts","../src/types/config.def.ts","../src/constants/defaults.ts","../src/types/internal.def.ts","../src/types/callbacks.def.ts","../src/main.ts","../src/common/jsonl.ts"],"x_google_ignoreList":[0],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,5 +3,5 @@ import{basename as e,dirname as t,join as n}from"node:path";import{tmpdir as r}f
|
|
|
3
3
|
[out:json][timeout:480];
|
|
4
4
|
nwr["${e.config.merge.osm_key}"];
|
|
5
5
|
out meta center;
|
|
6
|
-
`,i=t?await s.readFile(t,`utf8`):r;console.info(`fetching OSM data via overpass…`);let a=await fetch(`${n||`https://overpass-api.de/api/interpreter`}?data=${encodeURIComponent(i)}`,{headers:x(e.config)});if(!a.ok){let e=await a.text()||a.statusText;throw Error(`HTTP Error ${a.status} ${e}`)}let o=await a.json(),{pickTags:c}=C(e),l={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0};for(let t of o.elements)O(e,l,{id:`${t.type[0]}${t.id}`,centroid:t.type===`node`?[t.lon,t.lat]:t.center?[t.center.lon,t.center.lat]:[0,0],tags:c(t.tags),metadata:{changeset:t.changeset,timestamp:t.timestamp,user:t.user,version:t.version},sectors:[],flags:0});await k(e,l)}const j=`https://osm-internal.download.geofabrik.de`,M=`https://www.openstreetmap.org`,N=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function ie(e,t){if(t.startsWith(j)||e.warnings.push(`(!) Planet PBF url is not from ${j}, so metadata may not be available.`),!process.env.OSM_AUTH)return e.warnings.push(`(!) No OSM credentials, so the planet extract will not include metadata`),{url:t.replace(j,`https://download.geofabrik.de`).replace(`-internal.osm.pbf`,`.osm.pbf`),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=x(e.config),i=await fetch(`${j}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${M}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${M}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:N(await a.text())}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}});if(s.status!==200)throw Error(`Login failed`);o+=`; ${s.headers.get(`Set-Cookie`)}`;let c=await fetch(i.authorization_url,{headers:{...r,Cookie:o}}),l=c.url.startsWith(M)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:N(await c.text()),state:i.state,response_type:`code`,scope:`read_prefs`,nonce:``,code_challenge:``,code_challenge_method:``,commit:`Authorize`}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}}).then(e=>e.text()):await c.text();if(!l.includes(`gf_download_oauth`))throw Error(`No token`);return{url:t,cookies:l}}async function ae(n,r){let i=n.tempFileNames.pbf,{cookies:a,url:o}=await ie(n,r);if(await s.mkdir(t(i),{recursive:!0}),await s.rm(i,{recursive:!0,force:!0}),/^https?:\/\//.test(o)){console.info(`downloading from`,o,`to`,i);let n=m(`curl`,[`-L`,o,`-H`,`Cookie: ${a}`,`--output`,e(i)],{cwd:t(i)});n.stdout.pipe(process.stdout),n.stderr.pipe(process.stderr),await new Promise((e,t)=>{n.on(`close`,n=>n?t(n):e())}),await p(5e3)}else console.info(`copying file from`,o,`to`,i),await s.copyFile(o,i);console.info(i,await s.stat(i))}async function oe(e){let t=e.config.merge.osm_key,n=e.config.e2e_tests?.ignore_list_file_path;if(v&&!n)throw Error(`When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath`);let r=v&&n?JSON.parse(await s.readFile(n,`utf8`)):await fetch(`${b}/api/ignore_list/${encodeURIComponent(t)}?return_ids_only=true`).then(e=>e.json());await s.writeFile(e.tempFileNames.ignore_list,JSON.stringify(r.rows,null,2))}async function se(e,t){if(e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}let{pickTags:n}=C(e);console.info(`Starting preprocess of OSM data...`),await k(e,await new Promise((r,i)=>{let a={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},o=!1,s=!1;h.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{s||=!0;let r=t.type===`node`?t:t.centroid,i=t.meta;i&&(o=!0);let c=n(t.tags);O(e,a,{id:`${t.type[0]}${t.id}`,centroid:[r.lon,r.lat],metadata:i?{changeset:i.changeset,timestamp:i.timestamp?new Date(1e3*i.timestamp).toISOString():void 0,user:i.user,version:i.version}:void 0,tags:c,sectors:[],flags:0}),a.count%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{if(o||e.warnings.push(`No metadata extracted from the planet file!`),!s)throw Error(`Nothing extracted from the pbf file, presumably it’s invalid.`);r(a)}).on(`error`,i)}))}async function ce(e){if(await oe(e),e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached OSM data`);return}switch(e.config.o_data.source.type){case`pbf`:await ae(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:await A(e,e.config.o_data.source.overpass_query_file,e.config.o_data.source.overpass_server_url);break;case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function le(e,t,n){let r=[];for(let e in t.withRef){let i=e,a=t.withRef[i];!(i in n)&&!(a.flags&8)&&r.push(i)}let i={};for(let t in n){let r=t,a=n[r];for(let t of a.sectors){i[t]||={};let n=e.callbacks.getLocalKeyForSource(a);i[t][n]=r}}let a={},o=[];for(let n of r){let r=t.withRef[n],s=e.callbacks.getLocalKeyForOsm(r),c=new Set(r.sectors.map(e=>i[e]?.[s]).filter(Boolean));if(c.size>1&&console.warn(`${r.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`),c.size===1){let e=[...c][0];t.withRef[e]?o.push(n):a[e]=n}else o.push(n)}return{refsThatChanged:a,toDelete:o}}const ue=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`;function P(e,t){let n=Math.max(0,t.minLat-e.maxLat,e.minLat-t.maxLat),r=Math.max(0,t.minLng-e.maxLng,e.minLng-t.maxLng);return Math.hypot(n,r)}function F(e){return Math.max(Math.abs(e.maxLat-e.minLat),Math.abs(e.maxLng-e.minLng))}const I=new Map,L=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=I.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),I.set(t,n)),n===r}).map(e=>{let n=6371008.8*ee(e.centroid,t.centroid);return Object.assign(e,{offset:n})}).filter(e=>e.offset<200).toSorted((e,t)=>e.offset-t.offset)};async function R(e,t,n){let r=Object.keys(t).length,i=new Set(JSON.parse(await s.readFile(e.tempFileNames.ignore_list,`utf8`)));console.info(`processing deleted data...`),console.time(`matchDeletions`);let{refsThatChanged:a,toDelete:o}=le(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let c=0;console.time(`matchLoop`);let l={1:[],2:[],3:{},4:[],5:o,6:[]},u=Object.values(n.noRef),d={};for(let e in n.semi){let t=e.split(`;`);for(let[n,r]of t.entries())d[r]||=[],d[r].push({value:e,isFirst:!n})}for(let o in t){let s=o,f=a[s]||s;if(i.has(f)||f in a)continue;let p=n.withRef[f],m=t[s],h=n.duplicateRefs[f];if(d[f])if(d[f][0].isFirst)f=d[f][0].value;else continue;let g=n.semi[f];if(p&&g){let e=f.split(`;`);l[4].push({osm:[p.id,g.id],source:e})}else if(p)l[1].push({osm:p.id,source:s});else if(h)l[2].push({osm:h.map(e=>e.id),source:f});else if(g){let e=f.split(`;`);l[3][g.id]||=[],l[3][g.id]?.push(...e)}else{let t=L(e,m,u);t.length===1?l[1].push({source:f,osm:t[0].id}):l[6].push({source:f,osmCandidates:t.map(e=>e.id)})}c+=1,c%1e3||process.stdout.write(`${(c/r*100).toFixed(1)}% `)}return console.timeEnd(`matchLoop`),await s.writeFile(e.tempFileNames.matches,JSON.stringify(l,null,void 0)),{output:l}}const z=2e-4;function B({lat:e,lng:t}){let n=[[t,e+z],[t+z,e],[t,e-z],[t-z,e]];return n.push(n[0]),[n]}function V({lat:e,lng:t}){let n=[[t+z/2,e+z/2],[t+z/2,e-z/2],[t-z/2,e-z/2],[t-z/2,e+z/2]];return n.push(n[0]),[n]}function H(e){let t={minLat:1/0,minLng:1/0,maxLat:-1/0,maxLng:-1/0};function n([e,n]){let r=+n,i=+e;r<t.minLat&&(t.minLat=r),i<t.minLng&&(t.minLng=i),r>t.maxLat&&(t.maxLat=r),i>t.maxLng&&(t.maxLng=i)}for(let t of e)switch(t.geometry.type){case`Point`:n(t.geometry.coordinates);break;case`LineString`:t.geometry.coordinates.forEach(n);break;case`Polygon`:for(let e of t.geometry.coordinates)e.forEach(n);break;case`MultiPolygon`:for(let e of t.geometry.coordinates)for(let t of e)t.forEach(n);break;default:}return t}function U({minLng:e,minLat:t,maxLng:n,maxLat:r}){return{type:`Polygon`,coordinates:[[[e,t],[n,t],[n,r],[e,r],[e,t]]]}}function W(e,t,n,r,i=0){if(!r.length)return{};let a=H(r),o=D(U(a),e.config.merge.sector_resolution),s=i>10;if(s&&console.info(`(!) Couldn't split ${t} (${r.length} left)`),r.length<=110||s)return{[t]:{features:r,bbox:a,sectorIds:o,...n}};if(a.maxLat-a.minLat>a.maxLng-a.minLng){let o=a.minLat+(a.maxLat-a.minLat)/2,s=[[],[]];for(let e of r)s[+(g(e.geometry)[1]<o)].push(e);let[c,l]=s;return{...W(e,`${t}^N`,n,c,i+1),...W(e,`${t}^S`,n,l,i+1)}}let c=a.minLng+(a.maxLng-a.minLng)/2,l=[[],[]];for(let e of r)l[+(g(e.geometry)[0]<c)].push(e);let[u,d]=l;return{...W(e,`${t}^E`,n,u,i+1),...W(e,`${t}^W`,n,d,i+1)}}const{sin:de,cos:fe,floor:pe,sqrt:me,PI:he}=Math;function ge(e){let t=pe(me(e)),n=(e-t**2)*he/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*fe(n),t*de(n)];return[+r.toFixed(2),+i.toFixed(2)]}function _e(e){for(let t in e){let n=e[t],r={};for(let e of n.features)if(e.geometry.type===`Point`){let[t,n]=e.geometry.coordinates,i=ue(n,t,4);r[i]||=[],r[i].push(e)}for(let e of Object.values(r))if(e.length>1)for(let t=0;t<e.length;t+=1){let[n,r]=ge(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function G(e){return _(`sha256`).update(e,`utf8`).digest(`hex`)}function ve(e){let t=0,n=0,r=0,i=0;for(let a of e)switch(a.properties.__action){case`delete`:n+=1;break;case`move`:r+=1;break;case`edit`:i+=1;break;default:t+=1}return{count:[t&&`add ${t}`,n&&`delete ${n}`,r&&`move ${r}`,i&&`edit ${i}`].filter(e=>!!e).join(`, `),totalCount:t+n+r+i}}function K(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``).slice(0,100)}_${G(e).slice(0,6)}`}async function ye(e,t,r){let i=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!i)throw Error(`git_repository is not a valid github url`);let a=e.config.output?.folder||n(process.cwd(),`output`),o=`suburbs`,c=Object.entries(r).flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({category:e,group:t,title:[e,t].filter(Boolean).join(` - `),bbox:n.bbox,instructions:n.instructions,...ve(n.features)}))),l={fields:[],results:c.map(e=>({id:K(e.title),url:`https://${i[1]}.github.io/${i[2]}/${o}/${K(e.title)}.osmPatch.geo.json`,name:e.title,title:e.title,totalCount:e.totalCount,source:``,snippet:e.count,extent:[[e.bbox.minLng,e.bbox.minLat],[e.bbox.maxLng,e.bbox.maxLat]],instructions:e.instructions,groupCategories:[e.category,`/Categories/Addresses`]})).toSorted((e,t)=>e.name.localeCompare(t.name))},u={type:`FeatureCollection`,features:c.map(({bbox:e,...t})=>({type:`Feature`,geometry:U(e),properties:t})).toSorted((e,t)=>e.properties.title.localeCompare(t.properties.title))};await s.mkdir(n(a,o),{recursive:!0}),await s.writeFile(n(a,`index.json`),JSON.stringify(l,null,v?2:void 0)),await s.writeFile(n(a,`index.geo.json`),JSON.stringify(u,null,v?2:void 0)),await s.writeFile(n(a,`metrics.json`),JSON.stringify(t,null,2));for(let e of c){let t={type:`FeatureCollection`,...r[e.category][e.group]};await s.writeFile(n(a,o,`${K(e.title)}.osmPatch.geo.json`),JSON.stringify(t,null,v?2:void 0))}}function q(e,t){let n={...e};for(let e in t)n[e]?n[e]!==t[e]&&(n[e]+=`;${t[e]}`):n[e]=t[e];return n}function be(e){let t=!0;for(;t;){t=!1;for(let n in e){let r=e[n],i=r.sectorIds[0];if(r.features.length>40)continue;let a=Object.entries(e).filter(([e,t])=>t.sectorIds.includes(i)&&e!==n&&t.features.length<=40&&r.features.length+t.features.length<110).toSorted(([,e],[,t])=>P(r.bbox,e.bbox)-P(r.bbox,t.bbox))[0];if(!a)continue;let[o,s]=a,c=H([{type:`Feature`,geometry:U(r.bbox),properties:{}},{type:`Feature`,geometry:U(s.bbox),properties:{}}]);if(F(c)>4&&(F(r.bbox)<4||F(s.bbox)<4))continue;r.bbox=c,r.sectorIds=[...new Set([...r.sectorIds,...s.sectorIds])],r.changesetTags=q(r.changesetTags||{},s.changesetTags||{}),s.instructions&&(r.instructions||=``,r.instructions+=`\n\n${s.instructions}`,r.instructions.trim()),r.features.push(...s.features),delete e[n],delete e[o];let{name:l}=q({name:n},{name:o});e[l]=r,t=!0}}return e}async function xe(e,t,n,r,i){let a=new Set(JSON.parse(await s.readFile(e.tempFileNames.ignore_list,`utf8`))),o=0,c=0,l=0;function u(e){e.flags&1&&o++,e.flags&2&&c++,e.flags&8&&l++}for(let e of Object.values(n.withRef))u(e);for(let e of Object.values(n.noRef))u(e);for(let e of Object.values(n.semi))u(e);for(let e of Object.values(n.duplicateRefs))for(let t of e)u(t);let d={config:e.config,warnings:e.warnings,countsByPhase:{init:{sourceDataset:Object.keys(t).length,ignored:a.size,osm:{withRef:Object.keys(n.withRef).length,noRef:Object.keys(n.noRef).length,semi:Object.keys(n.semi).length,duplicateRefs:Object.values(n.duplicateRefs).flat().length,lastEditedByImporter:o,recentlyChanged:c,recentlyChecked:l}},matched:{1:r[1].length,2:r[2].length,3:Object.keys(r[3]).length,4:r[4].length,5:r[5].length,6:r[6].length},conflated:{create:0,edit:0,delete:0,perfect:0}}};for(let e of Object.values(i).flatMap(e=>Object.values(e)))for(let t of e.features)t.properties.__action===`edit`?d.countsByPhase.conflated.edit++:t.properties.__action===`delete`?d.countsByPhase.conflated.delete++:d.countsByPhase.conflated.create++;return d.countsByPhase.conflated.perfect=n.count-d.countsByPhase.conflated.edit-d.countsByPhase.conflated.delete,d}async function Se(e){if(!process.env.GITHUB_ACTIONS){console.info(`Skipping run history update, because not running in GitHub Actions`);return}let t=await te(`osm-conflation-engine`),n=await fetch(`${b}/api/run_history/${e}`,{method:`PUT`,headers:{Authorization:`Bearer ${t}`}});if(n.status!==200)throw Error(await n.text())}const J=`invalid value returned by your callback function`;function Ce(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function Y(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Ce(e.tags)}function X(e,t,n){if(!t&&!n)throw Error(`requires either o or s`);t&&(e.tags.__action||=`edit`);let[r,i]=t?.centroid||n?.centroid;return{type:`Feature`,id:t?.id||n?.id,geometry:e.tags.__action===`delete`?{type:`Polygon`,coordinates:V({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:B({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function we(e,t,n){let r={...n.noRef};for(let e of[`withRef`,`semi`,`duplicateRefs`])for(let t in n[e]){let i=n[e][t];if(Array.isArray(i))for(let e of i)r[e.id]=e;else r[i.id]=i}let i={};function a(t,n,r){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||={},i[n][r]||=[],i[n][r].push(...t.createFeatures)))}let o=JSON.parse(await s.readFile(e.tempFileNames.matches,`utf8`));for(let{source:n,osm:s}of o[1]){let o=r[s],c=t[n];if(o.flags&8)continue;let l=await e.callbacks.mergeOneToOne({osm:o,source:c});if(typeof l!=`object`)throw TypeError(J);if(!Y(l.diff))continue;let u=l.category||``,d=l.group||o.sectors[0];a(l.extra,u,d),i[u]||={},i[u][d]||=[],i[u][d].push(X(l.diff,o,c))}for(let{source:n,osm:s}of o[2]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=t[n],l=await e.callbacks.mergeOneToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,u=l.group||n.sectors[0];a(l.extra,s,u),i[s]||={},i[s][u]||=[],i[s][u].push(X(r,n,c))}}}for(let n in o[3]){let s=n,c=r[s],l=o[3][s].map(e=>t[e]);if(c.flags&8)continue;let u=await e.callbacks.mergeManyToOne?.({osm:c,source:l});if(!u)continue;if(typeof u!=`object`||typeof u.diff!=`object`)throw TypeError(J);if(!Y(u.diff))continue;let d=u.category||``,f=u.group||c.sectors[0];a(u.extra,d,f),i[d]||={},i[d][f]||=[],i[d][f].push(X(u.diff,c))}for(let{source:n,osm:s}of o[4]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=n.map(e=>t[e]),l=await e.callbacks.mergeManyToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,c=l.group||n.sectors[0];a(l.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(r,n))}}}for(let t of o[5]){let r=n.withRef[t];if(r.flags&8)continue;let o=await e.callbacks.deleteFeature?.({osm:r});if(!o)continue;if(typeof o!=`object`||typeof o.diff!=`object`)throw TypeError(J);if(!Y(o.diff))continue;let s=o.category||``,c=o.group||r.sectors[0];a(o.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(o.diff,r))}for(let r of o[6]){let o=t[r.source],s=await e.callbacks.create({source:o,osmCandidates:r.osmCandidates.map(e=>n.noRef[e])});if(!s)continue;if(typeof s!=`object`||typeof s.diff!=`object`)throw TypeError(J);if(!Y(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.category||``,u=s.group||c?.sectors[0]||`unknown`;a(s.extra,l,u),i[l]||={},i[l][u]||=[],i[l][u].push(X(s.diff,c,o))}let c={};for(let n in i){c[n]||={};for(let a in i[n]){let{changesetTags:o={},instructions:s}=await e.callbacks.getChangesetTags?.({category:n,group:a})||{};o.created_by||=`osm-conflation-engine`,o.import||=`yes`,o.source||=e.config.metadata.wiki_page;let l=i[n][a];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({category:n,group:a,osmData:r,sourceData:t,features:l});let u=W(e,a,{changesetTags:o,instructions:s},l);Object.assign(c[n],u)}_e(c[n]),be(c[n])}let l=await xe(e,t,n,o,c);return await ye(e,l,c),await Se(e.config.merge.osm_key),l}async function Z(e){console.info(`Reading source data into memory...`);let t=0,n={},r=l({input:i(e.config.source_data.file),crlfDelay:1/0});for await(let i of r){let r=JSON.parse(i),a=r.properties[e.config.merge.dataset_column];n[a]=Object.assign(r,{sectors:D(r.geometry,e.config.merge.sector_resolution),centroid:r.geometry.type===`Point`?r.geometry.coordinates:g(r.geometry)}),t++}console.info(`\tread ${t} rows.`),console.info(`Reading osm data into memory (withRef)...`);let a=await T(e.tempFileNames.osm_processed_with_ref,t=>t.tags[e.config.merge.osm_key]);console.info(`Reading osm data into memory (noRef)...`);let o=await T(e.tempFileNames.osm_processed_no_ref,e=>e.id);console.info(`Reading osm data into memory (other)...`);let c={withRef:a,noRef:o,...JSON.parse(await s.readFile(e.tempFileNames.osm_processed_other,`utf8`))};return console.info(` read data.`),{sourceData:n,osmData:c}}function Q(e){for(let t of e)if(!y.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${y.join(`, `)}`)}async function $(e){let t=G(e.metadata.name).slice(0,12),i=v?`-mock`:``,a=n(r(),`oce-${i}`,t);return await s.mkdir(a,{recursive:!0}),{id:t,tempFolder:a,tempFileNames:Object.fromEntries(Object.entries(ne).map(([e,t])=>[e.toLowerCase(),n(a,t)]))}}async function Te(e,t,n={}){let r=JSON.parse(await s.readFile(new URL(`config.schema.json`,import.meta.url),`utf8`)),i=new c().compile(r);if(!i(e))throw AggregateError(i.errors.map(e=>TypeError(e.message+JSON.stringify(e,null,2),{cause:e})),`Config file does not conform to the schema`);let{id:a,tempFolder:o,tempFileNames:l}=await $(e),u={use_cache:!!n.use_cache,id:a,tempFolder:o,tempFileNames:l,config:e,callbacks:t,warnings:[]};console.info(`temp folder:`,o);let{steps:d=y}=n;Q(d),d.includes(`download`)&&(console.time(`download`),await ce(u),console.timeEnd(`download`));let f,p;d.includes(`match`)&&({sourceData:f,osmData:p}=await Z(u),console.time(`match`),await R(u,f,p),console.timeEnd(`match`));let m;return d.includes(`conflate`)&&((!f||!p)&&({sourceData:f,osmData:p}=await Z(u)),console.time(`conflate`),m=await we(u,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}export{S as OsmFlags,$ as getTempFileNames,T as readJsonL,Te as run,E as writeJsonL};
|
|
6
|
+
`,i=t?await s.readFile(t,`utf8`):r;console.info(`fetching OSM data via overpass…`);let a=await fetch(`${n||`https://overpass-api.de/api/interpreter`}?data=${encodeURIComponent(i)}`,{headers:x(e.config)});if(!a.ok){let e=await a.text()||a.statusText;throw Error(`HTTP Error ${a.status} ${e}`)}let o=await a.json(),{pickTags:c}=C(e),l={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0};for(let t of o.elements)O(e,l,{id:`${t.type[0]}${t.id}`,centroid:t.type===`node`?[t.lon,t.lat]:t.center?[t.center.lon,t.center.lat]:[0,0],tags:c(t.tags),metadata:{changeset:t.changeset,timestamp:t.timestamp,user:t.user,version:t.version},sectors:[],flags:0});await k(e,l)}const j=`https://osm-internal.download.geofabrik.de`,M=`https://www.openstreetmap.org`,N=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function ie(e,t){if(t.startsWith(j)||e.warnings.push(`(!) Planet PBF url is not from ${j}, so metadata may not be available.`),!process.env.OSM_AUTH)return e.warnings.push(`(!) No OSM credentials, so the planet extract will not include metadata`),{url:t.replace(j,`https://download.geofabrik.de`).replace(`-internal.osm.pbf`,`.osm.pbf`),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=x(e.config),i=await fetch(`${j}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${M}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${M}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:N(await a.text())}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}});if(s.status!==200)throw Error(`Login failed`);o+=`; ${s.headers.get(`Set-Cookie`)}`;let c=await fetch(i.authorization_url,{headers:{...r,Cookie:o}}),l=c.url.startsWith(M)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:N(await c.text()),state:i.state,response_type:`code`,scope:`read_prefs`,nonce:``,code_challenge:``,code_challenge_method:``,commit:`Authorize`}),headers:{...r,Cookie:o,"Content-Type":`application/x-www-form-urlencoded`}}).then(e=>e.text()):await c.text();if(!l.includes(`gf_download_oauth`))throw Error(`No token`);return{url:t,cookies:l}}async function ae(n,r){let i=n.tempFileNames.pbf,{cookies:a,url:o}=await ie(n,r);if(await s.mkdir(t(i),{recursive:!0}),await s.rm(i,{recursive:!0,force:!0}),/^https?:\/\//.test(o)){console.info(`downloading from`,o,`to`,i);let n=m(`curl`,[`-L`,o,`-H`,`Cookie: ${a}`,`--output`,e(i)],{cwd:t(i)});n.stdout.pipe(process.stdout),n.stderr.pipe(process.stderr),await new Promise((e,t)=>{n.on(`close`,n=>n?t(n):e())}),await p(5e3)}else console.info(`copying file from`,o,`to`,i),await s.copyFile(o,i);console.info(i,await s.stat(i))}async function oe(e){let t=e.config.merge.osm_key,n=e.config.e2e_tests?.ignore_list_file_path;if(v&&!n)throw Error(`When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath`);let r=v&&n?JSON.parse(await s.readFile(n,`utf8`)):await fetch(`${b}/api/ignore_list/${encodeURIComponent(t)}?return_ids_only=true`).then(e=>e.json());await s.writeFile(e.tempFileNames.ignore_list,JSON.stringify(r.rows,null,2))}async function se(e,t){if(e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}let{pickTags:n}=C(e);console.info(`Starting preprocess of OSM data...`),await k(e,await new Promise((r,i)=>{let a={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},o=!1,s=!1;h.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{s||=!0;let r=t.type===`node`?t:t.centroid,i=t.meta;i&&(o=!0);let c=n(t.tags);O(e,a,{id:`${t.type[0]}${t.id}`,centroid:[r.lon,r.lat],metadata:i?{changeset:i.changeset,timestamp:i.timestamp?new Date(1e3*i.timestamp).toISOString():void 0,user:i.user,version:i.version}:void 0,tags:c,sectors:[],flags:0}),a.count%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{if(o||e.warnings.push(`No metadata extracted from the planet file!`),!s)throw Error(`Nothing extracted from the pbf file, presumably it’s invalid.`);r(a)}).on(`error`,i)}))}async function ce(e){if(await oe(e),e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached OSM data`);return}switch(e.config.o_data.source.type){case`pbf`:await ae(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:await A(e,e.config.o_data.source.overpass_query_file,e.config.o_data.source.overpass_server_url);break;case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function le(e,t,n){let r=[];for(let e in t.withRef){let i=e,a=t.withRef[i];!(i in n)&&!(a.flags&8)&&r.push(i)}let i={};for(let t in n){let r=t,a=n[r];for(let t of a.sectors){i[t]||={};let n=e.callbacks.getLocalKeyForSource(a);i[t][n]=r}}let a={},o=[];for(let n of r){let r=t.withRef[n],s=e.callbacks.getLocalKeyForOsm(r),c=new Set(r.sectors.map(e=>i[e]?.[s]).filter(Boolean));if(c.size>1&&console.warn(`${r.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`),c.size===1){let e=[...c][0];t.withRef[e]?o.push(n):a[e]=n}else o.push(n)}return{refsThatChanged:a,toDelete:o}}const ue=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`;function P(e,t){let n=Math.max(0,t.minLat-e.maxLat,e.minLat-t.maxLat),r=Math.max(0,t.minLng-e.maxLng,e.minLng-t.maxLng);return Math.hypot(n,r)}function F(e){return Math.max(Math.abs(e.maxLat-e.minLat),Math.abs(e.maxLng-e.minLng))}const I=new Map,L=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=I.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),I.set(t,n)),n===r}).map(e=>{let n=6371008.8*ee(e.centroid,t.centroid);return Object.assign(e,{offset:n})}).filter(e=>e.offset<200).toSorted((e,t)=>e.offset-t.offset)};async function R(e,t,n){let r=Object.keys(t).length,i=new Set(JSON.parse(await s.readFile(e.tempFileNames.ignore_list,`utf8`)));console.info(`processing deleted data...`),console.time(`matchDeletions`);let{refsThatChanged:a,toDelete:o}=le(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let c=0;console.time(`matchLoop`);let l={1:[],2:[],3:{},4:[],5:o,6:[]},u=Object.values(n.noRef),d={};for(let e in n.semi){let t=e.split(`;`);for(let[n,r]of t.entries())d[r]||=[],d[r].push({value:e,isFirst:!n})}for(let o in t){let s=o,f=a[s]||s;if(i.has(f)||f in a)continue;let p=n.withRef[f],m=t[s],h=n.duplicateRefs[f];if(d[f])if(d[f][0].isFirst)f=d[f][0].value;else continue;let g=n.semi[f];if(p&&g){let e=f.split(`;`);l[4].push({osm:[p.id,g.id],source:e})}else if(p)l[1].push({osm:p.id,source:s});else if(h)l[2].push({osm:h.map(e=>e.id),source:f});else if(g){let e=f.split(`;`);l[3][g.id]||=[],l[3][g.id]?.push(...e)}else{let t=L(e,m,u);t.length===1?l[1].push({source:f,osm:t[0].id}):l[6].push({source:f,osmCandidates:t.map(e=>e.id)})}c+=1,c%1e3||process.stdout.write(`${(c/r*100).toFixed(1)}% `)}return console.timeEnd(`matchLoop`),await s.writeFile(e.tempFileNames.matches,JSON.stringify(l,null,void 0)),{output:l}}const z=2e-4;function B({lat:e,lng:t}){let n=[[t,e+z],[t+z,e],[t,e-z],[t-z,e]];return n.push(n[0]),[n]}function V({lat:e,lng:t}){let n=[[t+z/2,e+z/2],[t+z/2,e-z/2],[t-z/2,e-z/2],[t-z/2,e+z/2]];return n.push(n[0]),[n]}function H(e){let t={minLat:1/0,minLng:1/0,maxLat:-1/0,maxLng:-1/0};function n([e,n]){let r=+n,i=+e;r<t.minLat&&(t.minLat=r),i<t.minLng&&(t.minLng=i),r>t.maxLat&&(t.maxLat=r),i>t.maxLng&&(t.maxLng=i)}for(let t of e)switch(t.geometry.type){case`Point`:n(t.geometry.coordinates);break;case`LineString`:t.geometry.coordinates.forEach(n);break;case`Polygon`:for(let e of t.geometry.coordinates)e.forEach(n);break;case`MultiPolygon`:for(let e of t.geometry.coordinates)for(let t of e)t.forEach(n);break;default:}return t}function U({minLng:e,minLat:t,maxLng:n,maxLat:r}){return{type:`Polygon`,coordinates:[[[e,t],[n,t],[n,r],[e,r],[e,t]]]}}function W(e,t,n,r,i=0){if(!r.length)return{};let a=H(r),o=D(U(a),e.config.merge.sector_resolution),s=i>10;if(s&&console.info(`(!) Couldn't split ${t} (${r.length} left)`),r.length<=110||s)return{[t]:{features:r,bbox:a,sectorIds:o,...n}};if(a.maxLat-a.minLat>a.maxLng-a.minLng){let o=a.minLat+(a.maxLat-a.minLat)/2,s=[[],[]];for(let e of r)s[+(g(e.geometry)[1]<o)].push(e);let[c,l]=s;return{...W(e,`${t}^N`,n,c,i+1),...W(e,`${t}^S`,n,l,i+1)}}let c=a.minLng+(a.maxLng-a.minLng)/2,l=[[],[]];for(let e of r)l[+(g(e.geometry)[0]<c)].push(e);let[u,d]=l;return{...W(e,`${t}^E`,n,u,i+1),...W(e,`${t}^W`,n,d,i+1)}}const{sin:de,cos:fe,floor:pe,sqrt:me,PI:he}=Math;function ge(e){let t=pe(me(e)),n=(e-t**2)*he/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*fe(n),t*de(n)];return[+r.toFixed(2),+i.toFixed(2)]}function _e(e){for(let t in e){let n=e[t],r={};for(let e of n.features)if(e.geometry.type===`Point`){let[t,n]=e.geometry.coordinates,i=ue(n,t,4);r[i]||=[],r[i].push(e)}for(let e of Object.values(r))if(e.length>1)for(let t=0;t<e.length;t+=1){let[n,r]=ge(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function G(e){return _(`sha256`).update(e,`utf8`).digest(`hex`)}function ve(e){let t=0,n=0,r=0,i=0;for(let a of e)switch(a.properties.__action){case`delete`:n+=1;break;case`move`:r+=1;break;case`edit`:i+=1;break;default:t+=1}return{count:[t&&`add ${t}`,n&&`delete ${n}`,r&&`move ${r}`,i&&`edit ${i}`].filter(e=>!!e).join(`, `),totalCount:t+n+r+i}}function K(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``).slice(0,100)}_${G(e).slice(0,6)}`}async function ye(e,t,r){let i=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!i)throw Error(`git_repository is not a valid github url`);let a=e.config.output?.folder||n(process.cwd(),`output`),o=`suburbs`,c=Object.entries(r).flatMap(([e,t])=>Object.entries(t).map(([t,n])=>({category:e,group:t,title:[e,t].filter(Boolean).join(` - `),bbox:n.bbox,instructions:n.instructions,...ve(n.features)}))),l={fields:[],results:c.map(e=>({id:K(e.title),url:`https://${i[1]}.github.io/${i[2]}/${o}/${K(e.title)}.osmPatch.geo.json`,name:e.title,title:e.title,totalCount:e.totalCount,source:``,snippet:e.count,extent:[[e.bbox.minLng,e.bbox.minLat],[e.bbox.maxLng,e.bbox.maxLat]],instructions:e.instructions,groupCategories:[e.category,`/Categories/Addresses`]})).toSorted((e,t)=>e.name.localeCompare(t.name))},u={type:`FeatureCollection`,features:c.map(({bbox:e,...t})=>({type:`Feature`,geometry:U(e),properties:t})).toSorted((e,t)=>e.properties.title.localeCompare(t.properties.title))};await s.mkdir(n(a,o),{recursive:!0}),await s.writeFile(n(a,`index.json`),JSON.stringify(l,null,v?2:void 0)),await s.writeFile(n(a,`index.geo.json`),JSON.stringify(u,null,v?2:void 0)),await s.writeFile(n(a,`metrics.json`),JSON.stringify(t,null,2));for(let e of c){let t={type:`FeatureCollection`,...r[e.category][e.group]};await s.writeFile(n(a,o,`${K(e.title)}.osmPatch.geo.json`),JSON.stringify(t,null,v?2:void 0))}}function q(e,t){let n={...e};for(let e in t)n[e]?n[e]!==t[e]&&(n[e]+=`;${t[e]}`):n[e]=t[e];return n}function be(e){let t=!0;for(;t;){t=!1;for(let n in e){let r=e[n],i=r.sectorIds[0];if(r.features.length>40)continue;let a=Object.entries(e).filter(([e,t])=>t.sectorIds.includes(i)&&e!==n&&t.features.length<=40&&r.features.length+t.features.length<110).toSorted(([,e],[,t])=>P(r.bbox,e.bbox)-P(r.bbox,t.bbox))[0];if(!a)continue;let[o,s]=a,c=H([{type:`Feature`,geometry:U(r.bbox),properties:{}},{type:`Feature`,geometry:U(s.bbox),properties:{}}]);if(F(c)>4&&(F(r.bbox)<4||F(s.bbox)<4))continue;r.bbox=c,r.sectorIds=[...new Set([...r.sectorIds,...s.sectorIds])],r.changesetTags=q(r.changesetTags||{},s.changesetTags||{}),s.instructions&&(r.instructions||=``,r.instructions+=`\n\n${s.instructions}`,r.instructions.trim()),r.features.push(...s.features),delete e[n],delete e[o];let{name:l}=q({name:n},{name:o});e[l]=r,t=!0}}return e}async function xe(e,t,n,r,i){let a=new Set(JSON.parse(await s.readFile(e.tempFileNames.ignore_list,`utf8`))),o=0,c=0,l=0;function u(e){e.flags&1&&o++,e.flags&2&&c++,e.flags&8&&l++}for(let e of Object.values(n.withRef))u(e);for(let e of Object.values(n.noRef))u(e);for(let e of Object.values(n.semi))u(e);for(let e of Object.values(n.duplicateRefs))for(let t of e)u(t);let d={config:e.config,warnings:e.warnings,countsByPhase:{init:{sourceDataset:Object.keys(t).length,ignored:a.size,osm:{withRef:Object.keys(n.withRef).length,noRef:Object.keys(n.noRef).length,semi:Object.keys(n.semi).length,duplicateRefs:Object.values(n.duplicateRefs).flat().length,lastEditedByImporter:o,recentlyChanged:c,recentlyChecked:l}},matched:{1:r[1].length,2:r[2].length,3:Object.keys(r[3]).length,4:r[4].length,5:r[5].length,6:r[6].length},conflated:{create:0,edit:0,delete:0,perfect:0}}};for(let e of Object.values(i).flatMap(e=>Object.values(e)))for(let t of e.features)t.properties.__action===`edit`?d.countsByPhase.conflated.edit++:t.properties.__action===`delete`?d.countsByPhase.conflated.delete++:d.countsByPhase.conflated.create++;return d.countsByPhase.conflated.perfect=n.count-d.countsByPhase.conflated.edit-d.countsByPhase.conflated.delete,d}async function Se(e){if(v)return;if(!process.env.GITHUB_ACTIONS){console.info(`Skipping run history update, because not running in GitHub Actions`);return}let t=await te(`osm-conflation-engine`),n=e.config.merge.osm_key,r=await fetch(`${b}/api/run_history/${n}`,{method:`PUT`,headers:{Authorization:`Bearer ${t}`},body:JSON.stringify(e)});if(r.status!==200)throw Error(await r.text())}const J=`invalid value returned by your callback function`;function Ce(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function Y(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Ce(e.tags)}function X(e,t,n){if(!t&&!n)throw Error(`requires either o or s`);t&&(e.tags.__action||=`edit`);let[r,i]=t?.centroid||n?.centroid;return{type:`Feature`,id:t?.id||n?.id,geometry:e.tags.__action===`delete`?{type:`Polygon`,coordinates:V({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:B({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function we(e,t,n){let r={...n.noRef};for(let e of[`withRef`,`semi`,`duplicateRefs`])for(let t in n[e]){let i=n[e][t];if(Array.isArray(i))for(let e of i)r[e.id]=e;else r[i.id]=i}let i={};function a(t,n,r){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||={},i[n][r]||=[],i[n][r].push(...t.createFeatures)))}let o=JSON.parse(await s.readFile(e.tempFileNames.matches,`utf8`));for(let{source:n,osm:s}of o[1]){let o=r[s],c=t[n];if(o.flags&8)continue;let l=await e.callbacks.mergeOneToOne({osm:o,source:c});if(typeof l!=`object`)throw TypeError(J);if(!Y(l.diff))continue;let u=l.category||``,d=l.group||o.sectors[0];a(l.extra,u,d),i[u]||={},i[u][d]||=[],i[u][d].push(X(l.diff,o,c))}for(let{source:n,osm:s}of o[2]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=t[n],l=await e.callbacks.mergeOneToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,u=l.group||n.sectors[0];a(l.extra,s,u),i[s]||={},i[s][u]||=[],i[s][u].push(X(r,n,c))}}}for(let n in o[3]){let s=n,c=r[s],l=o[3][s].map(e=>t[e]);if(c.flags&8)continue;let u=await e.callbacks.mergeManyToOne?.({osm:c,source:l});if(!u)continue;if(typeof u!=`object`||typeof u.diff!=`object`)throw TypeError(J);if(!Y(u.diff))continue;let d=u.category||``,f=u.group||c.sectors[0];a(u.extra,d,f),i[d]||={},i[d][f]||=[],i[d][f].push(X(u.diff,c))}for(let{source:n,osm:s}of o[4]){let o=s.map(e=>r[e]).filter(e=>!(e.flags&8)),c=n.map(e=>t[e]),l=await e.callbacks.mergeManyToMany?.({osm:o,source:c});if(l){if(typeof l!=`object`||typeof l.diffPerFeature!=`object`)throw TypeError(J);for(let e in l.diffPerFeature){let t=e,n=o.find(e=>e.id===t),r=l.diffPerFeature[t];if(typeof r!=`object`)throw TypeError(J);if(!Y(r))continue;let s=l.category||``,c=l.group||n.sectors[0];a(l.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(r,n))}}}for(let t of o[5]){let r=n.withRef[t];if(r.flags&8)continue;let o=await e.callbacks.deleteFeature?.({osm:r});if(!o)continue;if(typeof o!=`object`||typeof o.diff!=`object`)throw TypeError(J);if(!Y(o.diff))continue;let s=o.category||``,c=o.group||r.sectors[0];a(o.extra,s,c),i[s]||={},i[s][c]||=[],i[s][c].push(X(o.diff,r))}for(let r of o[6]){let o=t[r.source],s=await e.callbacks.create({source:o,osmCandidates:r.osmCandidates.map(e=>n.noRef[e])});if(!s)continue;if(typeof s!=`object`||typeof s.diff!=`object`)throw TypeError(J);if(!Y(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.category||``,u=s.group||c?.sectors[0]||`unknown`;a(s.extra,l,u),i[l]||={},i[l][u]||=[],i[l][u].push(X(s.diff,c,o))}let c={};for(let n in i){c[n]||={};for(let a in i[n]){let{changesetTags:o={},instructions:s}=await e.callbacks.getChangesetTags?.({category:n,group:a})||{};o.created_by||=`osm-conflation-engine`,o.import||=`yes`,o.source||=e.config.metadata.wiki_page;let l=i[n][a];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({category:n,group:a,osmData:r,sourceData:t,features:l});let u=W(e,a,{changesetTags:o,instructions:s},l);Object.assign(c[n],u)}_e(c[n]),be(c[n])}let l=await xe(e,t,n,o,c);return await ye(e,l,c),await Se(l),l}async function Z(e){console.info(`Reading source data into memory...`);let t=0,n={},r=l({input:i(e.config.source_data.file),crlfDelay:1/0});for await(let i of r){let r=JSON.parse(i),a=r.properties[e.config.merge.dataset_column];n[a]=Object.assign(r,{sectors:D(r.geometry,e.config.merge.sector_resolution),centroid:r.geometry.type===`Point`?r.geometry.coordinates:g(r.geometry)}),t++}console.info(`\tread ${t} rows.`),console.info(`Reading osm data into memory (withRef)...`);let a=await T(e.tempFileNames.osm_processed_with_ref,t=>t.tags[e.config.merge.osm_key]);console.info(`Reading osm data into memory (noRef)...`);let o=await T(e.tempFileNames.osm_processed_no_ref,e=>e.id);console.info(`Reading osm data into memory (other)...`);let c={withRef:a,noRef:o,...JSON.parse(await s.readFile(e.tempFileNames.osm_processed_other,`utf8`))};return console.info(` read data.`),{sourceData:n,osmData:c}}function Q(e){for(let t of e)if(!y.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${y.join(`, `)}`)}async function $(e){let t=G(e.metadata.name).slice(0,12),i=v?`-mock`:``,a=n(r(),`oce-${i}`,t);return await s.mkdir(a,{recursive:!0}),{id:t,tempFolder:a,tempFileNames:Object.fromEntries(Object.entries(ne).map(([e,t])=>[e.toLowerCase(),n(a,t)]))}}async function Te(e,t,n={}){let r=JSON.parse(await s.readFile(new URL(`config.schema.json`,import.meta.url),`utf8`)),i=new c().compile(r);if(!i(e))throw AggregateError(i.errors.map(e=>TypeError(e.message+JSON.stringify(e,null,2),{cause:e})),`Config file does not conform to the schema`);let{id:a,tempFolder:o,tempFileNames:l}=await $(e),u={use_cache:!!n.use_cache,id:a,tempFolder:o,tempFileNames:l,config:e,callbacks:t,warnings:[]};console.info(`temp folder:`,o);let{steps:d=y}=n;Q(d),d.includes(`download`)&&(console.time(`download`),await ce(u),console.timeEnd(`download`));let f,p;d.includes(`match`)&&({sourceData:f,osmData:p}=await Z(u),console.time(`match`),await R(u,f,p),console.timeEnd(`match`));let m;return d.includes(`conflate`)&&((!f||!p)&&({sourceData:f,osmData:p}=await Z(u)),console.time(`conflate`),m=await we(u,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}export{S as OsmFlags,$ as getTempFileNames,T as readJsonL,Te as run,E as writeJsonL};
|
|
7
7
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","fs","fs","fs","fs","getFirstCoord","hash","fs","fs","fs","fs","fs"],"sources":["../src/constants/defaults.ts","../src/types/callbacks.def.ts","../src/steps/download/util/validateOsmTagsInConfig.ts","../src/common/helpers.ts","../src/common/jsonl.ts","../src/common/getSector.ts","../src/steps/download/util/loadOsmFeature.ts","../src/steps/download/downloadFromOverpass.ts","../src/steps/download/downloadFromPlanetPbf.ts","../src/steps/download/fetchIgnoreList.ts","../src/steps/download/osmToJson.ts","../src/steps/download/index.ts","../src/steps/match/processDeletions.ts","../src/common/geo.ts","../src/steps/match/findPotentialOFeatures.ts","../src/steps/match/index.ts","../src/steps/conflate/createDiamond.ts","../src/common/calcBBox.ts","../src/common/bboxToPolygon.ts","../src/steps/conflate/splitUntilSmallEnough.ts","../src/steps/conflate/spreadToGrid.ts","../src/helpers.ts","../src/common/calcCount.ts","../src/steps/conflate/createIndexAndSaveToDisk.ts","../src/steps/conflate/mergeTinyDatasets.ts","../src/steps/conflate/stats/generateDataForWebsite.ts","../src/steps/conflate/stats/writeToRunHistory.ts","../src/steps/conflate/index.ts","../src/steps/match/readData.ts","../src/main.ts"],"sourcesContent":["import type { Config } from '../types/config.def.js';\n\n/** if a feature was last edited within this many DAYS, then it's \"recently edited\" */\nexport const RECENT_THRESHOLD = 90;\n\n/** can be overriden by the config */\nexport const DEFAULT_SECTOR_RESOLUTION = 3;\n\nexport const IS_UNIT_TEST = process.env.NODE_ENV === 'test';\n\n/**\n * this is not a hard minimum, if a dataset has fewer than this number,\n * we will try to merge it into the nearest datasets in the same sector.\n */\n// TODO: make configurable\nexport const MIN_ITEMS_PER_DATASET = 40;\nexport const MAX_ITEMS_PER_DATASET = 110;\nexport const MAX_BBOX_DEGREES = 4;\n\n/** can be customised in the config */\nexport const CHECK_DATE_KEY = 'check_date';\n\nexport const STEPS = <const>['download', 'match', 'conflate'];\nexport type Step = (typeof STEPS)[number];\n\nexport const FILE_NAMES = <const>{\n // o_data\n PBF: 'o.pbf',\n OVERPASS: 'o.geo.json',\n POSTPASS: 'o.geo.json',\n OSM_PROCESSED_WITH_REF: 'osm-processed-with-ref.jsonl',\n OSM_PROCESSED_NO_REF: 'osm-processed-no-ref.jsonl',\n OSM_PROCESSED_OTHER: 'osm-processed-other.json',\n\n // source data\n\n // other data\n IGNORE_LIST: 'ignore-list.json',\n\n // matched\n MATCHES: 'matches.json',\n};\nexport type FileNames = keyof typeof FILE_NAMES;\n\nexport const API_BASE_URL = 'https://osm-conflation-engine.kyle.kiwi';\nexport const REPO_URL = 'https://github.com/osm-nz/osm-conflation-engine';\n\nexport const getHttpHeaders = (config: Config) => ({\n 'User-Agent': `${REPO_URL} on behalf of ${config.metadata.wiki_page}`,\n});\n","import type {\n OsmPatchFeature,\n OsmFeature as StandardOsmFeature,\n Tags,\n} from 'osm-api';\nimport type { Feature, GeoJsonProperties, Geometry } from 'geojson';\nimport type { Step } from '../constants/defaults.js';\nimport type { MatchType, SourceData } from './internal.def.js';\nimport type { Config } from './config.def.js';\n\nexport type TagDiff = OsmPatchFeature['properties'];\n\n/** see microsoft/TypeScript#202 */\nexport type Identity<out T> = { _: T; readonly __: unique symbol };\n\nexport type OsmFeatureTypeShort = 'n' | 'w' | 'r';\nexport type OsmId = `${OsmFeatureTypeShort}${number}`;\nexport type DatasetId = string & Identity<'DatasetId'>;\n\nexport type Vec2 = [lon: number, lat: number];\n\nexport enum OsmFlags {\n None = 0,\n IsLastEditedByImporter = 1 << 0,\n IsRecentlyChanged = 1 << 1,\n IsChecked = 1 << 2,\n IsCheckedRecently = 1 << 3,\n}\n\nexport interface OsmFeature {\n id: OsmId;\n sectors: string[];\n centroid: Vec2;\n tags: Tags;\n metadata?: Partial<\n Pick<StandardOsmFeature, 'changeset' | 'version' | 'user' | 'timestamp'>\n >;\n flags: number & OsmFlags;\n}\n\nexport interface SourceDataFeature<\n G extends Geometry = Geometry,\n P extends GeoJsonProperties = GeoJsonProperties,\n> extends Feature<G, P> {\n /** list of sector IDs (h3 hexes) that this feature passes through */\n sectors: string[];\n centroid: Vec2;\n}\n\ntype MaybePromise<T> = T | Promise<T>;\n\nexport interface ConflationResultExtra {\n createFeatures?: OsmPatchFeature[];\n warnings?: string[];\n}\n\nexport interface ConflationDiff {\n tags: TagDiff;\n geometry?: Geometry;\n}\n\nexport interface SingleFeatureConflationResult {\n category?: string;\n group?: string;\n diff: ConflationDiff;\n extra?: ConflationResultExtra;\n}\n\nexport interface MutliFeatureConflationResult {\n category?: string;\n group?: string;\n diffPerFeature: { [id: OsmId]: ConflationDiff };\n extra?: ConflationResultExtra;\n}\n\n// this is the public API, so any break changes require a major version bump\nexport interface Callbacks<G extends Geometry, P extends GeoJsonProperties> {\n /**\n * called for every OSM feature, you should return a key which is unique\n * within its sector. For example, this could be the most important tags\n * concatenated together, if those tags are usually unique within the\n * local area (the sector).\n */\n getLocalKeyForOsm(osmFeature: OsmFeature): string;\n /**\n * see docs for {@link getLocalKeyForOsm}. This is the equivilant callback\n * for the source data, which is called for every feature.\n */\n getLocalKeyForSource(sourceFeature: SourceDataFeature<G, P>): string;\n\n /**\n * optional, by default a username which ends in `_import` is considered\n * to be an importer. Use this callback if you want to customise the\n * behaviour.\n */\n isImportUser?(username: string): boolean;\n\n // TODO: documentation for each\n mergeOneToOne(input: {\n source: SourceDataFeature<G, P>;\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n mergeOneToMany?(input: {\n source: SourceDataFeature<G, P>;\n osm: OsmFeature[];\n }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n mergeManyToOne?(input: {\n source: SourceDataFeature<G, P>[];\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n mergeManyToMany?(input: {\n source: SourceDataFeature<G, P>[];\n osm: OsmFeature[];\n }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n /**\n * called for OSM features which have a ref tag that does not exist in the\n * source dataset. You need to decide whether to:\n * (1) delete this feature; or\n * (2) only remove the import related tags; or\n * (3) do nothing.\n */\n deleteFeature?(input: {\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n /**\n * Calls for each feature in the source dataset where a matching feature\n * could not be found in OSM. If there are potential matches, these are\n * supplied using the `osmCandidates` argument. If you choose to accept\n * one of these candidates, return the `selection` property set to that\n * feature's {@link OsmId}. If not, return `selection: undefined`, which\n * will create a new OSM feature instead.\n */\n create(input: {\n source: SourceDataFeature<G, P>;\n osmCandidates: OsmFeature[];\n }): MaybePromise<\n | (SingleFeatureConflationResult & {\n /**\n * undefined means that no selection was made, so we\n * will create a new OSM feature instead\n */\n selection: OsmId | undefined;\n })\n | undefined\n >;\n\n /**\n * optional, called after the conflation stage once all diffs have been\n * allocated to a group. You can apply any addition transformations to\n * the features in each layer.\n */\n postprocessLayer?(input: {\n category: string;\n group: string;\n features: OsmPatchFeature[];\n osmData: Record<OsmId, OsmFeature>;\n sourceData: SourceData<G, P>;\n }): MaybePromise<OsmPatchFeature[] | undefined>;\n\n /**\n * optional, called after the conflation stage for every group. You can\n * return the changeset tags which will be applied to this group, and\n * optionally `instructions` which are shown in the website UI, before\n * someone starts working on this group.\n */\n getChangesetTags?(input: { category: string; group: string }): MaybePromise<{\n instructions?: string;\n changesetTags?: Tags;\n }>;\n}\nexport interface RunOptions {\n steps?: Step[];\n use_cache?: boolean;\n}\n\nexport interface ConflateResult {\n config: Config;\n warnings: string[];\n countsByPhase: {\n init: {\n sourceDataset: number;\n osm: {\n // these 4 are mutataly exclusive\n withRef: number;\n duplicateRefs: number;\n semi: number;\n noRef: number;\n\n // these are independant of the previous 4\n recentlyChecked: number;\n recentlyChanged: number;\n lastEditedByImporter: number;\n };\n ignored: number;\n };\n matched: Record<MatchType, number>;\n conflated: {\n create: number;\n edit: number;\n delete: number;\n perfect: number;\n };\n };\n}\n\nexport interface RunResult {\n conflate?: ConflateResult;\n}\n","import type { Tags } from 'osm-api';\nimport type { Tags as NullishTags } from 'pbf2json';\nimport { CHECK_DATE_KEY } from '../../../constants/defaults.js';\nimport type { Ctx } from '../../../types/internal.def.js';\n\nexport function validateOsmTagsInConfig(ctx: Ctx) {\n const checkDateKey = ctx.config.o_data.check_date_key || CHECK_DATE_KEY;\n\n const requiredKeys = new Set([\n // sanity check that the internal keys are included.\n checkDateKey,\n ctx.config.merge.osm_key,\n ]);\n const missingKeys = requiredKeys.difference(\n new Set(ctx.config.o_data.tags_to_keep),\n );\n if (missingKeys.size) {\n throw new Error(\n `config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...missingKeys].join(', ')}`,\n );\n }\n\n /** re-constructing regexes is expensive, so use a reference instead */\n const regExs: RegExp[] = [];\n for (const keyOrRegEx of ctx.config.o_data.tags_to_keep) {\n if (keyOrRegEx.startsWith('/') && keyOrRegEx.endsWith('/')) {\n regExs.push(new RegExp(keyOrRegEx.slice(1, -1)));\n }\n }\n\n /** cache every seen key, to avoid wasting time with regexes */\n const tagsToKeep: Record<string, boolean> = {};\n function keepKey(key: string) {\n if (key in tagsToKeep) return tagsToKeep[key];\n\n let keep = ctx.config.o_data.tags_to_keep.includes(key);\n\n if (!keep) {\n for (const regex of regExs) {\n if (regex.test(key)) {\n keep = true;\n break;\n }\n }\n }\n\n tagsToKeep[key] = keep;\n return keep;\n }\n\n function pickTags(allTags: NullishTags | undefined) {\n const tags: Tags = {};\n if (allTags) {\n for (const key in allTags) {\n const value = allTags[key];\n if (keepKey(key) && value) {\n tags[key] = value;\n }\n }\n }\n return tags;\n }\n\n return { pickTags };\n}\n","import { CheckDate } from '../types/index.js';\n\n/** number of years before we revisit a feature with check_date */\nconst CHECK_DATE_THRESHOLD_YEARS = 4;\n\n/** checks if an ISO date exists and if it it's less than X years old */\nexport const isChecked = (v: string | undefined): CheckDate => {\n if (!v) return CheckDate.No;\n\n const yearsAgo = (Date.now() - +new Date(v)) / 1000 / 60 / 60 / 24 / 365;\n return yearsAgo < CHECK_DATE_THRESHOLD_YEARS\n ? CheckDate.YesRecent\n : CheckDate.YesExpired;\n};\n","import { createReadStream, createWriteStream } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\n\n/** reads JSONL by parsing one line at a time */\nexport async function readJsonL<T, U extends string>(\n filePath: string,\n getKey: (row: T) => U,\n): Promise<Record<U, T>> {\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream });\n\n const rows = {} as Record<U, T>;\n for await (const line of rl) {\n const row = JSON.parse(line);\n const key = getKey(row);\n rows[key] = row;\n }\n return rows;\n}\n\n/** writes JSONL to a file by stringifying each line at a time */\nexport async function writeJsonL(filePath: string, rows: unknown[]) {\n const writeStream = createWriteStream(filePath, { encoding: 'utf8' });\n\n for (const row of rows) {\n writeStream.write(JSON.stringify(row));\n writeStream.write('\\n');\n }\n\n writeStream.end();\n\n await new Promise((resolve) => writeStream.on('finish', resolve));\n}\n","import type { Geometry } from 'geojson';\nimport {\n POLYGON_TO_CELLS_FLAGS,\n latLngToCell,\n polygonToCellsExperimental,\n} from 'h3-js';\nimport { DEFAULT_SECTOR_RESOLUTION } from '../constants/defaults.js';\n\n/** converts geojson geometry into a sorted list of h3 grids. */\nexport function getSector(\n geometry: Geometry,\n _resolution: number | undefined,\n): string[] {\n const resolution = _resolution || DEFAULT_SECTOR_RESOLUTION;\n switch (geometry.type) {\n case 'GeometryCollection': {\n throw new Error('not supported');\n }\n\n case 'Point': {\n return [\n latLngToCell(\n geometry.coordinates[1]!,\n geometry.coordinates[0]!,\n resolution,\n ),\n ];\n }\n case 'MultiPoint':\n case 'LineString':\n case 'MultiLineString':\n case 'Polygon': {\n return polygonToCellsExperimental(\n geometry.coordinates,\n resolution,\n POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n true,\n ).toSorted();\n }\n\n case 'MultiPolygon': {\n const grids = new Set<string>();\n for (const ring of geometry.coordinates) {\n const newGrids = polygonToCellsExperimental(\n ring,\n resolution,\n POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n true,\n );\n for (const grid of newGrids) grids.add(grid);\n }\n return [...grids].toSorted();\n }\n\n default: {\n throw new TypeError(geometry satisfies never);\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { isChecked } from '../../../common/helpers.js';\nimport { writeJsonL } from '../../../common/jsonl.js';\nimport {\n CHECK_DATE_KEY,\n RECENT_THRESHOLD,\n} from '../../../constants/defaults.js';\nimport {\n type DatasetId,\n type OsmFeature,\n OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n CheckDate,\n type Ctx,\n type OSMData,\n} from '../../../types/internal.def.js';\nimport { getSector } from '../../../common/getSector.js';\n\nconst THRESHOLD_DATE = ((d) => {\n d.setDate(d.getDate() - RECENT_THRESHOLD);\n return +d / 1000;\n})(new Date());\n\nexport function loadOsmFeature(ctx: Ctx, out: OSMData, object: OsmFeature) {\n out.count += 1;\n\n object.sectors = getSector(\n { type: 'Point', coordinates: object.centroid },\n ctx.config.merge.sector_resolution,\n );\n\n const checkDateKey = ctx.config.o_data.check_date_key || CHECK_DATE_KEY;\n\n // boolean flags\n if (+new Date(object.metadata?.timestamp ?? 0) > THRESHOLD_DATE) {\n object.flags |= OsmFlags.IsRecentlyChanged;\n }\n\n const isLastEditedByImporter =\n object.metadata?.user &&\n // TODO: cache the result per username\n (ctx.callbacks.isImportUser\n ? ctx.callbacks.isImportUser?.(object.metadata.user)\n : object.metadata?.user?.endsWith('_import'));\n if (object.metadata?.version === 1 || isLastEditedByImporter) {\n object.flags |= OsmFlags.IsLastEditedByImporter;\n }\n\n if (object.tags[checkDateKey]) {\n object.flags |= OsmFlags.IsChecked;\n }\n\n if (isChecked(object.tags[checkDateKey]) === CheckDate.YesRecent) {\n object.flags |= OsmFlags.IsCheckedRecently;\n }\n\n const primaryKey = <DatasetId | undefined>(\n object.tags[ctx.config.merge.osm_key]\n );\n if (primaryKey) {\n out.count++;\n // check if there is already an OSM object with the same linz ID\n\n // this node is 3rd+ one with this linzId\n if (out.duplicateRefs[primaryKey]) {\n out.duplicateRefs[primaryKey].push(object);\n }\n\n // this node is 2nd one with this linzId\n else if (out.withRef[primaryKey]) {\n out.duplicateRefs[primaryKey] = [out.withRef[primaryKey], object];\n delete out.withRef[primaryKey];\n }\n\n // the linz id has a semicolon in it - we don't like this\n // unless the address has alt_addr:* tags.\n else if (primaryKey.includes(';')) {\n const mergedIds = <DatasetId[]>primaryKey.split(';');\n for (const value of mergedIds) {\n out.semi[value] = object;\n }\n }\n\n // not a duplicate\n else {\n out.withRef[primaryKey] = object;\n }\n } else {\n out.noRef[object.id] = object;\n }\n}\n\nexport async function saveLoadedOsmFeatures(ctx: Ctx, out: OSMData) {\n const { withRef, noRef, ...other } = out;\n await writeJsonL(\n ctx.tempFileNames.osm_processed_with_ref,\n Object.values(withRef),\n );\n await writeJsonL(\n ctx.tempFileNames.osm_processed_no_ref,\n Object.values(noRef),\n );\n await fs.writeFile(\n ctx.tempFileNames.osm_processed_other,\n JSON.stringify(other),\n );\n}\n","import { promises as fs } from 'node:fs';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx, OSMData } from '../../types/internal.def.js';\nimport {\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/callbacks.def.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n loadOsmFeature,\n saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport async function downloadFromOverpass(\n ctx: Ctx,\n queryFile: string | undefined,\n serverUrl: string | undefined,\n) {\n const DEFAULT_SERVER_URL = 'https://overpass-api.de/api/interpreter';\n const DEFAULT_QUERY = `\n [out:json][timeout:480];\n nwr[\"${ctx.config.merge.osm_key}\"];\n out meta center;\n `;\n\n const query = queryFile\n ? await fs.readFile(queryFile, 'utf8')\n : DEFAULT_QUERY;\n\n console.info('fetching OSM data via overpass…');\n const response = await fetch(\n `${serverUrl || DEFAULT_SERVER_URL}?data=${encodeURIComponent(query)}`,\n { headers: getHttpHeaders(ctx.config) },\n );\n if (!response.ok) {\n const status = (await response.text()) || response.statusText;\n throw new Error(`HTTP Error ${response.status} ${status}`);\n }\n\n const responseJson = (await response.json()) as {\n elements: (StandardOsmFeature & {\n center?: { lat: number; lon: number };\n })[];\n };\n\n const { pickTags } = validateOsmTagsInConfig(ctx);\n\n const out: OSMData = {\n withRef: {},\n noRef: {},\n duplicateRefs: {},\n semi: {},\n count: 0,\n };\n for (const feature of responseJson.elements) {\n const object: OsmFeature = {\n id: `${feature.type[0]! as OsmFeatureTypeShort}${feature.id}`,\n centroid:\n feature.type === 'node'\n ? [feature.lon, feature.lat]\n : feature.center\n ? [feature.center.lon, feature.center.lat]\n : [0, 0],\n\n tags: pickTags(feature.tags),\n metadata: {\n changeset: feature.changeset,\n timestamp: feature.timestamp,\n user: feature.user,\n version: feature.version,\n },\n\n // added later\n sectors: [],\n flags: OsmFlags.None,\n };\n loadOsmFeature(ctx, out, object);\n }\n await saveLoadedOsmFeatures(ctx, out);\n}\n","import { setTimeout } from 'node:timers/promises';\nimport { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx } from '../../types/index.js';\n\nconst DOWNLOAD_SERVER = 'https://osm-internal.download.geofabrik.de';\nconst DOWNLOAD_SERVER_NO_METADATA = 'https://download.geofabrik.de';\nconst OSM_SERVER = 'https://www.openstreetmap.org';\n\nconst getCsrfToken = (html: string) =>\n html.match(/name=\"csrf-token\" content=\"([^\"]+)\"/)![1]!;\n\n/**\n * based on these docs: https://github.com/geofabrik/sendfile_osm_oauth_protector/blob/master/doc/client.md\n * This basically scrapes the OSM homepage to login programmatically,\n * bypassing CSRF and bypassing OAuth consent...\n */\nasync function getAuthToken(ctx: Ctx, pbfUrl: string) {\n if (!pbfUrl.startsWith(DOWNLOAD_SERVER)) {\n ctx.warnings.push(\n `(!) Planet PBF url is not from ${DOWNLOAD_SERVER}, so metadata may not be available.`,\n );\n }\n\n if (!process.env.OSM_AUTH) {\n ctx.warnings.push(\n '(!) No OSM credentials, so the planet extract will not include metadata',\n );\n return {\n url: pbfUrl\n .replace(DOWNLOAD_SERVER, DOWNLOAD_SERVER_NO_METADATA)\n .replace('-internal.osm.pbf', '.osm.pbf'),\n cookies: '',\n };\n }\n\n const credentials = new URLSearchParams(process.env.OSM_AUTH);\n const baseHeaders = getHttpHeaders(ctx.config);\n\n // 1. fetch config\n const authConfig = (await fetch(\n `${DOWNLOAD_SERVER}/get_cookie?action=get_authorization_url`,\n { method: 'POST', headers: baseHeaders },\n ).then((r) => r.json())) as {\n authorization_url: string;\n state: string;\n client_id: string;\n redirect_uri: string;\n };\n\n // 2. fetch home page to get session cookies + CSRF token\n const homePage = await fetch(`${OSM_SERVER}/login?cookie_test=true`, {\n headers: baseHeaders,\n });\n let cookieJar = homePage.headers.get('Set-Cookie')!;\n\n // 3. login\n const loginPage = await fetch(`${OSM_SERVER}/login`, {\n method: 'POST',\n body: new URLSearchParams({\n username: credentials.get('username')!,\n password: credentials.get('password')!,\n referer: '/',\n commit: 'Login',\n authenticity_token: getCsrfToken(await homePage.text()),\n }),\n headers: {\n ...baseHeaders,\n Cookie: cookieJar,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n });\n if (loginPage.status !== 200) throw new Error('Login failed');\n cookieJar += `; ${loginPage.headers.get('Set-Cookie')!}`;\n\n // 4. open the OAuth consent page\n const consentPage = await fetch(authConfig.authorization_url, {\n headers: { ...baseHeaders, Cookie: cookieJar },\n });\n\n // 5. if there is no consent, try to submit the form\n const authToken = consentPage.url.startsWith(OSM_SERVER)\n ? await fetch(authConfig.authorization_url, {\n method: 'POST',\n body: new URLSearchParams({\n client_id: authConfig.client_id,\n redirect_uri: authConfig.redirect_uri,\n authenticity_token: getCsrfToken(await consentPage.text()),\n state: authConfig.state,\n response_type: 'code',\n scope: 'read_prefs',\n nonce: '',\n code_challenge: '',\n code_challenge_method: '',\n commit: 'Authorize',\n }),\n headers: {\n ...baseHeaders,\n Cookie: cookieJar,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n }).then((r) => r.text())\n : await consentPage.text();\n\n if (!authToken.includes('gf_download_oauth')) throw new Error('No token');\n\n return {\n url: pbfUrl,\n cookies: authToken,\n };\n}\n\nexport async function downloadFromPlanetPbf(ctx: Ctx, pbfUrl: string) {\n const planetFile = ctx.tempFileNames.pbf;\n\n const { cookies, url } = await getAuthToken(ctx, pbfUrl);\n\n await fs.mkdir(dirname(planetFile), { recursive: true });\n await fs.rm(planetFile, { recursive: true, force: true });\n\n if (/^https?:\\/\\//.test(url)) {\n // it's an actuall URL, so fetch it\n console.info('downloading from', url, 'to', planetFile);\n const child = spawn(\n 'curl',\n ['-L', url, '-H', `Cookie: ${cookies}`, '--output', basename(planetFile)],\n { cwd: dirname(planetFile) },\n );\n\n child.stdout.pipe(process.stdout);\n child.stderr.pipe(process.stderr);\n\n await new Promise<void>((resolve, reject) => {\n child.on('close', (code) => (code ? reject(code) : resolve()));\n });\n\n // ensure that the file is written to the disk properly\n await setTimeout(5000);\n } else {\n // else: it's a path to a file on disk, so just copy the file.\n // this is only used for e2e tests by consumers, so no harm\n // in copying/duplicating the file.\n console.info('copying file from', url, 'to', planetFile);\n await fs.copyFile(url, planetFile);\n }\n\n console.info(planetFile, await fs.stat(planetFile));\n}\n","import { promises as fs } from 'node:fs';\nimport { API_BASE_URL, IS_UNIT_TEST } from '../../constants/defaults.js';\nimport type { Ctx } from '../../types/index.js';\n\nexport async function fetchIgnoreList(ctx: Ctx) {\n const refTag = ctx.config.merge.osm_key;\n\n const mockFilePath = ctx.config.e2e_tests?.ignore_list_file_path;\n if (IS_UNIT_TEST && !mockFilePath) {\n throw new Error(\n 'When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath',\n );\n }\n\n const obj: { rows: string[] } =\n IS_UNIT_TEST && mockFilePath\n ? JSON.parse(await fs.readFile(mockFilePath, 'utf8'))\n : await fetch(\n `${API_BASE_URL}/api/ignore_list/${encodeURIComponent(refTag)}?return_ids_only=true`,\n ).then((r) => r.json());\n\n await fs.writeFile(\n ctx.tempFileNames.ignore_list,\n JSON.stringify(obj.rows, null, 2),\n );\n}\n","import { existsSync } from 'node:fs';\nimport pbf2json, { type Item } from 'pbf2json';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport {\n type Ctx,\n type OSMData,\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/index.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n loadOsmFeature,\n saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport type PbfMetadata = Partial<\n Pick<StandardOsmFeature, 'changeset' | 'version' | 'user'> & {\n timestamp: number; // unlike the API, the golang code returns a number\n }\n>;\n\nexport async function osmToJson(ctx: Ctx, pbfFilter: string[]) {\n if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n console.info('Using cached osm data');\n return;\n }\n\n const { pickTags } = validateOsmTagsInConfig(ctx);\n\n console.info('Starting preprocess of OSM data...');\n const result = await new Promise<OSMData>((resolve, reject) => {\n const out: OSMData = {\n withRef: {},\n noRef: {},\n duplicateRefs: {},\n semi: {},\n count: 0,\n };\n\n let anyMetadata = false;\n\n let anythingSeen = false;\n pbf2json\n .createReadStream({\n file: ctx.tempFileNames.pbf,\n tags: pbfFilter,\n leveldb: '/tmp',\n // @ts-expect-error -- missing from typedefs since we\n // added this option in our fork.\n metadata: true,\n })\n .on(\n 'data',\n //\n (item: Item) => {\n anythingSeen ||= true;\n const coords = item.type === 'node' ? item : item.centroid;\n // @ts-expect-error -- missing from typedefs since we\n // added this option in our fork.\n const metadata: PbfMetadata | undefined = item.meta;\n if (metadata) anyMetadata = true;\n\n const tags = pickTags(item.tags);\n\n const object: OsmFeature = {\n id: `${<OsmFeatureTypeShort>item.type[0]}${item.id}`,\n centroid: [coords.lon, coords.lat],\n metadata: metadata\n ? {\n changeset: metadata.changeset,\n timestamp: metadata.timestamp\n ? new Date(1e3 * metadata.timestamp).toISOString()\n : undefined,\n user: metadata.user,\n version: metadata.version,\n }\n : undefined,\n tags,\n\n // added later\n sectors: [],\n flags: OsmFlags.None,\n };\n loadOsmFeature(ctx, out, object);\n\n if (!(out.count % 1000)) process.stdout.write('.');\n },\n )\n .on('finish', () => {\n if (!anyMetadata) {\n ctx.warnings.push('No metadata extracted from the planet file!');\n }\n if (!anythingSeen) {\n throw new Error(\n 'Nothing extracted from the pbf file, presumably it’s invalid.',\n );\n }\n resolve(out);\n })\n .on('error', reject);\n });\n\n await saveLoadedOsmFeatures(ctx, result);\n}\n","import { existsSync } from 'node:fs';\nimport type { Ctx } from '../../types/index.js';\nimport { downloadFromOverpass } from './downloadFromOverpass.js';\nimport { downloadFromPlanetPbf } from './downloadFromPlanetPbf.js';\nimport { fetchIgnoreList } from './fetchIgnoreList.js';\nimport { osmToJson } from './osmToJson.js';\n\nexport async function download(ctx: Ctx) {\n await fetchIgnoreList(ctx);\n\n if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n console.info('Using cached OSM data');\n return;\n }\n\n switch (ctx.config.o_data.source.type) {\n case 'pbf': {\n await downloadFromPlanetPbf(ctx, ctx.config.o_data.source.pbf_url);\n await osmToJson(ctx, ctx.config.o_data.source.pbf_filter);\n break;\n }\n\n case 'overpass': {\n await downloadFromOverpass(\n ctx,\n ctx.config.o_data.source.overpass_query_file,\n ctx.config.o_data.source.overpass_server_url,\n );\n break;\n }\n\n case 'postpass': {\n throw new Error('not supported yet');\n }\n\n default: {\n ctx.config.o_data.source satisfies never;\n }\n }\n}\n","import {\n type Ctx,\n type DatasetId,\n type OSMData,\n OsmFlags,\n type SourceData,\n} from '../../types/index.js';\n\nexport function processDeletions(\n ctx: Ctx,\n osmData: OSMData,\n sourceData: SourceData,\n) {\n const invalidIds: DatasetId[] = [];\n for (const _datasetId in osmData.withRef) {\n const datasetId = <DatasetId>_datasetId;\n const oFeature = osmData.withRef[datasetId]!;\n if (\n !(datasetId in sourceData) && // we delete every OSM node with a linzRef that does not exist in the LINZ data\n !(oFeature.flags & OsmFlags.IsCheckedRecently) // ...and it does not have a recent check_date\n ) {\n invalidIds.push(datasetId);\n }\n }\n\n const datasetByLocalKey: {\n [sector: string]: { [localKey: string]: DatasetId };\n } = {};\n for (const _datasetId in sourceData) {\n const datasetId = <DatasetId>_datasetId;\n const feature = sourceData[datasetId]!;\n for (const sector of feature.sectors) {\n datasetByLocalKey[sector] ||= {};\n const localKey = ctx.callbacks.getLocalKeyForSource(feature);\n\n // if (datasetByLocalKey[sector][localKey]) {\n // console.warn(\n // `localKey ‘${localKey}’ is not unique within sector ‘${sector}’`,\n // );\n // }\n datasetByLocalKey[sector][localKey] = datasetId;\n }\n }\n\n /**\n * cases where we're supposed to delete a feature and re-create an identical one in a\n * similar location. The smarter thing to do would be to simply change the ref.\n */\n const refsThatChanged: Record</* new */ DatasetId, /* old */ DatasetId> = {};\n const toDelete: DatasetId[] = [];\n\n for (const datasetIdToDelete of invalidIds) {\n const oFeature = osmData.withRef[datasetIdToDelete]!;\n\n const localKey = ctx.callbacks.getLocalKeyForOsm(oFeature);\n\n const newDatasetIds = new Set(\n oFeature.sectors\n .map((sector) => datasetByLocalKey[sector]?.[localKey])\n .filter(Boolean),\n );\n\n if (newDatasetIds.size > 1) {\n console.warn(\n `${oFeature.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`,\n );\n }\n if (newDatasetIds.size === 1) {\n const newDatasetId = [...newDatasetIds][0]!;\n if (osmData.withRef[newDatasetId]) {\n // linz ref changed && new one is already in osm -> so basically it didn't change. Normal delete\n // probably because address accidently existed twice in the LINZ db, with 2 different refs and LINZ fixed it by deleting one.\n toDelete.push(datasetIdToDelete);\n } else {\n // linz ref changed, and the new ref doesn't exist in OSM\n refsThatChanged[newDatasetId] = datasetIdToDelete;\n }\n } else {\n // no identical feature in osm, so delete it\n toDelete.push(datasetIdToDelete);\n }\n }\n return {\n refsThatChanged,\n toDelete,\n };\n}\n","import type { BBox, CoordKey } from '../types/internal.def.js';\n\n/**\n * converts a coordinate into a string key, for more performant\n * deduplication. 5dp = accurate to the nearest 1m, see\n * https://osm.wiki/Precision_of_coordinates\n */\nexport const getCoordKey = (lat: number, lon: number, accuraryDp = 5) =>\n <CoordKey>`${lat.toFixed(accuraryDp)},${lon.toFixed(accuraryDp)}`;\n\n/** metres */\nexport const EARTH_RADIUS = 6371008.8;\n\n/** @returns the distance (in degrees) between the closest two points of the two bboxes */\nexport function distanceBetweenBboxes(a: BBox, b: BBox) {\n const Δlat = Math.max(0, b.minLat - a.maxLat, a.minLat - b.maxLat);\n const Δlon = Math.max(0, b.minLng - a.maxLng, a.minLng - b.maxLng);\n return Math.hypot(Δlat, Δlon);\n}\n\n/** returns the larger of lat or lon */\nexport function getBboxMaxDimension(bbox: BBox) {\n return Math.max(\n Math.abs(bbox.maxLat - bbox.minLat),\n Math.abs(bbox.maxLng - bbox.minLng),\n );\n}\n","import { geoDistance } from 'd3-geo';\nimport type { Ctx, OsmFeature, SourceDataFeature } from '../../types/index.js';\nimport { EARTH_RADIUS } from '../../common/geo.js';\n\nconst featureToKey = new Map<OsmFeature, string>();\n\n/** If there are multiple osm addresses found, none of which have a linz ref, find the most likely ones */\nexport const findPotentialOFeatures = (\n ctx: Ctx,\n sourceDataFeature: SourceDataFeature,\n osmFeaturesWithNoRef: OsmFeature[],\n): OsmFeature[] => {\n const sourceKey = ctx.callbacks.getLocalKeyForSource(sourceDataFeature);\n\n const perfectMatches = osmFeaturesWithNoRef\n .filter((oFeature) => {\n // this 1st filter runs millions of times and needs to be fast\n let key = featureToKey.get(oFeature);\n if (!key) {\n key = ctx.callbacks.getLocalKeyForOsm(oFeature);\n featureToKey.set(oFeature, key);\n }\n return key === sourceKey;\n })\n .map((oFeature) => {\n // all following maps/filters only runs tens of times, so they can be expensive\n const offsetMetres =\n EARTH_RADIUS *\n geoDistance(oFeature.centroid, sourceDataFeature.centroid);\n\n return Object.assign(oFeature, { offset: offsetMetres });\n })\n // if our best guess is more than 200m from the gazetted location, it's definitely wrong\n .filter((o) => o.offset < 200)\n .toSorted((a, b) => a.offset - b.offset);\n\n return perfectMatches;\n};\n","import { promises as fs } from 'node:fs';\nimport {\n type Ctx,\n type DatasetId,\n type MatchOutput,\n MatchType,\n type OSMData,\n type SourceData,\n} from '../../types/index.js';\nimport { processDeletions } from './processDeletions.js';\nimport { findPotentialOFeatures } from './findPotentialOFeatures.js';\n\nexport async function match(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n) {\n const count = Object.keys(sourceData).length;\n\n const ignoreList = new Set<string>(\n JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n );\n\n console.info('processing deleted data...');\n console.time('matchDeletions');\n const { refsThatChanged, toDelete } = processDeletions(\n ctx,\n osmData,\n sourceData,\n );\n console.timeEnd('matchDeletions');\n\n console.info('Processing data...');\n let index = 0;\n console.time('matchLoop');\n const output: MatchOutput = {\n [MatchType.OneToOne]: [],\n [MatchType.OneToMany]: [],\n [MatchType.ManyToOne]: {},\n [MatchType.ManyToMany]: [],\n [MatchType.Delete]: toDelete,\n [MatchType.Guess]: [],\n };\n\n const osmFeaturesWithNoRef = Object.values(osmData.noRef);\n\n const individualRefsToSemiRef: Record<\n DatasetId,\n { value: DatasetId; isFirst: boolean }[]\n > = {};\n for (const ref in osmData.semi) {\n const parts = ref.split(';');\n for (const [j, part] of parts.entries()) {\n individualRefsToSemiRef[<DatasetId>part] ||= [];\n individualRefsToSemiRef[<DatasetId>part]!.push({\n value: <DatasetId>ref,\n isFirst: !j,\n });\n }\n }\n\n for (const _datasetId in sourceData) {\n const originalDatasetId = <DatasetId>_datasetId;\n let datasetId = refsThatChanged[originalDatasetId] || originalDatasetId;\n\n // someone has explicitly ignored this row, so act as if it doesn't exist.\n if (ignoreList.has(datasetId)) continue;\n\n // don't try to recreate the feature, we'll handle it when we reach the old ID\n if (datasetId in refsThatChanged) continue;\n\n // skip non-first\n\n const oFeature = osmData.withRef[datasetId];\n const sourceFeature = sourceData[originalDatasetId]!;\n const duplicate = osmData.duplicateRefs[datasetId];\n\n if (individualRefsToSemiRef[datasetId]) {\n // TODO: hardcoded 0 here is not good.\n if (individualRefsToSemiRef[datasetId]![0]!.isFirst) {\n datasetId = individualRefsToSemiRef[datasetId]![0]!.value!;\n } else {\n // it's not the first one, so skip\n continue;\n }\n }\n const semi = osmData.semi[datasetId];\n\n // (source:o)\n if (oFeature && semi) {\n const parts = <DatasetId[]>datasetId.split(';');\n output[MatchType.ManyToMany].push({\n osm: [oFeature.id, semi.id],\n source: parts,\n });\n } else if (oFeature) {\n // [1:1] found an exact match\n // the tags might be wrong, but we'll deal with that later\n output[MatchType.OneToOne].push({\n osm: oFeature.id,\n source: originalDatasetId,\n });\n } else if (duplicate) {\n // [1:many] there are multiple exact matches (1:many)\n output[MatchType.OneToMany].push({\n osm: duplicate.map((feature) => feature.id),\n source: datasetId,\n });\n } else if (semi) {\n // [many:1] there is one match, but it contains multiple source features merged\n const parts = <DatasetId[]>datasetId.split(';');\n output[MatchType.ManyToOne][semi.id] ||= [];\n output[MatchType.ManyToOne][semi.id]?.push(...parts);\n } else {\n // if there is no match, try to guess based on nearby candidates\n const possibleAddresses = findPotentialOFeatures(\n ctx,\n sourceFeature,\n osmFeaturesWithNoRef,\n );\n if (possibleAddresses.length === 1) {\n // [1:1] but the ref needs to be added\n output[MatchType.OneToOne].push({\n source: datasetId,\n osm: possibleAddresses[0]!.id,\n });\n } else {\n // [1:?]\n output[MatchType.Guess].push({\n source: datasetId,\n osmCandidates: possibleAddresses.map((f) => f.id),\n });\n }\n }\n\n index += 1;\n if (!(index % 1000)) {\n /* istanbul ignore next */\n process.stdout.write(`${((index / count) * 100).toFixed(1)}% `);\n }\n }\n\n console.timeEnd('matchLoop');\n\n await fs.writeFile(\n ctx.tempFileNames.matches,\n JSON.stringify(output, null, undefined),\n );\n\n return { output };\n}\n","import type { Vec2 as Coord } from '../../types/index.js';\n\n/** radius of the diamond */\nconst RADIUS = 0.0002;\n// 0.0002° ~= 0° 0' 0.72\"\n\n/** generates a bbox around a point, but as a diamond not a square */\nexport function createDiamond({\n lat,\n lng,\n}: {\n lat: number;\n lng: number;\n}): [ring1: Coord[]] {\n const diamond: Coord[] = [\n [lng, lat + RADIUS], // top\n [lng + RADIUS, lat], // right\n [lng, lat - RADIUS], // bottom\n [lng - RADIUS, lat], // left\n ];\n diamond.push(diamond[0]!); // make it a closed way\n\n return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n\n/** generates a bbox around a point, but as a square not a diamond */\nexport function createSquare({\n lat,\n lng,\n}: {\n lat: number;\n lng: number;\n}): [ring1: Coord[]] {\n const diamond: Coord[] = [\n [lng + RADIUS / 2, lat + RADIUS / 2], // top-right\n [lng + RADIUS / 2, lat - RADIUS / 2], // top-left\n [lng - RADIUS / 2, lat - RADIUS / 2], // bottom-left\n [lng - RADIUS / 2, lat + RADIUS / 2], // bottom-right\n ];\n diamond.push(diamond[0]!); // make it a closed way\n\n return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n","import type { Feature, Position } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function calcBBox(features: Feature[]): BBox {\n const bbox: BBox = {\n minLat: Infinity,\n minLng: Infinity,\n maxLat: -Infinity,\n maxLng: -Infinity,\n };\n\n function visit([_lng, _lat]: Position) {\n const lat = +_lat!;\n const lng = +_lng!;\n if (lat < bbox.minLat) bbox.minLat = lat;\n if (lng < bbox.minLng) bbox.minLng = lng;\n if (lat > bbox.maxLat) bbox.maxLat = lat;\n if (lng > bbox.maxLng) bbox.maxLng = lng;\n }\n\n /* eslint-disable unicorn/no-array-for-each -- deliberate */\n\n for (const f of features) {\n switch (f.geometry.type) {\n case 'Point': {\n visit(f.geometry.coordinates);\n\n break;\n }\n case 'LineString': {\n f.geometry.coordinates.forEach(visit);\n\n break;\n }\n case 'Polygon': {\n for (const ring of f.geometry.coordinates) ring.forEach(visit);\n\n break;\n }\n case 'MultiPolygon': {\n for (const member of f.geometry.coordinates) {\n for (const ring of member) ring.forEach(visit);\n }\n break;\n }\n\n default:\n // other geometry types are allowed, they just won't\n // count towards the bbox\n }\n }\n return bbox;\n}\n","import type { Polygon } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function bboxToPolygon({\n minLng,\n minLat,\n maxLng,\n maxLat,\n}: BBox): Polygon {\n return {\n type: 'Polygon',\n coordinates: [\n [\n [minLng, minLat], // SW\n [maxLng, minLat], // SE\n [maxLng, maxLat], // NE\n [minLng, maxLat], // NW\n [minLng, minLat], // SW\n ],\n ],\n };\n}\n","import type { OsmPatchFeature as GeoJsonFeature } from 'osm-api';\nimport { geoCentroid as getFirstCoord } from 'd3-geo';\nimport type {\n Ctx,\n HandlerReturnWithBBox,\n OutputLayer,\n} from '../../types/index.js';\nimport { MAX_ITEMS_PER_DATASET } from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { getSector } from '../../common/getSector.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\n\nexport function splitUntilSmallEnough(\n ctx: Ctx,\n name: string,\n metadata: Omit<OutputLayer, 'features' | 'bbox' | 'sectorIds'>,\n features: GeoJsonFeature[],\n depth = 0,\n): HandlerReturnWithBBox {\n if (!features.length) return {};\n\n const bbox = calcBBox(features);\n const sectorIds = getSector(\n bboxToPolygon(bbox),\n ctx.config.merge.sector_resolution,\n );\n\n const recursionLimit = depth > 10;\n if (recursionLimit) {\n console.info(`(!) Couldn't split ${name} (${features.length} left)`);\n }\n\n if (features.length <= MAX_ITEMS_PER_DATASET || recursionLimit) {\n // yay, it's small enough\n return { [name]: { features, bbox, sectorIds, ...metadata } };\n }\n\n // it's not small enough\n const tallerThanWide = bbox.maxLat - bbox.minLat > bbox.maxLng - bbox.minLng;\n if (tallerThanWide) {\n const midway = bbox.minLat + (bbox.maxLat - bbox.minLat) / 2;\n const out: [north: GeoJsonFeature[], south: GeoJsonFeature[]] = [[], []];\n for (const f of features) {\n out[+(getFirstCoord(f.geometry)[1] < midway)]!.push(f);\n }\n\n const [north, south] = out;\n\n return {\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^N`,\n metadata,\n north,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^S`,\n metadata,\n south,\n depth + 1,\n ),\n };\n }\n\n // else: it's wider than it is tall\n const midway = bbox.minLng + (bbox.maxLng - bbox.minLng) / 2;\n const out: [east: GeoJsonFeature[], west: GeoJsonFeature[]] = [[], []];\n for (const f of features) {\n out[+(getFirstCoord(f.geometry)[0] < midway)]!.push(f);\n }\n\n const [east, west] = out;\n\n return {\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^E`,\n metadata,\n east,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^W`,\n metadata,\n west,\n depth + 1,\n ),\n };\n}\n\n/** normalizes the weird ^N^S^E names generated by `splitUntilSmallEnough` */\nexport function normalizeName(\n input: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n const index: Record<string, number> = {};\n const output: HandlerReturnWithBBox = {};\n\n for (const name in input) {\n const newName = name.replace(/^([^^]+)(\\^\\w)+$/, (_, withoutSuffix) => {\n index[withoutSuffix] ||= 0;\n index[withoutSuffix] += 1;\n return `${withoutSuffix} ${index[withoutSuffix]}`;\n });\n\n output[newName] = input[name]!;\n }\n return output;\n}\n","import type { Feature, Point } from 'geojson';\nimport type {\n Vec2 as Coord,\n HandlerReturnWithBBox,\n} from '../../types/index.js';\nimport { getCoordKey } from '../../common/geo.js';\n\nconst { sin, cos, floor, sqrt, PI: π } = Math;\n\n/**\n * Determines the coordinate where we should place the n-th address, relative\n * to the original location which is (0,0)\n *\n * See https://desmos.com/calculator/nunxdg7x9b\n */\nexport function toFormation(n: number): Coord {\n const r = floor(sqrt(n));\n const θ = ((n - r ** 2) * π) / (2 * ((r + 1) ** 2 - r ** 2 - 1)) || 0;\n\n const [x, y] = [r * cos(θ), r * sin(θ)];\n return [+x.toFixed(2), +y.toFixed(2)];\n}\n\n/**\n * If there are multiple addresses in the same location,\n * we convert them into a stack if there are more than STACK_THRESHOLD.\n * If there are less, we spread them out in a grid formation around the\n * original point.\n */\nexport function shiftOverlappingPoints(\n sectors: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n for (const sectorName in sectors) {\n const sector = sectors[sectorName]!;\n const nodeByCoord: Record<string, Feature[]> = {};\n for (const feature of sector.features) {\n if (feature.geometry.type === 'Point') {\n const [lng, lat] = feature.geometry.coordinates as Coord;\n // round to nearest 0.05seconds of latitude/longitude in case the points are slightly off\n // same logic as in stackLinzData.ts\n const key = getCoordKey(lat, lng, 4);\n\n nodeByCoord[key] ||= [];\n nodeByCoord[key].push(feature);\n }\n }\n for (const nodes of Object.values(nodeByCoord)) {\n if (nodes.length > 1) {\n // multiple features in the same place, so offset the coordinates\n for (let index = 0; index < nodes.length; index += 1) {\n const [offsetX, offsetY] = toFormation(index);\n const [lng, lat] = (nodes[index]!.geometry as Point)\n .coordinates as Coord;\n nodes[index]!.geometry = {\n type: 'Point',\n coordinates: [lng + 0.000004 * offsetX, lat + 0.000004 * offsetY],\n };\n }\n }\n }\n }\n return sectors;\n}\n","import { createHash } from 'node:crypto';\n\nexport function sha256(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex');\n}\n","import type { OsmPatchFeature } from 'osm-api';\n\nexport function calcCount(features: OsmPatchFeature[]): {\n count: string;\n totalCount: number;\n} {\n let plus = 0;\n let minus = 0;\n let move = 0;\n let edit = 0;\n\n for (const f of features) {\n switch (f.properties.__action) {\n case 'delete': {\n minus += 1;\n break;\n }\n case 'move': {\n move += 1;\n break;\n }\n case 'edit': {\n edit += 1;\n break;\n }\n default: {\n plus += 1;\n }\n }\n }\n\n const strs = [\n plus && `add ${plus}`,\n minus && `delete ${minus}`,\n move && `move ${move}`,\n edit && `edit ${edit}`,\n ].filter((x: string | 0): x is string => !!x);\n\n return {\n count: strs.join(', '),\n totalCount: plus + minus + move + edit,\n };\n}\n","import { promises as fs } from 'node:fs';\nimport { join } from 'node:path';\nimport type { OsmPatch } from 'osm-api';\nimport type { Feature, FeatureCollection, Polygon } from 'geojson';\nimport type { ConflateResult, Ctx, OutputLayers } from '../../types/index.js';\nimport { IS_UNIT_TEST } from '../../constants/defaults.js';\nimport { sha256 as hash } from '../../helpers.js';\nimport { calcCount } from '../../common/calcCount.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\n\nfunction toId(suburb: string) {\n // macrons are url safe\n return `${suburb.replaceAll(/[^\\dA-Za-zĀāĒēĪīŌōŪū]/g, '').slice(0, 100)}_${hash(suburb).slice(0, 6)}`;\n}\n\nexport interface IndexFileProperties {\n category: string;\n group: string;\n title: string;\n instructions: string | undefined;\n totalCount: number;\n}\n\nexport async function createIndexAndSaveToDisk(\n ctx: Ctx,\n metrics: ConflateResult,\n suburbs: OutputLayers,\n): Promise<void> {\n const githubParts = ctx.config.metadata.git_repository.match(\n 'https://github.com/([^/]+)/([^/]+)',\n );\n if (!githubParts) throw new Error('git_repository is not a valid github url');\n const outputFolder =\n ctx.config.output?.folder || join(process.cwd(), 'output');\n const subFolderName = 'suburbs';\n\n const meta = Object.entries(suburbs).flatMap(([category, groups]) =>\n Object.entries(groups).map(([group, items]) => ({\n category,\n group,\n title: [category, group].filter(Boolean).join(' - '),\n bbox: items.bbox,\n instructions: items.instructions,\n ...calcCount(items.features),\n })),\n );\n\n // create index.json\n const indexFile = {\n fields: [],\n results: meta\n .map((v) => {\n return {\n id: toId(v.title),\n url: `https://${githubParts[1]}.github.io/${githubParts[2]}/${subFolderName}/${toId(v.title)}.osmPatch.geo.json`,\n name: v.title,\n title: v.title,\n totalCount: v.totalCount,\n source: '',\n snippet: v.count,\n extent: [\n [v.bbox.minLng, v.bbox.minLat],\n [v.bbox.maxLng, v.bbox.maxLat],\n ],\n instructions: v.instructions,\n groupCategories: [v.category, '/Categories/Addresses'],\n };\n })\n .toSorted((a, b) => a.name.localeCompare(b.name)),\n };\n\n // create index.geo.json\n const newIndexFile: FeatureCollection<Polygon, IndexFileProperties> = {\n type: 'FeatureCollection',\n features: meta\n .map(({ bbox, ...other }): Feature<Polygon, IndexFileProperties> => {\n return {\n type: 'Feature',\n geometry: bboxToPolygon(bbox),\n properties: other,\n };\n })\n .toSorted((a, b) => a.properties.title.localeCompare(b.properties.title)),\n };\n\n await fs.mkdir(join(outputFolder, subFolderName), { recursive: true });\n await fs.writeFile(\n join(outputFolder, 'index.json'),\n JSON.stringify(indexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.writeFile(\n join(outputFolder, 'index.geo.json'),\n JSON.stringify(newIndexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.writeFile(\n join(outputFolder, 'metrics.json'),\n JSON.stringify(metrics, null, 2),\n );\n\n // save each suburb\n for (const v of meta) {\n const geojson: OsmPatch = {\n type: 'FeatureCollection',\n ...suburbs[v.category]![v.group]!,\n };\n\n await fs.writeFile(\n join(outputFolder, subFolderName, `${toId(v.title)}.osmPatch.geo.json`),\n JSON.stringify(geojson, null, IS_UNIT_TEST ? 2 : undefined),\n );\n }\n}\n","import type { Tags } from 'osm-api';\nimport type { HandlerReturnWithBBox } from '../../types/index.js';\nimport {\n MAX_BBOX_DEGREES,\n MAX_ITEMS_PER_DATASET,\n MIN_ITEMS_PER_DATASET,\n} from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\nimport {\n distanceBetweenBboxes,\n getBboxMaxDimension,\n} from '../../common/geo.js';\n\nexport function mergeTags(a: Tags, b: Tags): Tags {\n const merged = { ...a };\n for (const key in b) {\n if (!merged[key]) {\n merged[key] = b[key]!;\n } else if (merged[key] !== b[key]) {\n merged[key] += `;${b[key]}`;\n }\n }\n return merged;\n}\n\n/**\n * having many tiny datasets is unhelpful, it's more convenient for us\n * to merge them into\n */\nexport function mergeTinyDatasets(\n sectors: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n // keep looping until there is nothing more that can be merged.\n let anyModificationsInPrevPass = true;\n while (anyModificationsInPrevPass) {\n anyModificationsInPrevPass = false;\n\n for (const sectorName in sectors) {\n const sector = sectors[sectorName]!;\n const mainSectorId = sector.sectorIds[0]!;\n\n // skip if it already has enough members\n if (sector.features.length > MIN_ITEMS_PER_DATASET) continue;\n\n // try to find another group in the same sector which\n // also has too few items.\n const bestOther = Object.entries(sectors)\n .filter(([otherName, other]) => {\n return (\n // must be in the same sector\n other.sectorIds.includes(mainSectorId) &&\n // skip self\n otherName !== sectorName &&\n // must have too few items\n other.features.length <= MIN_ITEMS_PER_DATASET &&\n // if we merge them, the total must not be too high\n sector.features.length + other.features.length <\n MAX_ITEMS_PER_DATASET\n );\n })\n .toSorted(\n ([, a], [, b]) =>\n // prefer the closest\n distanceBetweenBboxes(sector.bbox, a.bbox) -\n distanceBetweenBboxes(sector.bbox, b.bbox),\n )[0];\n\n if (!bestOther) continue;\n\n const [otherName, other] = bestOther;\n\n const newBbox = calcBBox([\n {\n type: 'Feature',\n geometry: bboxToPolygon(sector.bbox),\n properties: {},\n },\n {\n type: 'Feature',\n geometry: bboxToPolygon(other.bbox),\n properties: {},\n },\n ]);\n\n // If the new bbox is now ridiculously big, and the previous\n // bboxes were not this big, then abort the merge.\n if (\n getBboxMaxDimension(newBbox) > MAX_BBOX_DEGREES &&\n (getBboxMaxDimension(sector.bbox) < MAX_BBOX_DEGREES ||\n getBboxMaxDimension(other.bbox) < MAX_BBOX_DEGREES)\n ) {\n continue;\n }\n\n // combine them\n sector.bbox = newBbox;\n sector.sectorIds = [\n ...new Set([...sector.sectorIds, ...other.sectorIds]),\n ];\n sector.changesetTags = mergeTags(\n sector.changesetTags || {},\n other.changesetTags || {},\n );\n if (other.instructions) {\n sector.instructions ||= '';\n sector.instructions += `\\n\\n${other.instructions}`;\n sector.instructions.trim();\n }\n sector.features.push(...other.features);\n\n // deleting it now means it won't be visited again by this loop\n delete sectors[sectorName];\n delete sectors[otherName];\n\n const { name: mergedName } = mergeTags(\n { name: sectorName },\n { name: otherName },\n );\n sectors[mergedName!] = sector;\n anyModificationsInPrevPass = true;\n }\n }\n return sectors;\n}\n","import { promises as fs } from 'node:fs';\nimport {\n type ConflateResult,\n type OsmFeature,\n OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n type Ctx,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OutputLayers,\n type SourceData,\n} from '../../../types/internal.def.js';\n\nexport async function generateDataForWebsite(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n matches: MatchOutput,\n handlerReturn: OutputLayers,\n): Promise<ConflateResult> {\n const ignoreList = new Set<string>(\n JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n );\n\n // init phase\n let lastEditedByImporter = 0;\n let recentlyChanged = 0;\n let recentlyChecked = 0;\n function collectFlags(feature: OsmFeature) {\n if (feature.flags & OsmFlags.IsLastEditedByImporter) lastEditedByImporter++;\n if (feature.flags & OsmFlags.IsRecentlyChanged) recentlyChanged++;\n if (feature.flags & OsmFlags.IsCheckedRecently) recentlyChecked++;\n }\n for (const feature of Object.values(osmData.withRef)) collectFlags(feature);\n for (const feature of Object.values(osmData.noRef)) collectFlags(feature);\n for (const feature of Object.values(osmData.semi)) collectFlags(feature);\n for (const features of Object.values(osmData.duplicateRefs)) {\n for (const feature of features) collectFlags(feature);\n }\n\n const result: ConflateResult = {\n config: ctx.config,\n warnings: ctx.warnings,\n countsByPhase: {\n init: {\n sourceDataset: Object.keys(sourceData).length,\n ignored: ignoreList.size,\n osm: {\n withRef: Object.keys(osmData.withRef).length,\n noRef: Object.keys(osmData.noRef).length,\n semi: Object.keys(osmData.semi).length,\n duplicateRefs: Object.values(osmData.duplicateRefs).flat().length,\n lastEditedByImporter,\n recentlyChanged,\n recentlyChecked,\n },\n },\n matched: {\n [MatchType.OneToOne]: matches[MatchType.OneToOne].length,\n [MatchType.OneToMany]: matches[MatchType.OneToMany].length,\n [MatchType.ManyToOne]: Object.keys(matches[MatchType.ManyToOne]).length,\n [MatchType.ManyToMany]: matches[MatchType.ManyToMany].length,\n [MatchType.Delete]: matches[MatchType.Delete].length,\n [MatchType.Guess]: matches[MatchType.Guess].length,\n },\n conflated: { create: 0, edit: 0, delete: 0, perfect: 0 },\n },\n };\n\n // conflated phase\n for (const sectors of Object.values(handlerReturn).flatMap((v) =>\n Object.values(v),\n )) {\n for (const f of sectors.features) {\n if (f.properties.__action === 'edit') {\n result.countsByPhase.conflated.edit++;\n } else if (f.properties.__action === 'delete') {\n result.countsByPhase.conflated.delete++;\n } else {\n result.countsByPhase.conflated.create++;\n }\n }\n }\n result.countsByPhase.conflated.perfect =\n osmData.count -\n result.countsByPhase.conflated.edit -\n result.countsByPhase.conflated.delete;\n\n return result;\n}\n","import { getIDToken } from '@actions/core';\nimport { API_BASE_URL } from '../../../constants/defaults.js';\n\nexport async function writeToRunHistory(refTag: string) {\n if (!process.env.GITHUB_ACTIONS) {\n console.info(\n 'Skipping run history update, because not running in GitHub Actions',\n );\n return;\n }\n const token = await getIDToken('osm-conflation-engine');\n\n const result = await fetch(`${API_BASE_URL}/api/run_history/${refTag}`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${token}` },\n });\n if (result.status !== 200) {\n throw new Error(await result.text());\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OsmPatchFeature } from 'osm-api';\nimport {\n type ConflateResult,\n type ConflationDiff,\n type ConflationResultExtra,\n type Ctx,\n type DatasetId,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OsmFeature,\n OsmFlags,\n type OsmId,\n type OutputLayers,\n type SourceData,\n type SourceDataFeature,\n type TagDiff,\n} from '../../types/index.js';\nimport { createDiamond, createSquare } from './createDiamond.js';\nimport { splitUntilSmallEnough } from './splitUntilSmallEnough.js';\nimport { shiftOverlappingPoints } from './spreadToGrid.js';\nimport { createIndexAndSaveToDisk } from './createIndexAndSaveToDisk.js';\nimport { mergeTinyDatasets } from './mergeTinyDatasets.js';\nimport { generateDataForWebsite } from './stats/generateDataForWebsite.js';\nimport { writeToRunHistory } from './stats/writeToRunHistory.js';\n\nconst MSG = 'invalid value returned by your callback function';\n\nfunction isTagDiffEmpty(diff: TagDiff) {\n for (const key in diff) {\n if (key === '__action' && diff[key] === 'edit') continue;\n return false;\n }\n return true;\n}\n\nfunction hasDiff(diff: ConflationDiff) {\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (typeof diff.tags !== 'object') throw new TypeError(MSG);\n return !!diff.geometry || !isTagDiffEmpty(diff.tags);\n}\n\nfunction createFeature(\n diff: ConflationDiff,\n o?: OsmFeature,\n s?: SourceDataFeature,\n): OsmPatchFeature {\n if (!o && !s) throw new Error('requires either o or s');\n\n if (o) diff.tags.__action ||= 'edit';\n\n const [lng, lat] = (o?.centroid || s?.centroid)!;\n return {\n type: 'Feature',\n id: o?.id || s?.id,\n geometry:\n diff.tags.__action === 'delete'\n ? { type: 'Polygon', coordinates: createSquare({ lat, lng }) }\n : diff.geometry ||\n (diff.tags.__action === 'edit'\n ? { type: 'Polygon', coordinates: createDiamond({ lat, lng }) }\n : { type: 'Point', coordinates: [lng, lat] }),\n properties: diff.tags,\n };\n}\n\nexport async function conflate(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n): Promise<ConflateResult> {\n const osmDataById: Record<OsmId, OsmFeature> = { ...osmData.noRef };\n for (const cat of <const>['withRef', 'semi', 'duplicateRefs']) {\n for (const k in osmData[cat]) {\n const feature = osmData[cat][k as DatasetId]!;\n if (Array.isArray(feature)) {\n for (const item of feature) {\n osmDataById[item.id] = item;\n }\n } else {\n osmDataById[feature.id] = feature;\n }\n }\n }\n\n const output: {\n [category: string]: { [sector: string]: OsmPatchFeature[] };\n } = {};\n\n function handleExtra(\n extra: ConflationResultExtra | undefined,\n category: string,\n sector: string,\n ) {\n if (!extra) return;\n if (extra.warnings?.length) ctx.warnings.push(...extra.warnings);\n if (extra.createFeatures) {\n output[category] ||= {};\n output[category][sector] ||= [];\n output[category][sector].push(...extra.createFeatures);\n }\n }\n\n const matches: MatchOutput = JSON.parse(\n await fs.readFile(ctx.tempFileNames.matches, 'utf8'),\n );\n\n // 1.\n for (const { source: s, osm: o } of matches[MatchType.OneToOne]) {\n const oFeature = osmDataById[o]!;\n const sFeature = sourceData[s]!;\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n const result = await ctx.callbacks.mergeOneToOne({\n osm: oFeature,\n source: sFeature,\n });\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, sector);\n output[category] ||= {};\n output[category][sector] ||= [];\n output[category][sector].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n // 2.\n for (const { source, osm } of matches[MatchType.OneToMany]) {\n const oFeatures = osm\n .map((id) => osmDataById[id]!)\n .filter((oFeature) => !(oFeature.flags & OsmFlags.IsCheckedRecently));\n\n const sFeature = sourceData[source]!;\n\n const result = await ctx.callbacks.mergeOneToMany?.({\n osm: oFeatures,\n source: sFeature,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diffPerFeature !== 'object') {\n throw new TypeError(MSG);\n }\n\n for (const _osmId in result.diffPerFeature) {\n const osmId = <OsmId>_osmId;\n const oFeature = oFeatures.find((f) => f.id === osmId)!;\n const diff = result.diffPerFeature[osmId]!;\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(diff, oFeature, sFeature));\n }\n }\n\n // 3.\n for (const _osmId in matches[MatchType.ManyToOne]) {\n const osmId = <OsmId>_osmId;\n const oFeature = osmDataById[osmId]!;\n const sFeatures = matches[MatchType.ManyToOne][osmId]!.map(\n (id) => sourceData[id]!,\n );\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n const result = await ctx.callbacks.mergeManyToOne?.({\n osm: oFeature,\n source: sFeatures,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(result.diff, oFeature));\n }\n\n // 4.\n for (const { source, osm } of matches[MatchType.ManyToMany]) {\n const oFeatures = osm\n .map((id) => osmDataById[id]!)\n .filter((oFeature) => !(oFeature.flags & OsmFlags.IsCheckedRecently));\n\n const sFeature = source.map((id) => sourceData[id]!);\n\n const result = await ctx.callbacks.mergeManyToMany?.({\n osm: oFeatures,\n source: sFeature,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diffPerFeature !== 'object') {\n throw new TypeError(MSG);\n }\n\n for (const _osmId in result.diffPerFeature) {\n const osmId = <OsmId>_osmId;\n const oFeature = oFeatures.find((f) => f.id === osmId)!;\n const diff = result.diffPerFeature[osmId]!;\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(diff, oFeature));\n }\n }\n\n // 5. deletions\n for (const idToDelete of matches[MatchType.Delete]) {\n const oFeature = osmData.withRef[idToDelete]!;\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n // the business-side needs to decide for each feature, if it will outright\n // delete it, or just remove the relevant tags.\n const result = await ctx.callbacks.deleteFeature?.({ osm: oFeature });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(result.diff, oFeature));\n }\n\n // 6. to avoid reënqueuing these features twice, the business-side needs to\n // make a selection, and process the tagDiff at the same time.\n for (const pair of matches[MatchType.Guess]) {\n const sFeature = sourceData[pair.source]!;\n const result = await ctx.callbacks.create({\n source: sFeature,\n osmCandidates: pair.osmCandidates.map((osmId) => osmData.noRef[osmId]!),\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const oFeature = result.selection\n ? osmData.noRef[result.selection]!\n : undefined;\n\n const category = result.category || '';\n const group = result.group || oFeature?.sectors[0] || 'unknown';\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n const handlerReturn: OutputLayers = {};\n for (const category in output) {\n handlerReturn[category] ||= {};\n\n for (const group in output[category]) {\n const { changesetTags = {}, instructions } =\n (await ctx.callbacks.getChangesetTags?.({ category, group })) || {};\n\n // add some default changeset tags if not defined by the consumer\n /* eslint-disable dot-notation */\n changesetTags['created_by'] ||= 'osm-conflation-engine';\n changesetTags['import'] ||= 'yes';\n changesetTags['source'] ||= ctx.config.metadata.wiki_page;\n /* eslint-enable dot-notation */\n\n const features = output[category][group]!;\n if (ctx.callbacks.postprocessLayer) {\n await ctx.callbacks.postprocessLayer({\n category,\n group,\n osmData: osmDataById,\n sourceData,\n features,\n });\n }\n\n const groups = splitUntilSmallEnough(\n ctx,\n group,\n { changesetTags, instructions },\n features,\n );\n Object.assign(handlerReturn[category], groups);\n }\n\n shiftOverlappingPoints(handlerReturn[category]);\n mergeTinyDatasets(handlerReturn[category]);\n }\n\n const metrics = await generateDataForWebsite(\n ctx,\n sourceData,\n osmData,\n matches,\n handlerReturn,\n );\n await createIndexAndSaveToDisk(ctx, metrics, handlerReturn);\n // TODO: generate stats and stats history\n await writeToRunHistory(ctx.config.merge.osm_key);\n\n return metrics;\n}\n","import { createReadStream, promises as fs } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\nimport { geoCentroid } from 'd3-geo';\nimport type { Feature, GeoJsonProperties, Geometry } from 'geojson';\nimport type { Ctx, OSMData, SourceData, Vec2 } from '../../types/index.js';\nimport { getSector } from '../../common/getSector.js';\nimport { readJsonL } from '../../common/jsonl.js';\n\nexport async function readData<\n G extends Geometry = Geometry,\n P extends GeoJsonProperties = GeoJsonProperties,\n>(ctx: Ctx) {\n console.info('Reading source data into memory...');\n let count = 0;\n const sourceData: SourceData<G, P> = {};\n const readline = createInterface({\n input: createReadStream(ctx.config.source_data.file),\n crlfDelay: Infinity,\n });\n for await (const line of readline) {\n const feature: Feature<G, P> = JSON.parse(line);\n const id = feature.properties![ctx.config.merge.dataset_column];\n sourceData[id] = Object.assign(feature, {\n sectors: getSector(feature.geometry, ctx.config.merge.sector_resolution),\n centroid:\n feature.geometry.type === 'Point'\n ? <Vec2>feature.geometry.coordinates\n : geoCentroid(feature.geometry),\n });\n count++;\n }\n console.info(`\\tread ${count} rows.`);\n\n console.info('Reading osm data into memory (withRef)...');\n const withRef: OSMData['withRef'] = await readJsonL(\n ctx.tempFileNames.osm_processed_with_ref,\n (row) => row.tags[ctx.config.merge.osm_key]!,\n );\n console.info('Reading osm data into memory (noRef)...');\n const noRef: OSMData['noRef'] = await readJsonL(\n ctx.tempFileNames.osm_processed_no_ref,\n (row) => row.id,\n );\n console.info('Reading osm data into memory (other)...');\n const other: Omit<OSMData, 'noRef' | 'withRef'> = JSON.parse(\n await fs.readFile(ctx.tempFileNames.osm_processed_other, 'utf8'),\n );\n const osmData: OSMData = { withRef, noRef, ...other };\n console.info('\\tread data.');\n\n return { sourceData, osmData };\n}\n","import { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { promises as fs } from 'node:fs';\nimport { Ajv, type JSONSchemaType } from 'ajv';\nimport type { GeoJsonProperties, Geometry } from 'geojson';\nimport type {\n Callbacks,\n Config,\n ConflateResult,\n Ctx,\n OSMData,\n RunOptions,\n RunResult,\n SourceData,\n} from './types/index.js';\nimport {\n FILE_NAMES,\n IS_UNIT_TEST,\n STEPS,\n type Step,\n} from './constants/defaults.js';\nimport { download } from './steps/download/index.js';\nimport { match } from './steps/match/index.js';\nimport { conflate } from './steps/conflate/index.js';\nimport { sha256 } from './helpers.js';\nimport { readData } from './steps/match/readData.js';\n\nfunction validateSteps(steps: readonly unknown[]): asserts steps is Step[] {\n for (const value of steps) {\n if (!STEPS.includes(value as never)) {\n throw new Error(\n `‘${value}’ is not a valid step. The options are: ${STEPS.join(', ')}`,\n );\n }\n }\n}\n\nexport async function getTempFileNames(\n config: Config,\n): Promise<Pick<Ctx, 'id' | 'tempFolder' | 'tempFileNames'>> {\n const id = sha256(config.metadata.name).slice(0, 12);\n\n const prefix = IS_UNIT_TEST ? '-mock' : '';\n const tempFolder = join(tmpdir(), `oce-${prefix}`, id);\n await fs.mkdir(tempFolder, { recursive: true });\n\n const tempFileNames = <never>(\n Object.fromEntries(\n Object.entries(FILE_NAMES).map(([key, fileName]) => [\n key.toLowerCase(),\n join(tempFolder, fileName),\n ]),\n )\n );\n\n return { id, tempFolder, tempFileNames };\n}\n\nexport async function run<G extends Geometry, P extends GeoJsonProperties>(\n config: Config,\n callbacks: Callbacks<G, P>,\n runOptions: RunOptions = {},\n): Promise<RunResult> {\n const configFileSchema: JSONSchemaType<Config> = JSON.parse(\n await fs.readFile(new URL('config.schema.json', import.meta.url), 'utf8'),\n );\n\n const ajv = new Ajv();\n const validate = ajv.compile(configFileSchema);\n\n if (!validate(config)) {\n throw new AggregateError(\n validate.errors!.map(\n (error) =>\n new TypeError(error.message + JSON.stringify(error, null, 2), {\n cause: error,\n }),\n ),\n 'Config file does not conform to the schema',\n );\n }\n\n const { id, tempFolder, tempFileNames } = await getTempFileNames(config);\n\n const ctx: Ctx<G, P> = {\n use_cache: !!runOptions.use_cache,\n id,\n tempFolder,\n tempFileNames,\n config,\n callbacks,\n warnings: [],\n };\n\n console.info('temp folder:', tempFolder);\n\n // if unspecified, run all steps\n const { steps = STEPS } = runOptions;\n validateSteps(steps);\n\n if (steps.includes('download')) {\n console.time('download');\n await download(ctx);\n console.timeEnd('download');\n }\n\n let sourceData: SourceData<G, P> | undefined;\n let osmData: OSMData | undefined;\n\n if (steps.includes('match')) {\n ({ sourceData, osmData } = await readData(ctx));\n console.time('match');\n await match(ctx, sourceData, osmData);\n console.timeEnd('match');\n }\n\n let conflateResult: ConflateResult | undefined;\n if (steps.includes('conflate')) {\n if (!sourceData || !osmData) {\n ({ sourceData, osmData } = await readData(ctx));\n }\n console.time('conflate');\n conflateResult = await conflate(ctx, sourceData, osmData);\n console.timeEnd('conflate');\n }\n\n console.info('done.');\n return { conflate: conflateResult };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","fs","fs","fs","fs","getFirstCoord","hash","fs","fs","fs","fs","fs"],"sources":["../src/constants/defaults.ts","../src/types/callbacks.def.ts","../src/steps/download/util/validateOsmTagsInConfig.ts","../src/common/helpers.ts","../src/common/jsonl.ts","../src/common/getSector.ts","../src/steps/download/util/loadOsmFeature.ts","../src/steps/download/downloadFromOverpass.ts","../src/steps/download/downloadFromPlanetPbf.ts","../src/steps/download/fetchIgnoreList.ts","../src/steps/download/osmToJson.ts","../src/steps/download/index.ts","../src/steps/match/processDeletions.ts","../src/common/geo.ts","../src/steps/match/findPotentialOFeatures.ts","../src/steps/match/index.ts","../src/steps/conflate/createDiamond.ts","../src/common/calcBBox.ts","../src/common/bboxToPolygon.ts","../src/steps/conflate/splitUntilSmallEnough.ts","../src/steps/conflate/spreadToGrid.ts","../src/helpers.ts","../src/common/calcCount.ts","../src/steps/conflate/createIndexAndSaveToDisk.ts","../src/steps/conflate/mergeTinyDatasets.ts","../src/steps/conflate/stats/generateDataForWebsite.ts","../src/steps/conflate/stats/writeToRunHistory.ts","../src/steps/conflate/index.ts","../src/steps/match/readData.ts","../src/main.ts"],"sourcesContent":["import type { Config } from '../types/config.def.js';\n\n/** if a feature was last edited within this many DAYS, then it's \"recently edited\" */\nexport const RECENT_THRESHOLD = 90;\n\n/** can be overriden by the config */\nexport const DEFAULT_SECTOR_RESOLUTION = 3;\n\nexport const IS_UNIT_TEST = process.env.NODE_ENV === 'test';\n\n/**\n * this is not a hard minimum, if a dataset has fewer than this number,\n * we will try to merge it into the nearest datasets in the same sector.\n */\n// TODO: make configurable\nexport const MIN_ITEMS_PER_DATASET = 40;\nexport const MAX_ITEMS_PER_DATASET = 110;\nexport const MAX_BBOX_DEGREES = 4;\n\n/** can be customised in the config */\nexport const CHECK_DATE_KEY = 'check_date';\n\nexport const STEPS = <const>['download', 'match', 'conflate'];\nexport type Step = (typeof STEPS)[number];\n\nexport const FILE_NAMES = <const>{\n // o_data\n PBF: 'o.pbf',\n OVERPASS: 'o.geo.json',\n POSTPASS: 'o.geo.json',\n OSM_PROCESSED_WITH_REF: 'osm-processed-with-ref.jsonl',\n OSM_PROCESSED_NO_REF: 'osm-processed-no-ref.jsonl',\n OSM_PROCESSED_OTHER: 'osm-processed-other.json',\n\n // source data\n\n // other data\n IGNORE_LIST: 'ignore-list.json',\n\n // matched\n MATCHES: 'matches.json',\n};\nexport type FileNames = keyof typeof FILE_NAMES;\n\nexport const API_BASE_URL = 'https://osm-conflation-engine.kyle.kiwi';\nexport const REPO_URL = 'https://github.com/osm-nz/osm-conflation-engine';\n\nexport const getHttpHeaders = (config: Config) => ({\n 'User-Agent': `${REPO_URL} on behalf of ${config.metadata.wiki_page}`,\n});\n","import type {\n OsmPatchFeature,\n OsmFeature as StandardOsmFeature,\n Tags,\n} from 'osm-api';\nimport type {\n Feature,\n FeatureCollection,\n GeoJsonProperties,\n Geometry,\n Polygon,\n} from 'geojson';\nimport type { Step } from '../constants/defaults.js';\nimport type { MatchType, SourceData } from './internal.def.js';\nimport type { Config } from './config.def.js';\n\nexport type TagDiff = OsmPatchFeature['properties'];\n\n/** see microsoft/TypeScript#202 */\nexport type Identity<out T> = { _: T; readonly __: unique symbol };\n\nexport type OsmFeatureTypeShort = 'n' | 'w' | 'r';\nexport type OsmId = `${OsmFeatureTypeShort}${number}`;\nexport type DatasetId = string & Identity<'DatasetId'>;\n\nexport type Vec2 = [lon: number, lat: number];\n\nexport enum OsmFlags {\n None = 0,\n IsLastEditedByImporter = 1 << 0,\n IsRecentlyChanged = 1 << 1,\n IsChecked = 1 << 2,\n IsCheckedRecently = 1 << 3,\n}\n\nexport interface OsmFeature {\n id: OsmId;\n sectors: string[];\n centroid: Vec2;\n tags: Tags;\n metadata?: Partial<\n Pick<StandardOsmFeature, 'changeset' | 'version' | 'user' | 'timestamp'>\n >;\n flags: number & OsmFlags;\n}\n\nexport interface SourceDataFeature<\n G extends Geometry = Geometry,\n P extends GeoJsonProperties = GeoJsonProperties,\n> extends Feature<G, P> {\n /** list of sector IDs (h3 hexes) that this feature passes through */\n sectors: string[];\n centroid: Vec2;\n}\n\ntype MaybePromise<T> = T | Promise<T>;\n\nexport interface ConflationResultExtra {\n createFeatures?: OsmPatchFeature[];\n warnings?: string[];\n}\n\nexport interface ConflationDiff {\n tags: TagDiff;\n geometry?: Geometry;\n}\n\nexport interface SingleFeatureConflationResult {\n category?: string;\n group?: string;\n diff: ConflationDiff;\n extra?: ConflationResultExtra;\n}\n\nexport interface MutliFeatureConflationResult {\n category?: string;\n group?: string;\n diffPerFeature: { [id: OsmId]: ConflationDiff };\n extra?: ConflationResultExtra;\n}\n\n// this is the public API, so any break changes require a major version bump\nexport interface Callbacks<G extends Geometry, P extends GeoJsonProperties> {\n /**\n * called for every OSM feature, you should return a key which is unique\n * within its sector. For example, this could be the most important tags\n * concatenated together, if those tags are usually unique within the\n * local area (the sector).\n */\n getLocalKeyForOsm(osmFeature: OsmFeature): string;\n /**\n * see docs for {@link getLocalKeyForOsm}. This is the equivilant callback\n * for the source data, which is called for every feature.\n */\n getLocalKeyForSource(sourceFeature: SourceDataFeature<G, P>): string;\n\n /**\n * optional, by default a username which ends in `_import` is considered\n * to be an importer. Use this callback if you want to customise the\n * behaviour.\n */\n isImportUser?(username: string): boolean;\n\n // TODO: documentation for each\n mergeOneToOne(input: {\n source: SourceDataFeature<G, P>;\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n mergeOneToMany?(input: {\n source: SourceDataFeature<G, P>;\n osm: OsmFeature[];\n }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n mergeManyToOne?(input: {\n source: SourceDataFeature<G, P>[];\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n mergeManyToMany?(input: {\n source: SourceDataFeature<G, P>[];\n osm: OsmFeature[];\n }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n /**\n * called for OSM features which have a ref tag that does not exist in the\n * source dataset. You need to decide whether to:\n * (1) delete this feature; or\n * (2) only remove the import related tags; or\n * (3) do nothing.\n */\n deleteFeature?(input: {\n osm: OsmFeature;\n }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n /**\n * Calls for each feature in the source dataset where a matching feature\n * could not be found in OSM. If there are potential matches, these are\n * supplied using the `osmCandidates` argument. If you choose to accept\n * one of these candidates, return the `selection` property set to that\n * feature's {@link OsmId}. If not, return `selection: undefined`, which\n * will create a new OSM feature instead.\n */\n create(input: {\n source: SourceDataFeature<G, P>;\n osmCandidates: OsmFeature[];\n }): MaybePromise<\n | (SingleFeatureConflationResult & {\n /**\n * undefined means that no selection was made, so we\n * will create a new OSM feature instead\n */\n selection: OsmId | undefined;\n })\n | undefined\n >;\n\n /**\n * optional, called after the conflation stage once all diffs have been\n * allocated to a group. You can apply any addition transformations to\n * the features in each layer.\n */\n postprocessLayer?(input: {\n category: string;\n group: string;\n features: OsmPatchFeature[];\n osmData: Record<OsmId, OsmFeature>;\n sourceData: SourceData<G, P>;\n }): MaybePromise<OsmPatchFeature[] | undefined>;\n\n /**\n * optional, called after the conflation stage for every group. You can\n * return the changeset tags which will be applied to this group, and\n * optionally `instructions` which are shown in the website UI, before\n * someone starts working on this group.\n */\n getChangesetTags?(input: { category: string; group: string }): MaybePromise<{\n instructions?: string;\n changesetTags?: Tags;\n }>;\n}\nexport interface RunOptions {\n steps?: Step[];\n use_cache?: boolean;\n}\n\nexport interface ConflateResult {\n config: Config;\n warnings: string[];\n countsByPhase: {\n init: {\n sourceDataset: number;\n osm: {\n // these 4 are mutataly exclusive\n withRef: number;\n duplicateRefs: number;\n semi: number;\n noRef: number;\n\n // these are independant of the previous 4\n recentlyChecked: number;\n recentlyChanged: number;\n lastEditedByImporter: number;\n };\n ignored: number;\n };\n matched: Record<MatchType, number>;\n conflated: {\n create: number;\n edit: number;\n delete: number;\n perfect: number;\n };\n };\n}\n\nexport interface RunResult {\n conflate?: ConflateResult;\n}\n\nexport interface IndexFileProperties {\n category: string;\n group: string;\n title: string;\n instructions: string | undefined;\n totalCount: number;\n}\n\n/** the shape of `index.geo.json` */\nexport type IndexFile = FeatureCollection<Polygon, IndexFileProperties>;\n","import type { Tags } from 'osm-api';\nimport type { Tags as NullishTags } from 'pbf2json';\nimport { CHECK_DATE_KEY } from '../../../constants/defaults.js';\nimport type { Ctx } from '../../../types/internal.def.js';\n\nexport function validateOsmTagsInConfig(ctx: Ctx) {\n const checkDateKey = ctx.config.o_data.check_date_key || CHECK_DATE_KEY;\n\n const requiredKeys = new Set([\n // sanity check that the internal keys are included.\n checkDateKey,\n ctx.config.merge.osm_key,\n ]);\n const missingKeys = requiredKeys.difference(\n new Set(ctx.config.o_data.tags_to_keep),\n );\n if (missingKeys.size) {\n throw new Error(\n `config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...missingKeys].join(', ')}`,\n );\n }\n\n /** re-constructing regexes is expensive, so use a reference instead */\n const regExs: RegExp[] = [];\n for (const keyOrRegEx of ctx.config.o_data.tags_to_keep) {\n if (keyOrRegEx.startsWith('/') && keyOrRegEx.endsWith('/')) {\n regExs.push(new RegExp(keyOrRegEx.slice(1, -1)));\n }\n }\n\n /** cache every seen key, to avoid wasting time with regexes */\n const tagsToKeep: Record<string, boolean> = {};\n function keepKey(key: string) {\n if (key in tagsToKeep) return tagsToKeep[key];\n\n let keep = ctx.config.o_data.tags_to_keep.includes(key);\n\n if (!keep) {\n for (const regex of regExs) {\n if (regex.test(key)) {\n keep = true;\n break;\n }\n }\n }\n\n tagsToKeep[key] = keep;\n return keep;\n }\n\n function pickTags(allTags: NullishTags | undefined) {\n const tags: Tags = {};\n if (allTags) {\n for (const key in allTags) {\n const value = allTags[key];\n if (keepKey(key) && value) {\n tags[key] = value;\n }\n }\n }\n return tags;\n }\n\n return { pickTags };\n}\n","import { CheckDate } from '../types/index.js';\n\n/** number of years before we revisit a feature with check_date */\nconst CHECK_DATE_THRESHOLD_YEARS = 4;\n\n/** checks if an ISO date exists and if it it's less than X years old */\nexport const isChecked = (v: string | undefined): CheckDate => {\n if (!v) return CheckDate.No;\n\n const yearsAgo = (Date.now() - +new Date(v)) / 1000 / 60 / 60 / 24 / 365;\n return yearsAgo < CHECK_DATE_THRESHOLD_YEARS\n ? CheckDate.YesRecent\n : CheckDate.YesExpired;\n};\n","import { createReadStream, createWriteStream } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\n\n/** reads JSONL by parsing one line at a time */\nexport async function readJsonL<T, U extends string>(\n filePath: string,\n getKey: (row: T) => U,\n): Promise<Record<U, T>> {\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream });\n\n const rows = {} as Record<U, T>;\n for await (const line of rl) {\n const row = JSON.parse(line);\n const key = getKey(row);\n rows[key] = row;\n }\n return rows;\n}\n\n/** writes JSONL to a file by stringifying each line at a time */\nexport async function writeJsonL(filePath: string, rows: unknown[]) {\n const writeStream = createWriteStream(filePath, { encoding: 'utf8' });\n\n for (const row of rows) {\n writeStream.write(JSON.stringify(row));\n writeStream.write('\\n');\n }\n\n writeStream.end();\n\n await new Promise((resolve) => writeStream.on('finish', resolve));\n}\n","import type { Geometry } from 'geojson';\nimport {\n POLYGON_TO_CELLS_FLAGS,\n latLngToCell,\n polygonToCellsExperimental,\n} from 'h3-js';\nimport { DEFAULT_SECTOR_RESOLUTION } from '../constants/defaults.js';\n\n/** converts geojson geometry into a sorted list of h3 grids. */\nexport function getSector(\n geometry: Geometry,\n _resolution: number | undefined,\n): string[] {\n const resolution = _resolution || DEFAULT_SECTOR_RESOLUTION;\n switch (geometry.type) {\n case 'GeometryCollection': {\n throw new Error('not supported');\n }\n\n case 'Point': {\n return [\n latLngToCell(\n geometry.coordinates[1]!,\n geometry.coordinates[0]!,\n resolution,\n ),\n ];\n }\n case 'MultiPoint':\n case 'LineString':\n case 'MultiLineString':\n case 'Polygon': {\n return polygonToCellsExperimental(\n geometry.coordinates,\n resolution,\n POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n true,\n ).toSorted();\n }\n\n case 'MultiPolygon': {\n const grids = new Set<string>();\n for (const ring of geometry.coordinates) {\n const newGrids = polygonToCellsExperimental(\n ring,\n resolution,\n POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n true,\n );\n for (const grid of newGrids) grids.add(grid);\n }\n return [...grids].toSorted();\n }\n\n default: {\n throw new TypeError(geometry satisfies never);\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { isChecked } from '../../../common/helpers.js';\nimport { writeJsonL } from '../../../common/jsonl.js';\nimport {\n CHECK_DATE_KEY,\n RECENT_THRESHOLD,\n} from '../../../constants/defaults.js';\nimport {\n type DatasetId,\n type OsmFeature,\n OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n CheckDate,\n type Ctx,\n type OSMData,\n} from '../../../types/internal.def.js';\nimport { getSector } from '../../../common/getSector.js';\n\nconst THRESHOLD_DATE = ((d) => {\n d.setDate(d.getDate() - RECENT_THRESHOLD);\n return +d / 1000;\n})(new Date());\n\nexport function loadOsmFeature(ctx: Ctx, out: OSMData, object: OsmFeature) {\n out.count += 1;\n\n object.sectors = getSector(\n { type: 'Point', coordinates: object.centroid },\n ctx.config.merge.sector_resolution,\n );\n\n const checkDateKey = ctx.config.o_data.check_date_key || CHECK_DATE_KEY;\n\n // boolean flags\n if (+new Date(object.metadata?.timestamp ?? 0) > THRESHOLD_DATE) {\n object.flags |= OsmFlags.IsRecentlyChanged;\n }\n\n const isLastEditedByImporter =\n object.metadata?.user &&\n // TODO: cache the result per username\n (ctx.callbacks.isImportUser\n ? ctx.callbacks.isImportUser?.(object.metadata.user)\n : object.metadata?.user?.endsWith('_import'));\n if (object.metadata?.version === 1 || isLastEditedByImporter) {\n object.flags |= OsmFlags.IsLastEditedByImporter;\n }\n\n if (object.tags[checkDateKey]) {\n object.flags |= OsmFlags.IsChecked;\n }\n\n if (isChecked(object.tags[checkDateKey]) === CheckDate.YesRecent) {\n object.flags |= OsmFlags.IsCheckedRecently;\n }\n\n const primaryKey = <DatasetId | undefined>(\n object.tags[ctx.config.merge.osm_key]\n );\n if (primaryKey) {\n out.count++;\n // check if there is already an OSM object with the same linz ID\n\n // this node is 3rd+ one with this linzId\n if (out.duplicateRefs[primaryKey]) {\n out.duplicateRefs[primaryKey].push(object);\n }\n\n // this node is 2nd one with this linzId\n else if (out.withRef[primaryKey]) {\n out.duplicateRefs[primaryKey] = [out.withRef[primaryKey], object];\n delete out.withRef[primaryKey];\n }\n\n // the linz id has a semicolon in it - we don't like this\n // unless the address has alt_addr:* tags.\n else if (primaryKey.includes(';')) {\n const mergedIds = <DatasetId[]>primaryKey.split(';');\n for (const value of mergedIds) {\n out.semi[value] = object;\n }\n }\n\n // not a duplicate\n else {\n out.withRef[primaryKey] = object;\n }\n } else {\n out.noRef[object.id] = object;\n }\n}\n\nexport async function saveLoadedOsmFeatures(ctx: Ctx, out: OSMData) {\n const { withRef, noRef, ...other } = out;\n await writeJsonL(\n ctx.tempFileNames.osm_processed_with_ref,\n Object.values(withRef),\n );\n await writeJsonL(\n ctx.tempFileNames.osm_processed_no_ref,\n Object.values(noRef),\n );\n await fs.writeFile(\n ctx.tempFileNames.osm_processed_other,\n JSON.stringify(other),\n );\n}\n","import { promises as fs } from 'node:fs';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx, OSMData } from '../../types/internal.def.js';\nimport {\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/callbacks.def.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n loadOsmFeature,\n saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport async function downloadFromOverpass(\n ctx: Ctx,\n queryFile: string | undefined,\n serverUrl: string | undefined,\n) {\n const DEFAULT_SERVER_URL = 'https://overpass-api.de/api/interpreter';\n const DEFAULT_QUERY = `\n [out:json][timeout:480];\n nwr[\"${ctx.config.merge.osm_key}\"];\n out meta center;\n `;\n\n const query = queryFile\n ? await fs.readFile(queryFile, 'utf8')\n : DEFAULT_QUERY;\n\n console.info('fetching OSM data via overpass…');\n const response = await fetch(\n `${serverUrl || DEFAULT_SERVER_URL}?data=${encodeURIComponent(query)}`,\n { headers: getHttpHeaders(ctx.config) },\n );\n if (!response.ok) {\n const status = (await response.text()) || response.statusText;\n throw new Error(`HTTP Error ${response.status} ${status}`);\n }\n\n const responseJson = (await response.json()) as {\n elements: (StandardOsmFeature & {\n center?: { lat: number; lon: number };\n })[];\n };\n\n const { pickTags } = validateOsmTagsInConfig(ctx);\n\n const out: OSMData = {\n withRef: {},\n noRef: {},\n duplicateRefs: {},\n semi: {},\n count: 0,\n };\n for (const feature of responseJson.elements) {\n const object: OsmFeature = {\n id: `${feature.type[0]! as OsmFeatureTypeShort}${feature.id}`,\n centroid:\n feature.type === 'node'\n ? [feature.lon, feature.lat]\n : feature.center\n ? [feature.center.lon, feature.center.lat]\n : [0, 0],\n\n tags: pickTags(feature.tags),\n metadata: {\n changeset: feature.changeset,\n timestamp: feature.timestamp,\n user: feature.user,\n version: feature.version,\n },\n\n // added later\n sectors: [],\n flags: OsmFlags.None,\n };\n loadOsmFeature(ctx, out, object);\n }\n await saveLoadedOsmFeatures(ctx, out);\n}\n","import { setTimeout } from 'node:timers/promises';\nimport { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx } from '../../types/index.js';\n\nconst DOWNLOAD_SERVER = 'https://osm-internal.download.geofabrik.de';\nconst DOWNLOAD_SERVER_NO_METADATA = 'https://download.geofabrik.de';\nconst OSM_SERVER = 'https://www.openstreetmap.org';\n\nconst getCsrfToken = (html: string) =>\n html.match(/name=\"csrf-token\" content=\"([^\"]+)\"/)![1]!;\n\n/**\n * based on these docs: https://github.com/geofabrik/sendfile_osm_oauth_protector/blob/master/doc/client.md\n * This basically scrapes the OSM homepage to login programmatically,\n * bypassing CSRF and bypassing OAuth consent...\n */\nasync function getAuthToken(ctx: Ctx, pbfUrl: string) {\n if (!pbfUrl.startsWith(DOWNLOAD_SERVER)) {\n ctx.warnings.push(\n `(!) Planet PBF url is not from ${DOWNLOAD_SERVER}, so metadata may not be available.`,\n );\n }\n\n if (!process.env.OSM_AUTH) {\n ctx.warnings.push(\n '(!) No OSM credentials, so the planet extract will not include metadata',\n );\n return {\n url: pbfUrl\n .replace(DOWNLOAD_SERVER, DOWNLOAD_SERVER_NO_METADATA)\n .replace('-internal.osm.pbf', '.osm.pbf'),\n cookies: '',\n };\n }\n\n const credentials = new URLSearchParams(process.env.OSM_AUTH);\n const baseHeaders = getHttpHeaders(ctx.config);\n\n // 1. fetch config\n const authConfig = (await fetch(\n `${DOWNLOAD_SERVER}/get_cookie?action=get_authorization_url`,\n { method: 'POST', headers: baseHeaders },\n ).then((r) => r.json())) as {\n authorization_url: string;\n state: string;\n client_id: string;\n redirect_uri: string;\n };\n\n // 2. fetch home page to get session cookies + CSRF token\n const homePage = await fetch(`${OSM_SERVER}/login?cookie_test=true`, {\n headers: baseHeaders,\n });\n let cookieJar = homePage.headers.get('Set-Cookie')!;\n\n // 3. login\n const loginPage = await fetch(`${OSM_SERVER}/login`, {\n method: 'POST',\n body: new URLSearchParams({\n username: credentials.get('username')!,\n password: credentials.get('password')!,\n referer: '/',\n commit: 'Login',\n authenticity_token: getCsrfToken(await homePage.text()),\n }),\n headers: {\n ...baseHeaders,\n Cookie: cookieJar,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n });\n if (loginPage.status !== 200) throw new Error('Login failed');\n cookieJar += `; ${loginPage.headers.get('Set-Cookie')!}`;\n\n // 4. open the OAuth consent page\n const consentPage = await fetch(authConfig.authorization_url, {\n headers: { ...baseHeaders, Cookie: cookieJar },\n });\n\n // 5. if there is no consent, try to submit the form\n const authToken = consentPage.url.startsWith(OSM_SERVER)\n ? await fetch(authConfig.authorization_url, {\n method: 'POST',\n body: new URLSearchParams({\n client_id: authConfig.client_id,\n redirect_uri: authConfig.redirect_uri,\n authenticity_token: getCsrfToken(await consentPage.text()),\n state: authConfig.state,\n response_type: 'code',\n scope: 'read_prefs',\n nonce: '',\n code_challenge: '',\n code_challenge_method: '',\n commit: 'Authorize',\n }),\n headers: {\n ...baseHeaders,\n Cookie: cookieJar,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n }).then((r) => r.text())\n : await consentPage.text();\n\n if (!authToken.includes('gf_download_oauth')) throw new Error('No token');\n\n return {\n url: pbfUrl,\n cookies: authToken,\n };\n}\n\nexport async function downloadFromPlanetPbf(ctx: Ctx, pbfUrl: string) {\n const planetFile = ctx.tempFileNames.pbf;\n\n const { cookies, url } = await getAuthToken(ctx, pbfUrl);\n\n await fs.mkdir(dirname(planetFile), { recursive: true });\n await fs.rm(planetFile, { recursive: true, force: true });\n\n if (/^https?:\\/\\//.test(url)) {\n // it's an actuall URL, so fetch it\n console.info('downloading from', url, 'to', planetFile);\n const child = spawn(\n 'curl',\n ['-L', url, '-H', `Cookie: ${cookies}`, '--output', basename(planetFile)],\n { cwd: dirname(planetFile) },\n );\n\n child.stdout.pipe(process.stdout);\n child.stderr.pipe(process.stderr);\n\n await new Promise<void>((resolve, reject) => {\n child.on('close', (code) => (code ? reject(code) : resolve()));\n });\n\n // ensure that the file is written to the disk properly\n await setTimeout(5000);\n } else {\n // else: it's a path to a file on disk, so just copy the file.\n // this is only used for e2e tests by consumers, so no harm\n // in copying/duplicating the file.\n console.info('copying file from', url, 'to', planetFile);\n await fs.copyFile(url, planetFile);\n }\n\n console.info(planetFile, await fs.stat(planetFile));\n}\n","import { promises as fs } from 'node:fs';\nimport { API_BASE_URL, IS_UNIT_TEST } from '../../constants/defaults.js';\nimport type { Ctx } from '../../types/index.js';\n\nexport async function fetchIgnoreList(ctx: Ctx) {\n const refTag = ctx.config.merge.osm_key;\n\n const mockFilePath = ctx.config.e2e_tests?.ignore_list_file_path;\n if (IS_UNIT_TEST && !mockFilePath) {\n throw new Error(\n 'When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath',\n );\n }\n\n const obj: { rows: string[] } =\n IS_UNIT_TEST && mockFilePath\n ? JSON.parse(await fs.readFile(mockFilePath, 'utf8'))\n : await fetch(\n `${API_BASE_URL}/api/ignore_list/${encodeURIComponent(refTag)}?return_ids_only=true`,\n ).then((r) => r.json());\n\n await fs.writeFile(\n ctx.tempFileNames.ignore_list,\n JSON.stringify(obj.rows, null, 2),\n );\n}\n","import { existsSync } from 'node:fs';\nimport pbf2json, { type Item } from 'pbf2json';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport {\n type Ctx,\n type OSMData,\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/index.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n loadOsmFeature,\n saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport type PbfMetadata = Partial<\n Pick<StandardOsmFeature, 'changeset' | 'version' | 'user'> & {\n timestamp: number; // unlike the API, the golang code returns a number\n }\n>;\n\nexport async function osmToJson(ctx: Ctx, pbfFilter: string[]) {\n if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n console.info('Using cached osm data');\n return;\n }\n\n const { pickTags } = validateOsmTagsInConfig(ctx);\n\n console.info('Starting preprocess of OSM data...');\n const result = await new Promise<OSMData>((resolve, reject) => {\n const out: OSMData = {\n withRef: {},\n noRef: {},\n duplicateRefs: {},\n semi: {},\n count: 0,\n };\n\n let anyMetadata = false;\n\n let anythingSeen = false;\n pbf2json\n .createReadStream({\n file: ctx.tempFileNames.pbf,\n tags: pbfFilter,\n leveldb: '/tmp',\n // @ts-expect-error -- missing from typedefs since we\n // added this option in our fork.\n metadata: true,\n })\n .on(\n 'data',\n //\n (item: Item) => {\n anythingSeen ||= true;\n const coords = item.type === 'node' ? item : item.centroid;\n // @ts-expect-error -- missing from typedefs since we\n // added this option in our fork.\n const metadata: PbfMetadata | undefined = item.meta;\n if (metadata) anyMetadata = true;\n\n const tags = pickTags(item.tags);\n\n const object: OsmFeature = {\n id: `${<OsmFeatureTypeShort>item.type[0]}${item.id}`,\n centroid: [coords.lon, coords.lat],\n metadata: metadata\n ? {\n changeset: metadata.changeset,\n timestamp: metadata.timestamp\n ? new Date(1e3 * metadata.timestamp).toISOString()\n : undefined,\n user: metadata.user,\n version: metadata.version,\n }\n : undefined,\n tags,\n\n // added later\n sectors: [],\n flags: OsmFlags.None,\n };\n loadOsmFeature(ctx, out, object);\n\n if (!(out.count % 1000)) process.stdout.write('.');\n },\n )\n .on('finish', () => {\n if (!anyMetadata) {\n ctx.warnings.push('No metadata extracted from the planet file!');\n }\n if (!anythingSeen) {\n throw new Error(\n 'Nothing extracted from the pbf file, presumably it’s invalid.',\n );\n }\n resolve(out);\n })\n .on('error', reject);\n });\n\n await saveLoadedOsmFeatures(ctx, result);\n}\n","import { existsSync } from 'node:fs';\nimport type { Ctx } from '../../types/index.js';\nimport { downloadFromOverpass } from './downloadFromOverpass.js';\nimport { downloadFromPlanetPbf } from './downloadFromPlanetPbf.js';\nimport { fetchIgnoreList } from './fetchIgnoreList.js';\nimport { osmToJson } from './osmToJson.js';\n\nexport async function download(ctx: Ctx) {\n await fetchIgnoreList(ctx);\n\n if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n console.info('Using cached OSM data');\n return;\n }\n\n switch (ctx.config.o_data.source.type) {\n case 'pbf': {\n await downloadFromPlanetPbf(ctx, ctx.config.o_data.source.pbf_url);\n await osmToJson(ctx, ctx.config.o_data.source.pbf_filter);\n break;\n }\n\n case 'overpass': {\n await downloadFromOverpass(\n ctx,\n ctx.config.o_data.source.overpass_query_file,\n ctx.config.o_data.source.overpass_server_url,\n );\n break;\n }\n\n case 'postpass': {\n throw new Error('not supported yet');\n }\n\n default: {\n ctx.config.o_data.source satisfies never;\n }\n }\n}\n","import {\n type Ctx,\n type DatasetId,\n type OSMData,\n OsmFlags,\n type SourceData,\n} from '../../types/index.js';\n\nexport function processDeletions(\n ctx: Ctx,\n osmData: OSMData,\n sourceData: SourceData,\n) {\n const invalidIds: DatasetId[] = [];\n for (const _datasetId in osmData.withRef) {\n const datasetId = <DatasetId>_datasetId;\n const oFeature = osmData.withRef[datasetId]!;\n if (\n !(datasetId in sourceData) && // we delete every OSM node with a linzRef that does not exist in the LINZ data\n !(oFeature.flags & OsmFlags.IsCheckedRecently) // ...and it does not have a recent check_date\n ) {\n invalidIds.push(datasetId);\n }\n }\n\n const datasetByLocalKey: {\n [sector: string]: { [localKey: string]: DatasetId };\n } = {};\n for (const _datasetId in sourceData) {\n const datasetId = <DatasetId>_datasetId;\n const feature = sourceData[datasetId]!;\n for (const sector of feature.sectors) {\n datasetByLocalKey[sector] ||= {};\n const localKey = ctx.callbacks.getLocalKeyForSource(feature);\n\n // if (datasetByLocalKey[sector][localKey]) {\n // console.warn(\n // `localKey ‘${localKey}’ is not unique within sector ‘${sector}’`,\n // );\n // }\n datasetByLocalKey[sector][localKey] = datasetId;\n }\n }\n\n /**\n * cases where we're supposed to delete a feature and re-create an identical one in a\n * similar location. The smarter thing to do would be to simply change the ref.\n */\n const refsThatChanged: Record</* new */ DatasetId, /* old */ DatasetId> = {};\n const toDelete: DatasetId[] = [];\n\n for (const datasetIdToDelete of invalidIds) {\n const oFeature = osmData.withRef[datasetIdToDelete]!;\n\n const localKey = ctx.callbacks.getLocalKeyForOsm(oFeature);\n\n const newDatasetIds = new Set(\n oFeature.sectors\n .map((sector) => datasetByLocalKey[sector]?.[localKey])\n .filter(Boolean),\n );\n\n if (newDatasetIds.size > 1) {\n console.warn(\n `${oFeature.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`,\n );\n }\n if (newDatasetIds.size === 1) {\n const newDatasetId = [...newDatasetIds][0]!;\n if (osmData.withRef[newDatasetId]) {\n // linz ref changed && new one is already in osm -> so basically it didn't change. Normal delete\n // probably because address accidently existed twice in the LINZ db, with 2 different refs and LINZ fixed it by deleting one.\n toDelete.push(datasetIdToDelete);\n } else {\n // linz ref changed, and the new ref doesn't exist in OSM\n refsThatChanged[newDatasetId] = datasetIdToDelete;\n }\n } else {\n // no identical feature in osm, so delete it\n toDelete.push(datasetIdToDelete);\n }\n }\n return {\n refsThatChanged,\n toDelete,\n };\n}\n","import type { BBox, CoordKey } from '../types/internal.def.js';\n\n/**\n * converts a coordinate into a string key, for more performant\n * deduplication. 5dp = accurate to the nearest 1m, see\n * https://osm.wiki/Precision_of_coordinates\n */\nexport const getCoordKey = (lat: number, lon: number, accuraryDp = 5) =>\n <CoordKey>`${lat.toFixed(accuraryDp)},${lon.toFixed(accuraryDp)}`;\n\n/** metres */\nexport const EARTH_RADIUS = 6371008.8;\n\n/** @returns the distance (in degrees) between the closest two points of the two bboxes */\nexport function distanceBetweenBboxes(a: BBox, b: BBox) {\n const Δlat = Math.max(0, b.minLat - a.maxLat, a.minLat - b.maxLat);\n const Δlon = Math.max(0, b.minLng - a.maxLng, a.minLng - b.maxLng);\n return Math.hypot(Δlat, Δlon);\n}\n\n/** returns the larger of lat or lon */\nexport function getBboxMaxDimension(bbox: BBox) {\n return Math.max(\n Math.abs(bbox.maxLat - bbox.minLat),\n Math.abs(bbox.maxLng - bbox.minLng),\n );\n}\n","import { geoDistance } from 'd3-geo';\nimport type { Ctx, OsmFeature, SourceDataFeature } from '../../types/index.js';\nimport { EARTH_RADIUS } from '../../common/geo.js';\n\nconst featureToKey = new Map<OsmFeature, string>();\n\n/** If there are multiple osm addresses found, none of which have a linz ref, find the most likely ones */\nexport const findPotentialOFeatures = (\n ctx: Ctx,\n sourceDataFeature: SourceDataFeature,\n osmFeaturesWithNoRef: OsmFeature[],\n): OsmFeature[] => {\n const sourceKey = ctx.callbacks.getLocalKeyForSource(sourceDataFeature);\n\n const perfectMatches = osmFeaturesWithNoRef\n .filter((oFeature) => {\n // this 1st filter runs millions of times and needs to be fast\n let key = featureToKey.get(oFeature);\n if (!key) {\n key = ctx.callbacks.getLocalKeyForOsm(oFeature);\n featureToKey.set(oFeature, key);\n }\n return key === sourceKey;\n })\n .map((oFeature) => {\n // all following maps/filters only runs tens of times, so they can be expensive\n const offsetMetres =\n EARTH_RADIUS *\n geoDistance(oFeature.centroid, sourceDataFeature.centroid);\n\n return Object.assign(oFeature, { offset: offsetMetres });\n })\n // if our best guess is more than 200m from the gazetted location, it's definitely wrong\n .filter((o) => o.offset < 200)\n .toSorted((a, b) => a.offset - b.offset);\n\n return perfectMatches;\n};\n","import { promises as fs } from 'node:fs';\nimport {\n type Ctx,\n type DatasetId,\n type MatchOutput,\n MatchType,\n type OSMData,\n type SourceData,\n} from '../../types/index.js';\nimport { processDeletions } from './processDeletions.js';\nimport { findPotentialOFeatures } from './findPotentialOFeatures.js';\n\nexport async function match(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n) {\n const count = Object.keys(sourceData).length;\n\n const ignoreList = new Set<string>(\n JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n );\n\n console.info('processing deleted data...');\n console.time('matchDeletions');\n const { refsThatChanged, toDelete } = processDeletions(\n ctx,\n osmData,\n sourceData,\n );\n console.timeEnd('matchDeletions');\n\n console.info('Processing data...');\n let index = 0;\n console.time('matchLoop');\n const output: MatchOutput = {\n [MatchType.OneToOne]: [],\n [MatchType.OneToMany]: [],\n [MatchType.ManyToOne]: {},\n [MatchType.ManyToMany]: [],\n [MatchType.Delete]: toDelete,\n [MatchType.Guess]: [],\n };\n\n const osmFeaturesWithNoRef = Object.values(osmData.noRef);\n\n const individualRefsToSemiRef: Record<\n DatasetId,\n { value: DatasetId; isFirst: boolean }[]\n > = {};\n for (const ref in osmData.semi) {\n const parts = ref.split(';');\n for (const [j, part] of parts.entries()) {\n individualRefsToSemiRef[<DatasetId>part] ||= [];\n individualRefsToSemiRef[<DatasetId>part]!.push({\n value: <DatasetId>ref,\n isFirst: !j,\n });\n }\n }\n\n for (const _datasetId in sourceData) {\n const originalDatasetId = <DatasetId>_datasetId;\n let datasetId = refsThatChanged[originalDatasetId] || originalDatasetId;\n\n // someone has explicitly ignored this row, so act as if it doesn't exist.\n if (ignoreList.has(datasetId)) continue;\n\n // don't try to recreate the feature, we'll handle it when we reach the old ID\n if (datasetId in refsThatChanged) continue;\n\n // skip non-first\n\n const oFeature = osmData.withRef[datasetId];\n const sourceFeature = sourceData[originalDatasetId]!;\n const duplicate = osmData.duplicateRefs[datasetId];\n\n if (individualRefsToSemiRef[datasetId]) {\n // TODO: hardcoded 0 here is not good.\n if (individualRefsToSemiRef[datasetId]![0]!.isFirst) {\n datasetId = individualRefsToSemiRef[datasetId]![0]!.value!;\n } else {\n // it's not the first one, so skip\n continue;\n }\n }\n const semi = osmData.semi[datasetId];\n\n // (source:o)\n if (oFeature && semi) {\n const parts = <DatasetId[]>datasetId.split(';');\n output[MatchType.ManyToMany].push({\n osm: [oFeature.id, semi.id],\n source: parts,\n });\n } else if (oFeature) {\n // [1:1] found an exact match\n // the tags might be wrong, but we'll deal with that later\n output[MatchType.OneToOne].push({\n osm: oFeature.id,\n source: originalDatasetId,\n });\n } else if (duplicate) {\n // [1:many] there are multiple exact matches (1:many)\n output[MatchType.OneToMany].push({\n osm: duplicate.map((feature) => feature.id),\n source: datasetId,\n });\n } else if (semi) {\n // [many:1] there is one match, but it contains multiple source features merged\n const parts = <DatasetId[]>datasetId.split(';');\n output[MatchType.ManyToOne][semi.id] ||= [];\n output[MatchType.ManyToOne][semi.id]?.push(...parts);\n } else {\n // if there is no match, try to guess based on nearby candidates\n const possibleAddresses = findPotentialOFeatures(\n ctx,\n sourceFeature,\n osmFeaturesWithNoRef,\n );\n if (possibleAddresses.length === 1) {\n // [1:1] but the ref needs to be added\n output[MatchType.OneToOne].push({\n source: datasetId,\n osm: possibleAddresses[0]!.id,\n });\n } else {\n // [1:?]\n output[MatchType.Guess].push({\n source: datasetId,\n osmCandidates: possibleAddresses.map((f) => f.id),\n });\n }\n }\n\n index += 1;\n if (!(index % 1000)) {\n /* istanbul ignore next */\n process.stdout.write(`${((index / count) * 100).toFixed(1)}% `);\n }\n }\n\n console.timeEnd('matchLoop');\n\n await fs.writeFile(\n ctx.tempFileNames.matches,\n JSON.stringify(output, null, undefined),\n );\n\n return { output };\n}\n","import type { Vec2 as Coord } from '../../types/index.js';\n\n/** radius of the diamond */\nconst RADIUS = 0.0002;\n// 0.0002° ~= 0° 0' 0.72\"\n\n/** generates a bbox around a point, but as a diamond not a square */\nexport function createDiamond({\n lat,\n lng,\n}: {\n lat: number;\n lng: number;\n}): [ring1: Coord[]] {\n const diamond: Coord[] = [\n [lng, lat + RADIUS], // top\n [lng + RADIUS, lat], // right\n [lng, lat - RADIUS], // bottom\n [lng - RADIUS, lat], // left\n ];\n diamond.push(diamond[0]!); // make it a closed way\n\n return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n\n/** generates a bbox around a point, but as a square not a diamond */\nexport function createSquare({\n lat,\n lng,\n}: {\n lat: number;\n lng: number;\n}): [ring1: Coord[]] {\n const diamond: Coord[] = [\n [lng + RADIUS / 2, lat + RADIUS / 2], // top-right\n [lng + RADIUS / 2, lat - RADIUS / 2], // top-left\n [lng - RADIUS / 2, lat - RADIUS / 2], // bottom-left\n [lng - RADIUS / 2, lat + RADIUS / 2], // bottom-right\n ];\n diamond.push(diamond[0]!); // make it a closed way\n\n return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n","import type { Feature, Position } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function calcBBox(features: Feature[]): BBox {\n const bbox: BBox = {\n minLat: Infinity,\n minLng: Infinity,\n maxLat: -Infinity,\n maxLng: -Infinity,\n };\n\n function visit([_lng, _lat]: Position) {\n const lat = +_lat!;\n const lng = +_lng!;\n if (lat < bbox.minLat) bbox.minLat = lat;\n if (lng < bbox.minLng) bbox.minLng = lng;\n if (lat > bbox.maxLat) bbox.maxLat = lat;\n if (lng > bbox.maxLng) bbox.maxLng = lng;\n }\n\n /* eslint-disable unicorn/no-array-for-each -- deliberate */\n\n for (const f of features) {\n switch (f.geometry.type) {\n case 'Point': {\n visit(f.geometry.coordinates);\n\n break;\n }\n case 'LineString': {\n f.geometry.coordinates.forEach(visit);\n\n break;\n }\n case 'Polygon': {\n for (const ring of f.geometry.coordinates) ring.forEach(visit);\n\n break;\n }\n case 'MultiPolygon': {\n for (const member of f.geometry.coordinates) {\n for (const ring of member) ring.forEach(visit);\n }\n break;\n }\n\n default:\n // other geometry types are allowed, they just won't\n // count towards the bbox\n }\n }\n return bbox;\n}\n","import type { Polygon } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function bboxToPolygon({\n minLng,\n minLat,\n maxLng,\n maxLat,\n}: BBox): Polygon {\n return {\n type: 'Polygon',\n coordinates: [\n [\n [minLng, minLat], // SW\n [maxLng, minLat], // SE\n [maxLng, maxLat], // NE\n [minLng, maxLat], // NW\n [minLng, minLat], // SW\n ],\n ],\n };\n}\n","import type { OsmPatchFeature as GeoJsonFeature } from 'osm-api';\nimport { geoCentroid as getFirstCoord } from 'd3-geo';\nimport type {\n Ctx,\n HandlerReturnWithBBox,\n OutputLayer,\n} from '../../types/index.js';\nimport { MAX_ITEMS_PER_DATASET } from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { getSector } from '../../common/getSector.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\n\nexport function splitUntilSmallEnough(\n ctx: Ctx,\n name: string,\n metadata: Omit<OutputLayer, 'features' | 'bbox' | 'sectorIds'>,\n features: GeoJsonFeature[],\n depth = 0,\n): HandlerReturnWithBBox {\n if (!features.length) return {};\n\n const bbox = calcBBox(features);\n const sectorIds = getSector(\n bboxToPolygon(bbox),\n ctx.config.merge.sector_resolution,\n );\n\n const recursionLimit = depth > 10;\n if (recursionLimit) {\n console.info(`(!) Couldn't split ${name} (${features.length} left)`);\n }\n\n if (features.length <= MAX_ITEMS_PER_DATASET || recursionLimit) {\n // yay, it's small enough\n return { [name]: { features, bbox, sectorIds, ...metadata } };\n }\n\n // it's not small enough\n const tallerThanWide = bbox.maxLat - bbox.minLat > bbox.maxLng - bbox.minLng;\n if (tallerThanWide) {\n const midway = bbox.minLat + (bbox.maxLat - bbox.minLat) / 2;\n const out: [north: GeoJsonFeature[], south: GeoJsonFeature[]] = [[], []];\n for (const f of features) {\n out[+(getFirstCoord(f.geometry)[1] < midway)]!.push(f);\n }\n\n const [north, south] = out;\n\n return {\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^N`,\n metadata,\n north,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^S`,\n metadata,\n south,\n depth + 1,\n ),\n };\n }\n\n // else: it's wider than it is tall\n const midway = bbox.minLng + (bbox.maxLng - bbox.minLng) / 2;\n const out: [east: GeoJsonFeature[], west: GeoJsonFeature[]] = [[], []];\n for (const f of features) {\n out[+(getFirstCoord(f.geometry)[0] < midway)]!.push(f);\n }\n\n const [east, west] = out;\n\n return {\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^E`,\n metadata,\n east,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n //\n ctx,\n `${name}^W`,\n metadata,\n west,\n depth + 1,\n ),\n };\n}\n\n/** normalizes the weird ^N^S^E names generated by `splitUntilSmallEnough` */\nexport function normalizeName(\n input: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n const index: Record<string, number> = {};\n const output: HandlerReturnWithBBox = {};\n\n for (const name in input) {\n const newName = name.replace(/^([^^]+)(\\^\\w)+$/, (_, withoutSuffix) => {\n index[withoutSuffix] ||= 0;\n index[withoutSuffix] += 1;\n return `${withoutSuffix} ${index[withoutSuffix]}`;\n });\n\n output[newName] = input[name]!;\n }\n return output;\n}\n","import type { Feature, Point } from 'geojson';\nimport type {\n Vec2 as Coord,\n HandlerReturnWithBBox,\n} from '../../types/index.js';\nimport { getCoordKey } from '../../common/geo.js';\n\nconst { sin, cos, floor, sqrt, PI: π } = Math;\n\n/**\n * Determines the coordinate where we should place the n-th address, relative\n * to the original location which is (0,0)\n *\n * See https://desmos.com/calculator/nunxdg7x9b\n */\nexport function toFormation(n: number): Coord {\n const r = floor(sqrt(n));\n const θ = ((n - r ** 2) * π) / (2 * ((r + 1) ** 2 - r ** 2 - 1)) || 0;\n\n const [x, y] = [r * cos(θ), r * sin(θ)];\n return [+x.toFixed(2), +y.toFixed(2)];\n}\n\n/**\n * If there are multiple addresses in the same location,\n * we convert them into a stack if there are more than STACK_THRESHOLD.\n * If there are less, we spread them out in a grid formation around the\n * original point.\n */\nexport function shiftOverlappingPoints(\n sectors: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n for (const sectorName in sectors) {\n const sector = sectors[sectorName]!;\n const nodeByCoord: Record<string, Feature[]> = {};\n for (const feature of sector.features) {\n if (feature.geometry.type === 'Point') {\n const [lng, lat] = feature.geometry.coordinates as Coord;\n // round to nearest 0.05seconds of latitude/longitude in case the points are slightly off\n // same logic as in stackLinzData.ts\n const key = getCoordKey(lat, lng, 4);\n\n nodeByCoord[key] ||= [];\n nodeByCoord[key].push(feature);\n }\n }\n for (const nodes of Object.values(nodeByCoord)) {\n if (nodes.length > 1) {\n // multiple features in the same place, so offset the coordinates\n for (let index = 0; index < nodes.length; index += 1) {\n const [offsetX, offsetY] = toFormation(index);\n const [lng, lat] = (nodes[index]!.geometry as Point)\n .coordinates as Coord;\n nodes[index]!.geometry = {\n type: 'Point',\n coordinates: [lng + 0.000004 * offsetX, lat + 0.000004 * offsetY],\n };\n }\n }\n }\n }\n return sectors;\n}\n","import { createHash } from 'node:crypto';\n\nexport function sha256(text: string): string {\n return createHash('sha256').update(text, 'utf8').digest('hex');\n}\n","import type { OsmPatchFeature } from 'osm-api';\n\nexport function calcCount(features: OsmPatchFeature[]): {\n count: string;\n totalCount: number;\n} {\n let plus = 0;\n let minus = 0;\n let move = 0;\n let edit = 0;\n\n for (const f of features) {\n switch (f.properties.__action) {\n case 'delete': {\n minus += 1;\n break;\n }\n case 'move': {\n move += 1;\n break;\n }\n case 'edit': {\n edit += 1;\n break;\n }\n default: {\n plus += 1;\n }\n }\n }\n\n const strs = [\n plus && `add ${plus}`,\n minus && `delete ${minus}`,\n move && `move ${move}`,\n edit && `edit ${edit}`,\n ].filter((x: string | 0): x is string => !!x);\n\n return {\n count: strs.join(', '),\n totalCount: plus + minus + move + edit,\n };\n}\n","import { promises as fs } from 'node:fs';\nimport { join } from 'node:path';\nimport type { OsmPatch } from 'osm-api';\nimport type { Feature, FeatureCollection, Polygon } from 'geojson';\nimport type {\n ConflateResult,\n Ctx,\n IndexFileProperties,\n OutputLayers,\n} from '../../types/index.js';\nimport { IS_UNIT_TEST } from '../../constants/defaults.js';\nimport { sha256 as hash } from '../../helpers.js';\nimport { calcCount } from '../../common/calcCount.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\n\nfunction toId(suburb: string) {\n // macrons are url safe\n return `${suburb.replaceAll(/[^\\dA-Za-zĀāĒēĪīŌōŪū]/g, '').slice(0, 100)}_${hash(suburb).slice(0, 6)}`;\n}\n\nexport async function createIndexAndSaveToDisk(\n ctx: Ctx,\n metrics: ConflateResult,\n suburbs: OutputLayers,\n): Promise<void> {\n const githubParts = ctx.config.metadata.git_repository.match(\n 'https://github.com/([^/]+)/([^/]+)',\n );\n if (!githubParts) throw new Error('git_repository is not a valid github url');\n const outputFolder =\n ctx.config.output?.folder || join(process.cwd(), 'output');\n const subFolderName = 'suburbs';\n\n const meta = Object.entries(suburbs).flatMap(([category, groups]) =>\n Object.entries(groups).map(([group, items]) => ({\n category,\n group,\n title: [category, group].filter(Boolean).join(' - '),\n bbox: items.bbox,\n instructions: items.instructions,\n ...calcCount(items.features),\n })),\n );\n\n // create index.json\n const indexFile = {\n fields: [],\n results: meta\n .map((v) => {\n return {\n id: toId(v.title),\n url: `https://${githubParts[1]}.github.io/${githubParts[2]}/${subFolderName}/${toId(v.title)}.osmPatch.geo.json`,\n name: v.title,\n title: v.title,\n totalCount: v.totalCount,\n source: '',\n snippet: v.count,\n extent: [\n [v.bbox.minLng, v.bbox.minLat],\n [v.bbox.maxLng, v.bbox.maxLat],\n ],\n instructions: v.instructions,\n groupCategories: [v.category, '/Categories/Addresses'],\n };\n })\n .toSorted((a, b) => a.name.localeCompare(b.name)),\n };\n\n // create index.geo.json\n const newIndexFile: FeatureCollection<Polygon, IndexFileProperties> = {\n type: 'FeatureCollection',\n features: meta\n .map(({ bbox, ...other }): Feature<Polygon, IndexFileProperties> => {\n return {\n type: 'Feature',\n geometry: bboxToPolygon(bbox),\n properties: other,\n };\n })\n .toSorted((a, b) => a.properties.title.localeCompare(b.properties.title)),\n };\n\n await fs.mkdir(join(outputFolder, subFolderName), { recursive: true });\n await fs.writeFile(\n join(outputFolder, 'index.json'),\n JSON.stringify(indexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.writeFile(\n join(outputFolder, 'index.geo.json'),\n JSON.stringify(newIndexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.writeFile(\n join(outputFolder, 'metrics.json'),\n JSON.stringify(metrics, null, 2),\n );\n\n // save each suburb\n for (const v of meta) {\n const geojson: OsmPatch = {\n type: 'FeatureCollection',\n ...suburbs[v.category]![v.group]!,\n };\n\n await fs.writeFile(\n join(outputFolder, subFolderName, `${toId(v.title)}.osmPatch.geo.json`),\n JSON.stringify(geojson, null, IS_UNIT_TEST ? 2 : undefined),\n );\n }\n}\n","import type { Tags } from 'osm-api';\nimport type { HandlerReturnWithBBox } from '../../types/index.js';\nimport {\n MAX_BBOX_DEGREES,\n MAX_ITEMS_PER_DATASET,\n MIN_ITEMS_PER_DATASET,\n} from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\nimport {\n distanceBetweenBboxes,\n getBboxMaxDimension,\n} from '../../common/geo.js';\n\nexport function mergeTags(a: Tags, b: Tags): Tags {\n const merged = { ...a };\n for (const key in b) {\n if (!merged[key]) {\n merged[key] = b[key]!;\n } else if (merged[key] !== b[key]) {\n merged[key] += `;${b[key]}`;\n }\n }\n return merged;\n}\n\n/**\n * having many tiny datasets is unhelpful, it's more convenient for us\n * to merge them into\n */\nexport function mergeTinyDatasets(\n sectors: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n // keep looping until there is nothing more that can be merged.\n let anyModificationsInPrevPass = true;\n while (anyModificationsInPrevPass) {\n anyModificationsInPrevPass = false;\n\n for (const sectorName in sectors) {\n const sector = sectors[sectorName]!;\n const mainSectorId = sector.sectorIds[0]!;\n\n // skip if it already has enough members\n if (sector.features.length > MIN_ITEMS_PER_DATASET) continue;\n\n // try to find another group in the same sector which\n // also has too few items.\n const bestOther = Object.entries(sectors)\n .filter(([otherName, other]) => {\n return (\n // must be in the same sector\n other.sectorIds.includes(mainSectorId) &&\n // skip self\n otherName !== sectorName &&\n // must have too few items\n other.features.length <= MIN_ITEMS_PER_DATASET &&\n // if we merge them, the total must not be too high\n sector.features.length + other.features.length <\n MAX_ITEMS_PER_DATASET\n );\n })\n .toSorted(\n ([, a], [, b]) =>\n // prefer the closest\n distanceBetweenBboxes(sector.bbox, a.bbox) -\n distanceBetweenBboxes(sector.bbox, b.bbox),\n )[0];\n\n if (!bestOther) continue;\n\n const [otherName, other] = bestOther;\n\n const newBbox = calcBBox([\n {\n type: 'Feature',\n geometry: bboxToPolygon(sector.bbox),\n properties: {},\n },\n {\n type: 'Feature',\n geometry: bboxToPolygon(other.bbox),\n properties: {},\n },\n ]);\n\n // If the new bbox is now ridiculously big, and the previous\n // bboxes were not this big, then abort the merge.\n if (\n getBboxMaxDimension(newBbox) > MAX_BBOX_DEGREES &&\n (getBboxMaxDimension(sector.bbox) < MAX_BBOX_DEGREES ||\n getBboxMaxDimension(other.bbox) < MAX_BBOX_DEGREES)\n ) {\n continue;\n }\n\n // combine them\n sector.bbox = newBbox;\n sector.sectorIds = [\n ...new Set([...sector.sectorIds, ...other.sectorIds]),\n ];\n sector.changesetTags = mergeTags(\n sector.changesetTags || {},\n other.changesetTags || {},\n );\n if (other.instructions) {\n sector.instructions ||= '';\n sector.instructions += `\\n\\n${other.instructions}`;\n sector.instructions.trim();\n }\n sector.features.push(...other.features);\n\n // deleting it now means it won't be visited again by this loop\n delete sectors[sectorName];\n delete sectors[otherName];\n\n const { name: mergedName } = mergeTags(\n { name: sectorName },\n { name: otherName },\n );\n sectors[mergedName!] = sector;\n anyModificationsInPrevPass = true;\n }\n }\n return sectors;\n}\n","import { promises as fs } from 'node:fs';\nimport {\n type ConflateResult,\n type OsmFeature,\n OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n type Ctx,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OutputLayers,\n type SourceData,\n} from '../../../types/internal.def.js';\n\nexport async function generateDataForWebsite(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n matches: MatchOutput,\n handlerReturn: OutputLayers,\n): Promise<ConflateResult> {\n const ignoreList = new Set<string>(\n JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n );\n\n // init phase\n let lastEditedByImporter = 0;\n let recentlyChanged = 0;\n let recentlyChecked = 0;\n function collectFlags(feature: OsmFeature) {\n if (feature.flags & OsmFlags.IsLastEditedByImporter) lastEditedByImporter++;\n if (feature.flags & OsmFlags.IsRecentlyChanged) recentlyChanged++;\n if (feature.flags & OsmFlags.IsCheckedRecently) recentlyChecked++;\n }\n for (const feature of Object.values(osmData.withRef)) collectFlags(feature);\n for (const feature of Object.values(osmData.noRef)) collectFlags(feature);\n for (const feature of Object.values(osmData.semi)) collectFlags(feature);\n for (const features of Object.values(osmData.duplicateRefs)) {\n for (const feature of features) collectFlags(feature);\n }\n\n const result: ConflateResult = {\n config: ctx.config,\n warnings: ctx.warnings,\n countsByPhase: {\n init: {\n sourceDataset: Object.keys(sourceData).length,\n ignored: ignoreList.size,\n osm: {\n withRef: Object.keys(osmData.withRef).length,\n noRef: Object.keys(osmData.noRef).length,\n semi: Object.keys(osmData.semi).length,\n duplicateRefs: Object.values(osmData.duplicateRefs).flat().length,\n lastEditedByImporter,\n recentlyChanged,\n recentlyChecked,\n },\n },\n matched: {\n [MatchType.OneToOne]: matches[MatchType.OneToOne].length,\n [MatchType.OneToMany]: matches[MatchType.OneToMany].length,\n [MatchType.ManyToOne]: Object.keys(matches[MatchType.ManyToOne]).length,\n [MatchType.ManyToMany]: matches[MatchType.ManyToMany].length,\n [MatchType.Delete]: matches[MatchType.Delete].length,\n [MatchType.Guess]: matches[MatchType.Guess].length,\n },\n conflated: { create: 0, edit: 0, delete: 0, perfect: 0 },\n },\n };\n\n // conflated phase\n for (const sectors of Object.values(handlerReturn).flatMap((v) =>\n Object.values(v),\n )) {\n for (const f of sectors.features) {\n if (f.properties.__action === 'edit') {\n result.countsByPhase.conflated.edit++;\n } else if (f.properties.__action === 'delete') {\n result.countsByPhase.conflated.delete++;\n } else {\n result.countsByPhase.conflated.create++;\n }\n }\n }\n result.countsByPhase.conflated.perfect =\n osmData.count -\n result.countsByPhase.conflated.edit -\n result.countsByPhase.conflated.delete;\n\n return result;\n}\n","import { getIDToken } from '@actions/core';\nimport { API_BASE_URL, IS_UNIT_TEST } from '../../../constants/defaults.js';\nimport type { ConflateResult } from '../../../types/callbacks.def.js';\n\nexport async function writeToRunHistory(metrics: ConflateResult) {\n if (IS_UNIT_TEST) return;\n if (!process.env.GITHUB_ACTIONS) {\n console.info(\n 'Skipping run history update, because not running in GitHub Actions',\n );\n return;\n }\n const token = await getIDToken('osm-conflation-engine');\n\n const refTag = metrics.config.merge.osm_key;\n const result = await fetch(`${API_BASE_URL}/api/run_history/${refTag}`, {\n method: 'PUT',\n headers: { Authorization: `Bearer ${token}` },\n body: JSON.stringify(metrics),\n });\n if (result.status !== 200) {\n throw new Error(await result.text());\n }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OsmPatchFeature } from 'osm-api';\nimport {\n type ConflateResult,\n type ConflationDiff,\n type ConflationResultExtra,\n type Ctx,\n type DatasetId,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OsmFeature,\n OsmFlags,\n type OsmId,\n type OutputLayers,\n type SourceData,\n type SourceDataFeature,\n type TagDiff,\n} from '../../types/index.js';\nimport { createDiamond, createSquare } from './createDiamond.js';\nimport { splitUntilSmallEnough } from './splitUntilSmallEnough.js';\nimport { shiftOverlappingPoints } from './spreadToGrid.js';\nimport { createIndexAndSaveToDisk } from './createIndexAndSaveToDisk.js';\nimport { mergeTinyDatasets } from './mergeTinyDatasets.js';\nimport { generateDataForWebsite } from './stats/generateDataForWebsite.js';\nimport { writeToRunHistory } from './stats/writeToRunHistory.js';\n\nconst MSG = 'invalid value returned by your callback function';\n\nfunction isTagDiffEmpty(diff: TagDiff) {\n for (const key in diff) {\n if (key === '__action' && diff[key] === 'edit') continue;\n return false;\n }\n return true;\n}\n\nfunction hasDiff(diff: ConflationDiff) {\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (typeof diff.tags !== 'object') throw new TypeError(MSG);\n return !!diff.geometry || !isTagDiffEmpty(diff.tags);\n}\n\nfunction createFeature(\n diff: ConflationDiff,\n o?: OsmFeature,\n s?: SourceDataFeature,\n): OsmPatchFeature {\n if (!o && !s) throw new Error('requires either o or s');\n\n if (o) diff.tags.__action ||= 'edit';\n\n const [lng, lat] = (o?.centroid || s?.centroid)!;\n return {\n type: 'Feature',\n id: o?.id || s?.id,\n geometry:\n diff.tags.__action === 'delete'\n ? { type: 'Polygon', coordinates: createSquare({ lat, lng }) }\n : diff.geometry ||\n (diff.tags.__action === 'edit'\n ? { type: 'Polygon', coordinates: createDiamond({ lat, lng }) }\n : { type: 'Point', coordinates: [lng, lat] }),\n properties: diff.tags,\n };\n}\n\nexport async function conflate(\n ctx: Ctx,\n sourceData: SourceData,\n osmData: OSMData,\n): Promise<ConflateResult> {\n const osmDataById: Record<OsmId, OsmFeature> = { ...osmData.noRef };\n for (const cat of <const>['withRef', 'semi', 'duplicateRefs']) {\n for (const k in osmData[cat]) {\n const feature = osmData[cat][k as DatasetId]!;\n if (Array.isArray(feature)) {\n for (const item of feature) {\n osmDataById[item.id] = item;\n }\n } else {\n osmDataById[feature.id] = feature;\n }\n }\n }\n\n const output: {\n [category: string]: { [sector: string]: OsmPatchFeature[] };\n } = {};\n\n function handleExtra(\n extra: ConflationResultExtra | undefined,\n category: string,\n sector: string,\n ) {\n if (!extra) return;\n if (extra.warnings?.length) ctx.warnings.push(...extra.warnings);\n if (extra.createFeatures) {\n output[category] ||= {};\n output[category][sector] ||= [];\n output[category][sector].push(...extra.createFeatures);\n }\n }\n\n const matches: MatchOutput = JSON.parse(\n await fs.readFile(ctx.tempFileNames.matches, 'utf8'),\n );\n\n // 1.\n for (const { source: s, osm: o } of matches[MatchType.OneToOne]) {\n const oFeature = osmDataById[o]!;\n const sFeature = sourceData[s]!;\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n const result = await ctx.callbacks.mergeOneToOne({\n osm: oFeature,\n source: sFeature,\n });\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, sector);\n output[category] ||= {};\n output[category][sector] ||= [];\n output[category][sector].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n // 2.\n for (const { source, osm } of matches[MatchType.OneToMany]) {\n const oFeatures = osm\n .map((id) => osmDataById[id]!)\n .filter((oFeature) => !(oFeature.flags & OsmFlags.IsCheckedRecently));\n\n const sFeature = sourceData[source]!;\n\n const result = await ctx.callbacks.mergeOneToMany?.({\n osm: oFeatures,\n source: sFeature,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diffPerFeature !== 'object') {\n throw new TypeError(MSG);\n }\n\n for (const _osmId in result.diffPerFeature) {\n const osmId = <OsmId>_osmId;\n const oFeature = oFeatures.find((f) => f.id === osmId)!;\n const diff = result.diffPerFeature[osmId]!;\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(diff, oFeature, sFeature));\n }\n }\n\n // 3.\n for (const _osmId in matches[MatchType.ManyToOne]) {\n const osmId = <OsmId>_osmId;\n const oFeature = osmDataById[osmId]!;\n const sFeatures = matches[MatchType.ManyToOne][osmId]!.map(\n (id) => sourceData[id]!,\n );\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n const result = await ctx.callbacks.mergeManyToOne?.({\n osm: oFeature,\n source: sFeatures,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(result.diff, oFeature));\n }\n\n // 4.\n for (const { source, osm } of matches[MatchType.ManyToMany]) {\n const oFeatures = osm\n .map((id) => osmDataById[id]!)\n .filter((oFeature) => !(oFeature.flags & OsmFlags.IsCheckedRecently));\n\n const sFeature = source.map((id) => sourceData[id]!);\n\n const result = await ctx.callbacks.mergeManyToMany?.({\n osm: oFeatures,\n source: sFeature,\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diffPerFeature !== 'object') {\n throw new TypeError(MSG);\n }\n\n for (const _osmId in result.diffPerFeature) {\n const osmId = <OsmId>_osmId;\n const oFeature = oFeatures.find((f) => f.id === osmId)!;\n const diff = result.diffPerFeature[osmId]!;\n if (typeof diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(diff, oFeature));\n }\n }\n\n // 5. deletions\n for (const idToDelete of matches[MatchType.Delete]) {\n const oFeature = osmData.withRef[idToDelete]!;\n\n if (oFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n // the business-side needs to decide for each feature, if it will outright\n // delete it, or just remove the relevant tags.\n const result = await ctx.callbacks.deleteFeature?.({ osm: oFeature });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const category = result.category || '';\n const group = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(createFeature(result.diff, oFeature));\n }\n\n // 6. to avoid reënqueuing these features twice, the business-side needs to\n // make a selection, and process the tagDiff at the same time.\n for (const pair of matches[MatchType.Guess]) {\n const sFeature = sourceData[pair.source]!;\n const result = await ctx.callbacks.create({\n source: sFeature,\n osmCandidates: pair.osmCandidates.map((osmId) => osmData.noRef[osmId]!),\n });\n if (!result) continue;\n if (typeof result !== 'object') throw new TypeError(MSG);\n if (typeof result.diff !== 'object') throw new TypeError(MSG);\n if (!hasDiff(result.diff)) continue;\n\n const oFeature = result.selection\n ? osmData.noRef[result.selection]!\n : undefined;\n\n const category = result.category || '';\n const group = result.group || oFeature?.sectors[0] || 'unknown';\n handleExtra(result.extra, category, group);\n output[category] ||= {};\n output[category][group] ||= [];\n output[category][group].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n const handlerReturn: OutputLayers = {};\n for (const category in output) {\n handlerReturn[category] ||= {};\n\n for (const group in output[category]) {\n const { changesetTags = {}, instructions } =\n (await ctx.callbacks.getChangesetTags?.({ category, group })) || {};\n\n // add some default changeset tags if not defined by the consumer\n /* eslint-disable dot-notation */\n changesetTags['created_by'] ||= 'osm-conflation-engine';\n changesetTags['import'] ||= 'yes';\n changesetTags['source'] ||= ctx.config.metadata.wiki_page;\n /* eslint-enable dot-notation */\n\n const features = output[category][group]!;\n if (ctx.callbacks.postprocessLayer) {\n await ctx.callbacks.postprocessLayer({\n category,\n group,\n osmData: osmDataById,\n sourceData,\n features,\n });\n }\n\n const groups = splitUntilSmallEnough(\n ctx,\n group,\n { changesetTags, instructions },\n features,\n );\n Object.assign(handlerReturn[category], groups);\n }\n\n shiftOverlappingPoints(handlerReturn[category]);\n mergeTinyDatasets(handlerReturn[category]);\n }\n\n const metrics = await generateDataForWebsite(\n ctx,\n sourceData,\n osmData,\n matches,\n handlerReturn,\n );\n await createIndexAndSaveToDisk(ctx, metrics, handlerReturn);\n // TODO: generate stats and stats history\n await writeToRunHistory(metrics);\n\n return metrics;\n}\n","import { createReadStream, promises as fs } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\nimport { geoCentroid } from 'd3-geo';\nimport type { Feature, GeoJsonProperties, Geometry } from 'geojson';\nimport type { Ctx, OSMData, SourceData, Vec2 } from '../../types/index.js';\nimport { getSector } from '../../common/getSector.js';\nimport { readJsonL } from '../../common/jsonl.js';\n\nexport async function readData<\n G extends Geometry = Geometry,\n P extends GeoJsonProperties = GeoJsonProperties,\n>(ctx: Ctx) {\n console.info('Reading source data into memory...');\n let count = 0;\n const sourceData: SourceData<G, P> = {};\n const readline = createInterface({\n input: createReadStream(ctx.config.source_data.file),\n crlfDelay: Infinity,\n });\n for await (const line of readline) {\n const feature: Feature<G, P> = JSON.parse(line);\n const id = feature.properties![ctx.config.merge.dataset_column];\n sourceData[id] = Object.assign(feature, {\n sectors: getSector(feature.geometry, ctx.config.merge.sector_resolution),\n centroid:\n feature.geometry.type === 'Point'\n ? <Vec2>feature.geometry.coordinates\n : geoCentroid(feature.geometry),\n });\n count++;\n }\n console.info(`\\tread ${count} rows.`);\n\n console.info('Reading osm data into memory (withRef)...');\n const withRef: OSMData['withRef'] = await readJsonL(\n ctx.tempFileNames.osm_processed_with_ref,\n (row) => row.tags[ctx.config.merge.osm_key]!,\n );\n console.info('Reading osm data into memory (noRef)...');\n const noRef: OSMData['noRef'] = await readJsonL(\n ctx.tempFileNames.osm_processed_no_ref,\n (row) => row.id,\n );\n console.info('Reading osm data into memory (other)...');\n const other: Omit<OSMData, 'noRef' | 'withRef'> = JSON.parse(\n await fs.readFile(ctx.tempFileNames.osm_processed_other, 'utf8'),\n );\n const osmData: OSMData = { withRef, noRef, ...other };\n console.info('\\tread data.');\n\n return { sourceData, osmData };\n}\n","import { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { promises as fs } from 'node:fs';\nimport { Ajv, type JSONSchemaType } from 'ajv';\nimport type { GeoJsonProperties, Geometry } from 'geojson';\nimport type {\n Callbacks,\n Config,\n ConflateResult,\n Ctx,\n OSMData,\n RunOptions,\n RunResult,\n SourceData,\n} from './types/index.js';\nimport {\n FILE_NAMES,\n IS_UNIT_TEST,\n STEPS,\n type Step,\n} from './constants/defaults.js';\nimport { download } from './steps/download/index.js';\nimport { match } from './steps/match/index.js';\nimport { conflate } from './steps/conflate/index.js';\nimport { sha256 } from './helpers.js';\nimport { readData } from './steps/match/readData.js';\n\nfunction validateSteps(steps: readonly unknown[]): asserts steps is Step[] {\n for (const value of steps) {\n if (!STEPS.includes(value as never)) {\n throw new Error(\n `‘${value}’ is not a valid step. The options are: ${STEPS.join(', ')}`,\n );\n }\n }\n}\n\nexport async function getTempFileNames(\n config: Config,\n): Promise<Pick<Ctx, 'id' | 'tempFolder' | 'tempFileNames'>> {\n const id = sha256(config.metadata.name).slice(0, 12);\n\n const prefix = IS_UNIT_TEST ? '-mock' : '';\n const tempFolder = join(tmpdir(), `oce-${prefix}`, id);\n await fs.mkdir(tempFolder, { recursive: true });\n\n const tempFileNames = <never>(\n Object.fromEntries(\n Object.entries(FILE_NAMES).map(([key, fileName]) => [\n key.toLowerCase(),\n join(tempFolder, fileName),\n ]),\n )\n );\n\n return { id, tempFolder, tempFileNames };\n}\n\nexport async function run<G extends Geometry, P extends GeoJsonProperties>(\n config: Config,\n callbacks: Callbacks<G, P>,\n runOptions: RunOptions = {},\n): Promise<RunResult> {\n const configFileSchema: JSONSchemaType<Config> = JSON.parse(\n await fs.readFile(new URL('config.schema.json', import.meta.url), 'utf8'),\n );\n\n const ajv = new Ajv();\n const validate = ajv.compile(configFileSchema);\n\n if (!validate(config)) {\n throw new AggregateError(\n validate.errors!.map(\n (error) =>\n new TypeError(error.message + JSON.stringify(error, null, 2), {\n cause: error,\n }),\n ),\n 'Config file does not conform to the schema',\n );\n }\n\n const { id, tempFolder, tempFileNames } = await getTempFileNames(config);\n\n const ctx: Ctx<G, P> = {\n use_cache: !!runOptions.use_cache,\n id,\n tempFolder,\n tempFileNames,\n config,\n callbacks,\n warnings: [],\n };\n\n console.info('temp folder:', tempFolder);\n\n // if unspecified, run all steps\n const { steps = STEPS } = runOptions;\n validateSteps(steps);\n\n if (steps.includes('download')) {\n console.time('download');\n await download(ctx);\n console.timeEnd('download');\n }\n\n let sourceData: SourceData<G, P> | undefined;\n let osmData: OSMData | undefined;\n\n if (steps.includes('match')) {\n ({ sourceData, osmData } = await readData(ctx));\n console.time('match');\n await match(ctx, sourceData, osmData);\n console.timeEnd('match');\n }\n\n let conflateResult: ConflateResult | undefined;\n if (steps.includes('conflate')) {\n if (!sourceData || !osmData) {\n ({ sourceData, osmData } = await readData(ctx));\n }\n console.time('conflate');\n conflateResult = await conflate(ctx, sourceData, osmData);\n console.timeEnd('conflate');\n }\n\n console.info('done.');\n return { conflate: conflateResult };\n}\n"],"mappings":"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"}
|