{"version":3,"file":"index.mjs","names":["fs","fs","fs","fs","fs","fs","getFirstCoord","hash","fs","fs","fs","fs","fs"],"sources":["../src/constants/defaults.ts","../src/types/callbacks.def.ts","../src/steps/download/util/validateOsmTagsInConfig.ts","../src/types/internal.def.ts","../src/common/helpers.ts","../src/common/jsonl.ts","../src/common/getSector.ts","../src/steps/download/util/loadOsmFeature.ts","../src/steps/download/downloadFromOverpass.ts","../src/steps/download/downloadFromPlanetPbf.ts","../src/steps/download/downloadFromPostpass.ts","../src/steps/download/fetchIgnoreList.ts","../src/steps/download/osmToJson.ts","../src/steps/download/index.ts","../src/steps/match/processDeletions.ts","../src/common/geo.ts","../src/steps/match/findPotentialOsmFeatures.ts","../src/steps/match/index.ts","../src/steps/conflate/createDiamond.ts","../src/common/calcBBox.ts","../src/common/bboxToPolygon.ts","../src/steps/conflate/splitUntilSmallEnough.ts","../src/steps/conflate/spreadToGrid.ts","../src/helpers.ts","../src/common/calcCount.ts","../src/steps/conflate/stats/generateReports.ts","../src/steps/conflate/stats/amendRecursiveHistory.ts","../src/steps/conflate/createIndexAndSaveToDisk.ts","../src/steps/conflate/mergeTinyDatasets.ts","../src/steps/conflate/stats/generateDataForWebsite.ts","../src/steps/conflate/stats/writeToRunHistory.ts","../src/steps/conflate/index.ts","../src/steps/match/readData.ts","../src/api/tag2link.ts","../src/main.ts"],"sourcesContent":["import type { Config } from '../types/config.def.js';\n\n/** if a feature was last edited within this many DAYS, then it's \"recently edited\" */\nexport const RECENT_THRESHOLD = 90;\n\n/** can be overriden by the config */\nexport const DEFAULT_SECTOR_RESOLUTION = 3;\n\nexport const IS_UNIT_TEST = process.env.NODE_ENV === 'test';\n\n/**\n * this is not a hard minimum, if a dataset has fewer than this number,\n * we will try to merge it into the nearest datasets in the same sector.\n */\n// TODO: make configurable\nexport const MIN_ITEMS_PER_DATASET = 40;\nexport const MAX_ITEMS_PER_DATASET = 110;\nexport const MAX_BBOX_DEGREES = 4;\n\n/** can be customised in the config */\nexport const CHECK_DATE_KEY = 'check_date';\n\nexport const STEPS = <const>['download', 'match', 'conflate'];\nexport type Step = (typeof STEPS)[number];\n\nexport const FILE_NAMES = <const>{\n  // osm data\n  PBF: 'o.pbf',\n  OVERPASS: 'o.overpass.geo.json',\n  POSTPASS: 'o.postpass.geo.json',\n  OSM_PROCESSED_WITH_REF: 'osm-processed-with-ref.jsonl',\n  OSM_PROCESSED_NO_REF: 'osm-processed-no-ref.jsonl',\n  OSM_PROCESSED_OTHER: 'osm-processed-other.json',\n\n  // source data\n\n  // other data\n  IGNORE_LIST: 'ignore-list.json',\n\n  // matched\n  MATCHES: 'matches.json',\n};\nexport type FileNames = keyof typeof FILE_NAMES;\n\nexport const API_BASE_URL = 'https://osm-conflation-engine.kyle.kiwi';\nexport const REPO_URL = 'https://github.com/osm-nz/osm-conflation-engine';\n\nexport const getHttpHeaders = (config: Config) => ({\n  'User-Agent': `${REPO_URL} on behalf of ${config.metadata.wiki_page}`,\n});\n","import type {\n  OsmPatchFeature,\n  OsmFeature as StandardOsmFeature,\n  Tags,\n} from 'osm-api';\nimport type {\n  Feature,\n  FeatureCollection,\n  GeoJsonProperties,\n  Geometry,\n  Polygon,\n} from 'geojson';\nimport type { Step } from '../constants/defaults.js';\nimport type { MatchType, SourceData } from './internal.def.js';\nimport type { Config } from './config.def.js';\n\nexport type TagDiff = OsmPatchFeature['properties'];\n\n/** see microsoft/TypeScript#202 */\nexport type Identity<out T> = { _: T; readonly __: unique symbol };\n\nexport type OsmFeatureTypeShort = 'n' | 'w' | 'r';\nexport type OsmId = `${OsmFeatureTypeShort}${number}`;\nexport type DatasetId = string & Identity<'DatasetId'>;\n\nexport type Vec2 = [lon: number, lat: number];\n\n/** the action from an {@link OsmPatch} file */\nexport type Action =\n  'create' | (OsmPatchFeature['properties']['__action'] & {});\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  /** @deprecated this is poorly named, use `extraFeatures` instead */\n  createFeatures?: OsmPatchFeature[];\n  extraFeatures?: OsmPatchFeature[];\n  warnings?: string[];\n}\n\nexport interface ConflationDiff {\n  tags: TagDiff;\n  geometry?: Geometry;\n}\n\nexport interface SingleFeatureConflationResult {\n  category?: string;\n  group?: string;\n  diff: ConflationDiff | undefined;\n  extra?: ConflationResultExtra;\n}\n\nexport interface MutliFeatureConflationResult {\n  category?: string;\n  group?: string;\n  diffPerFeature: { [id: OsmId]: ConflationDiff };\n  extra?: ConflationResultExtra;\n}\n\n// this is the public API, so any break changes require a major version bump\nexport interface Callbacks<G extends Geometry, P extends GeoJsonProperties> {\n  /**\n   * called for every OSM feature, you should return a key which is unique\n   * within its sector. For example, this could be the most important tags\n   * concatenated together, if those tags are usually unique within the\n   * local area (the sector).\n   */\n  getLocalKeyForOsm(osmFeature: OsmFeature): string;\n  /**\n   * see docs for {@link getLocalKeyForOsm}. This is the equivilant callback\n   * for the source data, which is called for every feature.\n   */\n  getLocalKeyForSource(sourceFeature: SourceDataFeature<G, P>): string;\n\n  /**\n   * given a ref, this function should return alterantive variants for\n   * matching. For example, if the ref is `00123`, this function could\n   * return `123`, which will also be considered for matching.\n   */\n  getAltRefs?(ref: DatasetId): DatasetId[];\n\n  /**\n   * optional, by default a username which ends in `_import` is considered\n   * to be an importer. Use this callback if you want to customise the\n   * behaviour.\n   */\n  isImportUser?(username: string): boolean;\n\n  // TODO: documentation for each\n  mergeOneToOne(input: {\n    source: SourceDataFeature<G, P>;\n    osm: OsmFeature;\n  }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n  mergeOneToMany?(input: {\n    source: SourceDataFeature<G, P>;\n    osm: OsmFeature[];\n  }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n  mergeManyToOne?(input: {\n    source: SourceDataFeature<G, P>[];\n    osm: OsmFeature;\n  }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n  mergeManyToMany?(input: {\n    source: SourceDataFeature<G, P>[];\n    osm: OsmFeature[];\n  }): MaybePromise<MutliFeatureConflationResult | undefined>;\n\n  /**\n   * called for OSM features which have a ref tag that does not exist in the\n   * source dataset. You need to decide whether to:\n   * (1) delete this feature; or\n   * (2) only remove the import related tags; or\n   * (3) do nothing.\n   */\n  deleteFeature?(input: {\n    osm: OsmFeature;\n  }): MaybePromise<SingleFeatureConflationResult | undefined>;\n\n  /**\n   * Calls for each feature in the source dataset where a matching feature\n   * could not be found in OSM. If there are potential matches, these are\n   * supplied using the `osmCandidates` argument. If you choose to accept\n   * one of these candidates, return the `selection` property set to that\n   * feature's {@link OsmId}. If not, return `selection: undefined`, which\n   * will create a new OSM feature instead.\n   */\n  create(input: {\n    source: SourceDataFeature<G, P>;\n    osmCandidates: OsmFeature[];\n  }): MaybePromise<\n    | (SingleFeatureConflationResult & {\n        /**\n         * undefined means that no selection was made, so we\n         * will create a new OSM feature instead\n         */\n        selection: OsmId | undefined;\n      })\n    | undefined\n  >;\n\n  /**\n   * optional, called after the conflation stage once all diffs have been\n   * allocated to a group. You can apply any addition transformations to\n   * the features in each layer.\n   */\n  postprocessLayer?(input: {\n    category: string;\n    group: string;\n    features: OsmPatchFeature[];\n    osmData: Record<OsmId, OsmFeature>;\n    sourceData: SourceData<G, P>;\n  }): MaybePromise<void>;\n\n  /**\n   * optional, called after the conflation stage for every group. You can\n   * return the changeset tags which will be applied to this group, and\n   * optionally `instructions` which are shown in the website UI, before\n   * someone starts working on this group.\n   */\n  getChangesetTags?(input: { category: string; group: string }): MaybePromise<{\n    instructions?: string;\n    changesetTags?: Tags;\n  }>;\n\n  /**\n   * optional, called ONCE after conflation is finished. use this\n   * if you to add any other any other {@link OsmPatchFeature}\n   * features to the output, or any warnings.\n   */\n  addCustomLayers?(): MaybePromise<{\n    [category: string]: {\n      [group: string]: ConflationResultExtra;\n    };\n  }>;\n}\nexport interface RunOptions {\n  steps?: Step[];\n  use_cache?: boolean;\n}\n\nexport interface ConflateResult {\n  config: Config;\n  warnings: string[];\n  countsByPhase: {\n    init: {\n      sourceDataset: number;\n      osm: {\n        // these 4 are mutataly exclusive\n        withRef: number;\n        duplicateRefs: number;\n        semi: number;\n        noRef: number;\n\n        // these are independant of the previous 4\n        recentlyChecked: number;\n        recentlyChanged: number;\n        lastEditedByImporter: number;\n      };\n      ignored: number;\n    };\n    matched: Record<MatchType, number>;\n    conflated: ConflationResult;\n  };\n}\n\nexport interface ConflationResult {\n  create: number;\n  edit: number;\n  delete: number;\n  perfect: number;\n}\n\nexport interface RecursiveHistoryRow {\n  date: string;\n  conflated: ConflationResult;\n}\n\nexport interface RecursiveHistoryFile {\n  /** ISO Date */\n  lastUpdated: string;\n  rows: RecursiveHistoryRow[];\n}\n\nexport interface RunResult {\n  conflate?: ConflateResult;\n}\n\nexport interface IndexFileProperties {\n  id: string;\n  category: string;\n  group: string;\n  title: string;\n  instructions: string | undefined;\n  count: string;\n  totalCount: number;\n}\n\n/** the shape of `index.geo.json` */\nexport type IndexFile = FeatureCollection<Polygon, IndexFileProperties> & {\n  __reports: string[];\n};\n","import type { Tags } from 'osm-api';\nimport type { Tags as NullishTags } from 'pbf2json';\nimport { CHECK_DATE_KEY } from '../../../constants/defaults.js';\nimport type { Ctx } from '../../../types/internal.def.js';\n\nexport function validateOsmTagsInConfig(ctx: Ctx) {\n  const checkDateKey = ctx.config.osm_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.osm_data.tags_to_keep),\n  );\n  if (missingKeys.size) {\n    throw new Error(\n      `config.osm_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.osm_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.osm_data.tags_to_keep.includes(key);\n\n    if (!keep) {\n      for (const regex of regExs) {\n        if (regex.test(key)) {\n          keep = true;\n          break;\n        }\n      }\n    }\n\n    tagsToKeep[key] = keep;\n    return keep;\n  }\n\n  function pickTags(allTags: NullishTags | undefined) {\n    const tags: Tags = {};\n    if (allTags) {\n      for (const key in allTags) {\n        const value = allTags[key];\n        if (keepKey(key) && value) {\n          tags[key] = value;\n        }\n      }\n    }\n    return tags;\n  }\n\n  return { pickTags };\n}\n","import type { OsmPatchFeature, Tags } from 'osm-api';\nimport type { GeoJsonProperties, Geometry } from 'geojson';\nimport type { FileNames } from '../constants/defaults.js';\nimport type {\n  Callbacks,\n  DatasetId,\n  OsmFeature,\n  OsmId,\n  SourceDataFeature,\n} from './callbacks.def.js';\nimport type { Config } from './config.def.js';\n\ndeclare global {\n  namespace NodeJS {\n    interface ProcessEnv {\n      NODE_ENV?: 'test';\n      OSM_AUTH?: string;\n      GITHUB_ACTIONS?: 'true';\n    }\n  }\n}\n\nexport interface Ctx<\n  G extends Geometry = Geometry,\n  P extends GeoJsonProperties = GeoJsonProperties,\n> {\n  use_cache: boolean;\n  id: string;\n  tempFolder: string;\n  tempFileNames: Record<Lowercase<FileNames>, string>;\n  config: Config;\n  callbacks: Callbacks<G, P>;\n  formatterUrl: string | undefined;\n  warnings: string[];\n}\n\nexport interface SourceData<\n  G extends Geometry = Geometry,\n  P extends GeoJsonProperties = GeoJsonProperties,\n> {\n  [datasetId: DatasetId]: SourceDataFeature<G, P>;\n}\n\nexport enum CheckDate {\n  No,\n  YesRecent, // check_date is recent (less than X years ago)\n  YesExpired, // check_date is older than X years\n}\n\nexport interface OSMData {\n  withRef: {\n    [datasetId: DatasetId]: OsmFeature;\n  };\n  /** IDs that exist on multiple osm features */\n  duplicateRefs: {\n    [datasetId: DatasetId]: OsmFeature[];\n  };\n  /** osm features where the ref tag has a semicolon-delimited primary key tag */\n  semi: {\n    [datasetId: DatasetId]: OsmFeature;\n  };\n  noRef: {\n    [osmId: OsmId]: OsmFeature;\n  };\n  /** the number of features in OSM with the `ref:*` tag */\n  count: number;\n}\n\n/**\n * `source:osm`. this only describes the mapping between the two datasets.\n * This mapping is orthogonal to whether the tags are correct. That is\n * checked at a later stage.\n */\nexport enum MatchType {\n  OneToOne = 1,\n  OneToMany = 2,\n  ManyToOne = 3,\n  ManyToMany = 4,\n  /** 0:1 */\n  Delete = 5,\n  /** `1:unknown` there is no match, so we have guessed, and found some potential matches. */\n  Guess = 6,\n}\n\n/** `source:osm` */\nexport interface MatchOutput {\n  [MatchType.OneToOne]: { source: DatasetId; osm: OsmId }[];\n  [MatchType.OneToMany]: { source: DatasetId; osm: OsmId[] }[];\n  [MatchType.ManyToOne]: { [osmId: OsmId]: DatasetId[] };\n  [MatchType.ManyToMany]: { source: DatasetId[]; osm: OsmId[] }[];\n  [MatchType.Delete]: DatasetId[];\n  [MatchType.Guess]: { source: DatasetId; osmCandidates: OsmId[] }[];\n}\n\nexport type BBox = {\n  minLat: number;\n  maxLat: number;\n  minLng: number;\n  maxLng: number;\n};\n\nexport interface OutputLayer {\n  features: OsmPatchFeature[];\n  bbox: BBox;\n  sectorIds: string[];\n  instructions?: string;\n  changesetTags?: Tags;\n}\n\nexport interface HandlerReturnWithBBox {\n  [sectorName: string]: OutputLayer;\n}\n\nexport interface OutputLayers {\n  [categoryName: string]: {\n    [sectorName: string]: OutputLayer;\n  };\n}\n\nexport type CoordKey = `${number},${number}`;\n","import { CheckDate } from '../types/index.js';\n\n/** number of years before we revisit a feature with check_date */\nconst CHECK_DATE_THRESHOLD_YEARS = 4;\n\n/** checks if an ISO date exists and if it it's less than X years old */\nexport const isChecked = (v: string | undefined): CheckDate => {\n  if (!v) return CheckDate.No;\n\n  const yearsAgo = (Date.now() - +new Date(v)) / 1000 / 60 / 60 / 24 / 365;\n  return yearsAgo < CHECK_DATE_THRESHOLD_YEARS\n    ? CheckDate.YesRecent\n    : CheckDate.YesExpired;\n};\n","import { createReadStream, createWriteStream } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\n\n/** reads JSONL by parsing one line at a time */\nexport async function readJsonL<T, U extends string>(\n  filePath: string,\n  getKey: (row: T) => U,\n): Promise<Record<U, T>> {\n  const fileStream = createReadStream(filePath);\n  const rl = createInterface({ input: fileStream });\n\n  const rows = {} as Record<U, T>;\n  for await (const line of rl) {\n    const row = JSON.parse(line);\n    const key = getKey(row);\n    rows[key] = row;\n  }\n  return rows;\n}\n\n/** writes JSONL to a file by stringifying each line at a time */\nexport async function writeJsonL(filePath: string, rows: unknown[]) {\n  const writeStream = createWriteStream(filePath, { encoding: 'utf8' });\n\n  for (const row of rows) {\n    writeStream.write(JSON.stringify(row));\n    writeStream.write('\\n');\n  }\n\n  writeStream.end();\n\n  await new Promise((resolve) => writeStream.on('finish', resolve));\n}\n","import type { Geometry } from 'geojson';\nimport {\n  POLYGON_TO_CELLS_FLAGS,\n  latLngToCell,\n  polygonToCellsExperimental,\n} from 'h3-js';\nimport { DEFAULT_SECTOR_RESOLUTION } from '../constants/defaults.js';\n\n/** converts geojson geometry into a sorted list of h3 grids. */\nexport function getSector(\n  geometry: Geometry,\n  _resolution: number | undefined,\n): string[] {\n  const resolution = _resolution || DEFAULT_SECTOR_RESOLUTION;\n  switch (geometry.type) {\n    case 'GeometryCollection': {\n      throw new Error('not supported');\n    }\n\n    case 'Point': {\n      return [\n        latLngToCell(\n          geometry.coordinates[1]!,\n          geometry.coordinates[0]!,\n          resolution,\n        ),\n      ];\n    }\n    case 'MultiPoint':\n    case 'LineString':\n    case 'MultiLineString':\n    case 'Polygon': {\n      return polygonToCellsExperimental(\n        geometry.coordinates,\n        resolution,\n        POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n        true,\n      ).toSorted();\n    }\n\n    case 'MultiPolygon': {\n      const grids = new Set<string>();\n      for (const ring of geometry.coordinates) {\n        const newGrids = polygonToCellsExperimental(\n          ring,\n          resolution,\n          POLYGON_TO_CELLS_FLAGS.containmentOverlapping,\n          true,\n        );\n        for (const grid of newGrids) grids.add(grid);\n      }\n      return [...grids].toSorted();\n    }\n\n    default: {\n      throw new TypeError(geometry satisfies never);\n    }\n  }\n}\n","import { promises as fs } from 'node:fs';\nimport { isChecked } from '../../../common/helpers.js';\nimport { writeJsonL } from '../../../common/jsonl.js';\nimport {\n  CHECK_DATE_KEY,\n  RECENT_THRESHOLD,\n} from '../../../constants/defaults.js';\nimport {\n  type DatasetId,\n  type OsmFeature,\n  OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n  CheckDate,\n  type Ctx,\n  type OSMData,\n} from '../../../types/internal.def.js';\nimport { getSector } from '../../../common/getSector.js';\n\nconst THRESHOLD_DATE = ((d) => {\n  d.setDate(d.getDate() - RECENT_THRESHOLD);\n  return +d;\n})(new Date());\n\nexport function loadOsmFeature(ctx: Ctx, out: OSMData, object: OsmFeature) {\n  object.sectors = getSector(\n    { type: 'Point', coordinates: object.centroid },\n    ctx.config.merge.sector_resolution,\n  );\n\n  const checkDateKey = ctx.config.osm_data?.check_date_key || CHECK_DATE_KEY;\n\n  // boolean flags\n  if (+new Date(object.metadata?.timestamp ?? 0) > THRESHOLD_DATE) {\n    object.flags |= OsmFlags.IsRecentlyChanged;\n  }\n\n  const isLastEditedByImporter =\n    object.metadata?.user &&\n    // TODO: cache the result per username\n    (ctx.callbacks.isImportUser\n      ? ctx.callbacks.isImportUser?.(object.metadata.user)\n      : object.metadata?.user?.endsWith('_import'));\n  if (object.metadata?.version === 1 || isLastEditedByImporter) {\n    object.flags |= OsmFlags.IsLastEditedByImporter;\n  }\n\n  if (object.tags[checkDateKey]) {\n    object.flags |= OsmFlags.IsChecked;\n  }\n\n  if (isChecked(object.tags[checkDateKey]) === CheckDate.YesRecent) {\n    object.flags |= OsmFlags.IsCheckedRecently;\n  }\n\n  const primaryKey = <DatasetId | undefined>(\n    object.tags[ctx.config.merge.osm_key]\n  );\n  if (primaryKey) {\n    out.count++;\n    // check if there is already an OSM object with the same linz ID\n\n    // this node is 3rd+ one with this linzId\n    if (out.duplicateRefs[primaryKey]) {\n      out.duplicateRefs[primaryKey].push(object);\n    }\n\n    // this node is 2nd one with this linzId\n    else if (out.withRef[primaryKey]) {\n      out.duplicateRefs[primaryKey] = [out.withRef[primaryKey], object];\n      delete out.withRef[primaryKey];\n    }\n\n    // the linz id has a semicolon in it - we don't like this\n    // unless the address has alt_addr:* tags.\n    else if (primaryKey.includes(';')) {\n      const mergedIds = <DatasetId[]>primaryKey.split(';');\n      for (const value of mergedIds) {\n        out.semi[value] = object;\n      }\n    }\n\n    // not a duplicate\n    else {\n      out.withRef[primaryKey] = object;\n    }\n  } else {\n    out.noRef[object.id] = object;\n  }\n}\n\nexport async function saveLoadedOsmFeatures(ctx: Ctx, out: OSMData) {\n  const { withRef, noRef, ...other } = out;\n  await writeJsonL(\n    ctx.tempFileNames.osm_processed_with_ref,\n    Object.values(withRef),\n  );\n  await writeJsonL(\n    ctx.tempFileNames.osm_processed_no_ref,\n    Object.values(noRef),\n  );\n  await fs.writeFile(\n    ctx.tempFileNames.osm_processed_other,\n    JSON.stringify(other),\n  );\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx, OSMData } from '../../types/internal.def.js';\nimport {\n  type OsmFeature,\n  type OsmFeatureTypeShort,\n  OsmFlags,\n} from '../../types/callbacks.def.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n  loadOsmFeature,\n  saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport async function downloadFromOverpass(\n  ctx: Ctx,\n  queryFile: string | undefined,\n  serverUrl: string | undefined,\n) {\n  const cacheFile = ctx.tempFileNames.overpass;\n\n  if (ctx.use_cache && existsSync(cacheFile)) {\n    console.info('Using cached overpass response');\n    const cached: OSMData = JSON.parse(await fs.readFile(cacheFile, 'utf8'));\n    await saveLoadedOsmFeatures(ctx, cached);\n    return;\n  }\n\n  const DEFAULT_SERVER_URL = 'https://overpass-api.de/api/interpreter';\n  const DEFAULT_QUERY = `\n    [out:json][timeout:480];\n    nwr[\"${ctx.config.merge.osm_key}\"];\n    out meta center;\n  `;\n\n  const query = queryFile\n    ? await fs.readFile(queryFile, 'utf8')\n    : DEFAULT_QUERY;\n\n  console.info('fetching OSM data via overpass…');\n  const response = await fetch(\n    `${serverUrl || DEFAULT_SERVER_URL}?data=${encodeURIComponent(query)}`,\n    { headers: getHttpHeaders(ctx.config) },\n  );\n  if (!response.ok) {\n    const status = (await response.text()) || response.statusText;\n    throw new Error(`HTTP Error ${response.status} ${status}`);\n  }\n\n  const responseJson = (await response.json()) as {\n    elements: (StandardOsmFeature & {\n      center?: { lat: number; lon: number };\n    })[];\n  };\n\n  const { pickTags } = validateOsmTagsInConfig(ctx);\n\n  const out: OSMData = {\n    withRef: {},\n    noRef: {},\n    duplicateRefs: {},\n    semi: {},\n    count: 0,\n  };\n  for (const feature of responseJson.elements) {\n    const object: OsmFeature = {\n      id: `${feature.type[0]! as OsmFeatureTypeShort}${feature.id}`,\n      centroid:\n        feature.type === 'node'\n          ? [feature.lon, feature.lat]\n          : feature.center\n            ? [feature.center.lon, feature.center.lat]\n            : [0, 0],\n\n      tags: pickTags(feature.tags),\n      metadata: {\n        changeset: feature.changeset,\n        timestamp: feature.timestamp,\n        user: feature.user,\n        version: feature.version,\n      },\n\n      // added later\n      sectors: [],\n      flags: OsmFlags.None,\n    };\n    loadOsmFeature(ctx, out, object);\n  }\n\n  await fs.writeFile(cacheFile, JSON.stringify(out));\n  await saveLoadedOsmFeatures(ctx, out);\n}\n","import { setTimeout } from 'node:timers/promises';\nimport { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport { basename, dirname } from 'node:path';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx } from '../../types/index.js';\n\nconst DOWNLOAD_SERVER = 'https://osm-internal.download.geofabrik.de';\nconst DOWNLOAD_SERVER_NO_METADATA = 'https://download.geofabrik.de';\nconst OSM_SERVER = 'https://www.openstreetmap.org';\n\nconst getCsrfToken = (html: string) =>\n  html.match(/name=\"csrf-token\" content=\"([^\"]+)\"/)![1]!;\n\n/**\n * based on these docs: https://github.com/geofabrik/sendfile_osm_oauth_protector/blob/master/doc/client.md\n * This basically scrapes the OSM homepage to login programmatically,\n * bypassing CSRF and bypassing OAuth consent...\n */\nasync function getAuthToken(ctx: Ctx, pbfUrl: string) {\n  if (!pbfUrl.startsWith(DOWNLOAD_SERVER)) {\n    ctx.warnings.push(\n      `(!) Planet PBF url is not from ${DOWNLOAD_SERVER}, so metadata may not be available.`,\n    );\n  }\n\n  if (!process.env.OSM_AUTH) {\n    ctx.warnings.push(\n      '(!) No OSM credentials, so the planet extract will not include metadata',\n    );\n    return {\n      url: pbfUrl\n        .replace(DOWNLOAD_SERVER, DOWNLOAD_SERVER_NO_METADATA)\n        .replace('-internal.osm.pbf', '.osm.pbf'),\n      cookies: '',\n    };\n  }\n\n  const credentials = new URLSearchParams(process.env.OSM_AUTH);\n  const baseHeaders = getHttpHeaders(ctx.config);\n\n  // 1. fetch config\n  const authConfig = (await fetch(\n    `${DOWNLOAD_SERVER}/get_cookie?action=get_authorization_url`,\n    { method: 'POST', headers: baseHeaders },\n  ).then((r) => r.json())) as {\n    authorization_url: string;\n    state: string;\n    client_id: string;\n    redirect_uri: string;\n  };\n\n  // 2. fetch home page to get session cookies + CSRF token\n  const homePage = await fetch(`${OSM_SERVER}/login?cookie_test=true`, {\n    headers: baseHeaders,\n  });\n  let cookieJar = homePage.headers.get('Set-Cookie')!;\n\n  // 3. login\n  const loginPage = await fetch(`${OSM_SERVER}/login`, {\n    method: 'POST',\n    body: new URLSearchParams({\n      username: credentials.get('username')!,\n      password: credentials.get('password')!,\n      referer: '/',\n      commit: 'Login',\n      authenticity_token: getCsrfToken(await homePage.text()),\n    }),\n    headers: {\n      ...baseHeaders,\n      Cookie: cookieJar,\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n  });\n  if (loginPage.status !== 200) throw new Error('Login failed');\n  cookieJar += `; ${loginPage.headers.get('Set-Cookie')!}`;\n\n  // 4. open the OAuth consent page\n  const consentPage = await fetch(authConfig.authorization_url, {\n    headers: { ...baseHeaders, Cookie: cookieJar },\n  });\n\n  // 5. if there is no consent, try to submit the form\n  const authToken = consentPage.url.startsWith(OSM_SERVER)\n    ? await fetch(authConfig.authorization_url, {\n        method: 'POST',\n        body: new URLSearchParams({\n          client_id: authConfig.client_id,\n          redirect_uri: authConfig.redirect_uri,\n          authenticity_token: getCsrfToken(await consentPage.text()),\n          state: authConfig.state,\n          response_type: 'code',\n          scope: 'read_prefs',\n          nonce: '',\n          code_challenge: '',\n          code_challenge_method: '',\n          commit: 'Authorize',\n        }),\n        headers: {\n          ...baseHeaders,\n          Cookie: cookieJar,\n          'Content-Type': 'application/x-www-form-urlencoded',\n        },\n      }).then((r) => r.text())\n    : await consentPage.text();\n\n  if (!authToken.includes('gf_download_oauth')) throw new Error('No token');\n\n  return {\n    url: pbfUrl,\n    cookies: authToken,\n  };\n}\n\nexport async function downloadFromPlanetPbf(ctx: Ctx, pbfUrl: string) {\n  const planetFile = ctx.tempFileNames.pbf;\n\n  const { cookies, url } = await getAuthToken(ctx, pbfUrl);\n\n  await fs.mkdir(dirname(planetFile), { recursive: true });\n  await fs.rm(planetFile, { recursive: true, force: true });\n\n  if (/^https?:\\/\\//.test(url)) {\n    // it's an actuall URL, so fetch it\n    console.info('downloading from', url, 'to', planetFile);\n    const child = spawn(\n      'curl',\n      ['-L', url, '-H', `Cookie: ${cookies}`, '--output', basename(planetFile)],\n      { cwd: dirname(planetFile) },\n    );\n\n    child.stdout.pipe(process.stdout);\n    child.stderr.pipe(process.stderr);\n\n    await new Promise<void>((resolve, reject) => {\n      child.on('close', (code) => (code ? reject(code) : resolve()));\n    });\n\n    // ensure that the file is written to the disk properly\n    await setTimeout(5000);\n  } else {\n    // else: it's a path to a file on disk, so just copy the file.\n    // this is only used for e2e tests by consumers, so no harm\n    // in copying/duplicating the file.\n    console.info('copying file from', url, 'to', planetFile);\n    await fs.copyFile(url, planetFile);\n  }\n\n  console.info(planetFile, await fs.stat(planetFile));\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport { geoCentroid } from 'd3-geo';\nimport type { FeatureCollection, Geometry } from 'geojson';\nimport type { Tags } from 'osm-api';\nimport { getHttpHeaders } from '../../constants/defaults.js';\nimport type { Ctx, OSMData } from '../../types/internal.def.js';\nimport {\n  type OsmFeature,\n  type OsmFeatureTypeShort,\n  OsmFlags,\n  type OsmId,\n} from '../../types/callbacks.def.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n  loadOsmFeature,\n  saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\ntype PostpassResponse = FeatureCollection<\n  Geometry,\n  {\n    osm_type: Uppercase<OsmFeatureTypeShort>;\n    osm_id: number;\n    tags: Tags;\n  }\n>;\n\nexport async function downloadFromPostpass(\n  ctx: Ctx,\n  queryFile: string | undefined,\n  serverUrl: string | undefined,\n) {\n  const cacheFile = ctx.tempFileNames.postpass;\n\n  if (ctx.use_cache && existsSync(cacheFile)) {\n    console.info('Using cached postpass response');\n    const cached: OSMData = JSON.parse(await fs.readFile(cacheFile, 'utf8'));\n    await saveLoadedOsmFeatures(ctx, cached);\n    return;\n  }\n\n  const DEFAULT_SERVER_URL = 'https://postpass.geofabrik.de/api/interpreter';\n  const DEFAULT_QUERY = `\n    SELECT osm_type, osm_id, tags, ST_PointOnSurface(geom) AS geom\n    FROM postpass_pointlinepolygon\n    WHERE tags ? '${ctx.config.merge.osm_key}'\n  `;\n\n  const query = queryFile\n    ? await fs.readFile(queryFile, 'utf8')\n    : DEFAULT_QUERY;\n\n  console.info('fetching OSM data via postpass…');\n  const response = await fetch(serverUrl || DEFAULT_SERVER_URL, {\n    method: 'POST',\n    body: new URLSearchParams({ data: query }),\n    headers: getHttpHeaders(ctx.config),\n  });\n  if (!response.ok) {\n    const status = (await response.text()) || response.statusText;\n    throw new Error(`HTTP Error ${response.status} ${status}`);\n  }\n\n  const responseJson = (await response.json()) as PostpassResponse;\n\n  const { pickTags } = validateOsmTagsInConfig(ctx);\n\n  const out: OSMData = {\n    withRef: {},\n    noRef: {},\n    duplicateRefs: {},\n    semi: {},\n    count: 0,\n  };\n  const seen = new Set<OsmId>();\n  for (const feature of responseJson.features) {\n    const { osm_type, osm_id, tags } = feature.properties;\n    const id: OsmId = `${osm_type.toLowerCase() as OsmFeatureTypeShort}${osm_id}`;\n\n    if (seen.has(id)) continue;\n    seen.add(id);\n\n    const object: OsmFeature = {\n      id,\n      centroid: feature.geometry?.type ? geoCentroid(feature.geometry) : [0, 0],\n\n      tags: pickTags(tags),\n      metadata: undefined, // not supported by postpass\n\n      // added later\n      sectors: [],\n      flags: OsmFlags.None,\n    };\n    loadOsmFeature(ctx, out, object);\n  }\n\n  await fs.writeFile(cacheFile, JSON.stringify(out));\n  await saveLoadedOsmFeatures(ctx, out);\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: { result: 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.result, null, 2),\n  );\n}\n","import { existsSync } from 'node:fs';\nimport pbf2json, { type Item } from 'pbf2json';\nimport type { OsmFeature as StandardOsmFeature } from 'osm-api';\nimport {\n  type Ctx,\n  type OSMData,\n  type OsmFeature,\n  type OsmFeatureTypeShort,\n  OsmFlags,\n} from '../../types/index.js';\nimport { validateOsmTagsInConfig } from './util/validateOsmTagsInConfig.js';\nimport {\n  loadOsmFeature,\n  saveLoadedOsmFeatures,\n} from './util/loadOsmFeature.js';\n\nexport type PbfMetadata = Partial<\n  Pick<StandardOsmFeature, 'changeset' | 'version' | 'user'> & {\n    timestamp: number; // unlike the API, the golang code returns a number\n  }\n>;\n\nexport async function osmToJson(ctx: Ctx, pbfFilter: string[]) {\n  if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n    console.info('Using cached osm data');\n    return;\n  }\n\n  const { pickTags } = validateOsmTagsInConfig(ctx);\n\n  console.info('Starting preprocess of OSM data...');\n  const result = await new Promise<OSMData>((resolve, reject) => {\n    const out: OSMData = {\n      withRef: {},\n      noRef: {},\n      duplicateRefs: {},\n      semi: {},\n      count: 0,\n    };\n\n    let anyMetadata = false;\n\n    let anythingSeen = false;\n    let index = 0;\n    pbf2json\n      .createReadStream({\n        file: ctx.tempFileNames.pbf,\n        tags: pbfFilter,\n        leveldb: '/tmp',\n        // @ts-expect-error -- missing from typedefs since we\n        //                     added this option in our fork.\n        metadata: true,\n      })\n      .on(\n        'data',\n        //\n        (item: Item) => {\n          anythingSeen ||= true;\n          const coords = item.type === 'node' ? item : item.centroid;\n          // @ts-expect-error -- missing from typedefs since we\n          //                     added this option in our fork.\n          const metadata: PbfMetadata | undefined = item.meta;\n          if (metadata) anyMetadata = true;\n\n          const tags = pickTags(item.tags);\n\n          const object: OsmFeature = {\n            id: `${<OsmFeatureTypeShort>item.type[0]}${item.id}`,\n            centroid: [+coords.lon, +coords.lat],\n            metadata: metadata\n              ? {\n                  changeset: metadata.changeset,\n                  timestamp: metadata.timestamp\n                    ? new Date(1e3 * metadata.timestamp).toISOString()\n                    : undefined,\n                  user: metadata.user,\n                  version: metadata.version,\n                }\n              : undefined,\n            tags,\n\n            // added later\n            sectors: [],\n            flags: OsmFlags.None,\n          };\n          loadOsmFeature(ctx, out, object);\n\n          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        if (!anythingSeen) {\n          throw new Error(\n            'Nothing extracted from the pbf file, presumably it’s invalid.',\n          );\n        }\n        resolve(out);\n      })\n      .on('error', reject);\n  });\n\n  await saveLoadedOsmFeatures(ctx, result);\n}\n","import { existsSync } from 'node:fs';\nimport type { Ctx } from '../../types/index.js';\nimport { downloadFromOverpass } from './downloadFromOverpass.js';\nimport { downloadFromPlanetPbf } from './downloadFromPlanetPbf.js';\nimport { downloadFromPostpass } from './downloadFromPostpass.js';\nimport { fetchIgnoreList } from './fetchIgnoreList.js';\nimport { osmToJson } from './osmToJson.js';\n\nexport async function download(ctx: Ctx) {\n  await fetchIgnoreList(ctx);\n\n  if (ctx.use_cache && existsSync(ctx.tempFileNames.osm_processed_other)) {\n    console.info('Using cached OSM data');\n    return;\n  }\n\n  switch (ctx.config.osm_data.source.type) {\n    case 'pbf': {\n      await downloadFromPlanetPbf(ctx, ctx.config.osm_data.source.pbf_url);\n      await osmToJson(ctx, ctx.config.osm_data.source.pbf_filter);\n      break;\n    }\n\n    case 'overpass': {\n      await downloadFromOverpass(\n        ctx,\n        ctx.config.osm_data.source.overpass_query_file,\n        ctx.config.osm_data.source.overpass_server_url,\n      );\n      break;\n    }\n\n    case 'postpass': {\n      await downloadFromPostpass(\n        ctx,\n        ctx.config.osm_data.source.postpass_query_file,\n        ctx.config.osm_data.source.postpass_server_url,\n      );\n      break;\n    }\n\n    default: {\n      throw new TypeError(ctx.config.osm_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 osmFeature = 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      !(osmFeature.flags & OsmFlags.IsCheckedRecently) // ...and it does not have a recent check_date\n    ) {\n      // skip if an alernative form of this ref is accepted\n      const alts = ctx.callbacks\n        .getAltRefs?.(datasetId)\n        .some((ref) => ref in sourceData);\n      if (alts) continue;\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 osmFeature = osmData.withRef[datasetIdToDelete]!;\n\n    const localKey = ctx.callbacks.getLocalKeyForOsm(osmFeature);\n\n    const newDatasetIds = new Set(\n      osmFeature.sectors\n        .map((sector) => datasetByLocalKey[sector]?.[localKey])\n        .filter(Boolean),\n    );\n\n    if (newDatasetIds.size > 1) {\n      console.warn(\n        `${osmFeature.id} spans multiple sectors, and there are conflicting localKeys across the sectors. Therefore, it will be straight-up deleted`,\n      );\n    }\n    if (newDatasetIds.size === 1) {\n      const newDatasetId = [...newDatasetIds][0]!;\n      if (osmData.withRef[newDatasetId]) {\n        // linz ref changed && new one is already in osm -> so basically it didn't change. Normal delete\n        // probably because address accidently existed twice in the LINZ db, with 2 different refs and LINZ fixed it by deleting one.\n        toDelete.push(datasetIdToDelete);\n      } else {\n        // linz ref changed, and the new ref doesn't exist in OSM\n        refsThatChanged[newDatasetId] = datasetIdToDelete;\n      }\n    } else {\n      // no identical feature in osm, so delete it\n      toDelete.push(datasetIdToDelete);\n    }\n  }\n  return {\n    refsThatChanged,\n    toDelete,\n  };\n}\n","import type { BBox, CoordKey } from '../types/internal.def.js';\n\n/**\n * converts a coordinate into a string key, for more performant\n * deduplication. 5dp = accurate to the nearest 1m, see\n * https://osm.wiki/Precision_of_coordinates\n */\nexport const getCoordKey = (lat: number, lon: number, accuraryDp = 5) =>\n  <CoordKey>`${lat.toFixed(accuraryDp)},${lon.toFixed(accuraryDp)}`;\n\n/** metres */\nexport const EARTH_RADIUS = 6371008.8;\n\n/** @returns the distance (in degrees) between the closest two points of the two bboxes */\nexport function distanceBetweenBboxes(a: BBox, b: BBox) {\n  const Δlat = Math.max(0, b.minLat - a.maxLat, a.minLat - b.maxLat);\n  const Δlon = Math.max(0, b.minLng - a.maxLng, a.minLng - b.maxLng);\n  return Math.hypot(Δlat, Δlon);\n}\n\n/** returns the larger of lat or lon */\nexport function getBboxMaxDimension(bbox: BBox) {\n  return Math.max(\n    Math.abs(bbox.maxLat - bbox.minLat),\n    Math.abs(bbox.maxLng - bbox.minLng),\n  );\n}\n","import { geoDistance } from 'd3-geo';\nimport type { Ctx, OsmFeature, SourceDataFeature } from '../../types/index.js';\nimport { EARTH_RADIUS } from '../../common/geo.js';\n\nconst featureToKey = new Map<OsmFeature, string>();\n\n/** If there are multiple osm addresses found, none of which have a linz ref, find the most likely ones */\nexport const findPotentialOsmFeatures = (\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((osmFeature) => {\n      // this 1st filter runs millions of times and needs to be fast\n      let key = featureToKey.get(osmFeature);\n      if (!key) {\n        key = ctx.callbacks.getLocalKeyForOsm(osmFeature);\n        featureToKey.set(osmFeature, key);\n      }\n      return key === sourceKey;\n    })\n    .map((osmFeature) => {\n      // all following maps/filters only runs tens of times, so they can be expensive\n      const offsetMetres =\n        EARTH_RADIUS *\n        geoDistance(osmFeature.centroid, sourceDataFeature.centroid);\n\n      return Object.assign(osmFeature, { 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 OsmFeature,\n  type SourceData,\n} from '../../types/index.js';\nimport { processDeletions } from './processDeletions.js';\nimport { findPotentialOsmFeatures } from './findPotentialOsmFeatures.js';\n\nexport async function match(\n  ctx: Ctx,\n  sourceData: SourceData,\n  osmData: OSMData,\n) {\n  const count = Object.keys(sourceData).length;\n\n  const ignoreList = new Set<string>(\n    JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n  );\n\n  console.info('processing deleted data...');\n  console.time('matchDeletions');\n  const { refsThatChanged, toDelete } = processDeletions(\n    ctx,\n    osmData,\n    sourceData,\n  );\n  console.timeEnd('matchDeletions');\n\n  console.info('Processing data...');\n  let index = 0;\n  console.time('matchLoop');\n  const output: MatchOutput = {\n    [MatchType.OneToOne]: [],\n    [MatchType.OneToMany]: [],\n    [MatchType.ManyToOne]: {},\n    [MatchType.ManyToMany]: [],\n    [MatchType.Delete]: toDelete,\n    [MatchType.Guess]: [],\n  };\n\n  const osmFeaturesWithNoRef = Object.values(osmData.noRef);\n\n  const individualRefsToSemiRef: Record<\n    DatasetId,\n    { value: DatasetId; isFirst: boolean }[]\n  > = {};\n  for (const ref in osmData.semi) {\n    const parts = ref.split(';');\n    for (const [j, part] of parts.entries()) {\n      individualRefsToSemiRef[<DatasetId>part] ||= [];\n      individualRefsToSemiRef[<DatasetId>part]!.push({\n        value: <DatasetId>ref,\n        isFirst: !j,\n      });\n    }\n  }\n\n  // we need this because the source data could also use semicolons\n  // in the primary key. or the source data was preprocessed by some\n  // script that added semicolons.\n  // e.g. key would be `b`, fullRef would be `a;b`. This object is\n  // keyed on `a;b`\n  const semiByFullRef: Record<DatasetId, OsmFeature> = {};\n  for (const key in osmData.semi) {\n    const osmFeature = osmData.semi[<DatasetId>key]!;\n    const fullRef = <DatasetId | undefined>(\n      osmFeature.tags[ctx.config.merge.osm_key]\n    );\n    if (fullRef) semiByFullRef[fullRef] = osmFeature;\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 osmFeature =\n      osmData.withRef[datasetId] ||\n      ctx.callbacks\n        .getAltRefs?.(datasetId)\n        .map((alt) => osmData.withRef[alt])\n        .find(Boolean);\n\n    const sourceFeature = sourceData[originalDatasetId]!;\n    const duplicate = osmData.duplicateRefs[datasetId];\n    const exactSemi = semiByFullRef[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:osm)\n    if (osmFeature && semi) {\n      // [many:many]\n      const parts = <DatasetId[]>datasetId.split(';');\n      output[MatchType.ManyToMany].push({\n        osm: [osmFeature.id, semi.id],\n        source: parts,\n      });\n    } else if (osmFeature) {\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: osmFeature.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 (exactSemi) {\n      // [1:1] both datasets have a semicolon-delimited ref\n      output[MatchType.OneToOne].push({\n        osm: exactSemi.id,\n        source: originalDatasetId,\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 = findPotentialOsmFeatures(\n        ctx,\n        sourceFeature,\n        osmFeaturesWithNoRef,\n      );\n      if (possibleAddresses.length === 1) {\n        // [1:1] but the ref needs to be added\n        output[MatchType.OneToOne].push({\n          source: datasetId,\n          osm: possibleAddresses[0]!.id,\n        });\n      } else {\n        // [1:?]\n        output[MatchType.Guess].push({\n          source: datasetId,\n          osmCandidates: possibleAddresses.map((f) => f.id),\n        });\n      }\n    }\n\n    index += 1;\n    if (!(index % 1000)) {\n      /* istanbul ignore next */\n      process.stdout.write(`${((index / count) * 100).toFixed(1)}% `);\n    }\n  }\n\n  console.timeEnd('matchLoop');\n\n  await fs.writeFile(\n    ctx.tempFileNames.matches,\n    JSON.stringify(output, null, undefined),\n  );\n\n  return { output };\n}\n","import type { Vec2 as Coord } from '../../types/index.js';\n\n/** radius of the diamond */\nconst RADIUS = 0.0002;\n// 0.0002° ~= 0° 0' 0.72\"\n\n/** generates a bbox around a point, but as a diamond not a square */\nexport function createDiamond({\n  lat,\n  lng,\n}: {\n  lat: number;\n  lng: number;\n}): [ring1: Coord[]] {\n  const diamond: Coord[] = [\n    [lng, lat + RADIUS], // top\n    [lng + RADIUS, lat], // right\n    [lng, lat - RADIUS], // bottom\n    [lng - RADIUS, lat], // left\n  ];\n  diamond.push(diamond[0]!); // make it a closed way\n\n  return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n\n/** generates a bbox around a point, but as a square not a diamond */\nexport function createSquare({\n  lat,\n  lng,\n}: {\n  lat: number;\n  lng: number;\n}): [ring1: Coord[]] {\n  const diamond: Coord[] = [\n    [lng + RADIUS / 2, lat + RADIUS / 2], // top-right\n    [lng + RADIUS / 2, lat - RADIUS / 2], // top-left\n    [lng - RADIUS / 2, lat - RADIUS / 2], // bottom-left\n    [lng - RADIUS / 2, lat + RADIUS / 2], // bottom-right\n  ];\n  diamond.push(diamond[0]!); // make it a closed way\n\n  return [diamond]; // only 1 ring since it's an area, not a multipolygon\n}\n","import type { Feature, Position } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function calcBBox(features: Feature[]): BBox {\n  const bbox: BBox = {\n    minLat: Infinity,\n    minLng: Infinity,\n    maxLat: -Infinity,\n    maxLng: -Infinity,\n  };\n\n  function visit([_lng, _lat]: Position) {\n    const lat = +_lat!;\n    const lng = +_lng!;\n    if (lat < bbox.minLat) bbox.minLat = lat;\n    if (lng < bbox.minLng) bbox.minLng = lng;\n    if (lat > bbox.maxLat) bbox.maxLat = lat;\n    if (lng > bbox.maxLng) bbox.maxLng = lng;\n  }\n\n  /* eslint-disable unicorn/no-array-for-each -- deliberate */\n\n  for (const f of features) {\n    switch (f.geometry.type) {\n      case 'Point': {\n        visit(f.geometry.coordinates);\n\n        break;\n      }\n\n      case 'MultiPoint':\n      case 'LineString': {\n        f.geometry.coordinates.forEach(visit);\n\n        break;\n      }\n      case 'MultiLineString':\n      case 'Polygon': {\n        for (const ring of f.geometry.coordinates) ring.forEach(visit);\n\n        break;\n      }\n      case 'MultiPolygon': {\n        for (const member of f.geometry.coordinates) {\n          for (const ring of member) ring.forEach(visit);\n        }\n        break;\n      }\n\n      default:\n      // other geometry types are allowed, they just won't\n      // count towards the bbox\n    }\n  }\n  return bbox;\n}\n","import type { Polygon } from 'geojson';\nimport type { BBox } from '../types/internal.def.js';\n\nexport function bboxToPolygon({\n  minLng,\n  minLat,\n  maxLng,\n  maxLat,\n}: BBox): Polygon {\n  return {\n    type: 'Polygon',\n    coordinates: [\n      [\n        [minLng, minLat], // SW\n        [maxLng, minLat], // SE\n        [maxLng, maxLat], // NE\n        [minLng, maxLat], // NW\n        [minLng, minLat], // SW\n      ],\n    ],\n  };\n}\n","import type { OsmPatchFeature as GeoJsonFeature } from 'osm-api';\nimport { geoCentroid as getFirstCoord } from 'd3-geo';\nimport type {\n  Ctx,\n  HandlerReturnWithBBox,\n  OutputLayer,\n} from '../../types/index.js';\nimport { MAX_ITEMS_PER_DATASET } from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { getSector } from '../../common/getSector.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\n\nexport function splitUntilSmallEnough(\n  ctx: Ctx,\n  name: string,\n  metadata: Omit<OutputLayer, 'features' | 'bbox' | 'sectorIds'>,\n  features: GeoJsonFeature[],\n  depth = 0,\n): HandlerReturnWithBBox {\n  if (!features.length) return {};\n\n  const bbox = calcBBox(features);\n  const sectorIds = getSector(\n    bboxToPolygon(bbox),\n    ctx.config.merge.sector_resolution,\n  );\n\n  const recursionLimit = depth > 10;\n  if (recursionLimit) {\n    console.info(`(!) Couldn't split ${name} (${features.length} left)`);\n  }\n\n  if (features.length <= MAX_ITEMS_PER_DATASET || recursionLimit) {\n    // yay, it's small enough\n    return { [name]: { features, bbox, sectorIds, ...metadata } };\n  }\n\n  // it's not small enough\n  const tallerThanWide = bbox.maxLat - bbox.minLat > bbox.maxLng - bbox.minLng;\n  if (tallerThanWide) {\n    const midway = bbox.minLat + (bbox.maxLat - bbox.minLat) / 2;\n    const out: [north: GeoJsonFeature[], south: GeoJsonFeature[]] = [[], []];\n    for (const f of features) {\n      out[+(getFirstCoord(f.geometry)[1] < midway)]!.push(f);\n    }\n\n    const [north, south] = out;\n\n    return {\n      ...splitUntilSmallEnough(\n        //\n        ctx,\n        `${name}^N`,\n        metadata,\n        north,\n        depth + 1,\n      ),\n      ...splitUntilSmallEnough(\n        //\n        ctx,\n        `${name}^S`,\n        metadata,\n        south,\n        depth + 1,\n      ),\n    };\n  }\n\n  // else: it's wider than it is tall\n  const midway = bbox.minLng + (bbox.maxLng - bbox.minLng) / 2;\n  const out: [east: GeoJsonFeature[], west: GeoJsonFeature[]] = [[], []];\n  for (const f of features) {\n    out[+(getFirstCoord(f.geometry)[0] < midway)]!.push(f);\n  }\n\n  const [east, west] = out;\n\n  return {\n    ...splitUntilSmallEnough(\n      //\n      ctx,\n      `${name}^E`,\n      metadata,\n      east,\n      depth + 1,\n    ),\n    ...splitUntilSmallEnough(\n      //\n      ctx,\n      `${name}^W`,\n      metadata,\n      west,\n      depth + 1,\n    ),\n  };\n}\n\n/** normalizes the weird ^N^S^E names generated by `splitUntilSmallEnough` */\nexport function normaliseNames(\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 type { OsmFeatureType } from 'osm-api';\nimport type { LineString } from 'geojson';\nimport { geoDistance } from 'd3-geo';\nimport type {\n  Action,\n  OsmFeatureTypeShort,\n  OsmId,\n  Vec2,\n} from '../../../types/callbacks.def.js';\nimport {\n  type Ctx,\n  type MatchOutput,\n  MatchType,\n  type OutputLayers,\n} from '../../../types/internal.def.js';\nimport { EARTH_RADIUS } from '../../../common/geo.js';\n\nconst OSM_TYPES: Record<OsmFeatureTypeShort, OsmFeatureType> = {\n  n: 'node',\n  w: 'way',\n  r: 'relation',\n};\n\nconst osmIdToUrl = (osmId: string) =>\n  `https://osm.org/${OSM_TYPES[osmId[0] as OsmFeatureTypeShort]}/${osmId.slice(1)}`;\n\nconst osmIdToLink = (osmId: string) => `[${osmId}](${osmIdToUrl(osmId)})`;\n\nfunction generateMatchReport(ctx: Ctx, data: MatchOutput, type: MatchType) {\n  function sourceIdToLink(id: string) {\n    if (!ctx.formatterUrl) return id;\n    const url = ctx.formatterUrl.replaceAll('$1', id);\n    return `[${id}](${url})`;\n  }\n\n  let report = `# Matches – ${MatchType[type]}\\n\\n`;\n  const list = data[type];\n  if (Array.isArray(list)) {\n    for (const row of list) {\n      if (typeof row === 'string') {\n        report += `- ${row}\\n`; // delete is special\n        continue;\n      }\n\n      const source =\n        'source' in row\n          ? Array.isArray(row.source)\n            ? row.source\n            : [row.source]\n          : [];\n      const osm =\n        'osm' in row\n          ? Array.isArray(row.osm)\n            ? row.osm\n            : [row.osm]\n          : 'osmCandidates' in row\n            ? row.osmCandidates\n            : [];\n\n      const sep = 'osmCandidates' in row ? ' or ' : ' + ';\n\n      report += `- ${source.map(sourceIdToLink).join(' + ') || '?'} ⇄ ${osm.map(osmIdToLink).join(sep) || '?'}\\n`;\n    }\n  } else {\n    // must be many:1\n    for (const osmId in list) {\n      const source = list[osmId as OsmId] || [];\n      report += `- ${source.map(sourceIdToLink).join(' + ') || '?'} ⇄ ${osmIdToLink(osmId)}\\n`;\n    }\n  }\n\n  return report;\n}\n\nfunction generateConflationReport(data: OutputLayers, action: Action) {\n  let report = `# Conflation – ${action}\\n\\n`;\n  for (const i in data) {\n    const h2 = `\\n## ${i}\\n\\n`;\n    report += h2;\n    for (const j in data[i]) {\n      const h3 = `\\n### ${j}\\n\\n`;\n      report += h3;\n      for (const feature of data[i][j]!.features) {\n        const thisAction = feature.properties.__action || 'create';\n        if (thisAction !== action) continue;\n\n        const id =\n          action === 'create' ? feature.id : osmIdToLink(feature.id as OsmId);\n\n        // TODO: include diff before/after, can this be in stored in the osmPatch file too?\n        const tagDiff =\n          Object.entries(feature.properties)\n            .filter(([k]) => !k.startsWith('__'))\n            .map((kv) => kv.join('='))\n            .join(' + ') || '∅';\n\n        switch (action) {\n          case 'create':\n          case 'edit':\n          case 'delete': {\n            report += `- ${id}\\t${tagDiff}\\n`;\n            break;\n          }\n\n          case 'move': {\n            const [[oldLon, oldLat], [newLon, newLat]] = (\n              feature.geometry as LineString\n            ).coordinates as [Vec2, Vec2];\n\n            const distance =\n              EARTH_RADIUS * geoDistance([oldLon, oldLat], [newLon, newLat]);\n\n            const link = `${osmIdToUrl(feature.id as OsmId)}?mlat=${newLat}&mlon=${newLon}#map=${13}/${newLat}/${newLon}`;\n\n            report += `- [${feature.id}](${link})\\tneeds to move ${distance | 0}m to ${newLat},${newLon}\\n`;\n            break;\n          }\n\n          default: {\n            throw new TypeError(action satisfies never);\n          }\n        }\n      }\n\n      // delete the heading if the section is empty\n      if (report.endsWith(h3)) report = report.slice(0, -h3.length);\n    }\n\n    // delete the heading if the section is empty\n    if (report.endsWith(h2)) report = report.slice(0, -h2.length);\n  }\n  return report;\n}\n\nexport function generateReports(\n  ctx: Ctx,\n  matches: MatchOutput,\n  conflated: OutputLayers,\n) {\n  const reports: Record<string, string> = {\n    // match\n    'report-match-OneToOne.md': generateMatchReport(\n      ctx,\n      matches,\n      MatchType.OneToOne,\n    ),\n    'report-match-OneToMany.md': generateMatchReport(\n      ctx,\n      matches,\n      MatchType.OneToMany,\n    ),\n    'report-match-ManyToMany.md': generateMatchReport(\n      ctx,\n      matches,\n      MatchType.ManyToMany,\n    ),\n    'report-match-ManyToOne.md': generateMatchReport(\n      ctx,\n      matches,\n      MatchType.ManyToOne,\n    ),\n    'report-match-Delete.md': generateMatchReport(\n      ctx,\n      matches,\n      MatchType.Delete,\n    ),\n    'report-match-Guess.md': generateMatchReport(ctx, matches, MatchType.Guess),\n\n    // conflation\n    'report-conflation-create.md': generateConflationReport(\n      conflated,\n      'create',\n    ),\n    'report-conflation-edit.md': generateConflationReport(conflated, 'edit'),\n    'report-conflation-move.md': generateConflationReport(conflated, 'move'),\n    'report-conflation-delete.md': generateConflationReport(\n      conflated,\n      'delete',\n    ),\n  };\n\n  for (const report in reports) {\n    if (reports[report]!.length > 5_000_000) {\n      reports[report] = '_file too large_';\n    }\n  }\n\n  return reports;\n}\n","import {\n  API_BASE_URL,\n  IS_UNIT_TEST,\n  getHttpHeaders,\n} from '../../../constants/defaults.js';\nimport type {\n  ConflateResult,\n  RecursiveHistoryFile,\n  RecursiveHistoryRow,\n} from '../../../types/callbacks.def.js';\nimport type { Ctx } from '../../../types/internal.def.js';\n\nexport const RECURSIVE_HISTORY_FILE_NAME = 'metrics-history.json';\n\nconst EMPTY_HISTORY = () => ({ lastUpdated: '', rows: [] });\n\nexport async function amendRecursiveHistory(ctx: Ctx, metrics: ConflateResult) {\n  const refTag = ctx.config.merge.osm_key;\n\n  const historyFile: RecursiveHistoryFile = IS_UNIT_TEST\n    ? EMPTY_HISTORY()\n    : await fetch(\n        `${API_BASE_URL}/api/static/${refTag}/${RECURSIVE_HISTORY_FILE_NAME}`,\n        { headers: getHttpHeaders(ctx.config) },\n      ).then(async (response) => {\n        if (!response.ok) {\n          const status = (await response.text()) || response.statusText;\n          const error = new Error(`HTTP Error ${response.status} ${status}`);\n          if (response.status === 404) {\n            const wrapped = new ReferenceError(\n              'Recursive History does not exist, presumably this is the first run. History will be reset',\n              { cause: error },\n            );\n            console.error(wrapped);\n            return EMPTY_HISTORY();\n          }\n          throw error;\n        }\n        return (await response.json()) as RecursiveHistoryFile;\n      });\n\n  // mock the date in the test environment, otherwise the snapshot would update each time\n  const date = IS_UNIT_TEST\n    ? 'MOCK'\n    : new Date().toISOString().split('T', 1)[0]!;\n\n  const stats: RecursiveHistoryRow = {\n    date,\n    conflated: metrics.countsByPhase.conflated,\n  };\n\n  // don't create a duplicate row. the date is in the row so this is safe\n  const latestRow = historyFile.rows.at(-1);\n  if (JSON.stringify(latestRow) === JSON.stringify(stats)) {\n    console.info('No change in statistics, not updating table');\n  } else {\n    historyFile.rows.push(stats);\n  }\n\n  historyFile.lastUpdated = date;\n\n  return historyFile;\n}\n","import { promises as fs } from 'node:fs';\nimport { join } from 'node:path';\nimport type { OsmPatch } from 'osm-api';\nimport type { Feature, Polygon } from 'geojson';\nimport type {\n  ConflateResult,\n  Ctx,\n  IndexFile,\n  IndexFileProperties,\n  MatchOutput,\n  OutputLayers,\n} from '../../types/index.js';\nimport { IS_UNIT_TEST } from '../../constants/defaults.js';\nimport { sha256 as hash } from '../../helpers.js';\nimport { calcCount } from '../../common/calcCount.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\nimport { generateReports } from './stats/generateReports.js';\nimport {\n  RECURSIVE_HISTORY_FILE_NAME,\n  amendRecursiveHistory,\n} from './stats/amendRecursiveHistory.js';\n\nfunction toId(suburb: string) {\n  // macrons are url safe\n  return `${suburb.replaceAll(/[^\\dA-Za-zĀāĒēĪīŌōŪū]/g, '').slice(0, 100)}_${hash(suburb).slice(0, 6)}`;\n}\n\nexport async function createIndexAndSaveToDisk(\n  ctx: Ctx,\n  metrics: ConflateResult,\n  matches: MatchOutput,\n  output: OutputLayers,\n): Promise<void> {\n  const outputFolder =\n    ctx.config.output?.folder || join(process.cwd(), 'output');\n  const subFolderName = 'datasets';\n\n  const reports = generateReports(ctx, matches, output);\n\n  const meta = Object.entries(output).flatMap(([category, groups]) =>\n    Object.entries(groups).map(([group, items]) => {\n      const title = [category, group].filter(Boolean).join(' - ');\n      return {\n        id: toId(title),\n        category,\n        group,\n        title,\n        bbox: items.bbox,\n        instructions: items.instructions,\n        ...calcCount(items.features),\n      };\n    }),\n  );\n\n  // create index.geo.json\n  const newIndexFile: IndexFile = {\n    type: 'FeatureCollection',\n    features: meta\n      .map(({ bbox, ...other }): Feature<Polygon, IndexFileProperties> => {\n        return {\n          type: 'Feature',\n          geometry: bboxToPolygon(bbox),\n          properties: other,\n        };\n      })\n      .toSorted((a, b) => a.properties.title.localeCompare(b.properties.title)),\n\n    __reports: [\n      ...Object.keys(reports),\n      ...(ctx.config.output?.custom_report_file_names || []),\n    ],\n  };\n\n  const recursiveHistory = await amendRecursiveHistory(ctx, metrics);\n\n  await fs.mkdir(join(outputFolder, subFolderName), { recursive: true });\n  await fs.writeFile(\n    join(outputFolder, 'index.geo.json'),\n    JSON.stringify(newIndexFile, null, IS_UNIT_TEST ? 2 : undefined),\n  );\n  await fs.writeFile(\n    join(outputFolder, 'metrics.json'),\n    JSON.stringify(metrics, null, 2),\n  );\n  await fs.writeFile(\n    join(outputFolder, RECURSIVE_HISTORY_FILE_NAME),\n    JSON.stringify(recursiveHistory, null, 2),\n  );\n\n  for (const reportName in reports) {\n    await fs.writeFile(join(outputFolder, reportName), reports[reportName]!);\n  }\n\n  // save each suburb\n  for (const v of meta) {\n    const geojson: OsmPatch = {\n      type: 'FeatureCollection',\n      ...output[v.category]![v.group]!,\n    };\n\n    await fs.writeFile(\n      join(outputFolder, subFolderName, `${v.id}.osmPatch.geo.json`),\n      JSON.stringify(geojson, null, IS_UNIT_TEST ? 2 : undefined),\n    );\n  }\n}\n","import type { Tags } from 'osm-api';\nimport type { BBox, HandlerReturnWithBBox } from '../../types/index.js';\nimport {\n  MAX_BBOX_DEGREES,\n  MAX_ITEMS_PER_DATASET,\n  MIN_ITEMS_PER_DATASET,\n} from '../../constants/defaults.js';\nimport { calcBBox } from '../../common/calcBBox.js';\nimport { bboxToPolygon } from '../../common/bboxToPolygon.js';\nimport {\n  distanceBetweenBboxes,\n  getBboxMaxDimension,\n} from '../../common/geo.js';\n\nexport function mergeTags(a: Tags, b: Tags): Tags {\n  const merged = { ...a };\n  for (const key in b) {\n    if (!merged[key]) {\n      merged[key] = b[key]!;\n    } else if (merged[key] !== b[key]) {\n      merged[key] += `;${b[key]}`;\n    }\n  }\n  return merged;\n}\n\nfunction mergeBboxes(a: BBox, b: BBox): BBox {\n  return calcBBox([\n    { type: 'Feature', geometry: bboxToPolygon(a), properties: {} },\n    { type: 'Feature', geometry: bboxToPolygon(b), properties: {} },\n  ]);\n}\n\n/**\n * having many tiny datasets is unhelpful, it's more convenient for us\n * to merge them into\n */\nexport function mergeTinyDatasets(\n  sectors: HandlerReturnWithBBox,\n): HandlerReturnWithBBox {\n  // keep looping until there is nothing more that can be merged.\n  let anyModificationsInPrevPass = true;\n  while (anyModificationsInPrevPass) {\n    anyModificationsInPrevPass = false;\n\n    for (const sectorName in sectors) {\n      const sector = sectors[sectorName]!;\n\n      // skip if it already has enough members\n      if (sector.features.length > MIN_ITEMS_PER_DATASET) continue;\n\n      // try to find another group in the same sector which\n      // also has too few items.\n      const bestOther = Object.entries(sectors)\n        .filter(([otherName, other]) => {\n          return (\n            // at least 1 sector ID must overlap\n            other.sectorIds.some((id) => sector.sectorIds.includes(id)) &&\n            // skip self\n            otherName !== sectorName &&\n            // if we merge them, the total must not be too high\n            sector.features.length + other.features.length <\n              MAX_ITEMS_PER_DATASET\n          );\n        })\n        .toSorted(\n          ([, a], [, b]) =>\n            // prefer the closest\n            distanceBetweenBboxes(sector.bbox, a.bbox) -\n            distanceBetweenBboxes(sector.bbox, b.bbox),\n        )\n        .find(([, other]) => {\n          // If the new bbox is now ridiculously big, and the previous\n          // bboxes were not this big, then abort the merge.\n          const isMergedSmallEnough =\n            getBboxMaxDimension(mergeBboxes(sector.bbox, other.bbox)) <=\n            MAX_BBOX_DEGREES;\n\n          const isAlreadyTooBig =\n            getBboxMaxDimension(sector.bbox) > MAX_BBOX_DEGREES &&\n            getBboxMaxDimension(other.bbox) > MAX_BBOX_DEGREES;\n\n          return isMergedSmallEnough || isAlreadyTooBig;\n        });\n\n      if (!bestOther) continue;\n\n      const [otherName, other] = bestOther;\n\n      // combine them\n      sector.bbox = mergeBboxes(sector.bbox, other.bbox);\n      sector.sectorIds = [\n        ...new Set([...sector.sectorIds, ...other.sectorIds]),\n      ];\n      sector.changesetTags = mergeTags(\n        sector.changesetTags || {},\n        other.changesetTags || {},\n      );\n      if (other.instructions) {\n        sector.instructions ||= '';\n        if (!sector.instructions.includes(other.instructions)) {\n          sector.instructions += `\\n\\n${other.instructions}`.trim();\n        }\n      }\n      sector.features.push(...other.features);\n\n      // deleting it now means it won't be visited again by this loop\n      delete sectors[sectorName];\n      delete sectors[otherName];\n\n      const { name: mergedName } = mergeTags(\n        { name: sectorName },\n        { name: otherName },\n      );\n      sectors[mergedName!] = sector;\n      anyModificationsInPrevPass = true;\n    }\n  }\n  return sectors;\n}\n","import { promises as fs } from 'node:fs';\nimport {\n  type ConflateResult,\n  type OsmFeature,\n  OsmFlags,\n} from '../../../types/callbacks.def.js';\nimport {\n  type Ctx,\n  type MatchOutput,\n  MatchType,\n  type OSMData,\n  type OutputLayers,\n  type SourceData,\n} from '../../../types/internal.def.js';\n\nexport async function generateDataForWebsite(\n  ctx: Ctx,\n  sourceData: SourceData,\n  osmData: OSMData,\n  matches: MatchOutput,\n  handlerReturn: OutputLayers,\n): Promise<ConflateResult> {\n  const ignoreList = new Set<string>(\n    JSON.parse(await fs.readFile(ctx.tempFileNames.ignore_list, 'utf8')),\n  );\n\n  // init phase\n  let lastEditedByImporter = 0;\n  let recentlyChanged = 0;\n  let recentlyChecked = 0;\n\n  const semiFeatures = new Set(Object.values(osmData.semi));\n\n  function collectFlags(feature: OsmFeature) {\n    if (feature.flags & OsmFlags.IsLastEditedByImporter) lastEditedByImporter++;\n    if (feature.flags & OsmFlags.IsRecentlyChanged) recentlyChanged++;\n    if (feature.flags & OsmFlags.IsCheckedRecently) recentlyChecked++;\n  }\n  for (const feature of Object.values(osmData.withRef)) collectFlags(feature);\n  for (const feature of Object.values(osmData.noRef)) collectFlags(feature);\n  for (const feature of semiFeatures) collectFlags(feature);\n  for (const features of Object.values(osmData.duplicateRefs)) {\n    for (const feature of features) collectFlags(feature);\n  }\n\n  const result: ConflateResult = {\n    config: ctx.config,\n    warnings: ctx.warnings,\n    countsByPhase: {\n      init: {\n        sourceDataset: Object.keys(sourceData).length,\n        ignored: ignoreList.size,\n        osm: {\n          withRef: Object.keys(osmData.withRef).length,\n          noRef: Object.keys(osmData.noRef).length,\n          semi: semiFeatures.size,\n          duplicateRefs: Object.values(osmData.duplicateRefs).flat().length,\n          lastEditedByImporter,\n          recentlyChanged,\n          recentlyChecked,\n        },\n      },\n      matched: {\n        [MatchType.OneToOne]: matches[MatchType.OneToOne].length,\n        [MatchType.OneToMany]: matches[MatchType.OneToMany].length,\n        [MatchType.ManyToOne]: Object.keys(matches[MatchType.ManyToOne]).length,\n        [MatchType.ManyToMany]: matches[MatchType.ManyToMany].length,\n        [MatchType.Delete]: matches[MatchType.Delete].length,\n        [MatchType.Guess]: matches[MatchType.Guess].length,\n      },\n      conflated: { create: 0, edit: 0, delete: 0, perfect: 0 },\n    },\n  };\n\n  // conflated phase\n  for (const sectors of Object.values(handlerReturn).flatMap((v) =>\n    Object.values(v),\n  )) {\n    for (const f of sectors.features) {\n      if (f.properties.__action === 'edit') {\n        result.countsByPhase.conflated.edit++;\n      } else if (f.properties.__action === 'delete') {\n        result.countsByPhase.conflated.delete++;\n      } else {\n        result.countsByPhase.conflated.create++;\n      }\n    }\n  }\n  result.countsByPhase.conflated.perfect =\n    osmData.count -\n    result.countsByPhase.conflated.edit -\n    result.countsByPhase.conflated.delete;\n\n  return result;\n}\n","import { getIDToken } from '@actions/core';\nimport { API_BASE_URL, IS_UNIT_TEST } from '../../../constants/defaults.js';\nimport type { ConflateResult } from '../../../types/callbacks.def.js';\n\nexport async function writeToRunHistory(metrics: ConflateResult) {\n  if (IS_UNIT_TEST) return;\n  if (!process.env.GITHUB_ACTIONS) {\n    console.info(\n      'Skipping run history update, because not running in GitHub Actions',\n    );\n    return;\n  }\n  const token = await getIDToken('osm-conflation-engine');\n\n  const refTag = metrics.config.merge.osm_key;\n  const result = await fetch(`${API_BASE_URL}/api/run_history/${refTag}`, {\n    method: 'PUT',\n    headers: { Authorization: `Bearer ${token}` },\n    body: JSON.stringify(metrics),\n  });\n  if (result.status !== 200) {\n    throw new Error(await result.text());\n  }\n}\n","import { promises as fs } from 'node:fs';\nimport type { OsmPatchFeature } from 'osm-api';\nimport {\n  type ConflateResult,\n  type ConflationDiff,\n  type ConflationResultExtra,\n  type Ctx,\n  type DatasetId,\n  type MatchOutput,\n  MatchType,\n  type OSMData,\n  type OsmFeature,\n  OsmFlags,\n  type OsmId,\n  type OutputLayers,\n  type SourceData,\n  type SourceDataFeature,\n  type TagDiff,\n} from '../../types/index.js';\nimport { createDiamond, createSquare } from './createDiamond.js';\nimport {\n  normaliseNames,\n  splitUntilSmallEnough,\n} from './splitUntilSmallEnough.js';\nimport { shiftOverlappingPoints } from './spreadToGrid.js';\nimport { createIndexAndSaveToDisk } from './createIndexAndSaveToDisk.js';\nimport { mergeTinyDatasets } from './mergeTinyDatasets.js';\nimport { generateDataForWebsite } from './stats/generateDataForWebsite.js';\nimport { writeToRunHistory } from './stats/writeToRunHistory.js';\n\nconst MSG = 'invalid value returned by your callback function';\n\nfunction isTagDiffEmpty(diff: TagDiff) {\n  for (const key in diff) {\n    if (key === '__action' && diff[key] === 'edit') continue;\n    return false;\n  }\n  return true;\n}\n\nfunction hasDiff(diff: ConflationDiff | undefined): diff is ConflationDiff {\n  if (!diff) return false;\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  osm?: OsmFeature,\n  source?: SourceDataFeature,\n): OsmPatchFeature {\n  if (!osm && !source) throw new Error('requires either osm or source');\n\n  if (osm) diff.tags.__action ||= 'edit';\n\n  const [lng, lat] = (osm?.centroid || source?.centroid)!;\n  return {\n    type: 'Feature',\n    id: osm?.id || source?.id,\n    geometry:\n      diff.tags.__action === 'delete'\n        ? { type: 'Polygon', coordinates: createSquare({ lat, lng }) }\n        : diff.geometry ||\n          (diff.tags.__action === 'edit'\n            ? { type: 'Polygon', coordinates: createDiamond({ lat, lng }) }\n            : { type: 'Point', coordinates: [lng, lat] }),\n    properties: diff.tags,\n  };\n}\n\nexport async function conflate(\n  ctx: Ctx,\n  sourceData: SourceData,\n  osmData: OSMData,\n): Promise<ConflateResult> {\n  const osmDataById: Record<OsmId, OsmFeature> = { ...osmData.noRef };\n  for (const cat of <const>['withRef', 'semi', 'duplicateRefs']) {\n    for (const k in osmData[cat]) {\n      const feature = osmData[cat][k as DatasetId]!;\n      if (Array.isArray(feature)) {\n        for (const item of feature) {\n          osmDataById[item.id] = item;\n        }\n      } else {\n        osmDataById[feature.id] = feature;\n      }\n    }\n  }\n\n  const output: {\n    [category: string]: { [sector: string]: OsmPatchFeature[] };\n  } = {};\n\n  function handleExtra(\n    extra: ConflationResultExtra | undefined,\n    category: string,\n    sector: string,\n  ) {\n    if (!extra) return;\n    if (extra.warnings?.length) ctx.warnings.push(...extra.warnings);\n    const extraFeatures = extra.extraFeatures || extra.createFeatures;\n    if (extraFeatures) {\n      output[category] ||= {};\n      output[category][sector] ||= [];\n      output[category][sector].push(...extraFeatures);\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, osm } of matches[MatchType.OneToOne]) {\n    const osmFeature = osmDataById[osm]!;\n    const sourceFeature = sourceData[source]!;\n\n    if (osmFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n    const result = await ctx.callbacks.mergeOneToOne({\n      osm: osmFeature,\n      source: sourceFeature,\n    });\n    if (!result) continue;\n    if (typeof result !== 'object') throw new TypeError(MSG);\n\n    const category = result.category || '';\n    const sector = result.group || osmFeature.sectors[0]!;\n    handleExtra(result.extra, category, sector);\n\n    if (!hasDiff(result.diff)) continue;\n    output[category] ||= {};\n    output[category][sector] ||= [];\n    output[category][sector].push(\n      createFeature(result.diff, osmFeature, sourceFeature),\n    );\n  }\n\n  // 2.\n  for (const { source, osm } of matches[MatchType.OneToMany]) {\n    const osmFeatures = osm\n      .map((id) => osmDataById[id]!)\n      .filter((osmFeature) => !(osmFeature.flags & OsmFlags.IsCheckedRecently));\n\n    const sourceFeature = sourceData[source]!;\n\n    const result = await ctx.callbacks.mergeOneToMany?.({\n      osm: osmFeatures,\n      source: sourceFeature,\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    const category = result.category || '';\n    handleExtra(\n      result.extra,\n      category,\n      result.group || osmFeatures[0]?.sectors[0] || sourceFeature.sectors[0]!,\n    );\n\n    for (const _osmId in result.diffPerFeature) {\n      const osmId = <OsmId>_osmId;\n      const osmFeature = osmFeatures.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 group = result.group || osmFeature.sectors[0]!;\n      output[category] ||= {};\n      output[category][group] ||= [];\n      output[category][group].push(\n        createFeature(diff, osmFeature, sourceFeature),\n      );\n    }\n  }\n\n  // 3.\n  for (const _osmId in matches[MatchType.ManyToOne]) {\n    const osmId = <OsmId>_osmId;\n    const osmFeature = osmDataById[osmId]!;\n    const sourceFeatures = matches[MatchType.ManyToOne][osmId]!.map(\n      (id) => sourceData[id]!,\n    );\n\n    if (osmFeature.flags & OsmFlags.IsCheckedRecently) continue;\n\n    const result = await ctx.callbacks.mergeManyToOne?.({\n      osm: osmFeature,\n      source: sourceFeatures,\n    });\n    if (!result) continue;\n    if (typeof result !== 'object') throw new TypeError(MSG);\n\n    const category = result.category || '';\n    const group = result.group || osmFeature.sectors[0]!;\n    handleExtra(result.extra, category, group);\n\n    if (!hasDiff(result.diff)) continue;\n    output[category] ||= {};\n    output[category][group] ||= [];\n    output[category][group].push(createFeature(result.diff, osmFeature));\n  }\n\n  // 4.\n  for (const { source, osm } of matches[MatchType.ManyToMany]) {\n    const osmFeatures = osm\n      .map((id) => osmDataById[id]!)\n      .filter((osmFeature) => !(osmFeature.flags & OsmFlags.IsCheckedRecently));\n\n    const sourceFeature = source.map((id) => sourceData[id]!);\n\n    const result = await ctx.callbacks.mergeManyToMany?.({\n      osm: osmFeatures,\n      source: sourceFeature,\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    const category = result.category || '';\n    handleExtra(\n      result.extra,\n      category,\n      result.group ||\n        osmFeatures[0]?.sectors[0] ||\n        sourceFeature[0]!.sectors[0]!,\n    );\n\n    for (const _osmId in result.diffPerFeature) {\n      const osmId = <OsmId>_osmId;\n      const osmFeature = osmFeatures.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 group = result.group || osmFeature.sectors[0]!;\n      output[category] ||= {};\n      output[category][group] ||= [];\n      output[category][group].push(createFeature(diff, osmFeature));\n    }\n  }\n\n  // 5. deletions\n  for (const idToDelete of matches[MatchType.Delete]) {\n    const osmFeature = osmData.withRef[idToDelete]!;\n\n    if (osmFeature.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: osmFeature });\n    if (!result) continue;\n    if (typeof result !== 'object') throw new TypeError(MSG);\n\n    const category = result.category || '';\n    const group = result.group || osmFeature.sectors[0]!;\n    handleExtra(result.extra, category, group);\n\n    if (!hasDiff(result.diff)) continue;\n    output[category] ||= {};\n    output[category][group] ||= [];\n    output[category][group].push(createFeature(result.diff, osmFeature));\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 sourceFeature = sourceData[pair.source]!;\n    const result = await ctx.callbacks.create({\n      source: sourceFeature,\n      osmCandidates: pair.osmCandidates.map((osmId) => osmData.noRef[osmId]!),\n    });\n    if (!result) continue;\n    if (typeof result !== 'object') throw new TypeError(MSG);\n\n    const osmFeature = result.selection\n      ? osmData.noRef[result.selection]!\n      : undefined;\n\n    const category = result.category || '';\n    const group = result.group || osmFeature?.sectors[0] || 'unknown';\n    handleExtra(result.extra, category, group);\n\n    if (!hasDiff(result.diff)) continue;\n    output[category] ||= {};\n    output[category][group] ||= [];\n    output[category][group].push(\n      createFeature(result.diff, osmFeature, sourceFeature),\n    );\n  }\n\n  const result = await ctx.callbacks.addCustomLayers?.();\n  for (const category in result) {\n    for (const group in result[category]) {\n      handleExtra(result[category][group], category, group);\n    }\n  }\n\n  const handlerReturn: OutputLayers = {};\n  for (const category in output) {\n    handlerReturn[category] ||= {};\n\n    for (const group in output[category]) {\n      const { changesetTags = {}, instructions } =\n        (await ctx.callbacks.getChangesetTags?.({ category, group })) || {};\n\n      // add some default changeset tags if not defined by the consumer\n      /* eslint-disable dot-notation */\n      changesetTags['created_by'] ||= 'osm-conflation-engine';\n      changesetTags['import'] ||= 'yes';\n      changesetTags['source'] ||= ctx.config.metadata.wiki_page;\n      /* eslint-enable dot-notation */\n\n      const features = output[category][group]!;\n      if (ctx.callbacks.postprocessLayer) {\n        await ctx.callbacks.postprocessLayer({\n          category,\n          group,\n          osmData: osmDataById,\n          sourceData,\n          features,\n        });\n      }\n\n      for (const feature of features) {\n        const osmFeature = osmDataById[<OsmId>feature.id];\n        if (!feature.properties.__action || !osmFeature) continue;\n\n        feature.properties.__oldTags = {};\n        for (const key in feature.properties) {\n          if (key.startsWith('__')) continue;\n          feature.properties.__oldTags[key] = osmFeature.tags[key] || '';\n        }\n        if (feature.properties.__action === 'delete') {\n          feature.properties.__oldTags = osmFeature.tags;\n        }\n      }\n\n      let groups = splitUntilSmallEnough(\n        ctx,\n        group,\n        { changesetTags, instructions },\n        features,\n      );\n      groups = normaliseNames(groups);\n      Object.assign(handlerReturn[category], groups);\n    }\n\n    shiftOverlappingPoints(handlerReturn[category]);\n    mergeTinyDatasets(handlerReturn[category]);\n  }\n\n  const metrics = await generateDataForWebsite(\n    ctx,\n    sourceData,\n    osmData,\n    matches,\n    handlerReturn,\n  );\n  await createIndexAndSaveToDisk(ctx, metrics, matches, handlerReturn);\n  await writeToRunHistory(metrics);\n\n  return metrics;\n}\n","import { createReadStream, promises as fs } from 'node:fs';\nimport { createInterface } from 'node:readline/promises';\nimport { geoCentroid } from 'd3-geo';\nimport type { Feature, GeoJsonProperties, Geometry } from 'geojson';\nimport type { Ctx, OSMData, SourceData, Vec2 } from '../../types/index.js';\nimport { getSector } from '../../common/getSector.js';\nimport { readJsonL } from '../../common/jsonl.js';\n\nexport async function readData<\n  G extends Geometry = Geometry,\n  P extends GeoJsonProperties = GeoJsonProperties,\n>(ctx: Ctx) {\n  console.info('Reading source data into memory...');\n  let count = 0;\n  const sourceData: SourceData<G, P> = {};\n  const readline = createInterface({\n    input: createReadStream(ctx.config.source_data.file),\n    crlfDelay: Infinity,\n  });\n  for await (const line of readline) {\n    const feature: Feature<G, P> = JSON.parse(line);\n    const id = feature.properties![ctx.config.merge.dataset_column];\n    sourceData[id] = Object.assign(feature, {\n      sectors: getSector(feature.geometry, ctx.config.merge.sector_resolution),\n      centroid:\n        feature.geometry.type === 'Point'\n          ? <Vec2>feature.geometry.coordinates\n          : geoCentroid(feature.geometry),\n    });\n    count++;\n  }\n  console.info(`\\tread ${count} rows.`);\n\n  console.info('Reading osm data into memory (withRef)...');\n  const withRef: OSMData['withRef'] = await readJsonL(\n    ctx.tempFileNames.osm_processed_with_ref,\n    (row) => row.tags[ctx.config.merge.osm_key]!,\n  );\n  console.info('Reading osm data into memory (noRef)...');\n  const noRef: OSMData['noRef'] = await readJsonL(\n    ctx.tempFileNames.osm_processed_no_ref,\n    (row) => row.id,\n  );\n  console.info('Reading osm data into memory (other)...');\n  const other: Omit<OSMData, 'noRef' | 'withRef'> = JSON.parse(\n    await fs.readFile(ctx.tempFileNames.osm_processed_other, 'utf8'),\n  );\n  const osmData: OSMData = { withRef, noRef, ...other };\n  console.info('\\tread data.');\n\n  return { sourceData, osmData };\n}\n","import tag2link from 'tag2link' with { type: 'json' };\n\nconst RANKS = ['deprecated', 'normal', 'preferred'];\n\nexport function getFormatterUrl(key: string) {\n  // ESM typedefs are wrong\n  return (tag2link as never as typeof tag2link.default)\n    .filter((item) => item.key === `Key:${key}`)\n    .toSorted((a, b) => RANKS.indexOf(b.rank) - RANKS.indexOf(a.rank))[0]?.url;\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';\nimport { getFormatterUrl } from './api/tag2link.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    formatterUrl: getFormatterUrl(config.merge.osm_key),\n    warnings: [],\n  };\n\n  console.info('temp folder:', tempFolder);\n\n  // if unspecified, run all steps\n  const { steps = STEPS } = runOptions;\n  validateSteps(steps);\n\n  if (steps.includes('download')) {\n    console.time('download');\n    await download(ctx);\n    console.timeEnd('download');\n  }\n\n  let sourceData: SourceData<G, P> | undefined;\n  let osmData: OSMData | undefined;\n\n  if (steps.includes('match')) {\n    ({ sourceData, osmData } = await readData(ctx));\n    console.time('match');\n    await match(ctx, sourceData, osmData);\n    console.timeEnd('match');\n  }\n\n  let conflateResult: ConflateResult | undefined;\n  if (steps.includes('conflate')) {\n    if (!sourceData || !osmData) {\n      ({ sourceData, osmData } = await readData(ctx));\n    }\n    console.time('conflate');\n    conflateResult = await conflate(ctx, sourceData, osmData);\n    console.timeEnd('conflate');\n  }\n\n  console.info('done.');\n  return { conflate: conflateResult };\n}\n"],"mappings":"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"}