@osm-conflation-engine/cli 3.0.0 → 3.0.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.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("node:path"),l=require("node:os"),u=require("node:fs"),d=require("ajv"),f=require("node:timers/promises"),p=require("node:child_process"),m=require("pbf2json");m=s(m,1);let h=require("h3-js"),g=require("node:readline/promises"),_=require("d3-geo"),ee=require("node:crypto");const v=process.env.NODE_ENV===`test`,y=[`download`,`match`,`conflate`],te={PBF:`o.pbf`,OVERPASS:`o.geo.json`,POSTPASS:`o.geo.json`,OSM_PROCESSED_WITH_REF:`osm-processed-with-ref.jsonl`,OSM_PROCESSED_NO_REF:`osm-processed-no-ref.jsonl`,OSM_PROCESSED_OTHER:`osm-processed-other.json`,IGNORE_LIST:`ignore-list.json`,MATCHES:`matches.json`},ne=e=>({"User-Agent":`https://github.com/osm-nz/osm-conflation-engine on behalf of ${e.metadata.wiki_page}`}),b=`https://osm-internal.download.geofabrik.de`,x=`https://download.geofabrik.de`,S=`https://www.openstreetmap.org`,C=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function w(e,t){if(!t.startsWith(b)&&!t.startsWith(x)&&e.warnings.push(`(!) Planet PBF url is not from geofabrik.de, 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(b,x),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=ne(e.config),i=await fetch(`${b}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${S}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${S}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:C(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(S)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:C(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.replace(x,b),cookies:l}}async function T(e,t){let n=e.tempFileNames.pbf;if(e.use_cache&&(0,u.existsSync)(n)){console.info(`Using cached planet file`);return}let{cookies:r,url:i}=await w(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,p.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,f.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 E(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 u.promises.readFile(n,`utf8`)):await fetch(`https://osm-conflation-engine.kyle.kiwi/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))}let re=function(e){return e[e.None=0]=`None`,e[e.IsLastEditedByImporter=1]=`IsLastEditedByImporter`,e[e.IsRecentlyChanged=2]=`IsRecentlyChanged`,e[e.IsChecked=4]=`IsChecked`,e[e.IsCheckedRecently=8]=`IsCheckedRecently`,e}({});const ie=e=>e?(Date.now()-+new Date(e))/1e3/60/60/24/365<4?1:2:0;function D(e,t){let n=t||3;switch(e.type){case`GeometryCollection`:throw Error(`not supported`);case`Point`:return[(0,h.latLngToCell)(e.coordinates[1],e.coordinates[0],n)];case`MultiPoint`:case`LineString`:case`MultiLineString`:case`Polygon`:return(0,h.polygonToCellsExperimental)(e.coordinates,n,h.POLYGON_TO_CELLS_FLAGS.containmentOverlapping,!0).toSorted();case`MultiPolygon`:{let t=new Set;for(let r of e.coordinates){let e=(0,h.polygonToCellsExperimental)(r,n,h.POLYGON_TO_CELLS_FLAGS.containmentOverlapping,!0);for(let n of e)t.add(n)}return[...t].toSorted()}default:throw TypeError(e)}}async function O(e,t){let n=(0,g.createInterface)({input:(0,u.createReadStream)(e)}),r={};for await(let e of n){let n=JSON.parse(e),i=t(n);r[i]=n}return r}async function k(e,t){let n=(0,u.createWriteStream)(e,{encoding:`utf8`});for(let e of t)n.write(JSON.stringify(e)),n.write(`
|
|
2
|
-
`);n.end(),await new Promise(e=>n.on(`finish`,e))}const ae=(e=>(e.setDate(e.getDate()-90),e/1e3))(new Date);async function oe(e,t){if(e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}console.info(`Starting preprocess of OSM data...`);let{withRef:n,noRef:r,...i}=await new Promise((n,r)=>{let i={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},a=0,o=!1,s=e.config.o_data.check_date_key||`check_date`,c=new Set([s,e.config.merge.osm_key]).difference(new Set(e.config.o_data.tags_to_keep));if(c.size)throw Error(`config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...c].join(`, `)}`);let l=[];for(let t of e.config.o_data.tags_to_keep)t.startsWith(`/`)&&t.endsWith(`/`)&&l.push(new RegExp(t.slice(1,-1)));let u={};function d(t){if(t in u)return u[t];let n=e.config.o_data.tags_to_keep.includes(t);if(!n){for(let e of l)if(e.test(t)){n=!0;break}}return u[t]=n,n}m.default.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{let n=t.type===`node`?t:t.centroid,r=t.meta;r&&(o=!0);let c={};for(let e in t.tags){let n=t.tags[e];d(e)&&n&&(c[e]=n)}let l={id:`${t.type[0]}${t.id}`,centroid:[n.lon,n.lat],sectors:D({type:`Point`,coordinates:[n.lon,n.lat]},e.config.merge.sector_resolution),metadata:r?{changeset:r.changeset,timestamp:r.timestamp?new Date(1e3*r.timestamp).toISOString():void 0,user:r.user,version:r.version}:void 0,tags:c,flags:0};(r?.timestamp??0)>ae&&(l.flags|=2);let u=r?.user&&(e.callbacks.isImportUser?e.callbacks.isImportUser?.(r?.user):r?.user?.endsWith(`_import`));(r?.version===1||u)&&(l.flags|=1),c[s]&&(l.flags|=4),ie(c[s])===1&&(l.flags|=8);let f=t.tags[e.config.merge.osm_key];if(f)if(i.count++,i.duplicateRefs[f])i.duplicateRefs[f].push(l);else if(i.withRef[f])i.duplicateRefs[f]=[i.withRef[f],l],delete i.withRef[f];else if(f.includes(`;`)){let e=f.split(`;`);for(let t of e)i.semi[t]=l}else i.withRef[f]=l;else i.noRef[l.id]=l;a+=1,a%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{o||e.warnings.push(`No metadata extracted from the planet file!`),n(i)}).on(`error`,r)});await k(e.tempFileNames.osm_processed_with_ref,Object.values(n)),await k(e.tempFileNames.osm_processed_no_ref,Object.values(r)),await u.promises.writeFile(e.tempFileNames.osm_processed_other,JSON.stringify(i))}async function se(e){switch(await E(e),e.config.o_data.source.type){case`pbf`:await T(e,e.config.o_data.source.pbf_url),await oe(e,e.config.o_data.source.pbf_filter);break;case`overpass`:throw Error(`not supported yet`);case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function A(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 j=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`,M=new Map,N=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=M.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),M.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 P(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}=A(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let s=0;console.time(`match`);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=N(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(`match`),await u.promises.writeFile(e.tempFileNames.matches,JSON.stringify(c,null,void 0)),{output:c}}const F=2e-4;function I({lat:e,lng:t}){let n=[[t,e+F],[t+F,e],[t,e-F],[t-F,e]];return n.push(n[0]),[n]}function L({lat:e,lng:t}){let n=[[t+F/2,e+F/2],[t+F/2,e-F/2],[t-F/2,e-F/2],[t-F/2,e+F/2]];return n.push(n[0]),[n]}function R(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 z(e,t,n,r,i=0){if(!r.length)return{};let a=R(r),o=i>10;if(o&&console.info(`(!) Couldn't split ${e} (${r.length} left)`),r.length<=110||o)return{[e]:{features:r,bbox:a,instructions:t,changesetTags: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{...z(`${e}^N`,t,n,c,i+1),...z(`${e}^S`,t,n,l,i+1)}}let s=a.minLng+(a.maxLng-a.minLng)/2,c=[[],[]];for(let e of r)c[+((0,_.geoCentroid)(e.geometry)[0]<s)].push(e);let[l,u]=c;return{...z(`${e}^E`,t,n,l,i+1),...z(`${e}^W`,t,n,u,i+1)}}const{sin:B,cos:V,floor:H,sqrt:U,PI:W}=Math;function G(e){let t=H(U(e)),n=(e-t**2)*W/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*V(n),t*B(n)];return[+r.toFixed(2),+i.toFixed(2)]}function ce(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=j(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]=G(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function K(e){return(0,ee.createHash)(`sha256`).update(e,`utf8`).digest(`hex`)}function le(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 q(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``)}_${K(e).slice(0,6)}`}async function ue(e,t){let n=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!n)throw Error(`git_repository is not a valid github url`);let r=e.config.output?.folder||(0,c.join)(process.cwd(),`output`),i=`suburbs`,a=Object.entries(t).map(([e,t])=>({suburb:e,bbox:t.bbox,instructions:t.instructions,...le(t.features)})),o={fields:[],results:a.map(e=>{let t=e.suburb.replace(`ZZ `,``).replace(`Z `,``);return{id:q(e.suburb),url:`https://${n[1]}.github.io/${n[2]}/${i}/${q(e.suburb)}.osmPatch.geo.json`,name:t,title:t,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.suburb.startsWith(`ZZ`)?`/Categories/Preview`:`/Categories/Addresses`]}}).toSorted((e,t)=>e.name.localeCompare(t.name))};await u.promises.writeFile((0,c.join)(r,`index.json`),JSON.stringify(o,null,v?2:void 0)),await u.promises.mkdir((0,c.join)(r,i),{recursive:!0});for(let e of a){let{suburb:n}=e,a={type:`FeatureCollection`,...t[n]};a.changesetTags||={attribution:`https://wiki.openstreetmap.org/wiki/Contributors#LINZ`,created_by:`LINZ Data Import 2.0.0`,locale:`en-NZ`,source:`https://wiki.osm.org/LINZ`,comment:n},await u.promises.writeFile((0,c.join)(r,i,`${q(n)}.osmPatch.geo.json`),JSON.stringify(a,null,v?2:void 0))}}const J=`invalid value returned by your callback function`;function Y(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function X(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Y(e.tags)}function Z(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:L({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:I({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function de(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){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||=[],i[n].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(!X(l.diff))continue;let u=l.group||o.sectors[0];a(l.extra,u),i[u]||=[],i[u].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(u.diff))continue;let d=u.group||c.sectors[0];a(u.extra,d),i[d]||=[],i[d].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(o.diff))continue;let s=o.group||r.sectors[0];a(o.extra,s),i[s]||=[],i[s].push(Z(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(!X(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.group||c?.sectors[0]||`unknown`;a(s.extra,l),i[l]||=[],i[l].push(Z(s.diff,c,o))}let s={};for(let n in i){let{changesetTags:a={},instructions:o}=await e.callbacks.getChangesetTags?.(n)||{};a.created_by||=`osm-conflation-engine`,a.import||=`yes`,a.source||=e.config.metadata.wiki_page;let c=i[n];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({group:n,osmData:r,sourceData:t,features:c});let l=z(n,o,a,c);Object.assign(s,l)}ce(s),ue(e,s);let c={create:0,edit:0,delete:0,perfect:0};for(let e in s)for(let t of s[e].features)t.properties.__action===`edit`?c.edit++:t.properties.__action===`delete`?c.delete++:c.create++;return c.perfect=n.count-c.edit-c.delete,{counts:c}}async function Q(e){console.info(`Reading source data into memory...`);let t=0,n={},r=(0,g.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 O(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 O(e.tempFileNames.osm_processed_with_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 fe(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=K(e.metadata.name).slice(0,12),n=v?`-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(te).map(([e,t])=>[e.toLowerCase(),(0,c.join)(r,t)]))}}async function pe(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=y}=n;fe(l),l.includes(`download`)&&(console.time(`download`),await se(c),console.timeEnd(`download`));let f,p;l.includes(`match`)&&({sourceData:f,osmData:p}=await Q(c),console.time(`match`),await P(c,f,p),console.timeEnd(`match`));let m;return l.includes(`conflate`)&&({sourceData:f,osmData:p}=await Q(c),console.time(`conflate`),m=await de(c,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}exports.OsmFlags=re,exports.getTempFileNames=$,exports.readJsonL=O,exports.run=pe,exports.writeJsonL=k;
|
|
2
|
+
`);n.end(),await new Promise(e=>n.on(`finish`,e))}const ae=(e=>(e.setDate(e.getDate()-90),e/1e3))(new Date);async function oe(e,t){if(e.use_cache&&(0,u.existsSync)(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}console.info(`Starting preprocess of OSM data...`);let{withRef:n,noRef:r,...i}=await new Promise((n,r)=>{let i={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},a=0,o=!1,s=e.config.o_data.check_date_key||`check_date`,c=new Set([s,e.config.merge.osm_key]).difference(new Set(e.config.o_data.tags_to_keep));if(c.size)throw Error(`config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...c].join(`, `)}`);let l=[];for(let t of e.config.o_data.tags_to_keep)t.startsWith(`/`)&&t.endsWith(`/`)&&l.push(new RegExp(t.slice(1,-1)));let u={};function d(t){if(t in u)return u[t];let n=e.config.o_data.tags_to_keep.includes(t);if(!n){for(let e of l)if(e.test(t)){n=!0;break}}return u[t]=n,n}m.default.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{let n=t.type===`node`?t:t.centroid,r=t.meta;r&&(o=!0);let c={};for(let e in t.tags){let n=t.tags[e];d(e)&&n&&(c[e]=n)}let l={id:`${t.type[0]}${t.id}`,centroid:[n.lon,n.lat],sectors:D({type:`Point`,coordinates:[n.lon,n.lat]},e.config.merge.sector_resolution),metadata:r?{changeset:r.changeset,timestamp:r.timestamp?new Date(1e3*r.timestamp).toISOString():void 0,user:r.user,version:r.version}:void 0,tags:c,flags:0};(r?.timestamp??0)>ae&&(l.flags|=2);let u=r?.user&&(e.callbacks.isImportUser?e.callbacks.isImportUser?.(r?.user):r?.user?.endsWith(`_import`));(r?.version===1||u)&&(l.flags|=1),c[s]&&(l.flags|=4),ie(c[s])===1&&(l.flags|=8);let f=t.tags[e.config.merge.osm_key];if(f)if(i.count++,i.duplicateRefs[f])i.duplicateRefs[f].push(l);else if(i.withRef[f])i.duplicateRefs[f]=[i.withRef[f],l],delete i.withRef[f];else if(f.includes(`;`)){let e=f.split(`;`);for(let t of e)i.semi[t]=l}else i.withRef[f]=l;else i.noRef[l.id]=l;a+=1,a%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{o||e.warnings.push(`No metadata extracted from the planet file!`),n(i)}).on(`error`,r)});await k(e.tempFileNames.osm_processed_with_ref,Object.values(n)),await k(e.tempFileNames.osm_processed_no_ref,Object.values(r)),await u.promises.writeFile(e.tempFileNames.osm_processed_other,JSON.stringify(i))}async function se(e){switch(await E(e),e.config.o_data.source.type){case`pbf`:await T(e,e.config.o_data.source.pbf_url),await oe(e,e.config.o_data.source.pbf_filter);break;case`overpass`:throw Error(`not supported yet`);case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function A(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 j=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`,M=new Map,N=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=M.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),M.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 P(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}=A(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let s=0;console.time(`match`);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=N(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(`match`),await u.promises.writeFile(e.tempFileNames.matches,JSON.stringify(c,null,void 0)),{output:c}}const F=2e-4;function I({lat:e,lng:t}){let n=[[t,e+F],[t+F,e],[t,e-F],[t-F,e]];return n.push(n[0]),[n]}function L({lat:e,lng:t}){let n=[[t+F/2,e+F/2],[t+F/2,e-F/2],[t-F/2,e-F/2],[t-F/2,e+F/2]];return n.push(n[0]),[n]}function R(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 z(e,t,n,r,i=0){if(!r.length)return{};let a=R(r),o=i>10;if(o&&console.info(`(!) Couldn't split ${e} (${r.length} left)`),r.length<=110||o)return{[e]:{features:r,bbox:a,instructions:t,changesetTags: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{...z(`${e}^N`,t,n,c,i+1),...z(`${e}^S`,t,n,l,i+1)}}let s=a.minLng+(a.maxLng-a.minLng)/2,c=[[],[]];for(let e of r)c[+((0,_.geoCentroid)(e.geometry)[0]<s)].push(e);let[l,u]=c;return{...z(`${e}^E`,t,n,l,i+1),...z(`${e}^W`,t,n,u,i+1)}}const{sin:B,cos:V,floor:H,sqrt:U,PI:W}=Math;function G(e){let t=H(U(e)),n=(e-t**2)*W/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*V(n),t*B(n)];return[+r.toFixed(2),+i.toFixed(2)]}function ce(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=j(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]=G(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function K(e){return(0,ee.createHash)(`sha256`).update(e,`utf8`).digest(`hex`)}function le(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 q(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``)}_${K(e).slice(0,6)}`}async function ue(e,t){let n=e.config.metadata.git_repository.match(`https://github.com/([^/]+)/([^/]+)`);if(!n)throw Error(`git_repository is not a valid github url`);let r=e.config.output?.folder||(0,c.join)(process.cwd(),`output`),i=`suburbs`,a=Object.entries(t).map(([e,t])=>({suburb:e,bbox:t.bbox,instructions:t.instructions,...le(t.features)})),o={fields:[],results:a.map(e=>{let t=e.suburb.replace(`ZZ `,``).replace(`Z `,``);return{id:q(e.suburb),url:`https://${n[1]}.github.io/${n[2]}/${i}/${q(e.suburb)}.osmPatch.geo.json`,name:t,title:t,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.suburb.startsWith(`ZZ`)?`/Categories/Preview`:`/Categories/Addresses`]}}).toSorted((e,t)=>e.name.localeCompare(t.name))};await u.promises.writeFile((0,c.join)(r,`index.json`),JSON.stringify(o,null,v?2:void 0)),await u.promises.mkdir((0,c.join)(r,i),{recursive:!0});for(let e of a){let{suburb:n}=e,a={type:`FeatureCollection`,...t[n]};a.changesetTags||={attribution:`https://wiki.openstreetmap.org/wiki/Contributors#LINZ`,created_by:`LINZ Data Import 2.0.0`,locale:`en-NZ`,source:`https://wiki.osm.org/LINZ`,comment:n},await u.promises.writeFile((0,c.join)(r,i,`${q(n)}.osmPatch.geo.json`),JSON.stringify(a,null,v?2:void 0))}}const J=`invalid value returned by your callback function`;function Y(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function X(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Y(e.tags)}function Z(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:L({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:I({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function de(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){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||=[],i[n].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(!X(l.diff))continue;let u=l.group||o.sectors[0];a(l.extra,u),i[u]||=[],i[u].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(u.diff))continue;let d=u.group||c.sectors[0];a(u.extra,d),i[d]||=[],i[d].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(o.diff))continue;let s=o.group||r.sectors[0];a(o.extra,s),i[s]||=[],i[s].push(Z(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(!X(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.group||c?.sectors[0]||`unknown`;a(s.extra,l),i[l]||=[],i[l].push(Z(s.diff,c,o))}let s={};for(let n in i){let{changesetTags:a={},instructions:o}=await e.callbacks.getChangesetTags?.(n)||{};a.created_by||=`osm-conflation-engine`,a.import||=`yes`,a.source||=e.config.metadata.wiki_page;let c=i[n];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({group:n,osmData:r,sourceData:t,features:c});let l=z(n,o,a,c);Object.assign(s,l)}ce(s),ue(e,s);let c={create:0,edit:0,delete:0,perfect:0};for(let e in s)for(let t of s[e].features)t.properties.__action===`edit`?c.edit++:t.properties.__action===`delete`?c.delete++:c.create++;return c.perfect=n.count-c.edit-c.delete,{counts:c}}async function Q(e){console.info(`Reading source data into memory...`);let t=0,n={},r=(0,g.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 O(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 O(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 fe(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=K(e.metadata.name).slice(0,12),n=v?`-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(te).map(([e,t])=>[e.toLowerCase(),(0,c.join)(r,t)]))}}async function pe(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=y}=n;fe(l),l.includes(`download`)&&(console.time(`download`),await se(c),console.timeEnd(`download`));let f,p;l.includes(`match`)&&({sourceData:f,osmData:p}=await Q(c),console.time(`match`),await P(c,f,p),console.timeEnd(`match`));let m;return l.includes(`conflate`)&&({sourceData:f,osmData:p}=await Q(c),console.time(`conflate`),m=await de(c,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}exports.OsmFlags=re,exports.getTempFileNames=$,exports.readJsonL=O,exports.run=pe,exports.writeJsonL=k;
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{basename as e,dirname as t,join as n}from"node:path";import{tmpdir as r}from"node:os";import{createReadStream as i,createWriteStream as a,existsSync as o,promises as s}from"node:fs";import{Ajv as c}from"ajv";import{setTimeout as l}from"node:timers/promises";import{spawn as u}from"node:child_process";import d from"pbf2json";import{POLYGON_TO_CELLS_FLAGS as f,latLngToCell as p,polygonToCellsExperimental as m}from"h3-js";import{createInterface as h}from"node:readline/promises";import{geoCentroid as g,geoDistance as ee}from"d3-geo";import{createHash as te}from"node:crypto";const _=process.env.NODE_ENV===`test`,v=[`download`,`match`,`conflate`],ne={PBF:`o.pbf`,OVERPASS:`o.geo.json`,POSTPASS:`o.geo.json`,OSM_PROCESSED_WITH_REF:`osm-processed-with-ref.jsonl`,OSM_PROCESSED_NO_REF:`osm-processed-no-ref.jsonl`,OSM_PROCESSED_OTHER:`osm-processed-other.json`,IGNORE_LIST:`ignore-list.json`,MATCHES:`matches.json`},y=e=>({"User-Agent":`https://github.com/osm-nz/osm-conflation-engine on behalf of ${e.metadata.wiki_page}`}),b=`https://osm-internal.download.geofabrik.de`,x=`https://download.geofabrik.de`,S=`https://www.openstreetmap.org`,C=e=>e.match(/name="csrf-token" content="([^"]+)"/)[1];async function w(e,t){if(!t.startsWith(b)&&!t.startsWith(x)&&e.warnings.push(`(!) Planet PBF url is not from geofabrik.de, 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(b,x),cookies:``};let n=new URLSearchParams(process.env.OSM_AUTH),r=y(e.config),i=await fetch(`${b}/get_cookie?action=get_authorization_url`,{method:`POST`,headers:r}).then(e=>e.json()),a=await fetch(`${S}/login?cookie_test=true`,{headers:r}),o=a.headers.get(`Set-Cookie`),s=await fetch(`${S}/login`,{method:`POST`,body:new URLSearchParams({username:n.get(`username`),password:n.get(`password`),referer:`/`,commit:`Login`,authenticity_token:C(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(S)?await fetch(i.authorization_url,{method:`POST`,body:new URLSearchParams({client_id:i.client_id,redirect_uri:i.redirect_uri,authenticity_token:C(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.replace(x,b),cookies:l}}async function T(n,r){let i=n.tempFileNames.pbf;if(n.use_cache&&o(i)){console.info(`Using cached planet file`);return}let{cookies:a,url:c}=await w(n,r);if(await s.mkdir(t(i),{recursive:!0}),await s.rm(i,{recursive:!0,force:!0}),/^https?:\/\//.test(c)){console.info(`downloading from`,c,`to`,i);let n=u(`curl`,[`-L`,c,`-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 l(5e3)}else console.info(`copying file from`,c,`to`,i),await s.copyFile(c,i);console.info(i,await s.stat(i))}async function re(e){let t=e.config.merge.osm_key,n=e.config.e2e_tests?.ignore_list_file_path;if(_&&!n)throw Error(`When running in the unit test envrironment, you must supply config.e2e_tests.ignoreListFilePath`);let r=_&&n?JSON.parse(await s.readFile(n,`utf8`)):await fetch(`https://osm-conflation-engine.kyle.kiwi/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))}let ie=function(e){return e[e.None=0]=`None`,e[e.IsLastEditedByImporter=1]=`IsLastEditedByImporter`,e[e.IsRecentlyChanged=2]=`IsRecentlyChanged`,e[e.IsChecked=4]=`IsChecked`,e[e.IsCheckedRecently=8]=`IsCheckedRecently`,e}({});const ae=e=>e?(Date.now()-+new Date(e))/1e3/60/60/24/365<4?1:2:0;function E(e,t){let n=t||3;switch(e.type){case`GeometryCollection`:throw Error(`not supported`);case`Point`:return[p(e.coordinates[1],e.coordinates[0],n)];case`MultiPoint`:case`LineString`:case`MultiLineString`:case`Polygon`:return m(e.coordinates,n,f.containmentOverlapping,!0).toSorted();case`MultiPolygon`:{let t=new Set;for(let r of e.coordinates){let e=m(r,n,f.containmentOverlapping,!0);for(let n of e)t.add(n)}return[...t].toSorted()}default:throw TypeError(e)}}async function D(e,t){let n=h({input:i(e)}),r={};for await(let e of n){let n=JSON.parse(e),i=t(n);r[i]=n}return r}async function O(e,t){let n=a(e,{encoding:`utf8`});for(let e of t)n.write(JSON.stringify(e)),n.write(`
|
|
2
|
-
`);n.end(),await new Promise(e=>n.on(`finish`,e))}const oe=(e=>(e.setDate(e.getDate()-90),e/1e3))(new Date);async function se(e,t){if(e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}console.info(`Starting preprocess of OSM data...`);let{withRef:n,noRef:r,...i}=await new Promise((n,r)=>{let i={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},a=0,o=!1,s=e.config.o_data.check_date_key||`check_date`,c=new Set([s,e.config.merge.osm_key]).difference(new Set(e.config.o_data.tags_to_keep));if(c.size)throw Error(`config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...c].join(`, `)}`);let l=[];for(let t of e.config.o_data.tags_to_keep)t.startsWith(`/`)&&t.endsWith(`/`)&&l.push(new RegExp(t.slice(1,-1)));let u={};function f(t){if(t in u)return u[t];let n=e.config.o_data.tags_to_keep.includes(t);if(!n){for(let e of l)if(e.test(t)){n=!0;break}}return u[t]=n,n}d.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{let n=t.type===`node`?t:t.centroid,r=t.meta;r&&(o=!0);let c={};for(let e in t.tags){let n=t.tags[e];f(e)&&n&&(c[e]=n)}let l={id:`${t.type[0]}${t.id}`,centroid:[n.lon,n.lat],sectors:E({type:`Point`,coordinates:[n.lon,n.lat]},e.config.merge.sector_resolution),metadata:r?{changeset:r.changeset,timestamp:r.timestamp?new Date(1e3*r.timestamp).toISOString():void 0,user:r.user,version:r.version}:void 0,tags:c,flags:0};(r?.timestamp??0)>oe&&(l.flags|=2);let u=r?.user&&(e.callbacks.isImportUser?e.callbacks.isImportUser?.(r?.user):r?.user?.endsWith(`_import`));(r?.version===1||u)&&(l.flags|=1),c[s]&&(l.flags|=4),ae(c[s])===1&&(l.flags|=8);let d=t.tags[e.config.merge.osm_key];if(d)if(i.count++,i.duplicateRefs[d])i.duplicateRefs[d].push(l);else if(i.withRef[d])i.duplicateRefs[d]=[i.withRef[d],l],delete i.withRef[d];else if(d.includes(`;`)){let e=d.split(`;`);for(let t of e)i.semi[t]=l}else i.withRef[d]=l;else i.noRef[l.id]=l;a+=1,a%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{o||e.warnings.push(`No metadata extracted from the planet file!`),n(i)}).on(`error`,r)});await O(e.tempFileNames.osm_processed_with_ref,Object.values(n)),await O(e.tempFileNames.osm_processed_no_ref,Object.values(r)),await s.writeFile(e.tempFileNames.osm_processed_other,JSON.stringify(i))}async function k(e){switch(await re(e),e.config.o_data.source.type){case`pbf`:await T(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:throw Error(`not supported yet`);case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function A(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 j=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`,M=new Map,N=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=M.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),M.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 P(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}=A(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let c=0;console.time(`match`);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=N(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(`match`),await s.writeFile(e.tempFileNames.matches,JSON.stringify(l,null,void 0)),{output:l}}const F=2e-4;function I({lat:e,lng:t}){let n=[[t,e+F],[t+F,e],[t,e-F],[t-F,e]];return n.push(n[0]),[n]}function L({lat:e,lng:t}){let n=[[t+F/2,e+F/2],[t+F/2,e-F/2],[t-F/2,e-F/2],[t-F/2,e+F/2]];return n.push(n[0]),[n]}function R(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 z(e,t,n,r,i=0){if(!r.length)return{};let a=R(r),o=i>10;if(o&&console.info(`(!) Couldn't split ${e} (${r.length} left)`),r.length<=110||o)return{[e]:{features:r,bbox:a,instructions:t,changesetTags: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{...z(`${e}^N`,t,n,c,i+1),...z(`${e}^S`,t,n,l,i+1)}}let s=a.minLng+(a.maxLng-a.minLng)/2,c=[[],[]];for(let e of r)c[+(g(e.geometry)[0]<s)].push(e);let[l,u]=c;return{...z(`${e}^E`,t,n,l,i+1),...z(`${e}^W`,t,n,u,i+1)}}const{sin:B,cos:V,floor:H,sqrt:U,PI:W}=Math;function G(e){let t=H(U(e)),n=(e-t**2)*W/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*V(n),t*B(n)];return[+r.toFixed(2),+i.toFixed(2)]}function ce(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=j(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]=G(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function K(e){return te(`sha256`).update(e,`utf8`).digest(`hex`)}function le(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 q(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``)}_${K(e).slice(0,6)}`}async function ue(e,t){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||n(process.cwd(),`output`),a=`suburbs`,o=Object.entries(t).map(([e,t])=>({suburb:e,bbox:t.bbox,instructions:t.instructions,...le(t.features)})),c={fields:[],results:o.map(e=>{let t=e.suburb.replace(`ZZ `,``).replace(`Z `,``);return{id:q(e.suburb),url:`https://${r[1]}.github.io/${r[2]}/${a}/${q(e.suburb)}.osmPatch.geo.json`,name:t,title:t,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.suburb.startsWith(`ZZ`)?`/Categories/Preview`:`/Categories/Addresses`]}}).toSorted((e,t)=>e.name.localeCompare(t.name))};await s.writeFile(n(i,`index.json`),JSON.stringify(c,null,_?2:void 0)),await s.mkdir(n(i,a),{recursive:!0});for(let e of o){let{suburb:r}=e,o={type:`FeatureCollection`,...t[r]};o.changesetTags||={attribution:`https://wiki.openstreetmap.org/wiki/Contributors#LINZ`,created_by:`LINZ Data Import 2.0.0`,locale:`en-NZ`,source:`https://wiki.osm.org/LINZ`,comment:r},await s.writeFile(n(i,a,`${q(r)}.osmPatch.geo.json`),JSON.stringify(o,null,_?2:void 0))}}const J=`invalid value returned by your callback function`;function Y(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function X(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Y(e.tags)}function Z(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:L({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:I({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function de(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){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||=[],i[n].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(!X(l.diff))continue;let u=l.group||o.sectors[0];a(l.extra,u),i[u]||=[],i[u].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(u.diff))continue;let d=u.group||c.sectors[0];a(u.extra,d),i[d]||=[],i[d].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(o.diff))continue;let s=o.group||r.sectors[0];a(o.extra,s),i[s]||=[],i[s].push(Z(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(!X(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.group||c?.sectors[0]||`unknown`;a(s.extra,l),i[l]||=[],i[l].push(Z(s.diff,c,o))}let c={};for(let n in i){let{changesetTags:a={},instructions:o}=await e.callbacks.getChangesetTags?.(n)||{};a.created_by||=`osm-conflation-engine`,a.import||=`yes`,a.source||=e.config.metadata.wiki_page;let s=i[n];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({group:n,osmData:r,sourceData:t,features:s});let l=z(n,o,a,s);Object.assign(c,l)}ce(c),ue(e,c);let l={create:0,edit:0,delete:0,perfect:0};for(let e in c)for(let t of c[e].features)t.properties.__action===`edit`?l.edit++:t.properties.__action===`delete`?l.delete++:l.create++;return l.perfect=n.count-l.edit-l.delete,{counts:l}}async function Q(e){console.info(`Reading source data into memory...`);let t=0,n={},r=h({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:E(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 D(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 D(e.tempFileNames.osm_processed_with_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 fe(e){for(let t of e)if(!v.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${v.join(`, `)}`)}async function $(e){let t=K(e.metadata.name).slice(0,12),i=_?`-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 pe(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=v}=n;fe(d),d.includes(`download`)&&(console.time(`download`),await k(u),console.timeEnd(`download`));let f,p;d.includes(`match`)&&({sourceData:f,osmData:p}=await Q(u),console.time(`match`),await P(u,f,p),console.timeEnd(`match`));let m;return d.includes(`conflate`)&&({sourceData:f,osmData:p}=await Q(u),console.time(`conflate`),m=await de(u,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}export{ie as OsmFlags,$ as getTempFileNames,D as readJsonL,pe as run,O as writeJsonL};
|
|
2
|
+
`);n.end(),await new Promise(e=>n.on(`finish`,e))}const oe=(e=>(e.setDate(e.getDate()-90),e/1e3))(new Date);async function se(e,t){if(e.use_cache&&o(e.tempFileNames.osm_processed_other)){console.info(`Using cached osm data`);return}console.info(`Starting preprocess of OSM data...`);let{withRef:n,noRef:r,...i}=await new Promise((n,r)=>{let i={withRef:{},noRef:{},duplicateRefs:{},semi:{},count:0},a=0,o=!1,s=e.config.o_data.check_date_key||`check_date`,c=new Set([s,e.config.merge.osm_key]).difference(new Set(e.config.o_data.tags_to_keep));if(c.size)throw Error(`config.o_data.tags_to_keep is missing some tags that are referenced in other parts of the config: ${[...c].join(`, `)}`);let l=[];for(let t of e.config.o_data.tags_to_keep)t.startsWith(`/`)&&t.endsWith(`/`)&&l.push(new RegExp(t.slice(1,-1)));let u={};function f(t){if(t in u)return u[t];let n=e.config.o_data.tags_to_keep.includes(t);if(!n){for(let e of l)if(e.test(t)){n=!0;break}}return u[t]=n,n}d.createReadStream({file:e.tempFileNames.pbf,tags:t,leveldb:`/tmp`,metadata:!0}).on(`data`,t=>{let n=t.type===`node`?t:t.centroid,r=t.meta;r&&(o=!0);let c={};for(let e in t.tags){let n=t.tags[e];f(e)&&n&&(c[e]=n)}let l={id:`${t.type[0]}${t.id}`,centroid:[n.lon,n.lat],sectors:E({type:`Point`,coordinates:[n.lon,n.lat]},e.config.merge.sector_resolution),metadata:r?{changeset:r.changeset,timestamp:r.timestamp?new Date(1e3*r.timestamp).toISOString():void 0,user:r.user,version:r.version}:void 0,tags:c,flags:0};(r?.timestamp??0)>oe&&(l.flags|=2);let u=r?.user&&(e.callbacks.isImportUser?e.callbacks.isImportUser?.(r?.user):r?.user?.endsWith(`_import`));(r?.version===1||u)&&(l.flags|=1),c[s]&&(l.flags|=4),ae(c[s])===1&&(l.flags|=8);let d=t.tags[e.config.merge.osm_key];if(d)if(i.count++,i.duplicateRefs[d])i.duplicateRefs[d].push(l);else if(i.withRef[d])i.duplicateRefs[d]=[i.withRef[d],l],delete i.withRef[d];else if(d.includes(`;`)){let e=d.split(`;`);for(let t of e)i.semi[t]=l}else i.withRef[d]=l;else i.noRef[l.id]=l;a+=1,a%1e3||process.stdout.write(`.`)}).on(`finish`,()=>{o||e.warnings.push(`No metadata extracted from the planet file!`),n(i)}).on(`error`,r)});await O(e.tempFileNames.osm_processed_with_ref,Object.values(n)),await O(e.tempFileNames.osm_processed_no_ref,Object.values(r)),await s.writeFile(e.tempFileNames.osm_processed_other,JSON.stringify(i))}async function k(e){switch(await re(e),e.config.o_data.source.type){case`pbf`:await T(e,e.config.o_data.source.pbf_url),await se(e,e.config.o_data.source.pbf_filter);break;case`overpass`:throw Error(`not supported yet`);case`postpass`:throw Error(`not supported yet`);default:e.config.o_data.source}}function A(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 j=(e,t,n=5)=>`${e.toFixed(n)},${t.toFixed(n)}`,M=new Map,N=(e,t,n)=>{let r=e.callbacks.getLocalKeyForSource(t);return n.filter(t=>{let n=M.get(t);return n||(n=e.callbacks.getLocalKeyForOsm(t),M.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 P(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}=A(e,n,t);console.timeEnd(`matchDeletions`),console.info(`Processing data...`);let c=0;console.time(`match`);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=N(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(`match`),await s.writeFile(e.tempFileNames.matches,JSON.stringify(l,null,void 0)),{output:l}}const F=2e-4;function I({lat:e,lng:t}){let n=[[t,e+F],[t+F,e],[t,e-F],[t-F,e]];return n.push(n[0]),[n]}function L({lat:e,lng:t}){let n=[[t+F/2,e+F/2],[t+F/2,e-F/2],[t-F/2,e-F/2],[t-F/2,e+F/2]];return n.push(n[0]),[n]}function R(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 z(e,t,n,r,i=0){if(!r.length)return{};let a=R(r),o=i>10;if(o&&console.info(`(!) Couldn't split ${e} (${r.length} left)`),r.length<=110||o)return{[e]:{features:r,bbox:a,instructions:t,changesetTags: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{...z(`${e}^N`,t,n,c,i+1),...z(`${e}^S`,t,n,l,i+1)}}let s=a.minLng+(a.maxLng-a.minLng)/2,c=[[],[]];for(let e of r)c[+(g(e.geometry)[0]<s)].push(e);let[l,u]=c;return{...z(`${e}^E`,t,n,l,i+1),...z(`${e}^W`,t,n,u,i+1)}}const{sin:B,cos:V,floor:H,sqrt:U,PI:W}=Math;function G(e){let t=H(U(e)),n=(e-t**2)*W/(2*((t+1)**2-t**2-1))||0,[r,i]=[t*V(n),t*B(n)];return[+r.toFixed(2),+i.toFixed(2)]}function ce(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=j(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]=G(t),[i,a]=e[t].geometry.coordinates;e[t].geometry={type:`Point`,coordinates:[i+4e-6*n,a+4e-6*r]}}}return e}function K(e){return te(`sha256`).update(e,`utf8`).digest(`hex`)}function le(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 q(e){return`${e.replaceAll(/[^\dA-Za-zĀāĒēĪīŌōŪū]/g,``)}_${K(e).slice(0,6)}`}async function ue(e,t){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||n(process.cwd(),`output`),a=`suburbs`,o=Object.entries(t).map(([e,t])=>({suburb:e,bbox:t.bbox,instructions:t.instructions,...le(t.features)})),c={fields:[],results:o.map(e=>{let t=e.suburb.replace(`ZZ `,``).replace(`Z `,``);return{id:q(e.suburb),url:`https://${r[1]}.github.io/${r[2]}/${a}/${q(e.suburb)}.osmPatch.geo.json`,name:t,title:t,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.suburb.startsWith(`ZZ`)?`/Categories/Preview`:`/Categories/Addresses`]}}).toSorted((e,t)=>e.name.localeCompare(t.name))};await s.writeFile(n(i,`index.json`),JSON.stringify(c,null,_?2:void 0)),await s.mkdir(n(i,a),{recursive:!0});for(let e of o){let{suburb:r}=e,o={type:`FeatureCollection`,...t[r]};o.changesetTags||={attribution:`https://wiki.openstreetmap.org/wiki/Contributors#LINZ`,created_by:`LINZ Data Import 2.0.0`,locale:`en-NZ`,source:`https://wiki.osm.org/LINZ`,comment:r},await s.writeFile(n(i,a,`${q(r)}.osmPatch.geo.json`),JSON.stringify(o,null,_?2:void 0))}}const J=`invalid value returned by your callback function`;function Y(e){for(let t in e)if(!(t===`__action`&&e[t]===`edit`))return!1;return!0}function X(e){if(typeof e!=`object`||typeof e.tags!=`object`)throw TypeError(J);return!!e.geometry||!Y(e.tags)}function Z(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:L({lat:i,lng:r})}:e.geometry||(e.tags.__action===`edit`?{type:`Polygon`,coordinates:I({lat:i,lng:r})}:{type:`Point`,coordinates:[r,i]}),properties:e.tags}}async function de(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){t&&(t.warnings?.length&&e.warnings.push(...t.warnings),t.createFeatures&&(i[n]||=[],i[n].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(!X(l.diff))continue;let u=l.group||o.sectors[0];a(l.extra,u),i[u]||=[],i[u].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(u.diff))continue;let d=u.group||c.sectors[0];a(u.extra,d),i[d]||=[],i[d].push(Z(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(!X(r))continue;let s=l.group||n.sectors[0];a(l.extra,s),i[s]||=[],i[s].push(Z(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(!X(o.diff))continue;let s=o.group||r.sectors[0];a(o.extra,s),i[s]||=[],i[s].push(Z(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(!X(s.diff))continue;let c=s.selection?n.noRef[s.selection]:void 0,l=s.group||c?.sectors[0]||`unknown`;a(s.extra,l),i[l]||=[],i[l].push(Z(s.diff,c,o))}let c={};for(let n in i){let{changesetTags:a={},instructions:o}=await e.callbacks.getChangesetTags?.(n)||{};a.created_by||=`osm-conflation-engine`,a.import||=`yes`,a.source||=e.config.metadata.wiki_page;let s=i[n];e.callbacks.postprocessLayer&&await e.callbacks.postprocessLayer({group:n,osmData:r,sourceData:t,features:s});let l=z(n,o,a,s);Object.assign(c,l)}ce(c),ue(e,c);let l={create:0,edit:0,delete:0,perfect:0};for(let e in c)for(let t of c[e].features)t.properties.__action===`edit`?l.edit++:t.properties.__action===`delete`?l.delete++:l.create++;return l.perfect=n.count-l.edit-l.delete,{counts:l}}async function Q(e){console.info(`Reading source data into memory...`);let t=0,n={},r=h({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:E(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 D(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 D(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 fe(e){for(let t of e)if(!v.includes(t))throw Error(`‘${t}’ is not a valid step. The options are: ${v.join(`, `)}`)}async function $(e){let t=K(e.metadata.name).slice(0,12),i=_?`-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 pe(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=v}=n;fe(d),d.includes(`download`)&&(console.time(`download`),await k(u),console.timeEnd(`download`));let f,p;d.includes(`match`)&&({sourceData:f,osmData:p}=await Q(u),console.time(`match`),await P(u,f,p),console.timeEnd(`match`));let m;return d.includes(`conflate`)&&({sourceData:f,osmData:p}=await Q(u),console.time(`conflate`),m=await de(u,f,p),console.timeEnd(`conflate`)),console.info(`done.`),{conflate:m}}export{ie as OsmFlags,$ as getTempFileNames,D as readJsonL,pe as run,O as writeJsonL};
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","fs","fs","fs","getFirstCoord","hash","fs","fs","fs","fs"],"sources":["../src/constants/defaults.ts","../src/steps/download/downloadFromPlanetPbf.ts","../src/steps/download/fetchIgnoreList.ts","../src/types/callbacks.def.ts","../src/common/helpers.ts","../src/common/getSector.ts","../src/common/jsonl.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/steps/conflate/splitUntilSmallEnough.ts","../src/steps/conflate/spreadToGrid.ts","../src/helpers.ts","../src/common/calcCount.ts","../src/steps/conflate/createIndexAndSaveToDisk.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// TODO: make configurable\nexport const MAX_ITEMS_PER_DATASET = 110;\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 { setTimeout } from 'node:timers/promises';\nimport { spawn } from 'node:child_process';\nimport { existsSync, 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 (\n !pbfUrl.startsWith(DOWNLOAD_SERVER) &&\n !pbfUrl.startsWith(DOWNLOAD_SERVER_NO_METADATA)\n ) {\n ctx.warnings.push(\n '(!) Planet PBF url is not from geofabrik.de, 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.replace(DOWNLOAD_SERVER, DOWNLOAD_SERVER_NO_METADATA),\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.replace(DOWNLOAD_SERVER_NO_METADATA, DOWNLOAD_SERVER),\n cookies: authToken,\n };\n}\n\nexport async function downloadFromPlanetPbf(ctx: Ctx, pbfUrl: string) {\n const planetFile = ctx.tempFileNames.pbf;\n\n if (ctx.use_cache && existsSync(planetFile)) {\n console.info('Using cached planet file');\n return;\n }\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 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 { SourceData } from './internal.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 group?: string;\n diff: ConflationDiff;\n extra?: ConflationResultExtra;\n}\n\nexport interface MutliFeatureConflationResult {\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 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?(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 counts: {\n create: number;\n edit: number;\n delete: number;\n perfect: number;\n };\n}\n\nexport interface RunResult {\n conflate?: ConflateResult;\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 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 { 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 { existsSync, promises as fs } from 'node:fs';\nimport pbf2json, { type Item } from 'pbf2json';\nimport type { OsmFeature as StandardOsmFeature, Tags } from 'osm-api';\n\nimport { CHECK_DATE_KEY, RECENT_THRESHOLD } from '../../constants/defaults.js';\nimport {\n CheckDate,\n type Ctx,\n type DatasetId,\n type OSMData,\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/index.js';\nimport { isChecked } from '../../common/helpers.js';\nimport { getSector } from '../../common/getSector.js';\nimport { writeJsonL } from '../../common/jsonl.js';\n\nconst THRESHOLD_DATE = ((d) => {\n d.setDate(d.getDate() - RECENT_THRESHOLD);\n return +d / 1000;\n})(new Date());\n\ntype 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 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 let index = 0;\n\n let anyMetadata = false;\n\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 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 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: Tags = {};\n for (const key in item.tags) {\n const value = item.tags[key];\n if (keepKey(key) && value) {\n tags[key] = value;\n }\n }\n\n const object: OsmFeature = {\n id: `${<OsmFeatureTypeShort>item.type[0]}${item.id}`,\n centroid: [coords.lon, coords.lat],\n sectors: getSector(\n { type: 'Point', coordinates: [coords.lon, coords.lat] },\n ctx.config.merge.sector_resolution,\n ),\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 flags: OsmFlags.None,\n };\n\n // boolean flags\n if ((metadata?.timestamp ?? 0) > THRESHOLD_DATE) {\n object.flags |= OsmFlags.IsRecentlyChanged;\n }\n\n const isLastEditedByImporter =\n metadata?.user &&\n // TODO: cache the result per username\n (ctx.callbacks.isImportUser\n ? ctx.callbacks.isImportUser?.(metadata?.user)\n : metadata?.user?.endsWith('_import'));\n if (metadata?.version === 1 || isLastEditedByImporter) {\n object.flags |= OsmFlags.IsLastEditedByImporter;\n }\n\n if (tags[checkDateKey]) {\n object.flags |= OsmFlags.IsChecked;\n }\n\n if (isChecked(tags[checkDateKey]) === CheckDate.YesRecent) {\n object.flags |= OsmFlags.IsCheckedRecently;\n }\n\n const primaryKey = <DatasetId | undefined>(\n item.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 index += 1;\n if (!(index % 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 resolve(out);\n })\n .on('error', reject);\n });\n\n const { withRef, noRef, ...other } = result;\n\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 type { Ctx } from '../../types/index.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 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 throw new Error('not supported yet');\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 { 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","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('match');\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('match');\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 { OsmPatchFeature as GeoJsonFeature, Tags } from 'osm-api';\nimport { geoCentroid as getFirstCoord } from 'd3-geo';\nimport type { HandlerReturnWithBBox } from '../../types/index.js';\nimport { MAX_ITEMS_PER_DATASET } from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\n\nexport function splitUntilSmallEnough(\n name: string,\n instructions: string | undefined,\n changesetTags: Tags | undefined,\n features: GeoJsonFeature[],\n depth = 0,\n): HandlerReturnWithBBox {\n if (!features.length) return {};\n\n const bbox = calcBBox(features);\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, instructions, changesetTags } };\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 `${name}^N`,\n instructions,\n changesetTags,\n north,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n `${name}^S`,\n instructions,\n changesetTags,\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 `${name}^E`,\n instructions,\n changesetTags,\n east,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n `${name}^W`,\n instructions,\n changesetTags,\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 { Ctx, HandlerReturnWithBBox } 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';\n\nfunction toId(suburb: string) {\n // macrons are url safe\n return `${suburb.replaceAll(/[^\\dA-Za-zĀāĒēĪīŌōŪū]/g, '')}_${hash(suburb).slice(0, 6)}`;\n}\n\nexport async function createIndexAndSaveToDisk(\n ctx: Ctx,\n suburbs: HandlerReturnWithBBox,\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).map(([suburb, v]) => ({\n suburb,\n bbox: v.bbox,\n instructions: v.instructions,\n ...calcCount(v.features),\n }));\n\n // create index.json\n const indexFile = {\n fields: [],\n results: meta\n .map((v) => {\n const title = v.suburb.replace('ZZ ', '').replace('Z ', '');\n return {\n id: toId(v.suburb),\n url: `https://${githubParts[1]}.github.io/${githubParts[2]}/${subFolderName}/${toId(v.suburb)}.osmPatch.geo.json`,\n name: title,\n 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: [\n v.suburb.startsWith('ZZ')\n ? '/Categories/Preview'\n : '/Categories/Addresses',\n ],\n };\n })\n .toSorted((a, b) => a.name.localeCompare(b.name)),\n };\n await fs.writeFile(\n join(outputFolder, 'index.json'),\n JSON.stringify(indexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.mkdir(join(outputFolder, subFolderName), { recursive: true });\n\n // save each suburb\n for (const s of meta) {\n const { suburb } = s;\n const geojson: OsmPatch = {\n type: 'FeatureCollection',\n ...suburbs[suburb]!,\n };\n geojson.changesetTags ||= {\n attribution: 'https://wiki.openstreetmap.org/wiki/Contributors#LINZ',\n created_by: 'LINZ Data Import 2.0.0',\n locale: 'en-NZ',\n source: 'https://wiki.osm.org/LINZ',\n comment: suburb,\n };\n\n await fs.writeFile(\n join(outputFolder, subFolderName, `${toId(suburb)}.osmPatch.geo.json`),\n JSON.stringify(geojson, null, IS_UNIT_TEST ? 2 : undefined),\n );\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 HandlerReturnWithBBox,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OsmFeature,\n OsmFlags,\n type OsmId,\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';\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 featuresBySector: { [sector: string]: OsmPatchFeature[] } = {};\n\n function handleExtra(\n extra: ConflationResultExtra | undefined,\n sector: string,\n ) {\n if (!extra) return;\n if (extra.warnings?.length) ctx.warnings.push(...extra.warnings);\n if (extra.createFeatures) {\n featuresBySector[sector] ||= [];\n featuresBySector[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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature?.sectors[0] || 'unknown';\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n const handlerReturn: HandlerReturnWithBBox = {};\n for (const sector in featuresBySector) {\n const { changesetTags = {}, instructions } =\n (await ctx.callbacks.getChangesetTags?.(sector)) || {};\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 = featuresBySector[sector]!;\n if (ctx.callbacks.postprocessLayer) {\n await ctx.callbacks.postprocessLayer({\n group: sector,\n osmData: osmDataById,\n sourceData,\n features,\n });\n }\n\n const groups = splitUntilSmallEnough(\n sector,\n instructions,\n changesetTags,\n features,\n );\n Object.assign(handlerReturn, groups);\n }\n\n // TODO: merge tiny datasets into adjacent sectors (using h3 API)\n shiftOverlappingPoints(handlerReturn);\n createIndexAndSaveToDisk(ctx, handlerReturn);\n // TODO: generate stats and stats history\n\n const counts: ConflateResult['counts'] = {\n create: 0,\n edit: 0,\n delete: 0,\n perfect: 0,\n };\n for (const sector in handlerReturn) {\n for (const f of handlerReturn[sector]!.features) {\n if (f.properties.__action === 'edit') {\n counts.edit++;\n } else if (f.properties.__action === 'delete') {\n counts.delete++;\n } else {\n counts.create++;\n }\n }\n }\n\n counts.perfect = osmData.count - counts.edit - counts.delete;\n return { counts };\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_with_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>;\n let osmData: OSMData;\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 ({ sourceData, osmData } = await readData(ctx));\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","getFirstCoord","hash","fs","fs","fs","fs"],"sources":["../src/constants/defaults.ts","../src/steps/download/downloadFromPlanetPbf.ts","../src/steps/download/fetchIgnoreList.ts","../src/types/callbacks.def.ts","../src/common/helpers.ts","../src/common/getSector.ts","../src/common/jsonl.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/steps/conflate/splitUntilSmallEnough.ts","../src/steps/conflate/spreadToGrid.ts","../src/helpers.ts","../src/common/calcCount.ts","../src/steps/conflate/createIndexAndSaveToDisk.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// TODO: make configurable\nexport const MAX_ITEMS_PER_DATASET = 110;\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 { setTimeout } from 'node:timers/promises';\nimport { spawn } from 'node:child_process';\nimport { existsSync, 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 (\n !pbfUrl.startsWith(DOWNLOAD_SERVER) &&\n !pbfUrl.startsWith(DOWNLOAD_SERVER_NO_METADATA)\n ) {\n ctx.warnings.push(\n '(!) Planet PBF url is not from geofabrik.de, 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.replace(DOWNLOAD_SERVER, DOWNLOAD_SERVER_NO_METADATA),\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.replace(DOWNLOAD_SERVER_NO_METADATA, DOWNLOAD_SERVER),\n cookies: authToken,\n };\n}\n\nexport async function downloadFromPlanetPbf(ctx: Ctx, pbfUrl: string) {\n const planetFile = ctx.tempFileNames.pbf;\n\n if (ctx.use_cache && existsSync(planetFile)) {\n console.info('Using cached planet file');\n return;\n }\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 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 { SourceData } from './internal.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 group?: string;\n diff: ConflationDiff;\n extra?: ConflationResultExtra;\n}\n\nexport interface MutliFeatureConflationResult {\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 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?(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 counts: {\n create: number;\n edit: number;\n delete: number;\n perfect: number;\n };\n}\n\nexport interface RunResult {\n conflate?: ConflateResult;\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 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 { 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 { existsSync, promises as fs } from 'node:fs';\nimport pbf2json, { type Item } from 'pbf2json';\nimport type { OsmFeature as StandardOsmFeature, Tags } from 'osm-api';\n\nimport { CHECK_DATE_KEY, RECENT_THRESHOLD } from '../../constants/defaults.js';\nimport {\n CheckDate,\n type Ctx,\n type DatasetId,\n type OSMData,\n type OsmFeature,\n type OsmFeatureTypeShort,\n OsmFlags,\n} from '../../types/index.js';\nimport { isChecked } from '../../common/helpers.js';\nimport { getSector } from '../../common/getSector.js';\nimport { writeJsonL } from '../../common/jsonl.js';\n\nconst THRESHOLD_DATE = ((d) => {\n d.setDate(d.getDate() - RECENT_THRESHOLD);\n return +d / 1000;\n})(new Date());\n\ntype 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 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 let index = 0;\n\n let anyMetadata = false;\n\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 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 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: Tags = {};\n for (const key in item.tags) {\n const value = item.tags[key];\n if (keepKey(key) && value) {\n tags[key] = value;\n }\n }\n\n const object: OsmFeature = {\n id: `${<OsmFeatureTypeShort>item.type[0]}${item.id}`,\n centroid: [coords.lon, coords.lat],\n sectors: getSector(\n { type: 'Point', coordinates: [coords.lon, coords.lat] },\n ctx.config.merge.sector_resolution,\n ),\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 flags: OsmFlags.None,\n };\n\n // boolean flags\n if ((metadata?.timestamp ?? 0) > THRESHOLD_DATE) {\n object.flags |= OsmFlags.IsRecentlyChanged;\n }\n\n const isLastEditedByImporter =\n metadata?.user &&\n // TODO: cache the result per username\n (ctx.callbacks.isImportUser\n ? ctx.callbacks.isImportUser?.(metadata?.user)\n : metadata?.user?.endsWith('_import'));\n if (metadata?.version === 1 || isLastEditedByImporter) {\n object.flags |= OsmFlags.IsLastEditedByImporter;\n }\n\n if (tags[checkDateKey]) {\n object.flags |= OsmFlags.IsChecked;\n }\n\n if (isChecked(tags[checkDateKey]) === CheckDate.YesRecent) {\n object.flags |= OsmFlags.IsCheckedRecently;\n }\n\n const primaryKey = <DatasetId | undefined>(\n item.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 index += 1;\n if (!(index % 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 resolve(out);\n })\n .on('error', reject);\n });\n\n const { withRef, noRef, ...other } = result;\n\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 type { Ctx } from '../../types/index.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 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 throw new Error('not supported yet');\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 { 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","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('match');\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('match');\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 { OsmPatchFeature as GeoJsonFeature, Tags } from 'osm-api';\nimport { geoCentroid as getFirstCoord } from 'd3-geo';\nimport type { HandlerReturnWithBBox } from '../../types/index.js';\nimport { MAX_ITEMS_PER_DATASET } from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\n\nexport function splitUntilSmallEnough(\n name: string,\n instructions: string | undefined,\n changesetTags: Tags | undefined,\n features: GeoJsonFeature[],\n depth = 0,\n): HandlerReturnWithBBox {\n if (!features.length) return {};\n\n const bbox = calcBBox(features);\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, instructions, changesetTags } };\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 `${name}^N`,\n instructions,\n changesetTags,\n north,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n `${name}^S`,\n instructions,\n changesetTags,\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 `${name}^E`,\n instructions,\n changesetTags,\n east,\n depth + 1,\n ),\n ...splitUntilSmallEnough(\n `${name}^W`,\n instructions,\n changesetTags,\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 { Ctx, HandlerReturnWithBBox } 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';\n\nfunction toId(suburb: string) {\n // macrons are url safe\n return `${suburb.replaceAll(/[^\\dA-Za-zĀāĒēĪīŌōŪū]/g, '')}_${hash(suburb).slice(0, 6)}`;\n}\n\nexport async function createIndexAndSaveToDisk(\n ctx: Ctx,\n suburbs: HandlerReturnWithBBox,\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).map(([suburb, v]) => ({\n suburb,\n bbox: v.bbox,\n instructions: v.instructions,\n ...calcCount(v.features),\n }));\n\n // create index.json\n const indexFile = {\n fields: [],\n results: meta\n .map((v) => {\n const title = v.suburb.replace('ZZ ', '').replace('Z ', '');\n return {\n id: toId(v.suburb),\n url: `https://${githubParts[1]}.github.io/${githubParts[2]}/${subFolderName}/${toId(v.suburb)}.osmPatch.geo.json`,\n name: title,\n 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: [\n v.suburb.startsWith('ZZ')\n ? '/Categories/Preview'\n : '/Categories/Addresses',\n ],\n };\n })\n .toSorted((a, b) => a.name.localeCompare(b.name)),\n };\n await fs.writeFile(\n join(outputFolder, 'index.json'),\n JSON.stringify(indexFile, null, IS_UNIT_TEST ? 2 : undefined),\n );\n await fs.mkdir(join(outputFolder, subFolderName), { recursive: true });\n\n // save each suburb\n for (const s of meta) {\n const { suburb } = s;\n const geojson: OsmPatch = {\n type: 'FeatureCollection',\n ...suburbs[suburb]!,\n };\n geojson.changesetTags ||= {\n attribution: 'https://wiki.openstreetmap.org/wiki/Contributors#LINZ',\n created_by: 'LINZ Data Import 2.0.0',\n locale: 'en-NZ',\n source: 'https://wiki.osm.org/LINZ',\n comment: suburb,\n };\n\n await fs.writeFile(\n join(outputFolder, subFolderName, `${toId(suburb)}.osmPatch.geo.json`),\n JSON.stringify(geojson, null, IS_UNIT_TEST ? 2 : undefined),\n );\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 HandlerReturnWithBBox,\n type MatchOutput,\n MatchType,\n type OSMData,\n type OsmFeature,\n OsmFlags,\n type OsmId,\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';\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 featuresBySector: { [sector: string]: OsmPatchFeature[] } = {};\n\n function handleExtra(\n extra: ConflationResultExtra | undefined,\n sector: string,\n ) {\n if (!extra) return;\n if (extra.warnings?.length) ctx.warnings.push(...extra.warnings);\n if (extra.createFeatures) {\n featuresBySector[sector] ||= [];\n featuresBySector[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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature.sectors[0]!;\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].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 sector = result.group || oFeature?.sectors[0] || 'unknown';\n handleExtra(result.extra, sector);\n featuresBySector[sector] ||= [];\n featuresBySector[sector].push(\n createFeature(result.diff, oFeature, sFeature),\n );\n }\n\n const handlerReturn: HandlerReturnWithBBox = {};\n for (const sector in featuresBySector) {\n const { changesetTags = {}, instructions } =\n (await ctx.callbacks.getChangesetTags?.(sector)) || {};\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 = featuresBySector[sector]!;\n if (ctx.callbacks.postprocessLayer) {\n await ctx.callbacks.postprocessLayer({\n group: sector,\n osmData: osmDataById,\n sourceData,\n features,\n });\n }\n\n const groups = splitUntilSmallEnough(\n sector,\n instructions,\n changesetTags,\n features,\n );\n Object.assign(handlerReturn, groups);\n }\n\n // TODO: merge tiny datasets into adjacent sectors (using h3 API)\n shiftOverlappingPoints(handlerReturn);\n createIndexAndSaveToDisk(ctx, handlerReturn);\n // TODO: generate stats and stats history\n\n const counts: ConflateResult['counts'] = {\n create: 0,\n edit: 0,\n delete: 0,\n perfect: 0,\n };\n for (const sector in handlerReturn) {\n for (const f of handlerReturn[sector]!.features) {\n if (f.properties.__action === 'edit') {\n counts.edit++;\n } else if (f.properties.__action === 'delete') {\n counts.delete++;\n } else {\n counts.create++;\n }\n }\n }\n\n counts.perfect = osmData.count - counts.edit - counts.delete;\n return { counts };\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>;\n let osmData: OSMData;\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 ({ sourceData, osmData } = await readData(ctx));\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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"}
|