diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-09 22:17:04 +0200 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-11-09 22:17:04 +0200 |
| commit | 5eba467a7eb84409aa43df83de78ecb843a79d7b (patch) | |
| tree | d737d224b4b20ae4a29bad91125072018fdba80d /app | |
| parent | d9f3f48b634917c182fd8e0e764ef0575b1ce218 (diff) | |
| download | housing-5eba467a7eb84409aa43df83de78ecb843a79d7b.tar.zst | |
Update
Diffstat (limited to 'app')
| -rw-r--r-- | app/geometry.js | 5 | ||||
| -rw-r--r-- | app/main.js | 1 | ||||
| -rw-r--r-- | app/models.js | 88 |
3 files changed, 63 insertions, 31 deletions
diff --git a/app/geometry.js b/app/geometry.js index 9b89106..d0f5467 100644 --- a/app/geometry.js +++ b/app/geometry.js @@ -146,7 +146,10 @@ export class Geometry { return Polygon.fromGeoJSON(geojson); case "MultiLineString": return MultiLineString.fromGeoJSON(geojson); + case "MultiPolygon": + return Polygon.fromGeoJSON(geojson); default: + debugger; throw new Error(`Invalid GeoJSON object: missing required 'type' property`); } } @@ -1031,7 +1034,6 @@ export class Feature { * @param {string|number} [id] - Feature ID */ constructor(geometry, properties = {}, id = "") { - this.type = "Feature"; this.geometry = geometry; this.properties = properties; this.id = id; @@ -1070,7 +1072,6 @@ export class Collection { * @param {Feature[]} features - Feature array */ constructor(features = []) { - this.type = "FeatureCollection"; this.features = features; } diff --git a/app/main.js b/app/main.js index 30770ed..1273a11 100644 --- a/app/main.js +++ b/app/main.js @@ -456,7 +456,6 @@ export class App { DataProvider.getCoastline(), DataProvider.getMainRoads(), ]); - this.#districts = districts; this.#houses = houses; this.#trainStations = trainStations; 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", |
