aboutsummaryrefslogtreecommitdiffstats
path: root/app/models.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-11-09 22:59:02 +0200
committerPetri Hienonen <petri.hienonen@gmail.com>2025-11-11 15:35:03 +0200
commit909773f9d253c61183cc1f9f6193656957946be5 (patch)
tree136075e1946accedda0530dd25940b8931408c5a /app/models.js
parentbe7ec90b500ac68e053f2b58feb085247ef95817 (diff)
downloadhousing-909773f9d253c61183cc1f9f6193656957946be5.tar.zst
Add statistical areas
Diffstat (limited to 'app/models.js')
-rw-r--r--app/models.js200
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",