aboutsummaryrefslogtreecommitdiffstats
path: root/app/models.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-11-09 22:17:04 +0200
committerPetri Hienonen <petri.hienonen@gmail.com>2025-11-09 22:17:04 +0200
commit5eba467a7eb84409aa43df83de78ecb843a79d7b (patch)
treed737d224b4b20ae4a29bad91125072018fdba80d /app/models.js
parentd9f3f48b634917c182fd8e0e764ef0575b1ce218 (diff)
downloadhousing-5eba467a7eb84409aa43df83de78ecb843a79d7b.tar.zst
Update
Diffstat (limited to 'app/models.js')
-rw-r--r--app/models.js88
1 files changed, 60 insertions, 28 deletions
diff --git a/app/models.js b/app/models.js
index 2d85df5..9a0e2ca 100644
--- a/app/models.js
+++ b/app/models.js
@@ -1,4 +1,4 @@
-import { Bounds, Collection, Feature, LineString, Point, Polygon } from "geom";
+import { Bounds, Collection, Feature, Geometry, LineString, Point, Polygon } from "geom";
/** @typedef {{ lat: number, lng: number }} GeoPointJson */
/** @typedef {{ date: string, price: number }} PriceUpdateJson */
@@ -160,10 +160,12 @@ export class Geospatial {
export class District {
/**
* @param {string} name
+ * @param {string} municipality
* @param {Polygon} polygon
*/
- constructor(name, polygon) {
+ constructor(name, municipality, polygon) {
this.name = name;
+ this.municipality = municipality;
this.polygon = polygon;
}
@@ -172,14 +174,20 @@ export class District {
* @returns {District}
*/
static fromFeature(feature) {
- const name = "nimi_fi" in feature.properties ? feature.properties.nimi_fi : "";
+ const name =
+ "nimi" in feature.properties && typeof feature.properties.nimi === "string"
+ ? feature.properties.nimi
+ : "";
+ const municipality =
+ "kunta" in feature.properties && typeof feature.properties.kunta === "string"
+ ? feature.properties.kunta
+ : "";
const geometry = feature.geometry;
-
- if (name === null || name === undefined || !(geometry instanceof Polygon)) {
- throw new Error("Invalid district feature data");
+ if (!(geometry instanceof Polygon)) {
+ throw new Error(`Invalid district feature data ${geometry}`);
}
- return new District(name, geometry);
+ return new District(name, municipality, geometry);
}
/**
@@ -487,53 +495,77 @@ export class Filters {
}
export class DataProvider {
+ static couchBaseUrl = "https://couch.tammi.cc";
+ static wfsDbName = "helsinki_wfs";
+ static housesDbName = "asunnot";
+
+ /**
+ * Fetch all features for a layer as a GeoJSON FeatureCollection
+ * @param {string} layerName
+ * @returns {Promise<Collection>}
+ */
+ 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 params = new URLSearchParams({
+ include_docs: "true",
+ key: JSON.stringify(layerName),
+ });
+
+ const response = await fetch(`${viewUrl}?${params}`, {
+ headers: new Headers({ accept: "application/json" }),
+ mode: "cors",
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ const result = await response.json();
+ const features = result.rows
+ .map((row) => {
+ return row.doc.geometry && "type" in row.doc.geometry
+ ? new Feature(Geometry.fromGeoJSON(row.doc.geometry), row.doc.properties, row.doc._id)
+ : null;
+ })
+ .filter((x) => x !== null);
+ return new Collection(features);
+ }
+
/** @returns {Promise<Collection>} */
static async getCoastline() {
- return await DataProvider.getCollection("data/Seutukartta_meren_rantaviiva.json");
+ return await DataProvider.getCollectionFromCouch("Seutukartta_meren_rantaviiva");
}
/** @returns {Promise<Collection>} */
static async getMainRoads() {
- return await DataProvider.getCollection("data/Seutukartta_liikenne_paatiet.json");
+ return await DataProvider.getCollectionFromCouch("Seutukartta_liikenne_paatiet");
}
/** @returns {Promise<District[]>} */
static async getDistricts() {
- const collection = await DataProvider.getCollection("data/Seutukartta_aluejako_pienalue.json");
- return District.fromCollection(collection);
+ const collection = await DataProvider.getCollectionFromCouch("Seutukartta_aluejako_pienalue");
+ const districts = District.fromCollection(collection);
+ return districts;
}
/** @returns {Promise<TrainStation[]>} */
static async getTrainStations() {
- const collection = await DataProvider.getCollection(
- "data/Seutukartta_liikenne_juna_asema.json",
- );
+ const collection = await DataProvider.getCollectionFromCouch("Seutukartta_liikenne_juna_asema");
return TrainStation.fromCollection(collection);
}
/** @returns {Promise<TrainTracks[]>} */
static async getTrainTracks() {
- const collection = await DataProvider.getCollection("data/Seutukartta_liikenne_juna_rata.json");
+ const collection = await DataProvider.getCollectionFromCouch("Seutukartta_liikenne_juna_rata");
return TrainTracks.fromCollection(collection);
}
- /**
- * Load any GeoJSON file as Feature Collection
- * @param {string} url
- * @returns {Promise<Collection>}
- */
- static async getCollection(url) {
- const response = await fetch(url);
- if (!response.ok) throw new Error(`Failed to load GeoJSON from ${url}: ${response.status}`);
- const geojson = await response.json();
- return Collection.fromGeoJSON(geojson);
- }
-
/** @returns {Promise<House[]>} */
static async getHouses() {
try {
const response = await fetch(
- new URL("/asunnot/_all_docs?include_docs=true", "https://couch.tammi.cc"),
+ `${DataProvider.couchBaseUrl}/${DataProvider.housesDbName}/_all_docs?include_docs=true`,
{
headers: new Headers({ accept: "application/json" }),
mode: "cors",