From 5eba467a7eb84409aa43df83de78ecb843a79d7b Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 9 Nov 2025 22:17:04 +0200 Subject: Update --- app/geometry.js | 5 ++-- app/main.js | 1 - app/models.js | 88 +++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 63 insertions(+), 31 deletions(-) (limited to 'app') 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} + */ + 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} */ static async getCoastline() { - return await DataProvider.getCollection("data/Seutukartta_meren_rantaviiva.json"); + return await DataProvider.getCollectionFromCouch("Seutukartta_meren_rantaviiva"); } /** @returns {Promise} */ static async getMainRoads() { - return await DataProvider.getCollection("data/Seutukartta_liikenne_paatiet.json"); + return await DataProvider.getCollectionFromCouch("Seutukartta_liikenne_paatiet"); } /** @returns {Promise} */ 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} */ 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} */ 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} - */ - 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} */ 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", -- cgit v1.2.3-70-g09d2