diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-09 22:59:02 +0200 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-11 15:35:03 +0200 |
| commit | 909773f9d253c61183cc1f9f6193656957946be5 (patch) | |
| tree | 136075e1946accedda0530dd25940b8931408c5a /app/models.js | |
| parent | be7ec90b500ac68e053f2b58feb085247ef95817 (diff) | |
| download | housing-909773f9d253c61183cc1f9f6193656957946be5.tar.zst | |
Add statistical areas
Diffstat (limited to 'app/models.js')
| -rw-r--r-- | app/models.js | 200 |
1 files changed, 192 insertions, 8 deletions
diff --git a/app/models.js b/app/models.js index d2c5b55..b100ac8 100644 --- a/app/models.js +++ b/app/models.js @@ -73,11 +73,127 @@ import { Bounds, Collection, Feature, Geometry, LineString, Point, Polygon } fro */ /** - * API response structure - * @typedef {Object} ApiResponse - * @property {number} total_rows - * @property {number} offset - * @property {Array<{doc: HouseJson}>} rows + * Statistical Area Properties JSON structure + * @typedef {Object} StatisticalAreaPropertiesJson + * @property {number} id + * @property {number} osa_alueid + * @property {string} nimi + * @property {string} namn + * @property {number} kokotun + * @property {number} vuosi + * @property {number} vr_vakiy + * @property {number} vr_0_2 + * @property {number} vr_3_6 + * @property {number} vr_7_12 + * @property {number} vr_13_15 + * @property {number} vr_16_17 + * @property {number} vr_18_19 + * @property {number} vr_20_24 + * @property {number} vr_25_29 + * @property {number} vr_30_34 + * @property {number} vr_35_39 + * @property {number} vr_40_44 + * @property {number} vr_45_49 + * @property {number} vr_50_54 + * @property {number} vr_55_59 + * @property {number} vr_60_64 + * @property {number} vr_65_69 + * @property {number} vr_70_74 + * @property {number} vr_75_79 + * @property {number} vr_80_84 + * @property {number} vr_85_ + * @property {number} vr_kiel_su_sa + * @property {number} vr_kiel_ru + * @property {number} vr_kiel_vier + * @property {number} vm_synt + * @property {number} vm_kuol + * @property {number} vm_mu_tulo + * @property {number} vm_mu_lahto + * @property {number} vm_s_mu_tulo + * @property {number} vm_s_mu_lahto + * @property {number} ko_25_ + * @property {number} ko_tut_yht + * @property {number} ko_toinen + * @property {number} ko_al_kork + * @property {number} ko_yl_kork + * @property {number} ko_perus + * @property {number} tu_kesk + * @property {number} tu_ask_lkm + * @property {number} tu_pien + * @property {number} tu_med + * @property {string} ap_ask_lkm + * @property {string} ap_yhd_ask + * @property {string} ap_lper + * @property {number} ap_lper_l1 + * @property {number} ap_lper_l2 + * @property {number} ap_lper_l3 + * @property {number} ap_lper_l4 + * @property {string} ap_per_yht + * @property {number} ap_yhd_vanh + * @property {string} ra_rak + * @property {string} ra_rak_ker + * @property {string} ra_asrak + * @property {number} ra_as + * @property {number} ra_as_om + * @property {number} ra_as_vu + * @property {number} ra_as_asoik + * @property {number} ra_as_muu + * @property {number} ra_pt_as + * @property {number} ra_kt_as + * @property {number} ra_hu_1 + * @property {number} ra_hu_2 + * @property {number} ra_hu_3 + * @property {number} ra_hu_4 + * @property {number} ra_hu_5 + * @property {number} ra_hu_6 + * @property {number} ra_hu_muu + * @property {number} tp_tyopy + * @property {number} tp_a + * @property {number} tp_b + * @property {number} tp_c + * @property {number} tp_d + * @property {number} tp_e + * @property {number} tp_f + * @property {number} tp_g + * @property {number} tp_h + * @property {number} tp_i + * @property {number} tp_j + * @property {number} tp_k + * @property {number} tp_l + * @property {number} tp_m + * @property {number} tp_n + * @property {number} tp_o + * @property {number} tp_p + * @property {number} tp_q + * @property {number} tp_r + * @property {number} tp_s + * @property {number} tp_t + * @property {number} tp_u + * @property {number} tp_x + * @property {number} tp_asuky + * @property {number} tp_ + * @property {number} tp_tyol + * @property {number} tp_tyot + * @property {number} tp_tyov_ulk + * @property {number} tp_0_14 + * @property {number} tp_opisk_var + * @property {number} tp_elak + * @property {number} tp_tyotaste + * @property {number|null} vr_enn_2037 + * @property {number} vr_enn + * @property {string|null} paivitetty_tietopalveluun + */ + +/** + * Statistical Area JSON from CouchDB + * @typedef {Object} StatisticalAreaJson + * @property {string} _id + * @property {string} _rev + * @property {string} downloaded_at + * @property {Object} geometry + * @property {string} layer + * @property {StatisticalAreaPropertiesJson} properties + * @property {string} type */ export class PriceUpdate { @@ -155,6 +271,61 @@ export class Geospatial { } /** + * Represents a statistical area with demographic and housing data + */ +export class StatisticalArea { + /** + * @param {string} id + * @param {Polygon} polygon + * @param {StatisticalAreaPropertiesJson} properties + */ + constructor(id, polygon, properties) { + this.id = id; + this.polygon = polygon; + this.properties = properties; + } + + /** + * @param {Feature} feature + * @returns {StatisticalArea} + */ + static fromFeature(feature) { + const geometry = feature.geometry; + if (!(geometry instanceof Polygon)) { + throw new Error(`Invalid statistical area feature data ${geometry}`); + } + + return new StatisticalArea(feature.id, geometry, feature.properties); + } + + /** + * Convert Collection to StatisticalArea[] + * @param {Collection} collection + * @returns {StatisticalArea[]} + */ + static fromCollection(collection) { + return collection.features.map(StatisticalArea.fromFeature); + } + + /** + * Check if point is within this statistical area + * @param {Point} point + * @returns {boolean} + */ + contains(point) { + return this.polygon.within(point) || this.polygon.intersects(point); + } + + /** + * Get the centroid of the statistical area + * @returns {Point} + */ + get centroid() { + return this.polygon.centroid(); + } +} + +/** * Represents a geographic district with name and polygon */ export class District { @@ -506,15 +677,19 @@ export class DataProvider { */ static async getCollectionFromCouch(layerName) { // Use CouchDB view to get all features for the layer - const viewUrl = `${DataProvider.couchBaseUrl}/${DataProvider.wfsDbName}/_design/layers/_view/by_layer`; + const viewUrl = new URL( + `/${DataProvider.wfsDbName}/_design/layers/_view/by_layer`, + DataProvider.couchBaseUrl, + ); const params = new URLSearchParams({ - // biome-ignore lint/style/useNamingConvention: header names are not optional + // biome-ignore lint/style/useNamingConvention: url search params include_docs: "true", key: JSON.stringify(layerName), }); const response = await fetch(`${viewUrl}?${params}`, { headers: new Headers({ accept: "application/json" }), + method: "GET", mode: "cors", }); @@ -562,11 +737,20 @@ export class DataProvider { return TrainTracks.fromCollection(collection); } + /** @returns {Promise<StatisticalArea[]>} */ + static async getStatisticalAreas() { + const collection = await DataProvider.getCollectionFromCouch("Aluesarjat_avainluvut_2024"); + return StatisticalArea.fromCollection(collection); + } + /** @returns {Promise<House[]>} */ static async getHouses() { try { const response = await fetch( - `${DataProvider.couchBaseUrl}/${DataProvider.housesDbName}/_all_docs?include_docs=true`, + new URL( + `/${DataProvider.housesDbName}/_all_docs?include_docs=true`, + DataProvider.couchBaseUrl, + ), { headers: new Headers({ accept: "application/json" }), mode: "cors", |
