diff --git a/.env.sample.sh b/.env.sample.sh index a158fd1f4..53f16d910 100755 --- a/.env.sample.sh +++ b/.env.sample.sh @@ -100,11 +100,9 @@ export UTP_WEBHOOK_TOKEN_URL="https://login.microsoftonline.com/bdb27328-5c0b-41 # PTV export PTV_API_URL="https://api.palvelutietovaranto.trn.suomi.fi/api" export PTV_TOKEN_URL="https://palvelutietovaranto.trn.suomi.fi/api/auth/api-login" -export PTV_SERVICE_URL="https://api.palvelutietovaranto.trn.suomi.fi/api/v11/Service" -export PTV_SERVICE_LOCATION_URL="https://api.palvelutietovaranto.trn.suomi.fi/api/v11/ServiceChannel/ServiceLocation" -export PTV_ORG_ID=***FILL_THIS*** -export PTV_API_USERNAME=***FILL_THIS*** -export PTV_API_PASSWORD=***FILL_THIS*** +# Test env requires API credentials per org-id, so handled in the code +# export PTV_API_USERNAME=***FILL_THIS*** +# export PTV_API_PASSWORD=***FILL_THIS*** # Open AI export OPEN_AI_API_KEY=***FILL_THIS*** diff --git a/webapp/dev/user.clj b/webapp/dev/user.clj index 89ffa9bab..ef02284db 100644 --- a/webapp/dev/user.clj +++ b/webapp/dev/user.clj @@ -3,7 +3,8 @@ (:require [clojure.tools.namespace.repl] [integrant.repl :refer [reset reset-all halt go]] - [integrant.repl.state])) + [integrant.repl.state] + [lipas.data.prop-types :as prop-types])) (integrant.repl/set-prep! (fn [] (dissoc @(requiring-resolve 'lipas.backend.config/system-config) :lipas/nrepl))) @@ -35,6 +36,11 @@ (assert-running-system) (:lipas/search (current-system))) +(defn ptv + [] + (assert-running-system) + (:lipas/ptv (current-system))) + (defn reindex-search! [] ((requiring-resolve 'lipas.backend.search-indexer/main) (db) (search) "search")) @@ -61,4 +67,104 @@ (require '[migratus.core :as migratus]) (migratus/create nil "activities_status" :sql) + (migratus/create nil "roles" :edn) + (migratus/create nil "year_round_use" :sql) + + (require '[lipas.maintenance :as maintenance]) + (require '[lipas.backend.core :as core]) + + (def robot (core/get-user (db) "robot@lipas.fi")) + + (maintenance/merge-types (db) (search) (ptv) robot 4530 4510) + (maintenance/merge-types (db) (search) (ptv) robot 4520 4510) + (maintenance/merge-types (db) (search) (ptv) robot 4310 4320) + + (require '[lipas.data.types :as types]) + (require '[lipas.data.types-old :as types-old]) + (require '[lipas.data.prop-types :as prop-types]) + (require '[lipas.data.prop-types-old :as prop-types-old]) + (require '[clojure.set :as set]) + + (def new-types (set/difference + (set (keys types/all)) + (set (keys types-old/all)))) + + (require '[clojure.string :as str]) + + (for [type-code (conj new-types 113)] + (println + (format "INSERT INTO public.liikuntapaikkatyyppi( + id, tyyppikoodi, nimi_fi, nimi_se, kuvaus_fi, kuvaus_se, liikuntapaikkatyyppi_alaryhma, geometria, tason_nimi, nimi_en, kuvaus_en) + VALUES (%s, %s, '%s', '%s', '%s', '%s', %s, '%s', '%s', '%s', '%s');" + type-code + type-code + (get-in types/all [type-code :name :fi]) + (get-in types/all [type-code :name :se]) + (get-in types/all [type-code :description :fi]) + (get-in types/all [type-code :description :se]) + (get-in types/all [type-code :sub-category]) + (get-in types/all [type-code :geometry-type]) + (-> types/all + (get-in [type-code :name :fi]) + csk/->snake_case + (str/replace "ä" "a") + (str/replace "ö" "o") + (str/replace #"[^a-zA-Z]" "") + (->> (str "lipas_" type-code "_"))) + (get-in types/all [type-code :name :en]) + (get-in types/all [type-code :description :en])))) + + (def new-props (set/difference + (set (keys prop-types/all)) + (set (keys prop-types-old/all)))) + + new-props + + (require '[camel-snake-kebab.core :as csk]) + (def legacy-mapping (into {} (for [p new-props] + [(csk/->camelCaseKeyword p) p]))) + + (keys legacy-mapping) + + (def legacy-mapping-reverse (set/map-invert legacy-mapping)) + + (require '[lipas.data.prop-types :as prop-types]) + + (def data-types + "Legacy lipas supports only these. Others will be treated as strings" + {"boolean" "boolean" + "numeric" "numberic" + "string" "string"}) + + (doseq [[legacy-prop-k prop-k] legacy-mapping] + (println + (format "INSERT INTO public.ominaisuustyypit( + nimi_fi, tietotyyppi, kuvaus_fi, nimi_se, kuvaus_se, ui_nimi_fi, nimi_en, kuvaus_en, handle) + VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');" + (csk/->snake_case (get-in prop-types/all [prop-k :name :fi])) + (get data-types (get-in prop-types/all [prop-k :data-type]) "string") + (get-in prop-types/all [prop-k :description :fi]) + (get-in prop-types/all [prop-k :name :se]) + (get-in prop-types/all [prop-k :description :se]) + (get-in prop-types/all [prop-k :name :fi]) + (get-in prop-types/all [prop-k :name :en]) + (get-in prop-types/all [prop-k :description :en]) + (name legacy-prop-k)))) + + (doseq [p new-props + [type-code m] types/all] + (when (contains? (set (keys (:props m))) p) + (println (str "-- Type " type-code)) + (println (str "-- prop " p)) + (println + (format + "INSERT INTO public.tyypinominaisuus( + liikuntapaikkatyyppi_id, ominaisuustyyppi_id, prioriteetti) + VALUES (%s, %s, %s);" + (format "(select id from liikuntapaikkatyyppi where tyyppikoodi = %s)" + type-code) + (format "(select id from ominaisuustyypit where handle = '%s')" + (name (legacy-mapping-reverse p))) + (get-in types/all [type-code :props p :priority]))))) + ) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index fc3518800..62b0a97fc 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.11.5", "@hello-pangea/dnd": "^16.6.0", "@mapbox/togeojson": "^0.16.0", + "@mui/icons-material": "^5.16.7", "@mui/material": "^5.15.19", "@turf/area": "^6.5.0", "@turf/bbox": "^6.0.1", @@ -474,6 +475,32 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.16.7", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", diff --git a/webapp/package.json b/webapp/package.json index a6b3adf3f..fcc0f5399 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,6 +19,7 @@ "@emotion/styled": "^11.11.5", "@hello-pangea/dnd": "^16.6.0", "@mapbox/togeojson": "^0.16.0", + "@mui/icons-material": "^5.16.7", "@mui/material": "^5.15.19", "@turf/area": "^6.5.0", "@turf/bbox": "^6.0.1", diff --git a/webapp/resources/migrations/20241103113824-year-round-use.down.sql b/webapp/resources/migrations/20241103113824-year-round-use.down.sql new file mode 100644 index 000000000..d29bf0038 --- /dev/null +++ b/webapp/resources/migrations/20241103113824-year-round-use.down.sql @@ -0,0 +1,7 @@ +UPDATE sports_site +SET document = jsonb_set( + document, + '{properties}', + (document::jsonb->'properties')::jsonb - 'year-round-use?' +) +WHERE document::jsonb->'properties' ? 'year-round-use?'; diff --git a/webapp/resources/migrations/20241103113824-year-round-use.up.sql b/webapp/resources/migrations/20241103113824-year-round-use.up.sql new file mode 100644 index 000000000..b69dcec18 --- /dev/null +++ b/webapp/resources/migrations/20241103113824-year-round-use.up.sql @@ -0,0 +1,7 @@ +UPDATE sports_site +SET document = jsonb_set(document, '{properties, year-round-use?}', 'true') +WHERE id IN ( + SELECT id + FROM sports_site_current + WHERE document::jsonb->'properties'->>'winter-usage?' = 'true' OR + document::jsonb->'properties'->>'summer-usage?' = 'true') diff --git a/webapp/src/clj/lipas/backend/config.clj b/webapp/src/clj/lipas/backend/config.clj index 5c67a8884..900e61540 100644 --- a/webapp/src/clj/lipas/backend/config.clj +++ b/webapp/src/clj/lipas/backend/config.clj @@ -58,15 +58,13 @@ :s3-bucket (env! :aws-s3-bucket) :s3-bucket-prefix (env! :aws-s3-bucket-prefix)} :ptv - {:api-url (env! :ptv-api-url) - :token-url (env! :ptv-token-url) - :service-url (env! :ptv-service-url) - :service-location-url (env! :ptv-service-location-url) - :creds - {:org-id (env! :ptv-org-id) - :api - {:username (env! :ptv-api-username) - :password (env! :ptv-api-password)}}} + (let [test-env? (= "test" (:ptv-env e/env "prod"))] + {:env (:ptv-env e/env "prod") + :api-url (env! :ptv-api-url) + :token-url (env! :ptv-token-url) + :creds (when-not test-env? + {:api {:username (env! :ptv-api-username) + :password (env! :ptv-api-password)}})}) :open-ai {:api-key (env! :open-ai-api-key) :project "ptv" @@ -79,6 +77,7 @@ :search (ig/ref :lipas/search) :mailchimp (ig/ref :lipas/mailchimp) :aws (ig/ref :lipas/aws) + :ptv (ig/ref :lipas/ptv) :utp {:cms-api-url (env! :utp-cms-api-url) :cms-api-user (env! :utp-cms-api-user) diff --git a/webapp/src/clj/lipas/backend/core.clj b/webapp/src/clj/lipas/backend/core.clj index 987dcf77e..f08ab764f 100644 --- a/webapp/src/clj/lipas/backend/core.clj +++ b/webapp/src/clj/lipas/backend/core.clj @@ -1,37 +1,34 @@ (ns lipas.backend.core - (:require - [buddy.hashers :as hashers] - [cheshire.core :as json] - [clojure.core.async :as async] - [clojure.data.csv :as csv] - [clojure.java.jdbc :as jdbc] - [clojure.string :as str] - [dk.ative.docjure.spreadsheet :as excel] - [lipas.ai.core :as ai] - [lipas.backend.accessibility :as accessibility] - [lipas.backend.analysis.diversity :as diversity] - [lipas.backend.analysis.reachability :as reachability] - [lipas.backend.db.db :as db] - [lipas.backend.elevation :as elevation] - [lipas.backend.email :as email] - [lipas.backend.gis :as gis] - [lipas.backend.jwt :as jwt] - [lipas.backend.newsletter :as newsletter] - [lipas.backend.s3 :as s3] - [lipas.backend.search :as search] - [lipas.data.admins :as admins] - [lipas.data.cities :as cities] - [lipas.data.owners :as owners] - [lipas.data.types :as types] - [lipas.i18n.core :as i18n] - [lipas.integration.ptv.core :as ptv] - [lipas.integration.utp.cms :as utp-cms] - [lipas.reports :as reports] - [lipas.roles :as roles] - [lipas.utils :as utils] - [taoensso.timbre :as log]) - (:import - [java.io OutputStreamWriter])) + (:require [buddy.hashers :as hashers] + [cheshire.core :as json] + [clojure.core.async :as async] + [clojure.data.csv :as csv] + [clojure.java.jdbc :as jdbc] + [clojure.string :as str] + [dk.ative.docjure.spreadsheet :as excel] + [lipas.backend.accessibility :as accessibility] + [lipas.backend.analysis.diversity :as diversity] + [lipas.backend.analysis.reachability :as reachability] + [lipas.backend.db.db :as db] + [lipas.backend.elevation :as elevation] + [lipas.backend.email :as email] + [lipas.backend.gis :as gis] + [lipas.backend.jwt :as jwt] + [lipas.backend.newsletter :as newsletter] + [lipas.backend.s3 :as s3] + [lipas.backend.search :as search] + [lipas.data.admins :as admins] + [lipas.data.cities :as cities] + [lipas.data.owners :as owners] + [lipas.data.ptv :as ptv-data] + [lipas.data.types :as types] + [lipas.i18n.core :as i18n] + [lipas.integration.utp.cms :as utp-cms] + [lipas.reports :as reports] + [lipas.roles :as roles] + [lipas.utils :as utils] + [taoensso.timbre :as log]) + (:import [java.io OutputStreamWriter])) (def cache "Simple atom cache for things that (hardly) never change." (atom {})) @@ -495,14 +492,18 @@ [db {:keys [_lipas-ids _loi-ids] :as m}] (db/add-to-webhook-queue! db m)) +(defn sync-ptv! [tx search ptv-component user props] + (let [f (resolve 'lipas.backend.ptv.core/sync-ptv!)] + (f tx search ptv-component user props))) + ;; TODO refactor upsert-sports-site!, upsert-sports-site!* and ;; save-sports-site! to form more sensible API. (defn save-sports-site! "Saves sports-site to db and search and appends it to outbound integrations queue." - ([db search user sports-site] - (save-sports-site! db search user sports-site false)) - ([db search user sports-site draft?] + ([db search ptv user sports-site] + (save-sports-site! db search ptv user sports-site false)) + ([db search ptv user sports-site draft?] (jdbc/with-db-transaction [tx db] (let [resp (upsert-sports-site! tx user sports-site draft?) route? (-> resp :type :type-code types/all :geometry-type #{"LineString"})] @@ -524,7 +525,28 @@ (add-to-webhook-queue! tx {:lipas-ids [(:lipas-id resp)]})) - resp)))) + ;; Sync the site to PTV if + ;; - it was previously sent to PTV (we might archive it now if it no longer looks like PTV candidate) + ;; - it is PTV candidate now + ;; - do nothing (keep the previous data in PTV if site was previously sent there) if sync-enabled is false + ;; Note: if site status or something is updated in Lipas, so that the site is no longer candidate, + ;; that doesn't trigger update if sync-enabled is false. + (if (and (not draft?) + (:sync-enabled (:ptv resp)) + ;; TODO: Check privilage :ptv/basic or such + (or (ptv-data/ptv-candidate? resp) + (ptv-data/is-sent-to-ptv? resp))) + ;; NOTE: this will create a new sports-site rev. + ;; Make it instead update the sports-site already created in the tx? + ;; Otherwise each save-sports-site! will create two sports-site revs. + (let [new-ptv-data (sync-ptv! tx search ptv user + {:sports-site resp + :org-id (:org-id (:ptv resp)) + :lipas-id (:lipas-id resp) + :ptv (:ptv resp)})] + (log/infof "Sports site updated and PTV integration enabled") + (assoc resp :ptv new-ptv-data)) + resp))))) ;;; Cities ;;; @@ -905,103 +927,6 @@ [{:keys [_filename _data _user] :as params}] (utp-cms/upload-image! params)) -;; PTV -(defn get-ptv-integration-candidates - [search criteria] - (ptv/get-eligible-sites search criteria)) - -(defn generate-ptv-descriptions - [{:keys [client indices] :as _search} - {:keys [lipas-id]}] - (let [idx (get-in indices [:sports-site :search]) - doc (-> (search/fetch-document client idx lipas-id) :body :_source)] - (-> (ai/generate-ptv-descriptions doc) - :message - :content))) - -(defn make-overview - [sites] - {:city-name (->> sites first :search-meta :location :city :name) - :service-name (->> sites first :search-meta :type :sub-category :name) - :sports-facilities (for [site sites] - {:type (-> site :search-meta :type :name :fi)})}) - -(defn generate-ptv-service-descriptions - [search - {:keys [_id sub-category-id city-codes]}] - (let [type-codes (->> (types/by-sub-category sub-category-id) - (map :type-code)) - sites (ptv/get-eligible-sites search {:type-codes type-codes - :city-codes city-codes - :owners ["city" "city-main-owner"]}) - doc (make-overview sites)] - (-> (ai/generate-ptv-service-descriptions doc) - :message - :content))) - -(defn upsert-ptv-service! - [{:keys [id source-id] :as m}] - {:pre [(some? source-id)]} - (let [config {:org-id (get-in ptv/test-config [:creds :org-id])} - data (ptv/->ptv-service (merge config m))] - (if id - (ptv/update-service config id data) - (ptv/create-service config data)))) - -(defn fetch-ptv-services - [{:keys [org-id] :as m}] - {:pre [(some? org-id)]} - (ptv/get-org-services {} org-id)) - -(def persisted-ptv-keys [:languages - :summary - :description - :last-sync - :org-id - :sync-enabled - :service-integration - :descriptions-integration - :service-channel-integration - :service-ids - :service-channel-ids]) - -(defn upsert-ptv-service-location! - [db search user {:keys [org ptv-meta sports-site] :as m}] - (assert (:lipas-id sports-site)) - (let [site (db/get-sports-site db (:lipas-id sports-site)) - _ (assert (some? site) - (str "Sports site " (:lipas-id sports-site) " not found in DB")) - config {:org-id (get-in ptv/test-config [:creds :org-id])} - id (first (get-in site [:ptv :service-channel-ids])) - site (update site :ptv merge ptv-meta) - data (ptv/->ptv-service-location org gis/wgs84->tm35fin-no-wrap (enrich site)) - ptv-resp (if id - (ptv/update-service-location config id data) - (ptv/create-service-location config data)) - now (utils/timestamp)] - - (save-sports-site! db search user (-> site - (assoc :event-date now) - (assoc :ptv (-> ptv-meta - (select-keys persisted-ptv-keys) - (assoc :last-sync now))))) - - ptv-resp)) - -(defn save-ptv-integration-definitions - "Saves ptv definitions under key :ptv. Does not notify webhooks, - integrations or analysis queues since they're not likely interested - in this." - [db search user lipas-id->ptv-meta] - (jdbc/with-db-transaction [tx db] - (doseq [[lipas-id ptv] lipas-id->ptv-meta] - ;; TODO take when-let -> let and add assert - (when-let [site (-> (get-sports-site tx lipas-id) - (assoc :event-date (utils/timestamp)) - (assoc :ptv ptv))] - (upsert-sports-site! tx user site) - (index! search site :sync))))) - (comment (require '[lipas.backend.config :as config]) (def db-spec (:db config/default-config)) diff --git a/webapp/src/clj/lipas/backend/handler.clj b/webapp/src/clj/lipas/backend/handler.clj index 79ffbad73..52d606c41 100644 --- a/webapp/src/clj/lipas/backend/handler.clj +++ b/webapp/src/clj/lipas/backend/handler.clj @@ -1,25 +1,25 @@ (ns lipas.backend.handler - (:require - [clojure.java.io :as io] - [clojure.spec.alpha :as s] - [lipas.backend.core :as core] - [lipas.backend.jwt :as jwt] - [lipas.backend.middleware :as mw] - [lipas.roles :as roles] - [lipas.schema.core] - [lipas.utils :as utils] - [muuntaja.core :as m] - [reitit.coercion.spec] - [reitit.ring :as ring] - [reitit.ring.coercion :as coercion] - [reitit.ring.middleware.exception :as exception] - [reitit.ring.middleware.multipart :as multipart] - [reitit.ring.middleware.muuntaja :as muuntaja] - [reitit.swagger :as swagger] - [reitit.swagger-ui :as swagger-ui] - [ring.middleware.params :as params] - [ring.util.io :as ring-io] - [taoensso.timbre :as log])) + (:require [clojure.java.io :as io] + [clojure.spec.alpha :as s] + [lipas.backend.core :as core] + [lipas.backend.jwt :as jwt] + [lipas.backend.middleware :as mw] + [lipas.backend.ptv.handler :as ptv-handler] + [lipas.roles :as roles] + [lipas.schema.core] + [lipas.utils :as utils] + [muuntaja.core :as m] + [reitit.coercion.spec] + [reitit.ring :as ring] + [reitit.ring.coercion :as coercion] + [reitit.ring.middleware.exception :as exception] + [reitit.ring.middleware.multipart :as multipart] + [reitit.ring.middleware.muuntaja :as muuntaja] + [reitit.swagger :as swagger] + [reitit.swagger-ui :as swagger-ui] + [ring.middleware.params :as params] + [ring.util.io :as ring-io] + [taoensso.timbre :as log])) (defn exception-handler ([status type] @@ -45,7 +45,19 @@ ;; Return 500 and print stack trace for exceptions that are not ;; specifically handled - ::exception/default (exception-handler 500 :internal-server-error :print-stack)}) + ::exception/default (exception-handler 500 :internal-server-error :print-stack) + + :reitit.coercion/response-coercion + (let [handler (:reitit.coercion/response-coercion exception/default-handlers)] + (fn [e x] + (log/errorf e "Response coercion error") + (handler e x))) + + :reitit.coercion/request-coercion + (let [handler (:reitit.coercion/request-coercion exception/default-handlers)] + (fn [e x] + (log/errorf e "Request coercion error") + (handler e x)))}) (def exceptions-mw (exception/create-exception-middleware @@ -54,7 +66,7 @@ exception-handlers))) (defn create-app - [{:keys [db emailer search mailchimp aws]}] + [{:keys [db emailer search mailchimp aws ptv] :as ctx}] (ring/ring-handler (ring/router @@ -121,7 +133,7 @@ valid? (s/valid? spec body-params)] (if valid? {:status 201 - :body (core/save-sports-site! db search identity body-params draft?)} + :body (core/save-sports-site! db search ptv identity body-params draft?)} {:status 400 :body (s/explain-data spec body-params)})))}}] @@ -705,76 +717,7 @@ {:status 200 :body (core/search-lois-with-params search body-params)})}}] - ;; PTV - ["/actions/get-ptv-integration-candidates" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params]}] - {:status 200 - :body (core/get-ptv-integration-candidates search body-params)})}}] - - ["/actions/generate-ptv-descriptions" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params]}] - {:status 200 - :body (core/generate-ptv-descriptions search body-params)})}}] - - ["/actions/generate-ptv-service-descriptions" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params]}] - {:status 200 - :body (core/generate-ptv-service-descriptions search body-params)})}}] - - ["/actions/save-ptv-service" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params]}] - {:status 200 - :body (core/upsert-ptv-service! body-params)})}}] - - ["/actions/fetch-ptv-services" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params]}] - {:status 200 - :body (core/fetch-ptv-services body-params)})}}] - - ["/actions/save-ptv-service-location" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params identity]}] - {:status 200 - :body (core/upsert-ptv-service-location! db search identity body-params)})}}] - - ["/actions/save-ptv-meta" - {:post - {:no-doc false - :require-role :ptv/manage - :parameters {:body map?} - :handler - (fn [{:keys [body-params identity]}] - {:status 200 - :body (core/save-ptv-integration-definitions db search identity body-params)})}}]]] + (ptv-handler/routes ctx)]] {:data {:coercion reitit.coercion.spec/coercion diff --git a/webapp/src/clj/lipas/backend/ptv.clj b/webapp/src/clj/lipas/backend/ptv.clj index 48033285f..d063e51d4 100644 --- a/webapp/src/clj/lipas/backend/ptv.clj +++ b/webapp/src/clj/lipas/backend/ptv.clj @@ -1,8 +1,7 @@ (ns lipas.backend.ptv - (:require - [cheshire.core :as json] - [clj-http.client :as client] - [lipas.data.types :as types])) + (:require [cheshire.core :as json] + [clj-http.client :as client] + [lipas.data.types :as types])) ;; Exploring PTV prod data @@ -17,8 +16,8 @@ :query-params {:uri class-uri :page page}}) - :body - (json/decode keyword))) + :body + (json/decode keyword))) (defn get-services-channels-by-type [type page] @@ -26,8 +25,8 @@ {:headers headers :query-params {:page page}}) - :body - (json/decode keyword))) + :body + (json/decode keyword))) (comment @@ -40,10 +39,4 @@ "http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.1"] (def p1 (get-services-by-class "http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2" 1)) - (def p2 (get-services-by-class "http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2" 2)) - - - - - - ) + (def p2 (get-services-by-class "http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2" 2))) diff --git a/webapp/src/clj/lipas/ai/core.clj b/webapp/src/clj/lipas/backend/ptv/ai.clj similarity index 60% rename from webapp/src/clj/lipas/ai/core.clj rename to webapp/src/clj/lipas/backend/ptv/ai.clj index 8cebb35f3..c299e7563 100644 --- a/webapp/src/clj/lipas/ai/core.clj +++ b/webapp/src/clj/lipas/backend/ptv/ai.clj @@ -1,8 +1,10 @@ -(ns lipas.ai.core +(ns lipas.backend.ptv.ai (:require [cheshire.core :as json] [clj-http.client :as client] + [clojure.string :as str] [clojure.walk :as walk] [lipas.backend.config :as config] + [malli.json-schema :as json-schema] [taoensso.timbre :as log])) (def openai-config @@ -14,7 +16,7 @@ (def ptv-system-instruction "Olet avustaja, joka auttaa käyttäjiä tuottamaan sisältöä - Palvelutietovarantoon. Tuotat JSON-muotoista sisältöä. Sinulle + Palvelutietovarantoon. Sinulle esitetään kysymyksiä, ja käytät ensisijaisesti lähdeaineistoa ja toissijaisesti omaa tietoasi antaaksesi vastauksia. Noudata vastuksissa seuraavia tyyliohjeita: @@ -37,17 +39,10 @@ Annat vastaukset englanniksi, suomeksi ja ruotsiksi. Eri kieliversiot voivat poiketa kieliasultaan toisistaan. Tärkeää on, että kieliasu - on luettavaa ja selkeää. - -Vastauksesi sisältö on koodattu - JSON-objekteiksi. JSON-sisällön tarkka muoto voidaan antaa - kehotteessa. Anna käännetyt versiot kaikista pyydetyistä tiedoista - avaimilla \"fi\" suomeksi, \"se\" ruotsiksi ja \"en\" - englanniksi. Jos teksti sisältää lainauksia, käytä pakomerkkiä \\ - ennen lainausmerkkiä, jotta JSON-rakenne pysyy eheänä.") + on luettavaa ja selkeää.") (def ptv-system-instruction-v2 - "You are an assistant who helps users produce content for the Service Information Repository (Palvelutietovaranto). You generate content in JSON format. You will be asked questions and should primarily use source material and secondarily your own knowledge to provide answers. Follow these style guidelines in your responses: + "You are an assistant who helps users produce content for the Service Information Repository (Palvelutietovaranto). You will be asked questions and should primarily use source material and secondarily your own knowledge to provide answers. Follow these style guidelines in your responses: • Use a neutral tone in your responses. • Avoid promotional language. • The texts are not marketing communications. @@ -63,23 +58,51 @@ Vastauksesi sisältö on koodattu • Present only one topic per paragraph. • Divide the text into paragraphs. • A paragraph should contain a maximum of four sentences. -Provide answers in English, Finnish, and Swedish. Different language versions can differ in their phrasing. It is important that the language is readable and clear. - Your responses are encoded as JSON objects. The exact format of the JSON content can be specified in the prompt. Provide translated versions of all requested information with the keys \"fi\" for Finnish, \"se\" for Swedish, and \"en\" for English. If the text contains quotes, use an escape character \\ before the quotation mark to maintain the integrity of the JSON structure.") +Provide answers in English, Finnish, and Swedish. Different language versions can differ in their phrasing. It is important that the language is readable and clear.") (comment (println ptv-system-instruction-v2)) +(defn localized-string-schema [string-props] + [:map + {:closed true} + [:fi [:string string-props]] + [:se [:string string-props]] + [:en [:string string-props]]]) + +(def response-schema + [:map + {:closed true} + [:description (localized-string-schema nil)] + ;; Structured Outputs doesn't support maxLength + ;; https://platform.openai.com/docs/guides/structured-outputs#some-type-specific-keywords-are-not-yet-supported + ;; The prompt mentions summary should be max 150 chars + [:summary (localized-string-schema nil #_{:max 150})]]) + +(def Response + (json-schema/transform response-schema)) + (defn complete [{:keys [completions-url model n #_temperature top-p presence-penalty message-format max-tokens] - :or {message-format {:type "json_object"} - n 1 + :or {n 1 #_#_temperature 0 top-p 0.5 presence-penalty -2 max-tokens 4096}} system-instruction prompt] - (let [body {:model model + (let [;; Response format with JSON Schema should ensure + ;; the response is valid JSON and according to the schema, + ;; without specfying this in the prompts. + message-format (or message-format + {:type "json_schema" + :json_schema {:name "Response" + ;; This is probably needed? Providing an unsupported Schema, + ;; like with maxLength, without this doesn't throw an error, + ;; but with this enabled it does. + :strict true + :schema Response}}) + body {:model model :n n :max_tokens max-tokens #_#_:temperature temperature @@ -88,31 +111,36 @@ Provide answers in English, Finnish, and Swedish. Different language versions ca :response_format message-format :messages [{:role "system" :content system-instruction} {:role "user" :content prompt}]} + _ (log/infof "AI Prompt sent: %s" prompt) params {:headers default-headers - :body (json/encode body)}] - - (log/info prompt) - - (-> (client/post completions-url params) - :body - (json/decode keyword) - :choices - first - (update-in [:message :content] #(json/decode % keyword))))) + :body (json/encode body)} + result (-> (client/post completions-url params) + :body + (json/decode keyword) + :choices + first + (update-in [:message :content] #(json/decode % keyword)))] + (log/infof "AI Result: %s" result) + result)) (def generate-utp-descriptions-prompt "Laadi tämän viestin lopussa olevan JSON-rakenteen kuvaamasta liikuntapaikasta tiivistelmä (max 150 merkkiä) ja tekstikuvaus, jotka sopivat Palvelutietovarannossa palvelupaikan kuvaukseen. Yksityiskohtaiset rakennustekniset tiedot ja olosuhdetiedot jätetään kuvauksista - pois. Haluan vastauksen muodossa {\"description\": - {...käännökset...}, \"summary\" {...käännökset...}}. %s") + pois. %s") (defn ->prompt-doc [sports-site] - (walk/postwalk #(if (map? %) - (dissoc % :geoms :geometries :simple-geoms :images :videos :id :fids :event-date) - %) + ;; Might include (some) of the UTP data now? + ;; Could be a good thing, but might make the prompt data too large? + (walk/postwalk (fn [x] + (if (map? x) + (dissoc x :geoms :geometries :simple-geoms :images :videos :id :fids :event-date + ;; This includes the already generated summary/description, important that that isn't + ;; passed back into the AI. + :ptv) + x)) sports-site)) (defn generate-ptv-descriptions @@ -122,12 +150,33 @@ Provide answers in English, Finnish, and Swedish. Different language versions ca ptv-system-instruction-v2 (format generate-utp-descriptions-prompt (json/encode prompt-doc))))) +(def translate-to-other-langs-prompt + "Käännä tämän viestin lopussa olevat tiivistelmä (max 150 merkkiä) ja tekstikuvaus, jotka sopivat + Palvelutietovarannossa palvelupaikan kuvaukseen, kieleltä %s kielille %s. + %s") + +(defn translate-to-other-langs + [{:keys [from to summary description]}] + (complete openai-config + ptv-system-instruction-v2 + (format translate-to-other-langs-prompt + from + (str/join ", " to) + (json/encode {:summary summary + :description description})))) + +(comment + (translate-to-other-langs + {:from "fi" + :to ["en" "se"] + :summary "Limingan yleisurheilupyhättö, monipuolinen ulkoilma-alue." + :description "Limingan yleisurheilupyhättö on monipuolinen ulkoilma-alue, jossa on useita yleisurheilulajeja varten varustettuja kenttiä."})) + (def generate-utp-service-descriptions-prompt "Laadi tämän viestin lopussa olevan JSON-rakenteen kuvaamasta liikuntapaikasta tiivistelmä (max 150 merkkiä) ja pidempi tekstikuvaus, jotka sopivat Palvelutietovarannossa palvelun - kuvaukseen. Haluan vastauksen muodossa {\"description\": - {...käännökset...}, \"summary\" {...käännökset...}}. %s") + kuvaukseen. %s") (defn generate-ptv-service-descriptions [doc] @@ -145,5 +194,4 @@ Provide answers in English, Finnish, and Swedish. Different language versions ca (comment (get-models openai-config) - (complete openai-config ptv-system-instruction "Why volcanoes erupt?") - ) + (complete openai-config ptv-system-instruction "Why volcanoes erupt?")) diff --git a/webapp/src/clj/lipas/backend/ptv/core.clj b/webapp/src/clj/lipas/backend/ptv/core.clj new file mode 100644 index 000000000..a963cb302 --- /dev/null +++ b/webapp/src/clj/lipas/backend/ptv/core.clj @@ -0,0 +1,283 @@ +(ns lipas.backend.ptv.core + (:require [clojure.java.jdbc :as jdbc] + [lipas.backend.core :as core] + [lipas.backend.db.db :as db] + [lipas.backend.gis :as gis] + [lipas.backend.ptv.ai :as ai] + [lipas.backend.ptv.integration :as ptv] + [lipas.backend.search :as search] + [lipas.data.ptv :as ptv-data] + [lipas.data.types :as types] + [lipas.utils :as utils] + [taoensso.timbre :as log])) + +(defn get-ptv-integration-candidates + [search criteria] + (ptv/get-eligible-sites search criteria)) + +(defn generate-ptv-descriptions + [{:keys [client indices] :as _search} + lipas-id] + (let [idx (get-in indices [:sports-site :search]) + doc (-> (search/fetch-document client idx lipas-id) + :body + :_source)] + (-> (ai/generate-ptv-descriptions doc) + :message + :content))) + +(defn generate-ptv-descriptions-from-data + [doc] + (let [doc (core/enrich doc)] + (-> (ai/generate-ptv-descriptions doc) + :message + :content))) + +(defn translate-to-other-langs + [doc] + (-> (ai/translate-to-other-langs doc) + :message + :content + ;; Ensure the original from texts are kept as-is + (assoc-in [:summary (keyword (:from doc))] (:summary doc)) + (assoc-in [:description (keyword (:from doc))] (:description doc)))) + +(defn make-overview + [sites] + {:city-name (->> sites first :search-meta :location :city :name) + :service-name (->> sites first :search-meta :type :sub-category :name) + :sports-facilities (for [site sites] + {:type (-> site :search-meta :type :name :fi)})}) + +(defn generate-ptv-service-descriptions + [search + {:keys [_id sub-category-id city-codes overview]}] + (let [doc (or overview + (let [type-codes (->> (types/by-sub-category sub-category-id) + (map :type-code)) + sites (ptv/get-eligible-sites search {:type-codes type-codes + :city-codes city-codes + :owners ["city" "city-main-owner"]})] + (make-overview sites)))] + (-> (ai/generate-ptv-service-descriptions doc) + :message + :content))) + +(defn upsert-ptv-service! + [ptv {:keys [source-id] :as m}] + (let [data (ptv-data/->ptv-service m)] + ;; We have the source-id always? + ; (if source-id + ; (ptv/update-service ptv source-id data) + ; (ptv/create-service ptv data)) + ;; PTV update using sourceId gives 404 if the sourceId doesn't exist yet + (try + (ptv/update-service ptv source-id data) + (catch clojure.lang.ExceptionInfo e + (if (= 404 (:status (:resp (ex-data e)))) + (ptv/create-service ptv data) + (throw e)))))) + +(defn fetch-ptv-org + [ptv org-id] + (ptv/get-org ptv org-id)) + +(defn fetch-ptv-service-collections + [ptv org-id] + (ptv/get-org-service-collections ptv org-id)) + +(defn fetch-ptv-services + [ptv org-id] + (ptv/get-org-services ptv org-id)) + +(defn fetch-ptv-service-channels + [ptv org-id] + (ptv/get-org-service-channels ptv org-id)) + +(def persisted-ptv-keys [:languages + :summary + :description + :last-sync + :org-id + :sync-enabled + :service-integration + :descriptions-integration + :service-channel-integration + :service-ids + :service-channel-ids]) + +(defn upsert-ptv-service-location!* + [tx ptv-component search user {:keys [org-id site ptv archive?] :as _m}] + (let [id (-> ptv :service-channel-ids first) + ;; merge or just replace? + site (update site :ptv merge ptv) + ;; Use the same TS for sourceId, ptv last-sync and site event-date + now (utils/timestamp) + data (ptv-data/->ptv-service-location org-id gis/wgs84->tm35fin-no-wrap now (core/enrich site)) + data (cond-> data + archive? (assoc :publishingStatus "Deleted")) + ptv-resp (if id + (ptv/update-service-location ptv-component id data) + (ptv/create-service-location ptv-component data)) + ;; Store the new PTV info to Lipas DB + new-ptv-data (-> ptv + (select-keys persisted-ptv-keys) + (assoc :last-sync now + ;; Store the current type-code into ptv data, so this can be + ;; used to comapre if the services need to recalculated on site data update. + :previous-type-code (:type-code (:type site)) + :source-id (:sourceId ptv-resp) + ;; Store the PTV status so we can ignore Lipas archived places that we already archived in PTV. + :publishing-status (:publishingStatus ptv-resp) + ;; Take the created ID from ptv response and store to Lipas DB right away. + ;; TODO: Is there a case where this could be multiple ids? + :service-channel-ids [(:id ptv-resp)]) + (cond-> + archive? (dissoc :source-id + :service-channel-ids)))] + + (log/infof "Upserted (Lipas status: %s, updated: %s) service-location %s: %s" (:status site) (boolean id) data new-ptv-data) + + [ptv-resp new-ptv-data])) + +(defn upsert-ptv-service-location! + [db ptv-component search user {:keys [lipas-id org-id ptv archive?]}] + ;; FIXME: This is called from inside tx in save-sports-site! is that a problem? + ;; FIXME: Separate version from this fn for use in sync-ptv! which doesn't load the + ;; sports site from db etc.? + (jdbc/with-db-transaction [tx db] + (let [site (db/get-sports-site db lipas-id) + _ (assert (some? site) (str "Sports site " lipas-id " not found in DB")) + + [ptv-resp new-ptv-data] (upsert-ptv-service-location!* tx ptv-component search user + {:org-id org-id + :site site + :ptv ptv + :archive? archive?})] + + (let [resp (core/upsert-sports-site! tx + user + (assoc site + :event-date (:last-sync new-ptv-data) + :ptv new-ptv-data) + false)] + (core/index! search resp :sync)) + + ;; No need to re-index for search after ptv change + + {;; Return the updated :ptv meta for sports-site, to for the app-db + :ptv new-ptv-data + ;; Return :id :name, same as the list endpoint that is used in the UI to show the Palvelupaikka autocomplete + :ptv-resp {:id (:id ptv-resp) + :name (some (fn [x] + (when (and (= "Name" (:type x)) + (= "fi" (:language x))) + (:value x))) + (:serviceChannelNames ptv-resp))}}))) + +(comment + + (let [ptv-component (:lipas/ptv integrant.repl.state/system) + org-id ptv-data/liminka-org-id-test + services (:itemList (ptv/get-org-services ptv-component org-id))] + (->> services + (utils/index-by :sourceId) + keys))) + +;; Used through resolve due to circular dep +;; TODO: Check if code can be moved around to avoid this +^{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} +(defn sync-ptv! [tx search ptv-component user {:keys [sports-site ptv org-id lipas-id]}] + (try + (let [type-code (-> sports-site :type :type-code) + + previous-sent? (ptv-data/is-sent-to-ptv? sports-site) + candidate-now? (and (ptv-data/ptv-candidate? sports-site) + (ptv-data/ptv-ready? sports-site)) + + ;; If it looks like site that was previously sent to PTV is no longer + ;; a candidate, mark for it archival. + ;; The other function will mark the document Deleted when archive flag is true + to-archive? (and previous-sent? + (not candidate-now?)) + + type-code-changed? (not= type-code (:previous-type-code ptv)) + ptv (if type-code-changed? + (let [types types/all + ;; Figure out what services are available in PTV for the site organization + services (:itemList (ptv/get-org-services ptv-component org-id)) + source-id->service (->> services + (utils/index-by :sourceId)) + + ;; Check if services for the current/new site type-code exist + missing-services-input [{:service-ids #{} + :sub-category-id (-> sports-site :type :type-code types :sub-category) + :sub-cateogry (-> sports-site :search-meta :type :sub-category :name :fi)}] + missing-services (ptv-data/resolve-missing-services org-id source-id->service missing-services-input) + + ;; FE doesn't update the :ptv :service-ids, that is still handled here. + ;; This code just presumes the user has created the possibly missing Sercices + ;; in the FE first. + + _ (when (seq missing-services) + (throw (ex-info "Site needs a PTV Service that doesn't exists" + {:missing-services missing-services}))) + + ;; Remove old service-ids from :ptv data and add the new. + ;; Don't touch other service-ids in the data, those could have be added manually in UI or in PTV. + ;; NOTE: OK, PTV updates are likely lost, because our :ptv :service-ids is what the create/update from + ;; Lipas previously returned, so if PTV ServiceLocation was modified after that in PTV, we lose those changes. + old-sports-site (assoc-in sports-site [:type :type-code] (:previous-type-code ptv)) + old-service-ids (ptv-data/sports-site->service-ids types source-id->service old-sports-site) + new-service-ids (ptv-data/sports-site->service-ids types source-id->service sports-site)] + (log/infof "Site type changed %s => %s, service-ids updated %s => %s" + (:previous-type-code ptv) type-code + old-service-ids new-service-ids) + (update ptv :service-ids (fn [ids] + (let [x (set ids) + x (apply disj x old-service-ids) + x (into x new-service-ids)] + (vec x))))) + ptv) + + [_ptv-resp new-ptv-data] (upsert-ptv-service-location!* tx ptv-component search user + {:org-id org-id + :ptv ptv + :site sports-site + :archive? to-archive?})] + + (let [resp (core/upsert-sports-site! tx + user + (assoc sports-site + :event-date (:last-sync new-ptv-data) + :ptv new-ptv-data) + false)] + (core/index! search resp :sync)) + + new-ptv-data) + (catch Exception e + (let [new-ptv-data (assoc ptv :error {:message (.getMessage e) + :data (ex-data e)})] + (log/infof e "Sports site updated but PTV integration had an error") + (let [resp (core/upsert-sports-site! tx + user + (-> sports-site + (assoc :event-date (utils/timestamp)) + (assoc :ptv new-ptv-data)) + false)] + (core/index! search resp :sync) + (:ptv resp)))))) + +(defn save-ptv-integration-definitions + "Saves ptv definitions under key :ptv. Does not notify webhooks, + integrations or analysis queues since they're not likely interested + in this." + [db search user lipas-id->ptv-meta] + (jdbc/with-db-transaction [tx db] + (doseq [[lipas-id ptv] lipas-id->ptv-meta] + ;; TODO take when-let -> let and add assert + (when-let [site (-> (core/get-sports-site tx lipas-id) + (assoc :event-date (utils/timestamp)) + (assoc :ptv ptv))] + (core/upsert-sports-site! tx user site) + (core/index! search site :sync))))) diff --git a/webapp/src/clj/lipas/backend/ptv/handler.clj b/webapp/src/clj/lipas/backend/ptv/handler.clj new file mode 100644 index 000000000..050723b2a --- /dev/null +++ b/webapp/src/clj/lipas/backend/ptv/handler.clj @@ -0,0 +1,197 @@ +(ns lipas.backend.ptv.handler + (:require [lipas.backend.ptv.core :as ptv-core] + [reitit.coercion.malli] + [reitit.coercion.spec])) + +(defn localized-string-schema [string-props] + [:map + {:closed true} + [:fi [:string string-props]] + [:se [:string string-props]] + [:en [:string string-props]]]) + +(def integration-enum + [:enum "lipas-managed" "manual"]) + +(def ptv-meta + [:map + {:closed true} + ;; [:org-id :string] + [:sync-enabled :boolean] + + ;; These options aren't used now: + ;; TODO: Remove + [:service-channel-integration + {:optional true} + integration-enum] + [:service-integration + {:optional true} + integration-enum] + + [:service-channel-ids [:vector :string]] + ;; [:service-ids [:vector :string]] + ;; [:languages [:vector :string]] + + [:summary (localized-string-schema {:max 150})] + [:description (localized-string-schema {})]]) + +(def create-ptv-service-location + [:map + {:closed true} + [:org-id :string] + [:lipas-id :int] + [:ptv ptv-meta]]) + +(defn routes [{:keys [db search ptv] :as _ctx}] + ["" + {:coercion reitit.coercion.malli/coercion + :tags ["ptv"] + :no-doc false} + + ["/actions/get-ptv-integration-candidates" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:city-codes [:vector :int]] + [ :type-codes {:optional true} [:vector :int]] + [:owners [:vector :string]]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/get-ptv-integration-candidates search (-> req :parameters :body))})}}] + + ["/actions/generate-ptv-descriptions" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:lipas-id :int]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/generate-ptv-descriptions + search + (-> req :parameters :body :lipas-id))})}}] + + ["/actions/generate-ptv-descriptions-from-data" + {:post + {:require-privilege :ptv/manage + :coercion reitit.coercion.spec/coercion + :parameters {:body :lipas/new-or-existing-sports-site} + :handler + (fn [req] + {:status 200 + :body (ptv-core/generate-ptv-descriptions-from-data + (-> req :parameters :body))})}}] + + ["/actions/translate-to-other-langs" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:from :string] + [:to [:set :string]] + [:summary :string] + [:description :string]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/translate-to-other-langs + (-> req :parameters :body))})}}] + + ["/actions/generate-ptv-service-descriptions" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string] + [:city-codes [:vector :int]] + [:owners [:vector :string]] + [:supported-languages [:vector [:enum "fi" "se" "en"]]] + [:sourceId :string] + [:sub-category-id :int] + [:overview {:optional true + :description "Use this to replace the AI input with non-saved site information"} + [:maybe + [:map + [:city-name (localized-string-schema nil)] + [:service-name (localized-string-schema nil)] + [:sports-facilties [:vector + [:map + [:type :string]]]]]]]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/generate-ptv-service-descriptions search (-> req :parameters :body))})}}] + + ["/actions/fetch-ptv-org" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/fetch-ptv-org ptv (-> req :parameters :body :org-id))})}}] + + ["/actions/fetch-ptv-service-collections" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/fetch-ptv-service-collections ptv (-> req :parameters :body :org-id))})}}] + + ["/actions/save-ptv-service" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string] + [:city-codes [:vector :int]] + [:source-id :string] + [:sub-category-id :int] + [:languages [:vector [:enum "fi" "se" "en"]]] + [:summary (localized-string-schema {:max 150})] + [:description (localized-string-schema nil)]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/upsert-ptv-service! ptv (-> req :parameters :body))})}}] + + ["/actions/fetch-ptv-services" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/fetch-ptv-services ptv (-> req :parameters :body :org-id))})}}] + + ["/actions/fetch-ptv-service-channels" + {:post + {:require-privilege :ptv/manage + :parameters {:body [:map + [:org-id :string]]} + :handler + (fn [req] + {:status 200 + :body (ptv-core/fetch-ptv-service-channels ptv (-> req :parameters :body :org-id))})}}] + + ["/actions/save-ptv-service-location" + {:post + {:require-privilege :ptv/manage + :parameters {:body create-ptv-service-location} + :handler + (fn [{:keys [body-params identity]}] + {:status 200 + :body (ptv-core/upsert-ptv-service-location! db ptv search identity body-params)})}}] + + ["/actions/save-ptv-meta" + {:post + {:require-privilege :ptv/manage + :coercion reitit.coercion.spec/coercion + :parameters {:body :lipas.sports-site/ptv} + :handler + (fn [{:keys [identity] :as req}] + {:status 200 + :body (ptv-core/save-ptv-integration-definitions db search identity (-> req :parameters :body))})}}]]) diff --git a/webapp/src/clj/lipas/backend/ptv/integration.clj b/webapp/src/clj/lipas/backend/ptv/integration.clj new file mode 100644 index 000000000..ea84f332f --- /dev/null +++ b/webapp/src/clj/lipas/backend/ptv/integration.clj @@ -0,0 +1,292 @@ +(ns lipas.backend.ptv.integration + (:require [buddy.core.codecs.base64 :as b64] + [cheshire.core :as json] + [clj-http.client :as client] + [clojure.string :as str] + [lipas.backend.search :as search] + [lipas.data.ptv :as ptv-data] + [taoensso.timbre :as log])) + +;; ptv component, :lipas/ptv has the config and :tokens atom for org-id -> auth token storage + +;; In test the credential to create API tokens is dependant on the org-id, +;; so use this fn to hardcode the credentials. +;; These keys are public so can be leaked out... +(defn get-test-credentials [org-id] + (case org-id + ;; org 6 + "3d1759a2-e47a-4947-9a31-cab1c1e2512b" + {:username "API9@testi.fi" + :password "APIinterfaceUser9-1009*"} + + ;; org 9 + "7fdd7f84-e52a-4c17-a59a-d7c2a3095ed5" + {:username "API15@testi.fi" + :password "APIinterfaceUser15-1015*"} + + ;; org 10 + "52e0f6dc-ec1f-48d5-a0a2-7a4d8b657d53" + {:username "API17@testi.fi" + :password "APIinterfaceUser17-1017*"} + + nil)) + +(defn make-url [ptv & parts] + (apply str (:api-url ptv) parts)) + +(defn unix-time [] + (/ (System/currentTimeMillis) 1000)) + +(defn expired? + [{:keys [exp]}] + (< exp (long (unix-time)))) + +(defn test-env? + [url] + (str/includes? url ".trn.suomi.fi")) + +(defn authenticate + "If API account is connected to multiple organisations, user should + define Palveluhallinta organisation ID by using apiUserOrganisation parameter. + + If parameter is not given, then token return authentication (token) + for active organization (can be check from Palveluhallinta UI). + + In test-env token seems to be valid for 24h." + [{:keys [token-url username password org-id]}] + (let [token-key (if (test-env? token-url) :ptvToken :serviceToken) ; wtf + req {:url token-url + :method :post + :as :json + :accept :json + :content-type :json + :form-params (merge {:username username + :password password} + (when org-id + {:apiUserOrganisation org-id}))}] + (-> (client/request req) + :body + token-key))) + +(defn parse-payload + [token] + (-> token (str/split #"\.") second b64/decode (String.) (json/decode keyword))) + +(defn get-token + [ptv org-id] + ;; NOTE: deref + swap + (let [x (get @(:tokens ptv) org-id)] + (if (or (not x) (expired? (:payload x))) + (let [token-props (merge {:token-url (:token-url ptv) + :username (get-in ptv [:creds :api :username]) + :password (get-in ptv [:creds :api :password]) + :org-id org-id} + (when (= "test" (:env ptv)) + (get-test-credentials org-id))) + new-token (authenticate token-props) + payload (parse-payload new-token) + x {:token new-token + :payload payload}] + (log/infof "Create token %s => %s (%s)" org-id new-token payload) + (swap! (:tokens ptv) assoc org-id x) + (:token x)) + (:token x)))) + +(defn http + ([ptv auth-org-id req] (http ptv auth-org-id req false)) + ([ptv auth-org-id req retried?] + (let [token (get-token ptv auth-org-id) + req* (-> req + (assoc :accept :json + :as :json) + (assoc-in [:headers :Authorization] (str "bearer " token))) + req* (if (:form-params req*) + (assoc req* :content-type :json) + req*)] + (try + (client/request req*) + (catch clojure.lang.ExceptionInfo e + (let [d (ex-data e)] + ;; NOTE: Looks like tokens from yesterday aren't valid the next day even if they haven't "expired" yet? + (if (and (not retried?) + (= 401 (:status d)) + (= "Bearer error=\"invalid_token\", error_description=\"The access token is not valid.\"" + (get (:headers d) "WWW-Authenticate"))) + (do + (log/infof "Invalid token, trying to get a new token and retry") + (swap! (:tokens ptv) dissoc (:auth-org-id req)) + (http ptv auth-org-id req true)) + (throw (ex-info (format "HTTP Error: %s %s" (:status d) (:body d)) + {:resp d + :req req*} + nil))))))))) + +(defn get-org + [ptv org-id] + (let [params {:url (make-url ptv "/v11/Organization/" org-id) + :method :get}] + (-> (http ptv org-id params) + :body))) + +;; Need to proxy this with auth because otherwise the API doesn't +;; return :sourceId (wtf) +(defn get-org-services + [ptv org-id] + (let [params {:url (make-url ptv "/v11/Service/list/organization") + :method :get + :query-params {:organizationId org-id}}] + (-> (http ptv org-id params) + :body))) + +(defn get-org-service-collections + [ptv org-id] + (let [params {:url (make-url ptv "/v11/ServiceCollection/organization") + :method :get + :query-params {:organizationId org-id}}] + (-> (http ptv org-id params) + :body))) + +(defn get-org-service-channels + [ptv org-id] + ;; TODO: Solve paginations, if multiple pages, lazy seq and make multiple requests? + ;; Or should we handle pagination from FE? + ;; 500 should be fine in one response, what if we have 2000-5000 for some city/org? + (let [params {:url (make-url ptv "/v11/ServiceChannel/organization/" org-id) + :method :get}] + (-> (http ptv org-id params) + :body))) + +(defn create-service + [ptv + service] + (let [org-id (:mainResponsibleOrganization service) + params {:url (make-url ptv "/v11/Service") + :method :post + :form-params service}] + (log/infof "Create PTV service %s" service) + (-> (http ptv org-id params) + :body))) + +(defn get-service + [ptv org-id service-id] + (let [params {:url (make-url ptv "/v11/Service/" service-id) + :method :get}] + (-> (http ptv org-id params) + :body))) + +(defn update-service + [ptv + source-id + data] + (log/info "Update PTV service with id " source-id "and data" data) + (let [org-id (:mainResponsibleOrganization data) + params {:url (make-url ptv "/v11/Service/SourceId/" source-id) + :method :put + :form-params data}] + (-> (http ptv org-id params) + :body))) + +(defn create-service-location + [ptv service-location] + (let [org-id (-> service-location :organizationId) + params {:url (make-url ptv "/v11/ServiceChannel/ServiceLocation") + :auth-org-id org-id + :method :post + :form-params service-location}] + (log/info "Create PTV service location" service-location) + (-> (http ptv org-id params) + :body))) + +(defn update-service-location + [ptv service-location-id data] + (let [org-id (-> data :organizationId) + params {:url (make-url ptv "/v11/ServiceChannel/ServiceLocation/" service-location-id) + :method :put + :form-params data}] + (log/infof "req %s" params) + (-> (http ptv org-id params) + :body))) + +(defn get-eligible-sites + [{:keys [indices client] :as _search} + {:keys [city-codes type-codes owners] :as _criteria}] + (let [idx-name (get-in indices [:sports-site :search]) + params {:size 5000 + :track_total_hits 50000 + :_source {:excludes ["location.geometries.*" + "search-meta.location.geometries.*" + "search-meta.location.simple-geoms.*"]} + :query + {:bool + {;; Remove huoltorakennukset - they aren't PTV candidates + :must_not [{:term {:type.type-code 7000}}] + :must + (remove nil? + [{:terms {:status.keyword ["active" "out-of-service-temporarily"]}} + (when city-codes + {:terms {:location.city.city-code city-codes}}) + (when owners + {:terms {:owner owners}}) + (when type-codes + {:terms {:type.type-code type-codes}})])}}}] + ;; TODO: Remove 7000 - huoltorakennukset + (-> (search/search client idx-name params) + :body + :hits + :hits + (->> (map :_source))))) + +(comment + (require '[clojure.java.jdbc :as sql] + '[integrant.repl.state :as state] + '[lipas.backend.core :as core] + '[lipas.utils :as utils]) + + (def ptv* (:lipas/ptv state/system)) + + (get-org-services ptv* ptv-data/liminka-org-id-test) + + ;; Delete all org services + (doseq [x (:itemList (get-org-services ptv* ptv-data/liminka-org-id-test))] + (update-service ptv* + (:sourceId x) + {:mainResponsibleOrganization ptv-data/liminka-org-id-test + :publishingStatus "Deleted"})) + + (get-service ptv* + ptv-data/liminka-org-id-test + (-> (get-org-services {} ptv-data/liminka-org-id-test) + :itemList + first + :id)) + + (require 'user) + + (doseq [search-site (get-eligible-sites (user/search) + {:city-codes [425] + :owners ["city" "city-main-owner"]}) + :let [site (core/get-sports-site (user/db) (:lipas-id search-site))]] + (let [resp (core/upsert-sports-site! (user/db) + user/robot + (-> (dissoc site :ptv) + (assoc :event-date (utils/timestamp))) + false)] + (core/index! (user/search) resp :sync))) + + (doseq [search-site (get-eligible-sites (user/search) + {:city-codes [425] + :owners ["city" "city-main-owner"]}) + :let [site (core/get-sports-site (user/db) (:lipas-id search-site))]] + (core/index! (user/search) site :sync)) + + (get-org-service-channels ptv* ptv-data/liminka-org-id-test) + + ;; Delete all org service locations + (doseq [x (:itemList (get-org-service-channels ptv* ptv-data/liminka-org-id-test))] + (update-service-location ptv* (:id x) {:organizationId ptv-data/liminka-org-id-test + :publishingStatus "Deleted"})) + + (update-service-location ptv* + "fc768bb4-268c-4054-9b88-9ecc9a943452" + {:org-id ptv-data/liminka-org-id-test + :publishingStatus "Deleted"})) diff --git a/webapp/src/clj/lipas/backend/system.clj b/webapp/src/clj/lipas/backend/system.clj index 00d96da29..37fa02d79 100644 --- a/webapp/src/clj/lipas/backend/system.clj +++ b/webapp/src/clj/lipas/backend/system.clj @@ -77,8 +77,8 @@ (defmethod ig/halt-key! :lipas/open-ai [_ _m] ) -(defmethod ig/init-key :lipas/ptv [_ _config] - ) +(defmethod ig/init-key :lipas/ptv [_ config] + (assoc config :tokens (atom {}))) (defmethod ig/halt-key! :lipas/ptv [_ _m] ) diff --git a/webapp/src/clj/lipas/integration/old_lipas/sports_site.clj b/webapp/src/clj/lipas/integration/old_lipas/sports_site.clj index 3c5042177..529a9170d 100644 --- a/webapp/src/clj/lipas/integration/old_lipas/sports_site.clj +++ b/webapp/src/clj/lipas/integration/old_lipas/sports_site.clj @@ -198,7 +198,44 @@ :padelCourtsCount :padel-courts-count :rapidCanoeingCentre :rapid-canoeing-centre? :canoeingClub :canoeing-club? - :activityServiceCompany :activity-service-company?}) + :activityServiceCompany :activity-service-company? + :bikeOrienteering? :bike-orienteering?, + :field2FlexibleRink? :field-2-flexible-rink?, + :sportSpecification :sport-specification, + :travelModeInfo :travel-mode-info, + :spaceDivisible :space-divisible, + :auxiliaryTrainingArea? :auxiliary-training-area?, + :pyramidTablesCount :pyramid-tables-count, + :lightingInfo :lighting-info, + :customerServicePoint? :customer-service-point?, + :parkourHallEquipmentAndStructures + :parkour-hall-equipment-and-structures, + :waterPoint :water-point, + :activeSpaceWidthM :active-space-width-m, + :fitnessStairsLengthM :fitness-stairs-length-m, + :mirrorWall? :mirror-wall?, + :sleddingHill? :sledding-hill?, + :highestObstacleM :highest-obstacle-m, + :skiOrienteering? :ski-orienteering?, + :freeUse :free-use, + :totalBilliardTablesCount :total-billiard-tables-count, + :boatingServiceClass :boating-service-class, + :kaisaTablesCount :kaisa-tables-count, + :yearRoundUse? :year-round-use?, + :field1FlexibleRink? :field-1-flexible-rink?, + :mobileOrienteering? :mobile-orienteering?, + :field3FlexibleRink? :field-3-flexible-rink?, + :freeCustomerUse? :free-customer-use?, + :travelModes :travel-modes, + :heightOfBasketOrNetAdjustable? + :height-of-basket-or-net-adjustable?, + :caromTablesCount :carom-tables-count, + :changingRoomsM2 :changing-rooms-m2, + :poolTablesCount :pool-tables-count, + :ringetteBoundaryMarkings? :ringette-boundary-markings?, + :hsPoint :hs-point, + :snookerTablesCount :snooker-tables-count, + :activeSpaceLengthM :active-space-length-m}) (def prop-mappings-reverse (set/map-invert prop-mappings)) diff --git a/webapp/src/clj/lipas/integration/old_lipas/transform.clj b/webapp/src/clj/lipas/integration/old_lipas/transform.clj index 69fe3e54c..1d147d041 100644 --- a/webapp/src/clj/lipas/integration/old_lipas/transform.clj +++ b/webapp/src/clj/lipas/integration/old_lipas/transform.clj @@ -4,7 +4,8 @@ [clojure.spec.alpha :as spec] [lipas.data.types :as types] [lipas.integration.old-lipas.sports-site :as old] - [lipas.utils :as utils :refer [sreplace trim]])) + [lipas.utils :as utils :refer [sreplace trim]] + [clojure.string :as str])) (def helsinki-tz (java.time.ZoneId/of "Europe/Helsinki")) @@ -86,6 +87,10 @@ (comp old/surface-materials first)) (select-keys prop-keys) (assoc :info-fi (-> m :comment)) + (update :parkour-hall-equipment-and-structures + (fn [coll] (not-empty (str/join "," coll)))) + (update :travel-modes + (fn [coll] (not-empty (str/join "," coll)))) (set/rename-keys old/prop-mappings-reverse))) old/adapt-geoms utils/clean diff --git a/webapp/src/clj/lipas/integration/ptv/core.clj b/webapp/src/clj/lipas/integration/ptv/core.clj deleted file mode 100644 index 92348d2f0..000000000 --- a/webapp/src/clj/lipas/integration/ptv/core.clj +++ /dev/null @@ -1,194 +0,0 @@ -(ns lipas.integration.ptv.core - (:require - [buddy.core.codecs.base64 :as b64] - [cheshire.core :as json] - [clj-http.client :as client] - [clojure.string :as str] - [lipas.backend.config :as config] - [lipas.backend.search :as search] - [lipas.data.ptv :as ptv-data] - [lipas.utils :as utils] - [taoensso.timbre :as log])) - -;; Test creds are OK to "leak" to VCS since they're public anyway -(def test-config - (utils/deep-merge - (get config/default-config :ptv) - {:api-url "https://api.palvelutietovaranto.trn.suomi.fi/api" - :token-url "https://palvelutietovaranto.trn.suomi.fi/api/auth/api-login" - :service-url "https://api.palvelutietovaranto.trn.suomi.fi/api/v11/Service" - :service-location-url "https://api.palvelutietovaranto.trn.suomi.fi/api/v11/ServiceChannel/ServiceLocation" - :creds - {:org-id ptv-data/uta-org-id-test - :main-user - {:username "paakayttaja41.testi@testi.fi" - :password "Paatestaaja41-1041*"} - :maintainer - {:username "" - :password ""} - :api - #_{:username "API17@testi.fi" - :password "APIinterfaceUser17-1017*"} - {:username "API9@testi.fi" - :password "APIinterfaceUser9-1009*"}}})) - -#_(def test-config - {:api-url "https://api.palvelutietovaranto.trn.suomi.fi/api" - :token-url "https://palvelutietovaranto.trn.suomi.fi/api/auth/api-login" - :service-url "https://api.palvelutietovaranto.trn.suomi.fi/api/v11/Service" - :service-location-url "https://api.palvelutietovaranto.trn.suomi.fi/api/v11/ServiceChannel/ServiceLocation" - :creds - {:org-id ptv-data/uta-org-id-test - :main-user - {:username "paakayttaja35.testi@testi.fi" - :password "Paatestaaja35-1035*"} - :maintainer - {:username "yllapitaja35.testi@testi.fi" - :password "Yllapitajatestaaja35-1035*"} - :api - {:username "API14@testi.fi" - :password "APIinterfaceUser14-1014*"}}}) - -(def current-token (atom nil)) - -(defn unix-time [] - (/ (System/currentTimeMillis) 1000)) - -(defn expired? - [{:keys [exp]}] - (< exp (long (unix-time)))) - -(defn test-env? - [url] - (str/includes? url ".trn.suomi.fi")) - -(defn authenticate - "If API account is connected to multiple organisations, user should - define Palveluhallinta organisation ID by using apiUserOrganisation parameter. - - If parameter is not given, then token return authentication (token) - for active organization (can be check from Palveluhallinta UI). - - In test-env token seems to be valid for 24h." - [{:keys [token-url username password org-id]}] - (let [token-key (if (test-env? token-url) :ptvToken :serviceToken) ; wtf - params {:headers {:Content-Type "application/json"} - :body (json/encode - (merge {:username username :password password} - (when org-id - {:apiUserOrganisation org-id})))}] - (-> (client/post token-url params) - :body - (json/decode keyword) - token-key))) - -(defn parse-payload - [token] - (-> token (str/split #"\.") second b64/decode (String.) (json/decode keyword))) - -(defn get-token - [] - (if (or (not @current-token) (expired? (:payload @current-token))) - (let [new-token (authenticate {:token-url (:token-url test-config) - :username (get-in test-config [:creds :api :username]) - :password (get-in test-config [:creds :api :password]) - :org-id (get-in test-config [:creds :org-id])})] - (:token (reset! current-token {:token new-token - :payload (parse-payload new-token)}))) - (:token @current-token))) - -(def ->ptv-service ptv-data/->ptv-service) -(def ->ptv-service-location ptv-data/->ptv-service-location) - -;; Need to proxy this with auth because otherwise the API doesn't -;; return :sourceId (wtf) -(defn get-org-services - [{:keys [service-url token] - :or {service-url (:service-url test-config) - token (get-token)}} - org-id] - (let [params {:headers {:Content-Type "application/json" - :Authorization (str "bearer " token)} - :query-params {:organizationId org-id}}] - (-> (client/get (str service-url "/list/organization") params) - :body - (json/decode keyword)))) - -(defn create-service - [{:keys [service-url token] - :or {service-url (:service-url test-config) - token (get-token)}} - service] - (let [params {:headers {:Content-Type "application/json" - :Authorization (str "bearer " token)} - :body (json/encode service)}] - (log/info "Create PTV service" service) - (-> (client/post service-url params) - :body - (json/decode keyword)))) - -(defn update-service - [{:keys [service-url token _org-id] - :or {service-url (:service-url test-config) - token (get-token)}} - service-id - data] - (log/info "Update PTV service with id " service-id "and data" data) - (let [ params {:headers {:Content-Type "application/json" - :Authorization (str "bearer " token)} - :body (json/encode data)}] - (-> (client/put (str service-url "/" service-id) params) - :body - (json/decode keyword)))) - -(defn create-service-location - [{:keys [service-location-url token _org-id] - :or {service-location-url (:service-location-url test-config) - token (get-token)}} - service-location] - (let [params {:headers {:Content-Type "application/json" - :Authorization (str "bearer " token)} - :body (json/encode service-location)}] - (log/info "Create PTV service location" service-location) - (-> (client/post service-location-url params) - :body - (json/decode keyword)))) - -(defn update-service-location - [{:keys [service-location-url token _org-id] - :or {service-location-url (:service-location-url test-config) - token (get-token)}} - service-location-id - data] - (let [params {:headers {:Content-Type "application/json" - :Authorization (str "bearer " token)} - :body (json/encode data)}] - (-> (client/put (str service-location-url "/" service-location-id) params) - :body - (json/decode keyword)))) - -(defn get-eligible-sites - [{:keys [indices client] :as _search} - {:keys [city-codes type-codes owners] :as _criteria}] - (let [idx-name (get-in indices [:sports-site :search]) - params {:size 5000 - :track_total_hits 50000 - :_source {:excludes ["location.geometries.*" - "search-meta.location.geometries.*" - "search-meta.location.simple-geoms.*"]} - :query - {:bool - {:must - (remove nil? - [{:terms {:status.keyword ["active" "out-of-service-temporarily"]}} - (when city-codes - {:terms {:location.city.city-code city-codes}}) - (when owners - {:terms {:owner owners}}) - (when type-codes - {:terms {:type.type-code type-codes}})])}}}] - (-> (search/search client idx-name params) - :body - :hits - :hits - (->> (map :_source))))) diff --git a/webapp/src/clj/lipas/maintenance.clj b/webapp/src/clj/lipas/maintenance.clj index 08bfe6628..b03ac9be0 100644 --- a/webapp/src/clj/lipas/maintenance.clj +++ b/webapp/src/clj/lipas/maintenance.clj @@ -23,7 +23,7 @@ [taoensso.timbre :as log])) (defn merge-types - [db search user type-code-from type-code-to] + [db search ptv user type-code-from type-code-to] (let [types* (merge old-types/all types/all)] (assert (contains? types* type-code-from)) (assert (contains? types/all type-code-to)) @@ -33,11 +33,11 @@ (log/info "Migrating" (count sites) "type" type-code-from " -> " type-code-to) (doseq [site sites] (log/info "Migrating" (:lipas-id site)) - (core/save-sports-site! db search user (assoc-in site [:type :type-code] type-code-to))) + (core/save-sports-site! db search ptv user (assoc-in site [:type :type-code] type-code-to))) (log/info "All done!"))) (defn duplicate-point->area-draft - [db search user type-code-from type-code-to] + [db search ptv user type-code-from type-code-to] (let [types* (merge old-types/all types/all)] (assert (contains? types* type-code-from)) (assert (contains? types/all type-code-to)) @@ -47,7 +47,7 @@ (log/info "Migrating" (count sites) "type" type-code-from "(Point) ->" type-code-to "(Polygon)") (doseq [site sites] (log/info "Migrating" (:lipas-id site)) - (core/save-sports-site! db search user + (core/save-sports-site! db search ptv user (-> site (dissoc :lipas-id) (assoc :event-date (utils/timestamp)) diff --git a/webapp/src/cljc/lipas/data/activities.cljc b/webapp/src/cljc/lipas/data/activities.cljc index 1b6285412..f05dcef42 100644 --- a/webapp/src/cljc/lipas/data/activities.cljc +++ b/webapp/src/cljc/lipas/data/activities.cljc @@ -227,10 +227,10 @@ ;; NOTE: select default value has to be be manually applied into the data during save or somewhere ;; for this field, it is done on lipas.ui.utils/make-saveable :default "active" - :label {:fi "UTP tietojen tila" + :label {:fi "UTP-tietojen tila" :se "Status för UTP-information" :en "Status of UTP data"} - :description {:fi "Aktiivisia tietoja voidaan siirtaa Lipas-järjestelmästä eteenpäin. Luonnos tilaiset tiedot eivät siirry eteenpäin." + :description {:fi "Aktiivisia tietoja voidaan siirtää Lipas-järjestelmästä eteenpäin. Luonnos-tilaiset tiedot eivät siirry eteenpäin." :se "Aktiv data kan överföras vidare från Lipas-systemet. Data med status utkast överförs inte vidare." :en "Active data can be transferred onward from the Lipas system. Draft status data will not be transferred onward."} :opts status-opts}} @@ -496,7 +496,7 @@ :description {:fi "Jos kohde on pyhiinvaellusreitti, aktivoi liukukytkin. HUOM! Pyhiinvaellusreitti on ulkoilureitti, joka tarjoaa mahdollisuuden liikkumiseen, hiljentymiseen ja hengellisyyteen/henkisyyteen.  Reitin varrelle on rakennettu mobiilisti tai maastoon opasteita ja sisältöjä, jotka ohjaavat vaeltajaa." :se "" :en ""} - :label {:fi "Pyhinvaelluskohde" + :label {:fi "Pyhiinvaelluskohde" :se "" :en "Pilgrimage destination"}}}) diff --git a/webapp/src/cljc/lipas/data/materials.cljc b/webapp/src/cljc/lipas/data/materials.cljc index aa3b058f6..4b14afe92 100644 --- a/webapp/src/cljc/lipas/data/materials.cljc +++ b/webapp/src/cljc/lipas/data/materials.cljc @@ -48,7 +48,7 @@ "fiberglass" {:fi "Lasikuitu" :se "Glasfiber" :en "Fiberglass"} - "soil" {:fi "Maa / luonnonmukainen" + "soil" {:fi "Maa/luonnonmukainen" :se "Jordet" :en "Soil"} "wood" {:fi "Puu" @@ -57,9 +57,9 @@ "glass" {:fi "Lasi" :se nil :en "Glass"} - "synthetic" {:fi "Muovi / synteettinen" - :se "Plast / syntetisk" - :en "Plastic / synthetic"} + "synthetic" {:fi "Muovi/synteettinen" + :se "Plast/syntetisk" + :en "Plastic/synthetic"} "grass" {:fi "Nurmi" :se "Gräs" :en "Grass"} diff --git a/webapp/src/cljc/lipas/data/prop_types.cljc b/webapp/src/cljc/lipas/data/prop_types.cljc index 74da14d8c..35a80236e 100644 --- a/webapp/src/cljc/lipas/data/prop_types.cljc +++ b/webapp/src/cljc/lipas/data/prop_types.cljc @@ -2,11 +2,1818 @@ "Type codes went through a major overhaul in the summer of 2024. This namespace represents the changes made." (:require - [lipas.data.types :as types] - [lipas.data.prop-types-new :as new] - [lipas.data.prop-types-old :as old])) + [lipas.data.types :as types])) -(def all old/all) +(def all + {:height-m + {:name + {:fi "Tilan korkeus m", :se "Utrymmets höjd", :en "Venue's height"}, + :data-type "numeric", + :description + {:fi "Sisäliikuntatilan korkeus metreinä (matalin kohta)", + :se "Motionssalens höjd i meter (från lägsta punkten)", + :en "Height of the indoor sports facility in meters (lowest point)"}}, + :heating? + {:name {:fi "Lämmitys", :se "Uppvärmning", :en "Heating"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikassa lämmitys", + :se "Är idrottsplatsen utrustad med uppvärmning", + :en "Is there heating in the sports facility"}}, + :field-2-area-m2 + {:name + {:fi "2. kentän ala m2", + :se "Andra planens areal m2", + :en "2. field's area sq. m"}, + :data-type "numeric", + :description + {:fi "2. kentän pinta-ala neliömetreinä", + :se "Andra planens areal i kvadratmeter", + :en "2. field's area in square meters"}}, + :surface-material + {:name + {:fi "Pintamateriaali", :se "Ytmaterial", :en "Surface material"}, + :data-type "enum-coll", + :description + {:fi + "Liikunta-alueiden pääasiallinen pintamateriaali - tarkempi kuvaus liikuntapaikan eri tilojen pintamateriaalista voidaan antaa pintamateriaalin lisätietokentässä", + :se + "Huvudsakligt ytskikt för idrottsområden - en mer detaljerad beskrivning av ytskiktet i olika delar av idrottsanläggningen kan ges i tilläggsinformationsfältet för ytskikt.", + :en "Primary surface material of sports areas - a more detailed description of the surface material in different parts of the sports facility can be provided in the additional information field for surface material."}}, + :basketball-fields-count + {:name + {:fi "Koripallokentät lkm", + :se "Antalet korgbollsplaner", + :en "Basketball fields pcs"}, + :data-type "numeric", + :description + {:fi "Koripallokenttien lukumäärä", + :se "Antalet korgbollsplaner i salen", + :en "The number of basketball courts"}}, + :surface-material-info + {:name + {:fi "Pintamateriaali lisätieto", + :se "Ytterligare information om ytmaterialen", + :en "Surface material information"}, + :data-type "string", + :description + {:fi + "Syötä pintamateriaalin tarkempi kuvaus- esim. tekonurmen yleisnimitys ”esim. Kumirouhetekonurmi”, tuotenimi ja tieto täytemateriaalin laadusta (esim. biohajoava/perinteinen kumirouhe).", + :se "Ange en mer detaljerad beskrivning av ytskiktet - till exempel den allmänna beteckningen för konstgräs t.ex. gummigranulatkonstgräs, produktnamn och information om fyllnadsmaterialets kvalitet (t.ex. biologiskt nedbrytbart/traditionellt gummigranulat).", + :en "Enter a more detailed description of the surface material - for example, the general name for artificial turf “e.g., rubber granulate artificial turf,” product name, and information about the quality of the infill material (e.g., biodegradable/traditional rubber granulate)."}}, + :height-of-basket-or-net-adjustable? + {:name + {:fi "Korin tai verkon korkeus säädettävissä", + :se "Korgens eller nätets höjd är justerbar", + :en "Height of the basket or net is adjustable"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :holes-count + {:name + {:fi "Reikien/väylien lkm", + :se "Antal hål/fairways", + :en "Number of holes/fairways"}, + :data-type "numeric", + :description + {:fi "Väylien lukumäärä", :se "Antalet ranger", :en ""}}, + :skijump-hill-type + {:name + {:fi "Hyppyrimäen tyyppi", + :se "Hoppbackens typ", + :en "Type of ski jump hill"}, + :data-type "string", + :description + {:fi "Hyppyrimäen tyyppi (harjoitus, pienmäki, normaali, suurmäki)", + :se + "Typ av hoppbacke (övningsbacke, liten, normalbacke, storbacke)", + :en "Type of ski jump (training hill, small hill, normal hill, large hill)"}}, + :lifts-count + {:name {:fi "Hissit lkm", :se "Antalet skidliftar", :en "Lifts"}, + :data-type "numeric", + :description + {:fi "Hiihtohissien lukumäärä", + :se "Antal skidliftar i skidcentrumet", + :en "Number of ski lifts"}}, + :field-3-length-m + {:name + {:fi "3. kentän pituus m", + :se "Tredje planens längd m", + :en "3. field's length m"}, + :data-type "numeric", + :description + {:fi "3. kentän pituus metreinä", + :se "Tredje planens längd i meter", + :en "Length of the third field in meters"}}, + :pool-tracks-count + {:name + {:fi "1. altaan radat lkm", + :se "Första bassängens antal banor", + :en "Lanes in 1. pool"}, + :data-type "numeric", + :description + {:fi "1. altaan ratojen lukumäärä", + :se "Antal banor i första bassängen", + :en "Number of lanes in the first pool"}}, + :field-2-length-m + {:name + {:fi "2. kentän pituus m", + :se "Andra planens längd m", + :en "2. field's length m"}, + :data-type "numeric", + :description + {:fi "2. kentän pituus metreinä", + :se "Andra planens längd i meter", + :en "Length of the second field in meters"}}, + :plastic-outrun? + {:name + {:fi "Muovitettu alastulo", + :se "Plast-belagd landning", + :en "Plastic outrun"}, + :data-type "boolean", + :description + {:fi "Muovitettu hyppyrimäen alastulopaikka", + :se "Hoppbacken har plast-belagd landningsplats", + :en "Plastic-coated landing area of the ski jump"}}, + :automated-timing? + {:name + {:fi "Automaattinen ajanotto", + :se "Automatisk tidtagning", + :en "Automatic timing"}, + :data-type "boolean", + :description + {:fi "Varustus automaattiseen ajanottoon", + :se "Utrustning för automatisk tidtagning", + :en "Equipment for automatic timing"}}, + :freestyle-slope? + {:name {:fi "Kumparerinne", :se "Puckelpist", :en "Freestyle slope"}, + :data-type "boolean", + :description + {:fi "Hiihtokeskuksessa on kumparerinne", + :se "Skidcentret har en puckelpist", + :en "The ski resort has a mogul slope."}}, + :kiosk? + {:name {:fi "Kioski", :se "Kiosk", :en "Kiosk"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikalla kioski tai vastaava", + :se "Har idrottsplatsen en kiosk eller något liknande", + :en "Is there a kiosk or similar facility at the sports venue"}}, + :summer-usage? + {:name {:fi "Kesäkäyttö", :se "I sommarbruk", :en "Summer usage"}, + :data-type "boolean", + :description + {:fi "Käytössä myös kesäisin", + :se "Tillgänglig även på sommaren", + :en "Available also in the summer"}}, + :stand-capacity-person + {:name + {:fi "Katsomon kapasiteetti hlö", + :se "Läktarens person kapasitet", + :en "Stand size"}, + :data-type "numeric", + :description + {:fi "Katsomon koko kapasiteetti, henkilölukumäärä", + :se "Läktarens hela person kapasitet", + :en "Total capacity of the stands, number of people"}}, + :free-use? + {:name + {:fi "Kohde on vapaasti käytettävissä", + :se "Fri användning", + :en "Free access"}, + :data-type "boolean", + :description + {:fi + "Liikuntapaikka on vapaasti käytettävissä ilman vuorovarausta tai pääsymaksua", + :se + "Motionsplatsen är fri att användas utan tidsbokning eller entréavgift", + :en ""}}, + :pier? + {:name {:fi "Laituri", :se "Brygga", :en "Pier"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :sport-specification + {:name + {:fi "Lajitarkenne", + :se "Sportspecifikation", + :en "Sport specification"}, + :data-type "enum", + :opts + {"floor-disciplines" + {:label + {:fi "Lattialajit", + :en "Floor disciplines", + :se "Golvdiscipliner"}, + :description + {:fi "Voimistelulajit, jotka suoritetaan pääasiassa lattialla.", + :en + "Gymnastics disciplines that are primarily performed on the floor.", + :se "Gymnastikdiscipliner som huvudsakligen utförs på golvet."}}, + "apparatus-disciplines" + {:label + {:fi "Telinelajit", + :en "Apparatus disciplines", + :se "Redskapsgrenar"}, + :description + {:fi "Voimistelulajit, jotka suoritetaan erilaisilla telineillä.", + :en + "Gymnastics disciplines that are performed on various apparatus.", + :se "Gymnastikdiscipliner som utförs på olika redskap."}}, + "floor-and-apparatus" + {:label + {:fi "Lattia- ja telinelajit mahdollisia", + :en "Both floor and apparatus disciplines possible", + :se "Både golv- och redskapsgrenar möjliga"}, + :description + {:fi "Tilassa voidaan harjoitella sekä lattia- että telinelajeja.", + :en + "The space allows for practicing both floor and apparatus disciplines.", + :se + "Utrymmet möjliggör träning av både golv- och redskapsgrenar."}}, + "cheerleading-circus" + {:label + {:fi "Pääasiassa cheerleading- tai sirkusharjoittelukäyttöön", + :en "Mainly for cheerleading or circus training", + :se "Huvudsakligen för cheerleading- eller cirkusträning"}, + :description + {:fi + "Tila on ensisijaisesti tarkoitettu cheerleading- tai sirkusharjoitteluun.", + :en + "The space is primarily intended for cheerleading or circus training.", + :se + "Utrymmet är främst avsett för cheerleading- eller cirkusträning."}}, + "no-information" + {:label + {:fi "Ei tietoa", :en "No information", :se "Ingen information"}}}, + :description + {:fi "Valitse voimistelulaji, johon tila on pääasiassa tarkoitettu.", + :se "", + :en ""}}, + :may-be-shown-in-excursion-map-fi? + {:name + {:fi "Saa julkaista Retkikartta.fi-palvelussa", + :se "Får publiceras i Utflyktskarta.fi", + :en "May be shown in Excursionmap.fi"}, + :data-type "boolean", + :description + {:fi "Kohteen tiedot saa julkaista Retkikartta.fi-palvelussa", + :se "Information om motionsstället får publiceras i Retkikartta.fi", + :en ""}}, + :sprint-lanes-count + {:name + {:fi "Etusuorien lkm", + :se "Antalet raksträckor (framför läktaren)", + :en "Number of sprint lanes"}, + :data-type "numeric", + :description + {:fi "Etusuorien lukumäärä", + :se "Antalet raksträckor (framför läkataren)", + :en ""}}, + :javelin-throw-places-count + {:name + {:fi "Keihäänheittopaikat lkm", + :se "Spjutkastningsplatser st.", + :en "Number of javelin throw places"}, + :data-type "numeric", + :description + {:fi "Keihäänheittopaikkojen lukumäärä", + :se "Antalet spjutkastningsplatser", + :en ""}}, + :active-space-width-m + {:name + {:fi "Liikuntakäytössä olevan tilan leveys m", + :se "Bredd på aktivt utrymme m", + :en "Width of active space m"}, + :data-type "numeric", + :description + {:fi "Liikuntakäytössä olevan tilan leveys (m)", :se "", :en ""}}, + :tennis-courts-count + {:name + {:fi "Tenniskentät lkm", + :se "Antalet tennisplaner", + :en "Tennis courts pcs"}, + :data-type "numeric", + :description + {:fi "Tenniskenttien lukumäärä", + :se "Antalet tennisplaner", + :en "Number of tennis courts"}}, + :ski-service? + {:name {:fi "Suksihuolto", :se "Skidservice", :en "Ski service"}, + :data-type "boolean", + :description + {:fi "Suksihuoltopiste löytyy", + :se "Det finns en skidservicepunkt", + :en ""}}, + :field-1-length-m + {:name + {:fi "1. kentän pituus m", + :se "Första planens längd m", + :en "1. field's length m"}, + :data-type "numeric", + :description + {:fi "1. kentän pituus metreinä", + :se "Första planens längd i meter", + :en "Length of the first field in meters"}}, + :mirror-wall? + {:name {:fi "Peiliseinä", :se "Spegelvägg", :en "Mirror wall"}, + :data-type "boolean", + :description + {:fi "Liikuntatilassa vähintään yhdellä seinällä on kiinteät peilit", + :se "I idrottsutrymmet finns fasta speglar på minst en vägg.", + :en "The sports facility has fixed mirrors on at least one wall."}}, + :finish-line-camera? + {:name {:fi "Maalikamera", :se "Målkamera", :en "Finish line camera"}, + :data-type "boolean", + :description + {:fi "Liikuntapaikalla on maalikamera", + :se "Idrottsplatsen har en målkamera", + :en "The sports facility has a finish line camera."}}, + :travel-mode-info + {:name + {:fi "Kulkutavat, lisätieto", + :se "Resesätt, ytterligare information", + :en "Travel Modes, Additional Information"}, + :data-type "string", + :description + {:fi "Täsmennä soveltuvia kulkutapoja tarvittaessa", + :se "Specificera lämpliga resesätt vid behov", + :en "Specify suitable travel modes if necessary"}}, + :parking-place? + {:name + {:fi "Parkkipaikka", :se "Parkeringsplats", :en "Parking place"}, + :data-type "boolean", + :description + {:fi "Parkkipaikka käytettävissä", + :se "Tillgänglig parkeringsplats", + :en "Parking available"}}, + :canoeing-club? + {:name {:fi "Melontaseura", :se "", :en "Canoeing club"}, + :data-type "boolean", + :description + {:fi "Onko kyseessä melontaseuran tila", :se "", :en ""}}, + :total-billiard-tables-count + {:name + {:fi "Biljardipöydät yhteensä lkm", + :se "Totalt antal biljardbord", + :en "Total number of billiard tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :sledding-hill? + {:name {:fi "Pulkkamäki", :se "Pulkabacke", :en "Sledding hill"}, + :data-type "boolean", + :description + {:fi "Kohteessa on pulkkamäki", + :se "Det finns en pulkabacke på platsen.", + :en "There is a sledding hill at the location."}}, + :climbing-routes-count + {:name + {:fi "Kiipeilyreittien lkm", + :se "Antalet klättringsrutter", + :en "Climbing routes pcs"}, + :data-type "numeric", + :description + {:fi "Kiipeilyreittien lukumäärä", + :se "Antalet klättringsrutter", + :en "Number of climbing routes"}}, + :outdoor-exercise-machines? + {:name + {:fi "Kuntoilutelineitä", + :se "Gym apparater utomhus", + :en "Exercise machines outdoors"}, + :data-type "boolean", + :description + {:fi "Onko reitin varrella kuntoilulaitteita", + :se "Finns det gym apparater längs rutten", + :en "Are there fitness equipment along the route"}}, + :automated-scoring? + {:name + {:fi "Kirjanpitoautomaatti", + :se "Bokföringsautomat", + :en "Automatic scoring"}, + :data-type "boolean", + :description + {:fi "Keilaradalla on sähköinen pistelasku", + :se "Bowlingbanan har elektroniskt poängräknings system", + :en "The bowling alley has electronic scoring."}}, + :mobile-orienteering? + {:name + {:fi "Mobiilisuunnistusmahdollisuus", + :se "Mobilorientering möjlig", + :en "Mobile Orienteering Available"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :track-width-m + {:name + {:fi "Radan leveys m", :se "Banans bredd m", :en "Width of track m"}, + :data-type "numeric", + :description + {:fi "Juoksuradan, pyöräilyradan tms. leveys metreinä", + :se "Löpbanan, rundbanan el.dyl. bredd i meter", + :en "Width of the running track, cycling track, etc., in meters"}}, + :ice-climbing? + {:name {:fi "Jääkiipeily", :se "Isklättring", :en "Ice climbing"}, + :data-type "boolean", + :description + {:fi "Onko jääkiipeily mahdollista kiipeilypaikalla", + :se "Finns det möjlighet för isklättring vid klättringsplatsen", + :en "Is ice climbing possible at the climbing site"}}, + :field-length-m + {:name + {:fi "Kentän pituus m", + :se "Planens längd m", + :en "Length of field"}, + :data-type "numeric", + :description + {:fi "Kentän/kenttien pituus mahdollisine turva-alueineen metreinä", + :se "Planens/planernas längd i meter", + :en "Length of the field(s) including any safety areas in meters"}}, + :skijump-hill-material + {:name + {:fi "Vauhtimäen rakennemateriaali", + :se "Överbackens konstruktionsmaterial", + :en "Ski jump hill material"}, + :data-type "string", + :description + {:fi "Vauhtimäen rakennemateriaali", + :se "Överbackens konstruktionsmaterial (backhoppning)", + :en "Construction material of the inrun"}}, + :carom-tables-count + {:name + {:fi "Karapöydät lkm", + :se "Antal karombord", + :en "Number of carom tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :longest-slope-m + {:name + {:fi "Pisin rinne m", + :se "Längsta slalombacken m", + :en "Longest slope m"}, + :data-type "numeric", + :description + {:fi "Pisimmän rinteen pituus metreinä", + :se "Längsta slalombackens längd i meter", + :en "Length of the longest slope in meters"}}, + :circular-lanes-count + {:name + {:fi "Kiertävät radat lkm", + :se "Antalet cirkulerande löpbanor", + :en "Number of circular lanes"}, + :data-type "numeric", + :description + {:fi "Kiertävien juoksuratojen lukumäärä", + :se "Antalet cirkulerande löpbanor", + :en "Number of circular running tracks"}}, + :boat-launching-spot? + {:name + {:fi "Veneen vesillelaskupaikka", + :se "Sjösättningsplats för båtar", + :en "Place for launching a boat"}, + :data-type "boolean", + :description + {:fi "Mahdollisuus veneen vesillelaskuun", + :se "Sjösättningsplats för båtar", + :en "Possibility for boat launching"}}, + :parkour-hall-equipment-and-structures + {:name + {:fi "Parkour-salin varustelu ja rakenteet", + :se "Utrustning och strukturer i parkourhallen", + :en "Parkour hall equipment and structures"}, + :data-type "enum-coll", + :opts + {"fixed-obstacles" + {:label + {:fi "Kiinteät esteet/rakennelmat", + :en "Fixed obstacles/structures", + :se "Fasta hinder/strukturer"}, + :description + {:fi + "Pysyvästi asennetut esteet tai rakennelmat harjoittelua varten.", + :en + "Permanently installed obstacles or structures for training purposes.", + :se + "Permanent installerade hinder eller strukturer för träningsändamål."}}, + "movable-obstacles" + {:label + {:fi "Liikkuvat esteet/rakennelmat", + :en "Movable obstacles/structures", + :se "Flyttbara hinder/strukturer"}, + :description + {:fi + "Siirrettävät tai muunneltavat esteet ja rakennelmat harjoittelua varten.", + :en + "Movable or adjustable obstacles and structures for training purposes.", + :se + "Flyttbara eller justerbara hinder och strukturer för träningsändamål."}}, + "floor-acrobatics-area" + {:label + {:fi "Permanto/akrobatiatila", + :en "Floor/acrobatics area", + :se "Golv/akrobatikområde"}, + :description + {:fi + "Avoin tila lattiaharjoittelua ja akrobaattisia liikkeitä varten.", + :en "Open space for floor exercises and acrobatic movements.", + :se "Öppet utrymme för golvövningar och akrobatiska rörelser."}}, + "gym-strength-area" + {:label + {:fi "Kuntosali-/voimailutila", + :en "Gym/strength training area", + :se "Gym/styrketräningsområde"}, + :description + {:fi + "Alue, joka on varustettu kuntosalilaitteilla ja välineillä voimaharjoittelua varten.", + :en + "Area equipped with gym machines and equipment for strength training.", + :se + "Område utrustat med gymmaskiner och utrustning för styrketräning."}}}, + :description + {:fi "Valitse parkour-salissa olevat rakenteet tai varusteet", + :se "Välj de strukturer eller utrustningar som finns i parkourhallen", + :en "Select the structures or equipment available in the parkour gym"}}, + :ski-track-traditional? + {:name + {:fi "Perinteinen latu", + :se "Skidspår för klassisk stil", + :en "Traditional ski track"}, + :data-type "boolean", + :description + {:fi "Perinteisen tyylin hiihtomahdollisuus/latu-ura", + :se "Möjlighet att skida klassisk stil", + :en "Classic style skiing track available"}}, + :altitude-difference + {:name + {:fi "Korkeusero m", + :se "Höjdskillnad m", + :en "Altitude difference"}, + :data-type "numeric", + :description + {:fi "Reitin korkeusero metreinä", + :se "Ruttens höjdskillnad i meter", + :en "Elevation difference of the route in meters"}}, + :climbing-wall-height-m + {:name + {:fi "Kiipeilyseinän korkeus m", + :se "Klätterväggens höjd m", + :en "Climbing wall height"}, + :data-type "numeric", + :description + {:fi "Kiipeilyseinän korkeus metreinä (max)", + :se "Klätterväggens höjd i meter (max)", + :en "Height of the climbing wall in meters (max)"}}, + :route-width-m + {:name + {:fi "Reitin leveys m", + :se "Ruttens bredd m", + :en "Route's width m"}, + :data-type "numeric", + :description + {:fi "Reitin leveys metreinä", :se "Banans bredd i meter", :en ""}}, + :rapid-canoeing-centre? + {:name + {:fi "Koskimelontakeskus", + :se "Centrum för paddling", + :en "Rapid canoeing centre"}, + :data-type "boolean", + :description + {:fi "Kilpailujen järjestäminen mahdollista.", + :se "Möjligt att arrangera tävlingar.", + :en "Competitions possible."}}, + :beach-length-m + {:name + {:fi "Rannan pituus m", + :se "Strandens längd m", + :en "Length of beach m"}, + :data-type "numeric", + :description + {:fi "Hoidetun rannan pituus metreinä", + :se "Skötta strandens längd i meter", + :en "Length of the maintained beach in meters"}}, + :match-clock? + {:name {:fi "Ottelukello", :se "Matchklocka", :en "Match clock"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikalla ottelukello", + :se "Finns det en matchklocka vid idrottsplatsen", + :en "Is there a match clock at the sports facility"}}, + :sprint-track-length-m + {:name + {:fi "Etusuoran pituus m", + :se "Raksträckans längd (framför läktaren)", + :en "Length of sprint track"}, + :data-type "numeric", + :description + {:fi "Juoksuradan etusuoran pituus", + :se "Längden på löpbanans raksträcka", + :en "Length of the home straight of the running track"}}, + :inner-lane-length-m + {:name + {:fi "Sisäradan pituus m", + :se "Innerbanans längd m", + :en "Length of inner lane m"}, + :data-type "numeric", + :description + {:fi "Sisäradan pituus kiertävissä radoissa", + :se "Längden på innerbanan i de cirkulerande banorna", + :en "Length of the inside lane in circular tracks"}}, + :discus-throw-places + {:name + {:fi "Kiekonheittopaikat lkm", + :se "Diskusplatser st.", + :en "Number of discus throw places"}, + :data-type "numeric", + :description + {:fi "Kiekonheittopaikkojen lukumäärä", + :se "Antalet diskuskastningsplatser", + :en "Number of discus throw areas"}}, + :fields-count + {:name + {:fi "Kenttien lkm", :se "Antalet planer", :en "Number of fields"}, + :data-type "numeric", + :description + {:fi "Montako saman tyypin kenttää liikuntapaikassa on", + :se "Hur många planer av samma typ har motionsplatsen", + :en "How many of the same type of fields are there at the sports facility"}}, + :field-1-width-m + {:name + {:fi "1. kentän leveys m", + :se "Första planens bredd m", + :en "1. field's width m"}, + :data-type "numeric", + :description + {:fi "1. kentän leveys metreinä", + :se "Första planens bredd i meter", + :en "Width of the first field in meters"}}, + :field-3-width-m + {:name + {:fi "3. kentän leveys m", + :se "Tredje planens bredd m", + :en "3. field's width m"}, + :data-type "numeric", + :description + {:fi "3. kentän leveys metreinä", + :se "Tredje planens bredd i meter", + :en "Width of the third field in meters"}}, + :field-2-width-m + {:name + {:fi "2. kentän leveys m", + :se "Andra planens bredd m", + :en "2. field's width m"}, + :data-type "numeric", + :description + {:fi "2. kentän leveys metreinä", + :se "Andra planens bredd i meter", + :en "Width of the second field in meters"}}, + :badminton-courts-count + {:name + {:fi "Sulkapallokentät lkm", + :se "Antalet badmintonsplaner", + :en "Badminton courts pcs"}, + :data-type "numeric", + :description + {:fi "Sulkapallokenttien lukumäärä salissa", + :se "Antalet badmintonsplaner i salen", + :en "Number of badminton courts in the hall"}}, + :fitness-stairs-length-m + {:name + {:fi "Kuntoportaiden pituus m", + :se "Längden på tränings trapporna m", + :en "Length of the fitness stairs m"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :free-customer-use? + {:name + {:fi "Vapaa asiakaskäyttö", + :se "Fri kundanvändning", + :en "Free customer use"}, + :data-type "boolean", + :description + {:fi + "Liikuntapaikka on asiakkaiden käytettävissä esim. kulkukortilla ilman henkilökunnan läsnäoloa. Vapaa asiakaskäyttö voi olla rajattu tiettyihin kellonaikoihin.", + :se "Idrottsanläggningen är tillgänglig för kunder, t.ex. med ett passerkort, utan personal närvarande. Fri kundanvändning kan vara begränsad till vissa tider.", + :en "The sports facility is available for customer use, e.g., with an access card, without staff presence. Free customer access may be limited to certain hours."}}, + :hammer-throw-places-count + {:name + {:fi "Moukarinheittopaikat lkm", + :se "Antalet släggkastningsplatser", + :en "Hammer throw"}, + :data-type "numeric", + :description + {:fi "Moukarinheittopaikkojen lukumäärä", + :se "Antal platser för att släggkastning", + :en "Number of hammer throw areas"}}, + :may-be-shown-in-harrastuspassi-fi? + {:name + {:fi "Saa julkaista Harrastuspassi.fi-sovelluksessa", + :se "Får publiceras i Harrastuspassi.fi", + :en "May be shown in Harrastuspassi.fi"}, + :data-type "boolean", + :description + {:fi "Kohteen tiedot saa julkaista Harrastuspassi.fi-sovelluksessa", + :se + "När du kryssat för rutan ”Kan visas på Harrastuspassi.fi” flyttas uppgifterna om idrottsanläggningen automatisk till Harrastuspassi.fi –applikationen.", + :en + "When the option ”May be shown in Harrastuspassi.fi” is ticked, the information regarding the sport facility will be transferred automatically to the Harrastuspassi.fi application."}}, + :pool-width-m + {:name + {:fi "1. altaan leveys m", + :se "Första bassängens bredd m", + :en "1. pool's width"}, + :data-type "numeric", + :description + {:fi "1. altaan/pääaltaan leveys metreinä", + :se "Första/huvudbassängens bredd i meter", + :en ""}}, + :pool-min-depth-m + {:name + {:fi "1. altaan syvyys min m", + :se "1a bassängens djup min m", + :en "1. pool's depth min m"}, + :data-type "numeric", + :description + {:fi "1. altaan syvyys matalimmasta päästä metreinä", + :se "Första bassängens grundaste punkt i meter.", + :en "The depth of the shallowest end of the pool in meters."}}, + :padel-courts-count + {:name + {:fi "Padelkentät lkm", + :se "Antalet padelbanor", + :en "Number of padel courts"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :hs-point + {:name {:fi "HS-piste", :se "HS-punkt", :en "HS Point"}, + :data-type "numeric", + :description + {:fi "Hyppyrimäen HS-piste metreinä", + :se "HS-punkten i backhoppning i meter", + :en "Ski jumping hill HS point in "}}, + :ice-rinks-count + {:name {:fi "Kaukalot lkm", :se "Antalet rinkar", :en "Ice rinks"}, + :data-type "numeric", + :description + {:fi "Kaukaloiden lukumäärä", + :se "Antalet rinkar (hockey) det finns vid idrottsplatsen", + :en "The number of rinks."}}, + :field-1-area-m2 + {:name + {:fi "1. kentän ala m2", + :se "Första planens areal m2", + :en "1. field's area sq. m"}, + :data-type "numeric", + :description + {:fi "1. kentän pinta-ala neliömetreinä", + :se "Första planens areal i kvadratmeter", + :en "The area of the field in square meters."}}, + :k-point + {:name {:fi "K-piste", :se "K-punkt", :en "K point"}, + :data-type "numeric", + :description + {:fi "Hyppyrimäen k-piste metreinä", + :se "Hoppbackens k-punkt i meter", + :en "The K-point of the ski jumping hill in meters"}}, + :polevault-places-count + {:name + {:fi "Seiväshyppypaikat lkm", + :se "Antalet stavhoppsplatser", + :en "Pole vault"}, + :data-type "numeric", + :description + {:fi "Seiväshyppypaikkojen lukumäärä", + :se "Antalet stavhoppningsplatser", + :en "The number of pole vault areas."}}, + :group-exercise-rooms-count + {:name + {:fi "Ryhmäliikuntatilat lkm", + :se "Antalet gruppmotions utrymmen", + :en "Room for exercise groups"}, + :data-type "numeric", + :description + {:fi "Liikuntasalien ja ryhmäliikuntatilojen lukumäärä", + :se "Antalet gymnastiksalar och gruppmotions utrymmen", + :en "The number of sports halls and group exercise spaces"}}, + :snowpark-or-street? + {:name + {:fi "Parkki", :se "Trick/street pist", :en "Snow park/street"}, + :data-type "boolean", + :description + {:fi + "Onko rinnehiihtokeskuksessa ns. temppurinne, snowpark tai vastaava", + :se "Har skidcentrumet en trickbana, snowpark eller något liknande", + :en "Is there a so-called adventure slope, snowpark, or similar area in the ski resort"}}, + :field-2-flexible-rink? + {:name + {:fi "2. kenttä: onko joustokaukalo?", + :se "Fält 2: finns det flexibel rink?", + :en "Field 2: is there a flexible rink?"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :space-divisible + {:name + {:fi "Tila jaettavissa osiin", + :se "Utrymmet kan delas upp", + :en "Space can be divided"}, + :helper-text + {:fi "Moneenko osaan tila on jaettavissa (lkm)", + :se "Ange antalet delar som utrymmet kan delas in i", + :en + "Enter the number of sections into which the space can be divided"}, + :data-type "number", + :description + {:fi + "Onko tila jaettavissa osiin esim. jakoseinien tai -verhojen avulla", + :se + "Är utrymmet delbart i sektioner, till exempel med skiljeväggar eller gardiner", + :en + "Is the space divisible into sections, for example, with partition walls or curtains"}}, + :max-vertical-difference + {:name + {:fi "Korkeusero max m", + :se "Höjdskillnad max m", + :en "Max vertical difference"}, + :data-type "numeric", + :description + {:fi "Suurin korkeusero rinteissä", + :se "Största höjdskillnaden i slalombackorna", + :en "The greatest elevation difference in the slopes"}}, + :bowling-lanes-count + {:name + {:fi "Keilaradat lkm", + :se "Antalet bowlingbanor", + :en "Bowling lanes"}, + :data-type "numeric", + :description + {:fi "Keilaratojen lukumäärä", :se "Antalet bowlingbanor", :en ""}}, + :air-gun-shooting? + {:name + {:fi "Ilma-aseammunta", + :se "Luftgevärsskytte", + :en "Air gun shooting"}, + :data-type "boolean", + :description + {:fi "Ilma-aseammuntamahdollisuus", + :se "Möjlighet för luftgevärsskytte", + :en "Availability of air gun shooting"}}, + :gymnastic-routines-count + {:name + {:fi "Telinevoimistelusarjat lkm", + :se "Antalet redskapsgymnastikserier", + :en "Gymnastic routines"}, + :data-type "numeric", + :description + {:fi "Telinevoimistelun telinesarjojen lukumäärä", + :se "Antalet redskap för redskapsgymnastik", + :en "The number of apparatus for gymnastics routines"}}, + :toilet? + {:name {:fi "Yleisö-wc", :se "Allmän toalett", :en "Toilet"}, + :data-type "boolean", + :description + {:fi "Onko kohteessa yleiseen käyttöön tarkoitettuja wc-tiloja?", + :se "Är allmänna toaletten i användning", + :en "Are there public restroom facilities available at the location"}}, + :gymnastics-space? + {:name + {:fi "Telinevoimistelutila", + :se "Utrymme för redskapsgymnastik", + :en "Space for gymnastics"}, + :data-type "boolean", + :description + {:fi "Onko liikuntasalissa myös telinevoimistelutila", + :se "Har motionssalen också område/utrymme för redskapsgymnastik", + :en "Is there also a gymnastics area in the sports hall"}}, + :snooker-tables-count + {:name + {:fi "Snookerpöydät lkm", + :se "Antal snookerbord", + :en "Number of snooker tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :show-jumping? + {:name {:fi "Esteratsastus", :se "Banhoppning", :en "Show jumping"}, + :data-type "boolean", + :description + {:fi + "Onko ratsastuskentällä/maneesissa esteratsastukseen soveltuva varustus", + :se "Har ridplanen/ridhuset utrustning för banhoppning", + :en "Is there equipment suitable for show jumping in the riding arena/indoor arena"}}, + :shower? + {:name {:fi "Suihku", :se "Dusch", :en "Shower"}, + :data-type "boolean", + :description + {:fi "Onko suihku käytettävissä", + :se "Är duschen i användning", + :en "Is there a shower available"}}, + :rest-places-count + {:name + {:fi "Taukopaikat lkm", + :se "Antalet viloplatser", + :en "Rest places"}, + :data-type "numeric", + :description + {:fi "Montako taukopaikkaa reitin varrella on", + :se "Hur många viloplatser finns det längs med rutten", + :en "How many rest areas are there along the route"}}, + :changing-rooms? + {:name {:fi "Pukukopit", :se "Omklädningsrum", :en "Changing rooms"}, + :data-type "boolean", + :description + {:fi "Onko pukukoppeja", :se "Finns det omklädningsrum", :en ""}}, + :pistol-shooting? + {:name + {:fi "Pistooliammunta", :se "Pistolskytte", :en "Pistol shooting"}, + :data-type "boolean", + :description + {:fi "Pistooliammuntamahdollisuus", + :se "Möjlighet för pistolskytte", + :en "Availability of pistol shooting"}}, + :halfpipe-count + {:name + {:fi "Halfpipe lkm", :se "Antal halfpipe", :en "Halfpipe count"}, + :data-type "numeric", + :description + {:fi "Halfpipe, superpipe lukumäärät", + :se "Antal halfpipe", + :en "The number of halfpipes and superpipes"}}, + :shooting-positions-count + {:name + {:fi "Ampumapaikat lkm", + :se "Antalet skytteplatser", + :en "Shooting positions"}, + :data-type "numeric", + :description + {:fi "Montako ampumapaikkaa liikuntareitin varrella on", + :se "Hur många skytteplatser finns det längs motionsrutten", + :en "How many shooting spots are there along the exercise route"}}, + :running-track-surface-material + {:name + {:fi "Juoksuradan pintamateriaali", + :se "Löpbanans ytmaterial", + :en "Surface material for running track"}, + :data-type "string", + :description + {:fi "Juoksuradan pintamateriaali/päällyste", + :se "Löpbanans ytmaterial/pålägg", + :en "The surface material/overlay of the running track"}}, + :boating-service-class + {:name + {:fi "Venesatama- tai laituriluokka", + :se "Båthamn eller bryggklass", + :en "Boat harbor or pier class"}, + :description + {:fi + "https://ava.vaylapilvi.fi/ava/Julkaisut/MKL/mkl_2008-1_venesatamien_luokitus.pdf", + :se + "https://ava.vaylapilvi.fi/ava/Julkaisut/MKL/mkl_2008-1_venesatamien_luokitus.pdf", + :en + "https://ava.vaylapilvi.fi/ava/Julkaisut/MKL/mkl_2008-1_venesatamien_luokitus.pdf"}, + :data-type "enum", + :opts + {"home-harbor" + {:label {:fi "Kotisatama", :en "Home harbor", :se "Hemmahamn"}, + :description + {:fi + "Satama, jossa veneet pääasiallisesti säilytetään veneilykauden aikana ja jossa veneen omistaja joko omistaa tai hallitsee venepaikan. Satamat ovat yleensä kunnallisia, kaupallisia tai veneseurojen ylläpitämiä satamia.", + :en + "A harbor where boats are mainly stored during the boating season and where the boat owner either owns or controls the berth. Harbors are usually municipal, commercial, or maintained by boating clubs.", + :se + "En hamn där båtar huvudsakligen förvaras under båtsäsongen och där båtägaren antingen äger eller kontrollerar båtplatsen. Hamnarna är vanligtvis kommunala, kommersiella eller underhålls av båtklubbar."}}, + "visiting-harbor" + {:label + {:fi "Vierassatama (Palvelusatama, Vieraslaituri, Retkisatama)", + :en "Visiting harbor", + :se "Besökshamn"}, + :description + {:fi + "Satama, jossa veneretken tai matkapurjehduksen aikana voi käydä kaupassa, asioimassa, lepäämässä, yöpymässä tai veneen huollossa.", + :en + "A harbor where during a boating trip or sailing voyage you can go shopping, run errands, rest, stay overnight, or service the boat.", + :se + "En hamn där du under en båttur eller seglingsresa kan handla, göra ärenden, vila, övernatta eller serva båten."}}, + "safety-harbor" + {:label + {:fi "Turvasatama (Suojasatama, Hätäsatama)", + :en "Safety harbor", + :se "Säkerhetshamn"}, + :description + {:fi + "Satama, josta voidaan hakea suojaa tai saada ensiapua tai korjausapua.", + :en + "A harbor where you can seek shelter or get first aid or repair assistance.", + :se + "En hamn där man kan söka skydd eller få första hjälp eller reparationshjälp."}}, + "canoe-pier" + {:label {:fi "Melontalaituri", :en "Canoe pier", :se "Kanotbrygga"}, + :description + {:fi "Melontaan tarkoitettu laituri.", + :en "Pier intended for canoeing.", + :se "Brygga avsedd för kanotpaddling."}}, + "no-class" + {:label + {:fi + "Kohde ei täytä minkään venesatama- tai laituriluokan vaatimuksia (esim. rantautumispaikka)", + :en + "The target does not meet the requirements of any marina or dock class (e.g., landing place)", + :se + "Objektet uppfyller inte kraven för någon småbåtshamn- eller bryggklass (t.ex. landningsplats)"}, + :description + {:fi + "Kohde ei täytä minkään venesatama- tai laituriluokan vaatimuksia (esim. rantautumispaikka).", + :en + "The site does not meet the requirements of any boat harbor or pier class (e.g., landing place).", + :se + "Platsen uppfyller inte kraven för någon båthamn eller bryggklass (t.ex. landningsplats)."}}, + "no-information" + {:label + {:fi "Ei tietoa", + :se "Ingen information", + :en "No information"}}}}, + :tatamis-count + {:name + {:fi "Tatamit ja mattoalueet lkm", + :se "Antal tatami- och mattområden", + :en "Tatamis and mat areas"}, + :data-type "numeric", + :description + {:fi "Tatamien ja mattoalueiden lukumäärä", + :se "Antal tatami- och mattområden", + :en "Number of tatami and mat areas"}}, + :lit-route-length-km + {:name + {:fi "Valaistua reittiä km", + :se "Belyst rutt km", + :en "Lit route's length km"}, + :data-type "numeric", + :description + {:fi "Montako kilometriä reitistä on valaistua", + :se "Hur många km av rutten är uppbelyst", + :en "How many kilometers of the route are illuminated"}}, + :area-m2 + {:name + {:fi "Liikuntapinta-ala m2", + :se "Areal m2", + :en "Area in square meters"}, + :data-type "numeric", + :description + {:fi "Liikuntapaikan liikuntapinta-ala, neliömetreinä", + :se "Idrottsplatsens areal i kvadratmeter", + :en "The sports area of the facility in square meters"}}, + :field-width-m + {:name + {:fi "Kentän leveys m", :se "Planens bredd m", :en "Width of field"}, + :data-type "numeric", + :description + {:fi "Kentän/kenttien leveys mahdollisine turva-alueineen metreinä", + :se "Planens/planernas bredd i meter", + :en "The width of the field(s), including any safety zones, in meters"}}, + :cosmic-bowling? + {:name {:fi "Hohtokeilaus", :se "Discobowling", :en "Cosmic bowling"}, + :data-type "boolean", + :description + {:fi "Onko keilaradalla hohtokeilausmahdollisuus", + :se "Har bowlingsbanan möjlighet för discobowling", + :en "Is there an option for glow bowling at the bowling alley"}}, + :travel-modes + {:name {:fi "Kulkutavat", :se "Resesätt", :en "Travel Modes"}, + :data-type "enum-coll", + :opts + {"by-foot" + {:label {:fi "Jalan", :en "On Foot", :se "Till fots"}, + :description + {:fi "Liikkuminen jalkaisin", + :en "Traveling on foot", + :se "Att resa till fots"}}, + "snow-shoes" + {:label + {:fi "Lumikengillä", :en "With Snowshoes", :se "Med snöskor"}, + :description + {:fi "Liikkuminen lumikengillä", + :en "Traveling with snowshoes", + :se "Att resa med snöskor"}}, + "fat-bike" + {:label + {:fi "Läskipyörällä", :en "With Fat Bike", :se "Med fatbike"}, + :description + {:fi "Liikkuminen läskipyörällä", + :en "Traveling with a fat bike", + :se "Att resa med fatbike"}}}, + :description + {:fi "Lisää reitille soveltuvat kulkutavat", + :se "Lägg till lämpliga resesätt för rutten", + :en "Add suitable travel modes for the route"}}, + :wrestling-mats-count + {:name + {:fi "Painimatot lkm", + :se "Brottarmattor st.", + :en "Wrestling mats pcs"}, + :data-type "numeric", + :description + {:fi "Painimattojen lukumäärä", :se "Antal brottarmattor", :en ""}}, + :lighting-info + {:name + {:fi "Valaistuksen lisätieto", + :se "Ytterligare information om belysningen", + :en "Additional information about the lighting"}, + :data-type "string", + :description + {:fi "Esim. lux-määrä tai muu tarkentava tieto", + :se "T.ex. lux-mängd eller annan förtydligande information", + :en "E.g. lux amount or other specifying information"}}, + :eu-beach? + {:name {:fi "EU-uimaranta", :se "EU-badstrand", :en "EU beach"}, + :data-type "boolean", + :description + {:fi + "Uimaranta, joka täyttää EU-kriteerit uimaveden laadusta ja valvonnasta", + :se + "Badstrand, som fyller EU-kriterierna med kvaliteten och övervakningen av badvattnet", + :en "A swimming beach that meets EU criteria for bathing water quality and monitoring"}}, + :rifle-shooting? + {:name + {:fi "Kivääriammunta", :se "Gevärbana", :en "Rifle shooting places"}, + :data-type "boolean", + :description + {:fi "Kivääriammuntamahdollisuus", + :se "Möjlighet för gevärskytte", + :en "Availability of rifle shooting"}}, + :swimming-pool-count + {:name + {:fi "Altaiden lukumäärä", + :se "Antalet simbassänger", + :en "Number of swimming pools"}, + :data-type "numeric", + :description + {:fi + "Altaiden lukumäärä yhteensä. Syötä tieto tai laske automaattisesti.", + :se "Antalet simbassänger, också terapi bassänger", + :en "Total number of pools. Enter the information or calculate automatically."}}, + :pool-water-area-m2 + {:name + {:fi "Vesipinta-ala m2", + :se "Bassängernas vatten areal", + :en "Pool water area in sq. m"}, + :data-type "numeric", + :description + {:fi "Asiakaskäytössä oleva vesipinta-ala yhteensä.", + :se "Den totala vattenytan tillgänglig för kunder.", + :en "The total water surface area available for customers."}}, + :highest-obstacle-m + {:name + {:fi "Korkeimman esteen korkeus m", + :se "Höjden på den högsta hindret m", + :en "The height of the highest obstacle m"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :year-round-use? + {:name + {:fi "Ympärivuotinen käyttö", + :se "Året runt användning", + :en "Year-round Use"}, + :data-type "boolean", + :description + {:fi "Kohde on ympärivuotisessa käytössä", + :se "Platsen är i användning året runt", + :en "The location is in use year-round"}}, + :curling-lanes-count + {:name + {:fi "Curling-ratojen lukumäärä", + :se "Antal curlingbanor", + :en "Count of curling lanes"}, + :data-type "numeric", + :description + {:fi "Curling-ratojen lukumäärä", + :se "Antal curlingbanor", + :en "Number of curling lanes"}}, + :climbing-wall-width-m + {:name + {:fi "Kiipeilyseinän leveys m", + :se "Klätterväggens bredd m", + :en "Climbing wall width"}, + :data-type "numeric", + :description + {:fi "Kiipeilyseinän leveys metreinä sivusuunnassa", + :se "Bredden på klätterväggen i meter i sidled", + :en "Width of the climbing wall in meters laterally"}}, + :area-km2 + {:name + {:fi "Pinta-ala km2", + :se "Areal km2", + :en "Area in square kilometres"}, + :data-type "numeric", + :description + {:fi "Alueen pinta-ala neliökilometreinä", + :se "Områdets areal i kvadratkilometer", + :en "Area of the region in square kilometers"}}, + :scoreboard? + {:name {:fi "Tulostaulu", :se "Resultattavla", :en "Score board"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikalla tulostaulu/sähköinen tulostaulu", + :se "Finns det en resultattavla/elektronisk resultattavla på idrottsplatsen", + :en "Is there a scoreboard/electronic scoreboard at the sports facility"}}, + :futsal-fields-count + {:name + {:fi "Futsal-kentät lkm", + :se "Antalet Futsal-planer", + :en "Number of futsal fields"}, + :data-type "numeric", + :description + {:fi "Futsal-kenttien lukumäärä", + :se "Antal futsalplaner", + :en "Number of futsal courts"}}, + :ski-orienteering? + {:name + {:fi "Hiihtosuunnistus mahdollista", + :se "Skidorientering möjlig", + :en "Ski Orienteering Possible"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :training-wall? + {:name + {:fi "Lyöntiseinä", + :se "Vägg att träna på tennis", + :en "Training wall for tennis"}, + :data-type "boolean", + :description + {:fi "Onko tenniskentällä lyöntiseinä", + :se "Finns det en träningsvägg vid tennisplanen", + :en "Is there a hitting wall on the tennis court"}}, + :shotput-count + {:name + {:fi "Kuulantyöntöpaikat lkm", + :se "Antalet kulstötningsplatser", + :en "Shot put"}, + :data-type "numeric", + :description + {:fi "Kuulantyöntöpaikkojen lukumäärä", + :se "Antal kulstötningsplatser", + :en "Number of shot put areas"}}, + :active-space-length-m + {:name + {:fi "Liikuntakäytössä olevan tilan pituus m", + :se "Längd på aktivt utrymme m", + :en "Length of active space m"}, + :data-type "numeric", + :description + {:fi "Liikuntakäytössä olevan tilan pituus (m)", :se "", :en ""}}, + :longjump-places-count + {:name + {:fi "Pituus- ja kolmiloikkapaikat lkm", + :se "Antalet längd- och trestegshopp platser", + :en "Long jump"}, + :data-type "numeric", + :description + {:fi "Pituus- ja kolmiloikkapaikkojen lukumäärä", + :se "Antal längd- och trestegshoppsplatser", + :en "Number of long jump and triple jump areas"}}, + :football-fields-count + {:name + {:fi "Jalkapallokentät lkm", + :se "Antalet fotbollsplaner", + :en "Football fields pcs"}, + :data-type "numeric", + :description + {:fi "Montako jalkapallokenttää mahtuu saliin/halliin", + :se "Hur många fotbollsplaner ryms i salen/hallen", + :en "How many football (soccer) fields can fit in the hall"}}, + :floorball-fields-count + {:name + {:fi "Salibandykentät lkm", + :se "Antalet innebandyplaner", + :en "Floor ball field"}, + :data-type "numeric", + :description + {:fi "Salibandykenttien lukumäärä", + :se "Antalet innebandyplaner", + :en "Number of floorball courts"}}, + :auxiliary-training-area? + {:name + {:fi "Oheisharjoittelutila", + :se "Kompletterande träningsområde", + :en "Auxiliary training area"}, + :data-type "boolean", + :description + {:fi + "Onko kohteessa oheisharjoitteluun soveltuva tila? Oheisharjoittelutila on liikuntapaikan käyttäjille tarkoitettu erillinen pienliikuntatila, jota voidaan käyttää esim. lämmittelyyn tai oheisharjoitteluun. Tilan koko, varustelu ja pintamateriaali ovat oheisharjoitteluun soveltuvia.", + :se "Finns det en lämplig plats för kompletterande träning på platsen? En kompletterande träningsyta är en separat mindre träningsyta avsedd för användare av idrottsanläggningen, som kan användas till exempel för uppvärmning eller kompletterande träning. Utrymmets storlek, utrustning och ytskikt är lämpliga för kompletterande träning.", + :en "Is there a suitable area for supplementary training at the facility? A supplementary training area is a separate small exercise space intended for users of the sports facility, which can be used, for example, for warm-ups or supplementary training. The size, equipment, and surface material of the area are suitable for supplementary training."}}, + :equipment-rental? + {:name + {:fi "Välinevuokraus", + :se + "Uthyrning av idrottsutrustning t.ex. slalom,skidning, terräncyklar osv.", + :en "Equipment rental"}, + :data-type "boolean", + :description + {:fi "Välinevuokraus mahdollista", + :se "Möjlighet att hyra utrustning", + :en "Equipment rental available"}}, + :slopes-count + {:name + {:fi "Rinteiden lkm", + :se "Antalet slalombackar", + :en "Number of slopes"}, + :data-type "numeric", + :description + {:fi "Rinteiden määrä yhteensä", + :se "Totala antalet slalombackar", + :en "Total number of slopes"}}, + :pool-length-m + {:name + {:fi "1. altaan pituus m", + :se "Första bassängens längd", + :en "1. pool's length"}, + :data-type "numeric", + :description + {:fi "1. altaan/pääaltaan pituus metreinä", + :se "Första/huvudbassängens längd i meter", + :en "The length of the pool/main pool in meters."}}, + :other-pools-count + {:name + {:fi "Muut altaat lkm", + :se "Antalet andra bassänger", + :en "Number of other pools"}, + :data-type "numeric", + :description + {:fi "Porealtaiden, kylmäaltaiden yms lukumäärä yhteensä", + :se "Antalet övriga bassänger såsom bubbelpool, kallbassäng o.dyl.", + :en "Total number of hot tubs, cold pools, etc."}}, + :shortest-slope-m + {:name + {:fi "Lyhin rinne m", + :se "Kortaste slalombacken m", + :en "Shortest slope m"}, + :data-type "numeric", + :description + {:fi "Lyhimmän rinteen pituus metreinä", + :se "Kortaste skidbacken i meter", + :en "Length of the shortest slope in meters"}}, + :pool-tables-count + {:name + {:fi "Poolpöydät lkm", + :se "Antal poolbord", + :en "Number of pool tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :squash-courts-count + {:name + {:fi "Squash-kentät lkm", + :se "Antalet squashplaner", + :en "Squash courts"}, + :data-type "numeric", + :description + {:fi "Squash-kenttien lukumäärä", + :se "Antalet squash-planer", + :en "The number of squash courts"}}, + :changing-rooms-m2 + {:name + {:fi "Pukukoppien kokonaispinta-ala m²", + :se "Omklädningsrummens totala yta m²", + :en "Total area of the changing rooms in m²"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :ringette-boundary-markings? + {:name + {:fi "Ringeten rajamerkinnät", + :se "Gränsmarkeringar för ringette", + :en "Ringette boundary markings"}, + :data-type "boolean", + :description + {:fi "Onko kaukaloissa ringeten rajamerkinnät?", :se "", :en ""}}, + :boxing-rings-count + {:name + {:fi "Nyrkkeilykehät lkm", + :se "Antalet boxningsringar", + :en "Boxing rings pcs"}, + :data-type "numeric", + :description + {:fi "Nyrkkeilykehien lukumäärä", + :se "Antalet boxningsringar", + :en "The number of boxing rings"}}, + :ice-reduction? + {:name {:fi "Jäätymisenesto", :se "Frostskydd", :en "Ice reduction"}, + :data-type "boolean", + :description + {:fi "Jäätymisenestojärjestelmä talviuintipaikassa", + :se "Har vinterbadplatsen mekanism för frostskydd", + :en "Anti-freeze system at the winter swimming location"}}, + :activity-service-company? + {:name + {:fi "Ohjelmapalveluyritys", :se "", :en "Activity service company"}, + :data-type "boolean", + :description + {:fi "Toimiiko kohteessa ohjelmapalveluyritys.", :se "", :en ""}}, + :field-1-flexible-rink? + {:name + {:fi "1. kenttä: onko joustokaukalo?", + :se "Fält 1: finns det flexibel rink?", + :en "Field 1: is there a flexible rink?"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :fencing-bases-count + {:name + {:fi "Miekkailualustat lkm", + :se "Antalet fäktnings underlag", + :en "Fencing bases"}, + :data-type "numeric", + :description + {:fi "Miekkailualustojen lukumäärä", + :se "Antal underlägg avsedda för fäktning", + :en "The number of fencing mats"}}, + :weight-lifting-spots-count + {:name + {:fi "Painonnostopaikat/nostolavat lkm", + :se "Antal tyngdlyftningsplatser/lyftplattformar", + :en "Number of weightlifting areas/platforms"}, + :data-type "numeric", + :description + {:fi + "Painnostopaikkojen lukumäärä. Huom. nostolava on painonnostopaikka, joka kestää painojen pudottamisen", + :se + "Antal tyngdlyftningsplatser. Obs: En lyftplattform är en tyngdlyftningsplats som tål att vikter tappas.", + :en + "Number of weightlifting areas. Note: A lifting platform is a weightlifting area that can withstand the dropping of weights."}}, + :landing-places-count + {:name + {:fi "Alastulomontut lkm", + :se "Antalet landningsgropar", + :en "Landing places"}, + :data-type "numeric", + :description + {:fi "Alastulomonttujen lukumäärä", + :se "Antalet landnigsgropar", + :en "The number of landing pits"}}, + :bike-orienteering? + {:name + {:fi "Pyöräsuunnistus mahdollista", + :se "Cykelorientering möjlig", + :en "Bike Orienteering Possible"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :toboggan-run? + {:name {:fi "Ohjaskelkkamäki", :se "Rodelbana", :en "Toboggan run"}, + :data-type "boolean", + :description + {:fi "Onko rinnehiihtokeskuksessa ohjaskelkkamäki", + :se "Har skidcentrumet en rodelbana", + :en "Is there a guided sledding slope at the ski resort?"}}, + :sauna? + {:name {:fi "Sauna", :se "Bastu", :en "Sauna"}, + :data-type "boolean", + :description + {:fi "Onko sauna käytettävissä", + :se "Är bastun i användning", + :en "Is there a sauna available"}}, + :jumps-count + {:name + {:fi "Hyppyrien lkm", + :se "Antalet hoppbackar", + :en "Number of jumps"}, + :data-type "numeric", + :description + {:fi "Hyppyrien lukumäärä", :se "Antalet hoppbackar", :en ""}}, + :table-tennis-count + {:name + {:fi "Pöytätennispöytien lkm", + :se "Antalet bord för bordtennis", + :en "Table tennis table count"}, + :data-type "numeric", + :description + {:fi "Pingis-/pöytätennispöytien lukumäärä", + :se "Antal bordtennisbord (pingisbord)", + :en "The number of table tennis tables"}}, + :pool-max-depth-m + {:name + {:fi "1. altaan syvyys max m", + :se "Första bassängens max djup m", + :en "1. pool's depth max m"}, + :data-type "numeric", + :description + {:fi "1. altaan syvyys syvimmästä päästä metreinä", + :se "Första bassängens djupaste punkt i meter", + :en "The depth of the deepest end of the pool in meter"}}, + :loudspeakers? + {:name {:fi "Äänentoisto", :se "Ljudåtergivning", :en "Loudspeakers"}, + :data-type "boolean", + :description + {:fi + "Onko liikuntapaikalla välineistö ja valmius kenttäkuulutuksiin", + :se + "Har motionsplatsen utrustning och färdighet till att göra utrop", + :en "Does the sports facility have equipment and readiness for making announcements"}}, + :customer-service-point? + {:name + {:fi "Myynti- tai asiakaspalvelupiste", + :en "Sales or customer service point", + :se "Försäljnings- eller kundservicepunkt"}, + :description + {:fi + "Liikuntapaikalla on pysyvä myynti- tai asiakaspalvelupiste, josta on saatavissa asiakaspalvelua. Myynti- tai asiakaspalvelupiste voi olla rajoitetusti auki liikuntapaikan käyttöaikojen puitteissa.", + :se + "Det finns en permanent försäljnings- eller kundservicestation på idrottsanläggningen där kundservice är tillgänglig. Försäljnings- eller kundservicestationen kan ha begränsade öppettider inom idrottsanläggningens användningstider.", + :en + "There is a permanent sales or customer service point at the sports facility, where customer service is available. The sales or customer service point may have limited opening hours within the usage hours of the sports facility."}, + :data-type "boolean"}, + :shotgun-shooting? + {:name + {:fi "Haulikkoammunta", + :se "Hagelgevärsskytte", + :en "Shotgun shooting"}, + :data-type "boolean", + :description + {:fi "Haulikkoammuntamahdollisuus", + :se "Möjlighet för hagelskytte", + :en "Availability of shotgun shooting"}}, + :water-point + {:name {:fi "Vesipiste", :en "Water point", :se "Vattenpunkt"}, + :description {:fi "", :se "", :en ""}, + :data-type "enum", + :opts + {"year-round" + {:label {:fi "Ympärivuotinen", :se "Året runt", :en "Year-round"}}, + "seasonal" + {:label + {:fi "Kausittaisesti käytössä", + :se "Säsongsvis i bruk", + :en "Seasonally in use"}}}}, + :lit-slopes-count + {:name + {:fi "Valaistut rinteet lkm", + :se "Antalet belysta slalombackar", + :en "Number of lit slopes"}, + :data-type "numeric", + :description + {:fi "Montako rinnettä on valaistu", + :se "Hur många belysta slalombackar finns det", + :en "How many slopes are illuminated"}}, + :green? + {:name {:fi "Puttausviheriö", :se "Puttnings green", :en "Green"}, + :data-type "boolean", + :description + {:fi "Onko golfkentällä puttausviheriö", + :se "Finns det en puttnings green vid golfbanan", + :en "Is there a putting green on the golf course"}}, + :free-rifle-shooting? + {:name + {:fi "Pienoiskivääriammunta", + :se "Miniatyrgevärsskytte", + :en "Free rifle shooting"}, + :data-type "boolean", + :description + {:fi "Pienoiskivääriammuntamahdollisuus", + :se "Möjlighet förminiatyrgevärskytte", + :en "Availability of small bore rifle shooting"}}, + :winter-usage? + {:name {:fi "Talvikäyttö", :se "Vinterbruk", :en "Winter usage"}, + :data-type "boolean", + :description + {:fi "Liikuntapaikka on käytössä myös talvisin", + :se "Motionsplatsen är i bruk under vintern", + :en ""}}, + :ligthing? + {:name {:fi "Valaistus", :se "Belysning", :en "Lighting"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikka valaistu", + :se "Är idrottsplatsen uppbelyst", + :en "Is the sports facility illuminated"}}, + :field-3-area-m2 + {:name + {:fi "3. kentän ala m2", + :se "Tredje planens areal m2", + :en "3. field's area sq. m"}, + :data-type "numeric", + :description + {:fi "3. kentän pinta-ala neliömetreinä", + :se "Tredje planens areal i kvadratmeter", + :en "The area of the field in square meters"}}, + :accessibility-info + {:name + {:fi "Linkki esteettömyystietoon", + :se "Länk till tillgänglighetsinformation", + :en "Link to accessibility information"}, + :data-type "string", + :description + {:fi + "Syötä linkki verkkosivulle, jossa on kuvattu kohteen esteettömyyteen liittyvät tiedot", + :se + "Ange länken till webbplatsen där information om objektets tillgänglighet beskrivs.", + :en + "Enter the link to the website where the accessibility information of the location is described."}}, + :covered-stand-person-count + {:name + {:fi "Katettua katsomoa hlö", + :se "Takbeläggda läktarens person mängd", + :en "Stand with roof"}, + :data-type "numeric", + :description + {:fi "Katetun katsomon henkilömäärä", + :se "Hur mycket av läktaren är täckt med tak, antalet personer", + :en "The seating capacity of the covered grandstand"}}, + :playground? + {:name {:fi "Leikkipuisto", :se "Lekpark", :en "Playground"}, + :data-type "boolean", + :description + {:fi "Onko liikuntapaikan yhteydessä leikkipuisto", + :se "Finns det en lekpark i samband med idrottsplatsen", + :en "Is there a playground associated with the sports facility"}}, + :handball-fields-count + {:name + {:fi "Käsipallokentät lkm", + :se "Antalet handbollsplaner", + :en "Handball fields pcs"}, + :data-type "numeric", + :description + {:fi "Käsipallokenttien lukumäärä salissa", + :se "Antalet handbollsplaner som ryms i salen/hallen", + :en "The number of handball courts in the hall"}}, + :p-point + {:name {:fi "P-piste", :se "P-punkt", :en "P point"}, + :data-type "numeric", + :description + {:fi "Hyppyrimäen P-piste metreinä", + :se "Hoppbackens P-punkt i meter", + :en "The P-point of the ski jumping hill in meters"}}, + :inruns-material + {:name + {:fi "Vauhtimäen latumateriaali", + :se "Överbackens spårmaterial", + :en "Inrun's material"}, + :data-type "string", + :description + {:fi "Hyppyrimäen vauhtimäen materiaali", + :se "Hoppbackens spårmaterial vid överbacken", + :en "The material of the take-off ramp of the ski jumping hill"}}, + :pyramid-tables-count + {:name + {:fi "Pyramidipöydät lkm", + :se "Antal pyramidbord", + :en "Number of pyramid tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :basketball-field-type + {:name + {:fi "Koripallokentän tyyppi", + :se "Korgbollsplanens typ", + :en "Type of basketball field"}, + :data-type "string", + :description + {:fi + "Onko liikuntapaikka normaali koripallokenttä, minikoripallokenttä vai yhden korin koripallokenttä", + :se + "Har idrottsplaten en normal korgbollsplan, mini-korgbollsplan eller bara en korg", + :en "Is the sports facility a standard basketball court, a mini basketball court, or a one-basket basketball court"}}, + :kaisa-tables-count + {:name + {:fi "Kaisapöydät lkm", + :se "Antal kaisabord", + :en "Number of kaisa tables"}, + :data-type "numeric", + :description {:fi "", :se "", :en ""}}, + :volleyball-fields-count + {:name + {:fi "Lentopallokentät lkm", + :se "Antalet volleybollplaner", + :en "Volleyball field"}, + :data-type "numeric", + :description + {:fi "Lentopallokenttien lukumäärä", + :se "Antalet volleybollplaner", + :en "The number of volleyball courts"}}, + :boat-places-count + {:name + {:fi "Venepaikat lkm", :se "Antalet båtplats", :en "Boat places"}, + :data-type "numeric", + :description + {:fi "Venepaikkojen lukumäärä", :se "Antalet båtplatser", :en ""}}, + :pool-temperature-c + {:name + {:fi "1. altaan lämpö c", + :se "Första bassängens temperatur c", + :en "1. pool's temperature c"}, + :data-type "numeric", + :description + {:fi "1. altaan veden lämpötila celsiusasteina", + :se "Första bassängens vatten temperatur i celcius", + :en "The water temperature of the pool in degrees Celsius"}}, + :climbing-wall? + {:name {:fi "Kiipeilyseinä", :se "Klättervägg", :en "Climbing wall"}, + :data-type "boolean", + :description + {:fi "Onko kohteessa kiipeilyseinä", + :se "Finns det en klättervägg på platsen", + :en "Is there a climbing wall at the location"}}, + :ski-track-freestyle? + {:name + {:fi "Luistelu-ura", :se "Fristils spår", :en "Freestyle ski track"}, + :data-type "boolean", + :description + {:fi "Vapaan tyylin latu-ura/luistelu-ura", + :se "Skidspår för fristil", + :en "Free style track/skating track"}}, + :spinning-hall? + {:name {:fi "Spinning-sali", :se "Spinning sal", :en "Spinning hall"}, + :data-type "boolean", + :description + {:fi "Salissa spinning-varustus", + :se "Salen har spinning utrustning", + :en "Spinning equipment in the hall"}}, + :other-platforms? + {:name + {:fi "Muut hyppytelineet", + :se "Andra hoppställningar", + :en "Other platforms"}, + :data-type "boolean", + :description + {:fi "Uimahyppytelineet rannalla", + :se "Hopptornen vid stranden", + :en "Diving boards at the beach"}}, + :school-use? + {:name + {:fi "Koululiikuntapaikka", + :se "Skolidrottsplats", + :en "Sport facility in school use"}, + :data-type "boolean", + :description + {:fi "Liikuntapaikkaa käytetään koulujen liikuntatunneilla", + :se "Idrottsplatsen används under skolornas gymnastiktimmar", + :en "The sports facility is used for school physical education classes"}}, + :highjump-places-count + {:name + {:fi "Korkeushyppypaikat lkm", + :se "Antalet höjdhopps platser", + :en "High jump"}, + :data-type "numeric", + :description + {:fi "Korkeushppypaikkojen lukumäärä", + :se "Antalet höjdhoppsplatser", + :en "The number of high jump areas"}}, + :light-roof? + {:name + {:fi "Kevytkate", :se "Lättvikts takläggning", :en "Light roof"}, + :data-type "boolean", + :description + {:fi "Kentälle voidaan asentaa kevytkate tai muu tilapäinen katos", + :se + "På planen kan installeras en lättvikts takläggning eller något annat tillfälligt tak", + :en "A lightweight cover or another temporary canopy can be installed on the field"}}, + :route-length-km + {:name + {:fi "Reitin pituus km", + :se "Ruttens längd km", + :en "Route's length km"}, + :data-type "numeric", + :description + {:fi "Reitin pituus kilometreinä", + :se "Ruttens längd i kilometer", + :en "The length of the route in kilometers"}}, + :field-3-flexible-rink? + {:name + {:fi "3. kenttä: onko joustokaukalo?", + :se "Fält 3: finns det flexibel rink?", + :en "Field 3: is there a flexible rink?"}, + :data-type "boolean", + :description {:fi "", :se "", :en ""}}, + :exercise-machines-count + {:name + {:fi "Kuntoilulaitteet lkm", + :se "Antalet gym apparater", + :en "Number of exercise machines"}, + :data-type "numeric", + :description + {:fi "Kuntoilulaitteiden lukumäärä", + :se "Antalet gym apparater", + :en "The number of exercise machines"}}, + :track-type + {:name {:fi "Ratatyyppi", :se "Typ av bana", :en "Type of track"}, + :data-type "string", + :description {:fi "Radan tyyppi", :se "Banans typ", :en "The type of the track"}}, + :training-spot-surface-material + {:name + {:fi "Suorituspaikan pintamateriaali", + :se "Prestationsplatsens ytmaterial", + :en "Surface material for training spot"}, + :data-type "string", + :description + {:fi "Esim. keihäänheittopaikan pintamateriaali/päällys", + :se "T.ex. spjutkastningsplatsens ytmaterial/överläggning", + :en "E.g. the surface material/overlay of the javelin throw area."}}, + :range? + {:name + {:fi "Harjoitusalue/range", :se "Övningszon/Range", :en "Range"}, + :data-type "boolean", + :description + {:fi "Onko golfin harjoitusalue/range", + :se "Finns det övningsområde/range för golf", + :en "Is there a golf practice area/range"}}, + :track-length-m + {:name + {:fi "Radan pituus m", + :se "Banans längd i m", + :en "Length of track m"}, + :data-type "numeric", + :description + {:fi "Juoksuradan, pyöräilyradan tms. pituus metreinä", + :se "Löpbanans, rundbanans el.dyl. längd i meter", + :en "The length of the running track, cycling track, etc., in meters"}}}) (def used (let [used (set (mapcat (comp keys :props second) types/all))] diff --git a/webapp/src/cljc/lipas/data/prop_types_new.cljc b/webapp/src/cljc/lipas/data/prop_types_new.cljc index a0f43b79c..e6e2fee5f 100644 --- a/webapp/src/cljc/lipas/data/prop_types_new.cljc +++ b/webapp/src/cljc/lipas/data/prop_types_new.cljc @@ -123,8 +123,8 @@ :se "Antal hål/fairways" :en "Number of holes/fairways"}) - ;; Add new :ligthing-info prop - (assoc :ligthing-info + ;; Add new :lighting-info prop + (assoc :lighting-info {:name {:fi "Valaistuksen lisätieto" :se "Ytterligare information om belysningen" :en "Additional information about the lighting"} @@ -134,7 +134,6 @@ :se "T.ex. lux-mängd eller annan förtydligande information" :en "E.g. lux amount or other specifying information"}}) - ;; Update :weight-lifting-spots-count name and description (assoc-in [:weight-lifting-spots-count :name] {:fi "Painonnostopaikat/nostolavat lkm" @@ -164,6 +163,12 @@ :se "Länk till tillgänglighetsinformation" :en "Link to accessibility information"}) + ;; Update :accessibility-info description + (assoc-in [:accessibility-info :description] + {:fi "Syötä linkki verkkosivulle, jossa on kuvattu kohteen esteettömyyteen liittyvät tiedot" + :se "Ange länken till webbplatsen där information om objektets tillgänglighet beskrivs." + :en "Enter the link to the website where the accessibility information of the location is described."}) + ;; Update Halfpipe name (assoc-in [:halfpipe-count :name] {:fi "Halfpipe lkm" @@ -213,18 +218,26 @@ :en ""}}) ;; Add new :space-divisible prop + + ;; Toteutus esim: Valinta Kyllä/Ei -> Jos kyllä, täydennettävä + ;; kenttä "Tila voidaan jakaa x osaan" (voidaan syöttää lukuarvo + ;; esim. 2, joka tarkoittaa että tila voidaan jakaa kahteen + ;; osaan). (assoc :space-divisible - {:name {:fi "Tila jaettavissa osiin" - :se "Utrymmet kan delas upp" - :en "Space can be divided"} - :data-type "boolean" + {:name {:fi "Tila jaettavissa osiin" + :se "Utrymmet kan delas upp" + :en "Space can be divided"} + :helper-text {:fi "Syötä numero moneenko osaan tila on jaettavissa" + :se "Ange antalet delar som utrymmet kan delas in i" + :en "Enter the number of sections into which the space can be divided"} + :data-type "number" :description - {:fi "" - :se "" - :en ""}}) + {:fi "Onko tila jaettavissa osiin esim. jakoseinien tai -verhojen avulla" + :se "Är utrymmet delbart i sektioner, till exempel med skiljeväggar eller gardiner?" + :en "Is the space divisible into sections, for example, with partition walls or curtains?"}}) ;; Add new :auxiliary-training-area prop - (assoc :auxiliary-training-area + (assoc :auxiliary-training-area? {:name {:fi "Oheisharjoittelutila" :se "Kompletterande träningsområde" :en "Auxiliary training area"} @@ -235,11 +248,50 @@ :en ""}}) ;; Add new :sport-specification prop + + ;; Ominaisuuden nimi esim. "Lajitarkenne". Vastaava + ;; toiminnallisuus kuin veneilyn palvelupaikoissa - eli + ;; lisätiedoissa voidaan tarkentaa, minkä voimistelulajin + ;; harrastamiseen kohde on pääasiassa tarkoitettu. Vaihtoehdot: + ;; a. Lattialajit b. Telinelajit c. Lattia- ja telinelajit + ;; mahdollisia d. Pääasiassa cheerleading- tai + ;; sirkusharjoittelukäyttöön e. Ei tietoa (assoc :sport-specification {:name {:fi "Lajitarkenne" :se "Sportspecifikation" :en "Sport specification"} - :data-type "string" + :data-type "enum" + :opts {"floor-disciplines" + {:label {:fi "Lattialajit" :en "Floor disciplines" :se "Golvdiscipliner"} + :description + {:fi "Voimistelulajit, jotka suoritetaan pääasiassa lattialla." + :en "Gymnastics disciplines that are primarily performed on the floor." + :se "Gymnastikdiscipliner som huvudsakligen utförs på golvet."}} + + "apparatus-disciplines" + {:label {:fi "Telinelajit" :en "Apparatus disciplines" :se "Redskapsgrenar"} + :description + {:fi "Voimistelulajit, jotka suoritetaan erilaisilla telineillä." + :en "Gymnastics disciplines that are performed on various apparatus." + :se "Gymnastikdiscipliner som utförs på olika redskap."}} + + "floor-and-apparatus" + {:label {:fi "Lattia- ja telinelajit mahdollisia" :en "Both floor and apparatus disciplines possible" :se "Både golv- och redskapsgrenar möjliga"} + :description + {:fi "Tilassa voidaan harjoitella sekä lattia- että telinelajeja." + :en "The space allows for practicing both floor and apparatus disciplines." + :se "Utrymmet möjliggör träning av både golv- och redskapsgrenar."}} + + "cheerleading-circus" + {:label {:fi "Pääasiassa cheerleading- tai sirkusharjoittelukäyttöön" :en "Mainly for cheerleading or circus training" :se "Huvudsakligen för cheerleading- eller cirkusträning"} + :description + {:fi "Tila on ensisijaisesti tarkoitettu cheerleading- tai sirkusharjoitteluun." + :en "The space is primarily intended for cheerleading or circus training." + :se "Utrymmet är främst avsett för cheerleading- eller cirkusträning."}} + + "no-information" + {:label {:fi "Ei tietoa" :en "No information" :se "Ingen information"}}} + :description {:fi "" :se "" @@ -283,7 +335,34 @@ {:name {:fi "Parkour-salin varustelu ja rakenteet" :se "Utrustning och strukturer i parkourhallen" :en "Parkour hall equipment and structures"} - :data-type "string" + :data-type "enum-coll" + :opts {"fixed-obstacles" + {:label {:fi "Kiinteät esteet / rakennelmat" :en "Fixed obstacles / structures" :se "Fasta hinder / strukturer"} + :description + {:fi "Pysyvästi asennetut esteet tai rakennelmat harjoittelua varten." + :en "Permanently installed obstacles or structures for training purposes." + :se "Permanent installerade hinder eller strukturer för träningsändamål."}} + + "movable-obstacles" + {:label {:fi "Liikkuvat esteet / rakennelmat" :en "Movable obstacles / structures" :se "Flyttbara hinder / strukturer"} + :description + {:fi "Siirrettävät tai muunneltavat esteet ja rakennelmat harjoittelua varten." + :en "Movable or adjustable obstacles and structures for training purposes." + :se "Flyttbara eller justerbara hinder och strukturer för träningsändamål."}} + + "floor-acrobatics-area" + {:label {:fi "Permanto/akrobatiatila" :en "Floor/acrobatics area" :se "Golv/akrobatikområde"} + :description + {:fi "Avoin tila lattiaharjoittelua ja akrobaattisia liikkeitä varten." + :en "Open space for floor exercises and acrobatic movements." + :se "Öppet utrymme för golvövningar och akrobatiska rörelser."}} + + "gym-strength-area" + {:label {:fi "Kuntosali-/voimailutila" :en "Gym/strength training area" :se "Gym/styrketräningsområde"} + :description + {:fi "Alue, joka on varustettu kuntosalilaitteilla ja välineillä voimaharjoittelua varten." + :en "Area equipped with gym machines and equipment for strength training." + :se "Område utrustat med gymmaskiner och utrustning för styrketräning."}}} :description {:fi "" :se "" @@ -412,21 +491,139 @@ (assoc-in [:auxiliary-training-area? :description :fi] "Onko kohteessa oheisharjoitteluun soveltuva tila? Oheisharjoittelutila on liikuntapaikan käyttäjille tarkoitettu erillinen pienliikuntatila, jota voidaan käyttää esim. lämmittelyyn tai oheisharjoitteluun. Tilan koko, varustelu ja pintamateriaali ovat oheisharjoitteluun soveltuvia.") ;; Ominaisuuden nimi esim. "Lajitarkenne". Vastaava toiminnallisuus kuin veneilyn palvelupaikoissa - eli lisätiedoissa voidaan tarkentaa, minkä voimistelulajin harrastamiseen kohde on pääasiassa tarkoitettu. Vaihtoehdot: a. Lattialajit b. Telinelajit c. Lattia- ja telinelajit mahdollisia d. Pääasiassa cheerleading- tai sirkusharjoittelukäyttöön e. Ei tietoa (assoc-in [:sport-specification :description :fi] "Valitse voimistelulaji, johon tila on pääasiassa tarkoitettu.") - (assoc-in [:free-use :name :fi] "Kohde on vapaasti käytettävissä") + (assoc-in [:free-use? :name :fi] "Kohde on vapaasti käytettävissä") (assoc-in [:active-space-width-m :description :fi] "Liikuntakäytössä olevan tilan leveys (m)") (assoc-in [:active-space-length-m :description :fi] "Liikuntakäytössä olevan tilan pituus (m)") (assoc-in [:mirror-wall? :description :fi] "Liikuntatilassa vähintään yhdellä seinällä on kiinteät peilit") (assoc-in [:highest-obstacle-m :description :fi] "Korkeimman esteen korkeus (m)") ;; Pintamateriaalikentän valittavissa seuraavista ne ominaisuudet, jotka saliin sopivat: a) Kiinteät esteet / rakennelmat b) Liikkuvat esteet / rakennelmat c) Permanto/akrobatiatila d) Kuntosali-/voimailutila (assoc-in [:parkour-hall-equipment-and-structures :description :fi] "Valitse parkour-salissa olevat rakenteet tai varusteet") - (assoc-in [:ringette-boundary-markings :description :fi] "Onko kaukaloissa ringeten rajamerkinnät?") + (assoc-in [:ringette-boundary-markings? :description :fi] "Onko kaukaloissa ringeten rajamerkinnät?") (assoc-in [:swimming-pool-count :name :fi] "Altaiden lukumäärä") ;; Laskenta mahdollista tehdä automaattisesti syötettyjen altaiden perusteella (lasketaan yksinkertaisesti kaikki altaat yhteen tai luvun voi tarvittaessa korjata käsin, samaan tapaan kuin reittien pituuslaskuri toimii) (assoc-in [:swimming-pool-count :description :fi] "Altaiden lukumäärä yhteensä. Syötä tieto tai laske automaattisesti.") (assoc-in [:pool-water-area-m2 :description :fi] "Asiakaskäytössä oleva vesipinta-ala yhteensä.") + + ;;; Maasto ja loput ;;; + + ;; Add new pulkkamäki prop-type + (assoc :sledding-hill? + {:name + {:fi "Pulkkamäki" + :se "Pulkabacke" + :en "Sledding hill"} + :data-type "boolean" + :description + {:fi "Kohteessa on pulkkamäki" + :se "Det finns en pulkabacke på platsen." + :en "There is a sledding hill at the location."}}) + + + ;; Add new kulkutavat prop-type + (assoc :travel-modes + {:name {:fi "Kulkutavat" + :se "Resesätt" + :en "Travel Modes"} + :data-type "enum-coll" + :opts {"by-foot" + {:label {:fi "Jalan" :en "On Foot" :se "Till fots"} + :description + {:fi "Liikkuminen jalkaisin" + :en "Traveling on foot" + :se "Att resa till fots"}} + + "snow-shoes" + {:label {:fi "Lumikengillä" :en "With Snowshoes" :se "Med snöskor"} + :description + {:fi "Liikkuminen lumikengillä" + :en "Traveling with snowshoes" + :se "Att resa med snöskor"}} + + "fat-bike" + {:label {:fi "Läskipyörällä" :en "With Fat Bike" :se "Med fatbike"} + :description + {:fi "Liikkuminen läskipyörällä" + :en "Traveling with a fat bike" + :se "Att resa med fatbike"}}} + :description + {:fi "Lisää reitille soveltuvat kulkutavat" + :se "Lägg till lämpliga resesätt för rutten" + :en "Add suitable travel modes for the route"}}) + + ;; Add new travel-mode-info prop-type + (assoc :travel-mode-info + {:name {:fi "Kulkutavat, lisätieto" + :se "Resesätt, ytterligare information" + :en "Travel Modes, Additional Information"} + :data-type "string" + :description + {:fi "Täsmennä soveltuvia kulkutapoja tarvittaessa" + :se "Specificera lämpliga resesätt vid behov" + :en "Specify suitable travel modes if necessary"}}) + + ;; Add new HS point prop-type + (assoc :hs-point + {:name {:fi "HS-piste", :se "HS-punkt", :en "HS Point"} + :data-type "numeric" + :description + {:fi "Hyppyrimäen HS-piste metreinä" + :se "HS-punkten i backhoppning i meter" + :en "Ski jumping hill HS point in "}}) + + ;; Add new mobile-orienteering-available? prop-type + (assoc :mobile-orienteering? + {:name {:fi "Mobiilisunnistusmahdollisuus" + :se "Mobilorientering möjlig" + :en "Mobile Orienteering Available"} + :data-type "boolean" + :description + {:fi "" + :se "" + :en ""}}) + + ;; Add new bike-orienteering-available? prop-type + (assoc :bike-orienteering? + {:name {:fi "Pyöräsuunnistus mahdollista" + :se "Cykelorientering möjlig" + :en "Bike Orienteering Possible"} + :data-type "boolean" + :description + {:fi "" + :se "" + :en ""}}) + + ;; Add new ski-orienteering? prop-type + (assoc :ski-orienteering? + {:name {:fi "Hiihtosuunnistus mahdollista" + :se "Skidorientering möjlig" + :en "Ski Orienteering Possible"} + :data-type "boolean" + :description + {:fi "" + :se "" + :en ""}}) + + ;; Add new ympärivuotinen käyttö prop-type + (assoc :year-round-use? + {:name {:fi "Ympärivuotinen käyttö" + :se "Året runt användning" + :en "Year-round Use"} + :data-type "boolean" + :description + {:fi "Kohde on ympärivuotisessa käytössä" + :se "Platsen är i användning året runt" + :en "The location is in use year-round"}}) + + )) (def used (let [used (set (mapcat (comp keys :props second) types/all))] (select-keys all used))) + +(comment + (require '[clojure.pprint :as pprint]) + #?(:clj (spit "/tmp/prop-types.edn" (with-out-str (pprint/pprint all)))) + + ) diff --git a/webapp/src/cljc/lipas/data/prop_types_old.cljc b/webapp/src/cljc/lipas/data/prop_types_old.cljc index ca724bf6b..5cee2175e 100644 --- a/webapp/src/cljc/lipas/data/prop_types_old.cljc +++ b/webapp/src/cljc/lipas/data/prop_types_old.cljc @@ -804,9 +804,9 @@ :en ""}}, :curling-lanes-count {:name - {:fi "Curling-ratojen lkm", + {:fi "Curling-ratojen lukumäärä", :se "Antal curlingbanor", - :en "How many curling lanes"}, + :en "Count of curling lanes"}, :data-type "numeric", :description {:fi "Curling-ratojen lukumäärä", diff --git a/webapp/src/cljc/lipas/data/ptv.cljc b/webapp/src/cljc/lipas/data/ptv.cljc index a00b101d5..65d5af9f7 100644 --- a/webapp/src/cljc/lipas/data/ptv.cljc +++ b/webapp/src/cljc/lipas/data/ptv.cljc @@ -1,7 +1,8 @@ (ns lipas.data.ptv - (:require - [lipas.data.types :as types] - [lipas.utils :as utils])) + (:require [clojure.string :as str] + [lipas.data.types :as types] + [lipas.utils :as utils] + [taoensso.timbre :as log])) ;; Utajärven jäähalli ;; https://api.palvelutietovaranto.suomi.fi/api/v11/ServiceChannel/8604a900-be6b-4f9d-8024-a272e07afba3?showHeader=false @@ -13,14 +14,52 @@ ;; json-patch https://github.com/borgeby/clj-json-pointer +;; org 10 #_(def uta-org-id-test "52e0f6dc-ec1f-48d5-a0a2-7a4d8b657d53") ;; Testiorganisaatio 6 (Kunta) (def uta-org-id-test "3d1759a2-e47a-4947-9a31-cab1c1e2512b") -< + +;; org 9 +(def liminka-org-id-test "7fdd7f84-e52a-4c17-a59a-d7c2a3095ed5") + +;; org 8 #_(def uta-org-id-test "92374b0f-7d3c-4017-858e-666ee3ca2761") #_(def uta-org-id-prod "7b83257d-06ad-4e3b-985d-16a5c9d3fced") +;; TODO: Tulossa 5 kuntaa, muut: +;; (Lumijoki. Pyhäjärvi, Ii, Liminka ja Oulu sekä tietenkin bonuksena Utajärvi). + +(def organizations + [{:name "Utajärven kunta (test)" + :props {:org-id uta-org-id-test + :city-codes [889] + :owners ["city" "city-main-owner"] + :supported-languages ["fi" "se" "en"]}} + {:name "Limingan kunta (test)" + :props {:org-id liminka-org-id-test + :city-codes [425] + :owners ["city" "city-main-owner"] + :supported-languages ["fi" "se" "en"]}} ]) + +;; For adding default params to some requests from the FE +;; NOTE: This should eventually be replaced with Lipas organizations. +;; TODO: Not sure if e.g. owners and supported-languages should be +;; hardcoded to the same values for everyone? +(def org-id->params + (reduce (fn [acc x] + (assoc acc (:org-id (:props x)) + (:props x))) + {} + organizations)) + +;; For UI org dropdown +(def orgs + (mapv (fn [x] + {:name (:name x) + :id (:org-id (:props x))}) + organizations)) + (def lang->locale {"fi" :fi, "sv" :se, "en" :en}) @@ -31,6 +70,10 @@ (def default-langs ["fi"]) +(defn ->service-source-id + [org-id sub-category-id] + (str "lipas-" org-id "-" sub-category-id)) + (defn ->ptv-service [{:keys [org-id city-codes source-id sub-category-id languages _description _summary] :or {languages default-langs} :as m}] @@ -39,7 +82,11 @@ sub-cat (get types/sub-categories sub-category-id) main-cat (get types/main-categories (parse-long (:main-category sub-cat)))] - {:sourceId source-id + {:sourceId (or source-id + (let [ts (str/replace (utils/timestamp) #":" "-") + x (str "lipas-" org-id "-" sub-category-id "-" ts)] + (log/infof "Creating new PTV Service source-id %s" x) + x)) #_#_:keywords (let [tags (:tags type)] (for [locale [:fi :se :en] @@ -134,24 +181,31 @@ })) (defn ->ptv-service-location - [org + [_org coord-transform-fn - {:keys [ptv lipas-id location search-meta] :as sports-site}] + now + {:keys [status ptv lipas-id location search-meta] :as sports-site}] (let [languages (-> ptv (get :languages default-langs) (->> (map lipas-lang->ptv-lang)) set) type (get types/all (get-in sports-site [:type :type-code])) - sub-cat (get types/sub-categories (:sub-category type)) - main-cat (get types/main-categories (:main-category type))] + _sub-cat (get types/sub-categories (:sub-category type)) + _main-cat (get types/main-categories (:main-category type))] (println "PTV data") (prn ptv) - (println "Langauges resolved" languages) - (prn location) + ; (println "Languages resolved" languages) + ; (prn location) {:organizationId (:org-id ptv) - :sourceId (str "lipas-" (:org-id ptv) "-" lipas-id) + ;; Keep using existing sourceId for sites that were already initialized in PTV, + ;; generate a new unique ID (with timestamp) for new sites. + :sourceId (or (:source-id ptv) + (let [ts (str/replace now #":" "-") + x (str "lipas-" (:org-id ptv) "-" lipas-id "-" ts)] + (log/infof "Creating new PTV ServiceLocation source-id %s" x) + x)) :serviceChannelNames (keep identity (let [fallback (get-in sports-site [:name])] [(when (contains? languages "fi") @@ -207,7 +261,10 @@ :latitude lat :longitude lon})}] - :publishingStatus "Published" ; Draft | Published + :publishingStatus (case status + ("incorrect-data" "out-of-service-permanently") "Deleted" + "Published") + ; Draft | Published ;; Link services by serviceId :services (-> sports-site :ptv :service-ids) @@ -299,8 +356,136 @@ :service-channel-ids #{"ssid-1"}}))) - (->ptv-service-location nil (constantly [123 456]) uta-jh-with-ptv-meta) + (->ptv-service-location nil (constantly [123 456]) nil uta-jh-with-ptv-meta) ) + +(defn parse-service-source-id [source-id] + ()) + +(defn index-services [services] + ) + +(defn resolve-missing-services + "Infer services (sub-categories) that need to be created in PTV and + attached to sports-sites." + [org-id services sports-sites] + (let [source-ids (->> services + vals + (keep :sourceId) + set)] + (->> sports-sites + (filter (fn [{:keys [ptv]}] (empty? (:service-ids ptv)))) + (map (fn [site] {:source-id (->service-source-id org-id (:sub-category-id site)) + :sub-category (-> site :sub-category) + :sub-category-id (-> site :sub-category-id)})) + distinct + (remove (fn [m] (contains? source-ids (:source-id m))))))) + +(defn sub-category-id->service [org-id source-id->service sub-category-id] + (get source-id->service (->service-source-id org-id sub-category-id))) + +(defn parse-summary + "Returns first line-delimited paragraph." + [s] + (when (string? s) + (first (str/split s #"\r?\n")))) + +(defn resolve-service-channel-name + "Sometimes these seem to have the name under undocumented :name + property and sometimes under documented :serviceChannelNames + array. Wtf." + [service-channel] + (or (:name service-channel) + (some (fn [m] + (when (= "fi" (:language m)) + (:value m))) + (:serviceChannelNames service-channel)))) + +(defn detect-name-conflict + [sports-site service-channels] + (let [s1 (some-> sports-site :name str/trim str/lower-case) + attached-channels (-> sports-site :ptv :service-channel-ids set)] + (some (fn [service-channel] + (let [ssname (resolve-service-channel-name service-channel) + s2 (some-> ssname str/trim str/lower-case)] + (when (and + (not (contains? attached-channels (:id service-channel))) + (= s1 s2)) + {:service-channel-id (:id service-channel)}))) + service-channels))) + +(defn sports-site->ptv-input [{:keys [types org-id org-defaults org-langs]} service-channels services site] + (let [service-id (-> site :ptv :service-ids first) + service-channel-id (-> site :ptv :service-channel-ids first) + summary (-> site :ptv :summary) + description (-> site :ptv :description) + + last-sync (-> site :ptv :last-sync)] + {:valid (boolean (and (some-> description :fi count (> 5)) + (some-> summary :fi count (> 5)))) + :lipas-id (:lipas-id site) + :name (:name site) + :event-date (:event-date site) + ;; :event-date-human (some-> (:event-date site) utils/->human-date-time-at-user-tz) + :name-conflict (detect-name-conflict site (vals service-channels)) + :marketing-name (:marketing-name site) + :type (-> site :search-meta :type :name :fi) + :sub-category (-> site :search-meta :type :sub-category :name :fi) + :sub-category-id (-> site :type :type-code types :sub-category) + :org-id org-id + :admin (-> site :search-meta :admin :name :fi) + :owner (-> site :search-meta :owner :name :fi) + :summary summary + :description description + :languages (or (-> site :ptv :languages) org-langs) + + :sync-enabled (get-in site [:ptv :sync-enabled] true) + :last-sync last-sync + ;; :last-sync-human (some-> last-sync utils/->human-date-time-at-user-tz) + + :sync-status (cond + (not last-sync) :not-synced + (= (:event-date site) last-sync) :ok + :else :out-of-date) + + :service-ids (-> site :ptv :service-ids) + :service-name (-> services (get service-id) :serviceNames + (->> (some #(when (= "fi" (:language %)) (:value %))))) + :service-channel-id service-channel-id + :service-channel-ids (-> site :ptv :service-channel-ids) + :service-channel-name (-> (get service-channels service-channel-id) + (resolve-service-channel-name))})) + +(defn sports-site->service-ids [types source-id->service sports-site] + (let [sub-cat-id (-> sports-site :type :type-code types :sub-category) + org-id (-> sports-site :ptv :org-id) + source-id (str "lipas-" org-id "-" sub-cat-id)] + (when-let [service (get source-id->service source-id)] + #{(:id service)}))) + +(defn is-sent-to-ptv? + "Check if the :ptv data shows that the site has been sent to PTV previously" + [site] + (let [{:keys [ptv]} site] + (and (-> ptv :service-channel-ids first) + (:source-id ptv) + (= "Published" (:publishing-status ptv))))) + +(defn ptv-candidate? + "Does the site look like it should be sent to the ptv?" + [site] + (let [{:keys [status owner]} site + type-code (-> site :type :type-code)] + (boolean (and (not (contains? #{"incorrect-data" "out-of-service-permanently"} status)) + (#{"city" "city-main-owner"} owner) + (not (#{7000} type-code)))))) + +(defn ptv-ready? + [site] + (let [{:keys [ptv]} site + {:keys [summary description]} ptv] + (boolean (and (some-> description :fi count (> 5)) + (some-> summary :fi count (> 5)))))) diff --git a/webapp/src/cljc/lipas/data/styles.cljc b/webapp/src/cljc/lipas/data/styles.cljc index 91430281f..d53dd86a8 100644 --- a/webapp/src/cljc/lipas/data/styles.cljc +++ b/webapp/src/cljc/lipas/data/styles.cljc @@ -126,11 +126,6 @@ :radius 9, :fill {:color "#8fed75"}, :stroke {:color "#000000"}}, - 1190 - {:shape "circle", - :radius 9, - :fill {:color "#ffffff"}, - :stroke {:color "#000000"}}, 4422 {:shape "linestring", :stroke @@ -154,10 +149,6 @@ :radius 9, :fill {:color "#73d28b"}, :stroke {:color "#004800"}}, - 106 - {:shape "polygon", - :fill {:color "#50e76a"}, - :stroke {:color "#000000", :width 1.5}}, 4610 {:shape "circle", :radius 9, @@ -217,11 +208,6 @@ :radius 9, :fill {:color "#00c100"}, :stroke {:color "#ff5500"}}, - 4840 - {:shape "circle", - :radius 9, - :fill {:color "#dcc210"}, - :stroke {:color "#aa0000"}}, 1510 {:shape "circle", :radius 9, @@ -314,7 +300,7 @@ :stroke {:color "#000000"}}, 109 {:shape "polygon", - :fill {:color "#94d024"}, + :fill {:color "#1f805f"}, :stroke {:color "#000000", :width 1.5}}, 5160 {:shape "square", @@ -426,7 +412,7 @@ :stroke {:color "#000000"}}, 103 {:shape "polygon", - :fill {:color "#57fba0"}, + :fill {:color "#57fbc4"}, :stroke {:color "#000000", :width 1.5}}, 201 {:shape "circle", @@ -467,7 +453,7 @@ :stroke {:color "#000000"}}, 107 {:shape "polygon", - :fill {:color "#daab0c"}, + :fill {:color "#c18bd6"}, :stroke {:color "#000000", :width 1.5}}, 6110 {:shape "circle", @@ -523,10 +509,6 @@ :radius 9, :fill {:color "#62d53c"}, :stroke {:color "#000000"}}, - 1650 - {:shape "polygon", - :fill {:color "#62d53c"}, - :stroke {:color "#000000" :width 1.5}}, 2250 {:shape "square", :radius 9, @@ -757,18 +739,80 @@ :line-join "round", :line-dash [5 2]}, :fill {:color "#000000"}} + ;; Rullahiihtorata + 4407 + {:shape "linestring", + :stroke + {:color "#b0a92c", + :width 3.5, + :line-cap "round", + :line-join "round", + :line-dash [5 2]}, + :fill {:color "#000000"}} + ;; Monikäyttöreitti + 4406 + {:shape "linestring", + :stroke + {:color "#665e8a", + :width 3.5, + :line-cap "round", + :line-join "round", + :line-dash [5 2]}, + :fill {:color "#000000"}} + ;; Koiravaljakkoreitti + 4441 + {:shape "linestring", + :stroke + {:color "#80a172", + :width 3.5, + :line-cap "round", + :line-join "round", + :line-dash [5 2]}, + :fill {:color "#000000"}} + ;; Ovaalirata + 6150 + {:shape "circle", + :radius 9, + :fill {:color "#f0c39e"}, + :stroke {:color "#000000"}} + ;;Pulkkamäki + 1190 + {:shape "circle", + :radius 9, + :fill {:color "#f0c0eb"}, + :stroke {:color "#000000"}} + ;; Golfkenttä (alue) + 1650 + {:shape "polygon", + :fill {:color "#daab0c"}, + :stroke {:color "#000000" :width 1.5}} + ;; Sisäleikki-/aktiviteettipuisto: 2225 {:shape "square", :radius 9, - :fill {:color "#ac6c46"}, + :fill {:color "#d9936a"}, :stroke {:color "#000000"}} + ;; Biljardisali 2620 {:shape "square", :radius 9, - :fill {:color "#ac6c46"}, + :fill {:color "#9afcc0"}, :stroke {:color "#000000"}} + ;; Vesiurheilukeskus 3250 {:shape "circle", :radius 9, - :fill {:color "#aaaaff"}, - :stroke {:color "#000000"}}}) + :fill {:color "#60669c"}, + :stroke {:color "#000000"}} + ;; Jousiammuntamaastorata + 4840 + {:shape "circle", + :radius 9, + :fill {:color "#dcc210"}, + :stroke {:color "#000000"}} + ;; Monikäyttöalueet ja virkistysmetsät, joissa on virkistyspalveluita + 106 + {:shape "polygon", + :fill {:color "#99d18f"}, + :stroke {:color "#000000" :width 1.5}} + }) diff --git a/webapp/src/cljc/lipas/data/types.cljc b/webapp/src/cljc/lipas/data/types.cljc index 01c6c182c..f545246c1 100644 --- a/webapp/src/cljc/lipas/data/types.cljc +++ b/webapp/src/cljc/lipas/data/types.cljc @@ -1,24 +1,4692 @@ (ns lipas.data.types - "Type codes went through a major overhaul in the summer of 2024. This - namespace hosts a controlled place to roll-out the changes." + "Categorization of sports sites." (:require - [lipas.data.types-old :as old] - #_[lipas.data.types-new :as new] [lipas.utils :as utils])) (def main-categories - old/main-categories) + {0 + {:type-code 0, + :name + {:fi "Virkistyskohteet ja palvelut", + :se "Rekreationsanläggningar och tjänster", + :en "Recreational destinations and services"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p37350"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2"]}}, + 1000 + {:type-code 1000, + :name + {:fi "Ulkokentät ja liikuntapuistot", + :se "Utomhusplaner och idrottsparker", + :en "Outdoor fields and sports parks"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p69291" + "http://www.yso.fi/onto/koko/p67276"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2"]}}, + 2000 + {:type-code 2000, + :name + {:fi "Sisäliikuntatilat", + :se "Anläggningar för inomhusidrott", + :en "Indoor sports facilities"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p69660"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.1"]}}, + 3000 + {:type-code 3000, + :name + {:fi "Vesiliikuntapaikat", + :se "Anläggningar för vattenidrott", + :en "Water sports facilities"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p18621"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.1" + "http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2"]}}, + 4000 + {:type-code 4000, + :name + {:fi "Maastoliikuntapaikat", + :se "Anläggningar för terrängidrott", + :en "Cross-country sports facilities"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p12424"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2"]}}, + 5000 + {:type-code 5000, + :name + {:fi "Veneily, ilmailu ja moottoriurheilu", + :se "Anläggningar för båtsport, flygsport och motorsport", + :en "Boating, aviation and motor sports"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p34055" + "http://www.yso.fi/onto/koko/p36083" + "http://www.yso.fi/onto/koko/p18298" + "http://www.yso.fi/onto/koko/p75772" + "http://www.yso.fi/onto/koko/p31773"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.2"]}}, + 6000 + {:type-code 6000, + :name + {:fi "Eläinurheilualueet", + :se "Anläggningar för djursport", + :en "Animal sports areas"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10416" + "http://www.yso.fi/onto/koko/p8973"], + :service-classes + ["http://uri.suomi.fi/codelist/ptv/ptvserclass2/code/P27.1"]}}, + 7000 + {:type-code 7000, + :name + {:fi "Huoltorakennukset", + :se "Servicebyggnader", + :en "Maintenance/service buildings"}, + :ptv {:ontology-urls [], :service-classes []}}}) (def sub-categories - old/sub-categories) + {2100 + {:type-code 2100, + :name + {:fi "Kuntoilukeskukset ja liikuntasalit", + :se "Konditionsidrottscentra och idrottssalar", + :en "Fitness centres and sports halls"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p30560" + "http://www.yso.fi/onto/koko/p85878"]}, + :main-category "2000"}, + 5200 + {:type-code 5200, + :name + {:fi "Urheiluilmailualueet", + :se "Områden för flygsport", + :en "Sport aviation areas"}, + :main-category "5000"}, + 4200 + {:type-code 4200, + :name + {:fi "Katetut talviurheilupaikat", + :se "Vintersportplatser under tak", + :en "Covered winter sports facilities"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p8460"]}, + :main-category "4000"}, + 2200 + {:type-code 2200, + :name + {:fi "Liikuntahallit", :se "Idrottshallar", :en "Sports halls"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p33522"]}, + :main-category "2000"}, + 1 + {:type-code 1, + :name + {:fi "Virkistys- ja retkeilyalueet", + :se "Rekreations- och friluftsområden", + :en "Recreational and outdoor areas "}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p37350" + "http://www.yso.fi/onto/koko/p33303"]}, + :main-category "0"}, + 7000 + {:type-code 7000, + :name + {:fi "Huoltotilat", + :se "Servicebyggnader", + :en "Maintenance/service buildings"}, + :main-category "7000"}, + 4400 + {:type-code 4400, + :name + {:fi "Liikunta- ja ulkoilureitit", + :se "Idrotts- och friluftsleder", + :en "Sports and outdoor recreation routes "}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p32315"]}, + :main-category "4000"}, + 1300 + {:type-code 1300, + :name {:fi "Pallokentät", :se "Bollplaner", :en "Ball games courts"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p75504"]}, + :main-category "1000"}, + 6100 + {:type-code 6100, + :name {:fi "Hevosurheilu", :se "Hästsport", :en "Equestrian sports"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p35523"]}, + :main-category "6000"}, + 4700 + {:type-code 4700, + :name + {:fi "Kiipeilypaikat", + :se "Klättringsplatser", + :en "Climbing venues"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p4261"]}, + :main-category "4000"}, + 3100 + {:type-code 3100, + :name + {:fi "Uima-altaat, hallit ja kylpylät", + :se "Simbassänger, hallar och badinrättningar", + :en "Indoor swimming pools, halls and spas"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p1459" + "http://www.yso.fi/onto/koko/p11070" + "http://www.yso.fi/onto/koko/p35112"]}, + :main-category "3000"}, + 2500 + {:type-code 2500, + :name {:fi "Jäähallit", :se "Ishallar", :en "Ice-skating arenas"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p11376"]}, + :main-category "2000"}, + 1200 + {:type-code 1200, + :name + {:fi "Yleisurheilukentät ja -paikat", + :se "Planer och platser för friidrott", + :en "Athletics fields and venues"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p85607"]}, + :main-category "1000"}, + 5300 + {:type-code 5300, + :name + {:fi "Moottoriurheilualueet", + :se "Områden för motorsport", + :en "Motor sports areas"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p31773"]}, + :main-category "5000"}, + 1600 + {:type-code 1600, + :name {:fi "Golfkentät", :se "Golfbanor", :en "Golf courses"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p32648"]}, + :main-category "1000"}, + 1500 + {:type-code 1500, + :name + {:fi "Jääurheilualueet ja luonnonjäät", + :se "Isidrottsområden och naturisar", + :en "Ice sports areas and sites with natural ice"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p75572"]}, + :main-category "1000"}, + 6200 + {:type-code 6200, + :name {:fi "Koiraurheilu", :se "Hundsport", :en "Dog sports"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p72589"]}, + :main-category "6000"}, + 3200 + {:type-code 3200, + :name + {:fi "Maauimalat ja uimarannat", + :se "Utebassänger och badstränder", + :en "Open air pools and beaches"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p32279" + "http://www.yso.fi/onto/koko/p76123" + "http://www.yso.fi/onto/koko/p13459"]}, + :main-category "3000"}, + 2 + {:type-code 2, + :name + {:fi "Retkeilyn palvelut", + :se "Utflyktstjänster", + :en "Hiking facilities"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p36881"]}, + :main-category "0"}, + 1100 + {:type-code 1100, + :name + {:fi "Lähiliikunta ja liikuntapuistot", + :se "Närmotion och idrottsparker", + :en "Neighbourhood sports facilities and parks "}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p84486" + "http://www.yso.fi/onto/koko/p69291"]}, + :main-category "1000"}, + 4100 + {:type-code 4100, + :name + {:fi "Laskettelurinteet ja rinnehiihtokeskukset", + :se "Slalombackar och alpina skidcentra", + :en "Ski slopes and downhill ski resorts"}, + :ptv + {:ontology-urls + ["http://www.yso.fi/onto/koko/p10549" + "http://www.yso.fi/onto/koko/p84378" + "http://www.yso.fi/onto/koko/p4432"]}, + :main-category "4000"}, + 5100 + {:type-code 5100, + :name + {:fi "Veneurheilupaikat", + :se "Platser för båtsport", + :en "Boating sports facilities"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p75772"]}, + :main-category "5000"}, + 2600 + {:type-code 2600, + :name + {:fi "Keilahallit ja biljardisalit", + :se "Bowlinghallar och biljardsalonger", + :en "Bowling alleys and billiard halls"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p9812"]}, + :main-category "2000"}, + 2300 + {:type-code 2300, + :name + {:fi "Yksittäiset lajikohtaiset sisäliikuntapaikat", + :se "Enstaka grenspecifika anläggningar för inomhusidrott", + :en "Indoor venues for various sports "}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p66287"]}, + :main-category "2000"}, + 4300 + {:type-code 4300, + :name {:fi "Hyppyrimäet", :se "Hoppbackar", :en "Ski jumping hills"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p72457"]}, + :main-category "4000"}, + 4500 + {:type-code 4500, + :name + {:fi "Suunnistusalueet", + :se "Orienteringsområden", + :en "Orienteering areas"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p5355"]}, + :main-category "4000"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p18298"]}, + 4600 + {:type-code 4600, + :name + {:fi "Maastohiihtokeskukset", + :se "Längdåkningscentra", + :en "Cross-country ski resorts"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p75831"]}, + :main-category "4000"}, + 4800 + {:type-code 4800, + :name + {:fi "Ampumaurheilupaikat", + :se "Sportskytteplatser", + :en "Shooting sports facilities"}, + :ptv {:ontology-urls ["http://www.yso.fi/onto/koko/p25336"]}, + :main-category "4000"}}) -(def all old/all) +(def all + {1530 + {:description + {:fi + "Luisteluun, jääkiekkoon, kaukalopalloon, curlingiin tai muuhun jääurheiluun tarkoitettu kaukalo. Käytössä talvikaudella.", + :se + "Rink avsedd för skridskoåkning, ishockey, rinkbandy osv. Används under vintersäsongen.", + :en + "Rink intended for ice-skating, ice hockey, rink bandy, etc."}, + :tags {:fi ["jääkiekkokaukalo"]}, + :name {:fi "Kaukalo", :se "Rink", :en "Rink"}, + :type-code 1530, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :changing-rooms-m2 {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 1520 + {:description + {:fi + "Luisteluun tarkoitettu luonnonmukainen kenttä. Jäädytetään käyttökuntoon talvikaudelle.", + :se + "Plan avsedd för skridskoåkning. Används under vintersäsongen.", + :en "Field intended for ice-skating."}, + :tags {:fi []}, + :name + {:fi "Luistelukenttä", + :se "Skridskobana", + :en "Ice-skating field"}, + :type-code 1520, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :fields-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :changing-rooms-m2 {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 2320 + {:description + {:fi + "Pysyvästi voimisteluun varustettu tila. Voimistelutilassa on erilaisia kiinteitä voimistelutelineitä ja -rakenteita (esim. volttimonttu, rekki tai trampoliini). Myös cheerleadingin ja sirkusharjoittelun olosuhteet luetaan voimistelutiloiksi. Tarkempi olosuhdetieto kerrotaan lisätiedoissa.", + :se "Permanent utrustning för att träna redskapsgymnastik.", + :en "Space permanently equipped for artistic gymnastics."}, + :tags {:fi ["monttu" "rekki" "nojapuut"]}, + :name + {:fi "Voimistelutila", + :se "Utrymme för redskapsgymnastik", + :en "Artistic gymnastics facility"}, + :type-code 2320, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :sport-specification {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :space-divisible {:priority 70}, + :gymnastic-routines-count {:priority 80}, + :area-m2 {:priority 90}, + :landing-places-count {:priority 80}, + :school-use? {:priority 40}}}, + 6130 + {:description + {:fi + "Pysyvästi esteratsastukseen varusteltu kenttä tai alue ulkona.", + :se "Bana med permanent utrustning för banhoppning", + :en "Field permanently equipped for show jumping. Outdoors."}, + :tags {:fi ["ratsastuskenttä"]}, + :name + {:fi "Esteratsastuskenttä/-alue", + :se "Bana för banhoppning", + :en "Show jumping field"}, + :type-code 6130, + :main-category 6000, + :status "active", + :sub-category 6100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}}}, + 1395 + {:description + {:fi + "Yksi tai useampi pöytätennispöytä ulkona. Pöytätennispöydän tulee olla sijoitettu niin, että pelaamiseen on riittävä tila pöydän ympärillä. Pöydän tulee olla pelikäyttöön soveltuva.", + :se + "Ett eller flera bordtennisbord utomhus. Bordet är lämpligt för spel och bör vara placerat så att det finns tillräckligt utrymme för spel.", + :en + "One or more outdoor table tennis tables in the same area. The table must be positioned so that there is enough space for playing. The table must be suitable for the sport."}, + :tags {:fi ["pöytätennis" "pingis" "ping pong"]}, + :name + {:fi "Pöytätennisalue", + :se "Område med bordtennisbord", + :en "Table tennis area"}, + :type-code 1395, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :table-tennis-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :water-point {:priority 70}, + :lighting-info {:priority 50}}}, + 6210 + {:description + {:fi + "Koiran koulutukseen, agilityyn tai muuhun harjoittamiseen varattu ulkoalue.", + :se + "Område reserverat för hundträning, agility eller annan hundhobby.", + :en + "Area reserved for dog training, agility or other dog sports."}, + :tags {:fi ["agility" "koirakenttä"]}, + :name + {:fi "Koiraurheilualue", + :se "Område för hundsport", + :en "Dog sports area"}, + :type-code 6210, + :main-category 6000, + :status "active", + :sub-category 6200, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 1370 + {:description + {:fi + "Tennikseen tarkoitettu kenttä. Mahdollinen lyöntiseinä ja kentän pintamateriaali merkitään lisätietoihin.", + :se + "En eller flera tennisbanor på samma område. Antalet banor, ytmaterial mm i karakteristika. Även uppgift om slagväggen.", + :en + "One or more tennis courts in the same area. Number of courts, surface material, etc. specified in properties, including information about a potential hit wall."}, + :tags {:fi ["tenniskenttä"]}, + :name + {:fi "Tenniskenttä", + :se "Område med tennisbanor", + :en "Tennis court area"}, + :type-code 1370, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :training-wall? {:priority 80}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 1360 + {:description + {:fi + "Pesäpalloon tarkoitettu kenttä. Jos kentän yhteydessä on katsomoita, lisätään kentän nimeen stadion-sana. Vähintään kansallisen tason pelipaikka. Pintamateriaali on esim. hiekka, hiekkatekonurmi tai muu synteettinen päällyste. Kentän koko on vähintään 50 x 100 m.", + :se + "En bobollsplan, kan ha flera läktare. Minimikrav: spelplats på nationell nivå. Sand, konstgräs med sand / annan syntetisk beläggning. >50 x 100 m.", + :en + "Finnish baseball field, may include stands. Can host at least national-level games. Sand, artificial turf / other synthetic surface, > 50 x 100 m. "}, + :tags {:fi ["pesäpallostadion"]}, + :name + {:fi "Pesäpallokenttä", :se "Bobollsplan", :en "Baseball field"}, + :type-code 1360, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :customer-service-point? {:priority 70}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :school-use? {:priority 40}}}, + 110 + {:description + {:fi + "Erämaalailla perustetut alueet pohjoisimmassa Lapissa. Metsähallitus tietolähteenä.", + :se + "Grundade enligt ödemarkslagen, i nordligaste Lappland. Källa: Forststyrelsen.", + :en + "Areas located in northernmost Lapland, established based on the Wildeness Act (1991/62). Source of information Metsähallitus."}, + :tags {:fi []}, + :name + {:fi "Erämaa-alue", :se "Vildmarksområden", :en "Wilderness area"}, + :type-code 110, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2360 + {:description + {:fi "Pysyvästi käytössä oleva ampumarata sisätiloissa.", + :se "Permanent skjutbana inomhus.", + :en "Permanent indoor shooting range."}, + :tags {:fi ["ilmakivääri" "ilma-ase" "ammunta"]}, + :name + {:fi "Sisäampumarata", + :se "Inomhusskjutbana", + :en "Indoor shooting range"}, + :type-code 2360, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:surface-material {:priority 79}, + :surface-material-info {:priority 78}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :air-gun-shooting? {:priority 80}, + :pistol-shooting? {:priority 80}, + :shooting-positions-count {:priority 80}, + :area-m2 {:priority 90}, + :rifle-shooting? {:priority 80}, + :free-rifle-shooting? {:priority 80}, + :school-use? {:priority 40}, + :track-length-m {:priority 80}}}, + 5310 + {:description + {:fi + "Useiden eri moottoriurheilun lajien suorituspaikkoja, huoltotilat olemassa.", + :se + "Platser för flera olika motorsportgrenar, serviceutrymmen finns.", + :en + "Venues for various motor sports; service premises available."}, + :tags {:fi []}, + :name + {:fi "Moottoriurheilukeskus", + :se "Centrum för motorsport", + :en "Motor sports centre"}, + :type-code 5310, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :track-length-m {:priority 90}}}, + 1560 + {:description + {:fi + "Alamäkiluistelua varten vuosittain rakennettava rata. Käytössä talvikaudella.", + :se "Permanent bana byggd för utförsåkning.", + :en "Permanent track built for downhill skating. "}, + :tags {:fi ["luistelu" "alamäkiluistelu"]}, + :name + {:fi "Alamäkiluistelurata", + :se "Skridskobana för utförsåkning", + :en "Downhill skating track"}, + :type-code 1560, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :lifts-count {:priority 70}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :altitude-difference {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :track-length-m {:priority 90}}}, + 205 + {:description + {:fi + "Rantautumiseen osoitettu paikka, ei järjestettyjä palveluita.", + :se + "Plats som anvisats för ilandstigning, inga ordnade tjänster.", + :en "Place intended for landing by boat, no services provided."}, + :tags {:fi ["laituri" "taukopaikka"]}, + :name + {:fi "Rantautumispaikka", + :se "Ilandstigningsplats", + :en "Boat dock"}, + :type-code 205, + :main-category 0, + :status "deprecated", + :sub-category 2, + :geometry-type "Point", + :props + {:toilet? {:priority 80}, + :boat-launching-spot? {:priority 90}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :pier? {:priority 80}, + :school-use? {:priority 70}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2150 + {:description + {:fi + "Muun rakennuksen yhteydessä oleva avoin liikuntatila, joka sopii monipuolisesti erilaisten liikuntamuotojen harrastamiseen. Salin liikuntapinta-ala vaihtelee tyypillisesti alle 300 neliöstä noin 750 neliöön. Esim. koulurakennuksessa sijaitseva liikuntasali.", + :se + "En idrottssal som är ansluten till en annan byggnad. Storlek och höjd anges i karakteristika.", + :en + "A gymnastics hall connected to another building. Size and height specified in properties."}, + :tags {:fi ["jumppasali" "voimistelusali"]}, + :name + {:fi "Liikuntasali", :se "Idrottssal", :en "Gymnastics hall"}, + :type-code 2150, + :main-category 2000, + :status "active", + :sub-category 2100, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :basketball-fields-count {:priority 80}, + :surface-material-info {:priority 88}, + :free-use? {:priority 40}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :space-divisible {:priority 70}, + :gymnastics-space? {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :futsal-fields-count {:priority 80}, + :football-fields-count {:priority 80}, + :floorball-fields-count {:priority 80}, + :handball-fields-count {:priority 80}, + :volleyball-fields-count {:priority 80}, + :spinning-hall? {:priority 80}, + :school-use? {:priority 40}}}, + 2210 + {:description + {:fi + "Liikuntahalli on itsenäinen rakennus, jossa voi olla useita liikuntatiloja tai osiin jaettavissa oleva pääsali.", + :se + "Idrottshall med utrymmen för flera idrottsgrenar eller med ett i mindre sektioner indelbart huvudidrottsutrymme. Storleken varierar mellan ca 750 och 4999 m2. Inkluderar inomhusaktivitetsparker med faciliteter för flera fysiska aktiviteter.", + :en + "Building containing facilities for various sports or the main sports area can be split into smaller sections. Hall size varies between app. 750 - 4999 square meters. Includes indoor activity parks with facilities for multiple physical activities."}, + :tags {:fi ["urheilutalo" "urheiluhalli"]}, + :name {:fi "Liikuntahalli", :se "Idrottshall", :en "Sports hall "}, + :type-code 2210, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :basketball-fields-count {:priority 80}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :sprint-lanes-count {:priority 80}, + :javelin-throw-places-count {:priority 80}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :circular-lanes-count {:priority 80}, + :match-clock? {:priority 70}, + :inner-lane-length-m {:priority 80}, + :discus-throw-places {:priority 80}, + :badminton-courts-count {:priority 80}, + :hammer-throw-places-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :polevault-places-count {:priority 80}, + :group-exercise-rooms-count {:priority 80}, + :space-divisible {:priority 70}, + :toilet? {:priority 70}, + :gymnastics-space? {:priority 80}, + :running-track-surface-material {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :futsal-fields-count {:priority 80}, + :shotput-count {:priority 80}, + :longjump-places-count {:priority 80}, + :football-fields-count {:priority 80}, + :floorball-fields-count {:priority 80}, + :auxiliary-training-area? {:priority 80}, + :squash-courts-count {:priority 80}, + :customer-service-point? {:priority 70}, + :accessibility-info {:priority 40}, + :handball-fields-count {:priority 80}, + :volleyball-fields-count {:priority 80}, + :climbing-wall? {:priority 80}, + :school-use? {:priority 40}, + :highjump-places-count {:priority 80}}}, + 101 + {:description + {:fi + "Sijaitsevat taajamissa, max 1 km asutuksesta. Toimivat kävely-, leikki-, oleskelu-, lenkkeily- ja pyöräilypaikkoina. Kaavamerkintä V tai VL. Esimerkkejä lähi- tai ulkoilupuistoista: leikkipuistot, liikennepuistot, perhepuistot, oleskelupuistot, keskuspuistot ja kirkkopuistot.", + :se + "I tätorter, i omedelbar närhet till bebyggelse. Avsedd för daglig användning. Plats för lek, vistelse och promenader. Planbeteckning VL. Till exempel en lekpark.", + :en + "In population centres, in or near residential areas. Intended for daily use. Used for play, recreation and walks. Plan symbol VL. E.g. a playground."}, + :tags {:fi ["puisto" "lähiliikuntapaikka"]}, + :name + {:fi "Lähi-/ulkoilupuisto", + :se "Närpark", + :en "Neighbourhood park"}, + :type-code 101, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:playground? {:priority 90}, + :area-km2 {:priority 100}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :water-point {:priority 80}, + :toilet? {:priority 80}}}, + 102 + {:description + {:fi + "Päivittäin käytettäviä alueita, max 1 km asunnoista. Toimivat kävely-, leikki-, oleskelu-, lenkkeily- ja pyöräilypaikkoina. Kevyt liikenne voi mennä ulkoilupuiston läpi. Voi sisältää puistoa, metsää, peltoa, niittyä, vesialuetta. Kaavamerkintä V tai VL.", + :se + "Områden avsedda för daglig använding, max på 1 kilometers avstånd från bebyggelse. Fungerar som ett område för promenader, lekar, vistelse, joggning och cykling. Lätt trafikled kan fara igenom friluftsparken. Området kan bestå av park, skog, åker, äng och vattenled. Planbeteckning V eller VL.", + :en + "Used daily, max. 1 km from residential areas. Intended for walks, play, recreation, jogging and cycling. There may be bicycle and pedestrian traffic across the park. May consist of park, forest, fields, meadows, bodies of water. Symbol V or VL."}, + :tags {:fi ["puisto"]}, + :name + {:fi "Ulkoilupuisto", :se "Friluftspark", :en "Leisure park"}, + :type-code 102, + :main-category 0, + :status "deprecated", + :sub-category 1, + :geometry-type "Polygon", + :props + {:playground? {:priority 90}, + :area-km2 {:priority 100}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :school-use? {:priority 70}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 7000 + {:description + {:fi + "Liikuntapaikan tai -paikkojen yhteydessä oleva, liikuntapaikan ylläpitoa tai käyttöä palveleva rakennus. Voi sisältää varastoja, pukuhuoneita, suihkutiloja yms.", + :se "Servicebyggnader i anslutning till idrottsanläggningar.", + :en + "Maintenance buildings in connection with sports facilities."}, + :tags {:fi ["konesuoja" "huoltotila"]}, + :name + {:fi "Huoltorakennukset", + :se "Servicebyggnader", + :en "Maintenance/service buildings"}, + :type-code 7000, + :main-category 7000, + :status "active", + :sub-category 7000, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :ski-service? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :equipment-rental? {:priority 70}, + :sauna? {:priority 70}, + :school-use? {:priority 40}}}, + 1110 + {:description + {:fi + "Liikuntapuisto on useita liikuntapaikkoja käsittävä liikunta-alue. Liikuntapuistossa voi olla esim. erilaisia kenttiä, uimaranta, kuntorata, monitoimihalli, leikkipuisto jne. koottuna samalle alueelle. Lipakseen tallennetaan sekä tieto liikuntapuistosta että yksittäiset liikuntapaikat, joita puisto sisältää. Liikuntapaikat lasketaan omiksi paikoikseen.", + :se + "En idrottspark är ett idrottsområde med flera idrottsplatser. Där kan finnas olika planer, badstrand, konditionsbana, allaktivitetshall, lekpark osv samlade på samma område. I Lipas lagras uppgifter om såväl idrottsparken som enstaka faciliteter som finns i parken. Varje motionsplats räknas som en plats.", + :en + "A sports park is an area including several sports facilities, e.g., different fields, beach, a jogging track, a multi-purpose hall, a playground. 'Lipas' contains information both on the sports park and the individual sports facilities found there. The sports facilities are listed as individual items in the classification."}, + :tags {:fi ["puisto" "lähiliikunta" "lähiliikuntapaikka"]}, + :name {:fi "Liikuntapuisto", :se "Idrottspark", :en "Sports park"}, + :type-code 1110, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Polygon", + :props + {:free-use? {:priority 50}, + :fields-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :ligthing? {:priority 70}, + :accessibility-info {:priority 50}, + :playground? {:priority 80}, + :school-use? {:priority 50}}}, + 3250 + {:description + {:fi + "Vesiurheilukeskuksessa on vesistössä sijaitsevia liikuntapalveluita tai palvelukokonaisuus, joka voi muodostua erilaisista veden päällä tai vedessä olevista suorituspaikoista tai -radoista."}, + :tags {:fi []}, + :name {:fi "Vesiurheilukeskus"}, + :type-code 3250, + :main-category 3000, + :status "active", + :sub-category 3200, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :pier? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :changing-rooms? {:priority 70}, + :pool-water-area-m2 {:priority 90}, + :sauna? {:priority 70}, + :customer-service-point? {:priority 70}}}, + 6220 + {:description + {:fi + "Erityisesti koiraharrastusta, agilityä, koulutusta tms. varten varustettu halli.", + :se + "Hall som utrustats särskilt för hundhobby, agility, träning osv.", + :en + "Hall specifically equipped for dog sports, agility, training, etc."}, + :tags {:fi ["agility" "koirahalli"]}, + :name + {:fi "Koiraurheiluhalli", + :se "Hundsporthall", + :en "Dog sports hall"}, + :type-code 6220, + :main-category 6000, + :status "active", + :sub-category 6200, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}}}, + 4530 + {:description + {:fi + "Pyöräillen tapahtuvaan suunnistamiseen, alueesta on pyöräsuunnistukseen soveltuva kartta.", + :se "Karta över område som lämpar sig för cykelorientering.", + :en "A map for mountain bike orienteering available."}, + :tags {:fi []}, + :name + {:fi "Pyöräsuunnistusalue", + :se "Cykelorienteringsområde", + :en " Mountain bike orienteering area"}, + :type-code 4530, + :main-category 4000, + :status "deprecated", + :sub-category 4500, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :school-use? {:priority 40}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4720 + {:description + {:fi + "Merkitty luonnon kallio, jota voi käyttää kiipeilyyn. Jääkiipeily lisätietoihin. Myös boulderointikalliot.", + :se + "Märkt berg i naturen. Isklättring i tilläggsinformation. Även berg för bouldering.", + :en + "Marked natural cliff. Ice climbing specified in additional information. Also includes bouldering cliffs."}, + :tags {:fi []}, + :name + {:fi "Kiipeilykallio", :se "Klätterberg", :en "Climbing rock"}, + :type-code 4720, + :main-category 4000, + :status "active", + :sub-category 4700, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :climbing-routes-count {:priority 80}, + :ice-climbing? {:priority 80}, + :climbing-wall-height-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :climbing-wall-width-m {:priority 90}, + :ligthing? {:priority 60}}}, + 1330 + {:description + {:fi + "Rantalentopallokenttä, pehmeä alusta. Kohde voi sijaita muuallakin kuin rannalla.", + :se + "Beachvolleybollplan, mjuk grund. Kan också ha annat läge än stranden.", + :en + "Beach volleyball court, soft basement. May also be located far from a beach."}, + :tags {:fi ["rantalentopallo" "rantalentopallokenttä"]}, + :name + {:fi "Beachvolley-/rantalentopallokenttä", + :se "Beachvolleyplan", + :en "Beach volleyball court"}, + :type-code 1330, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :height-of-basket-or-net-adjustable? {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}}}, + 206 + {:description + {:fi + "Rakennettu tulentekopaikka tai keittokatos. Kohde voi olla esimerkiksi maasta eristetty katoksellinen tulisija tai tulisija avotulelle. Tulentekopaikan tarkempi kuvaus ja tieto mahdollisista rajoituksista lisätietoihin.", + :se + "En byggd eldningsplats eller ett kokskjul. Objektet kan till exempel vara en eldstad med tak som är isolerad från marken eller en eldstad för öppen eld. En mer detaljerad beskrivning av eldningsplatsen och information om eventuella begränsningar finns i ytterligare information.", + :en + "A constructed fireplace or cooking shelter. The site can be, for example, a covered fireplace isolated from the ground or a fireplace for open fires. A more detailed description of the fireplace and information about any possible restrictions can be found in the additional information."}, + :tags + {:fi + ["nuotiopaikka" + "keittokatos" + "grillauspaikka" + "ruoka" + "taukopaikka"]}, + :name + {:fi "Ruoanlaitto- / tulentekopaikka", + :se "Matlagningsplats", + :en "Cooking facilities"}, + :type-code 206, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :toilet? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :free-use? {:priority 60}}}, + 4830 + {:description + {:fi + "Ulkona tai sisällä sijaitseva jousiammuntarata. Radan käyttö edellyttää erillistä lupaa, seuran jäsenyyttä tai harjoitusvuoroa. Radan varustus ja soveltuvat lajit kuvataan lisätiedoissa.", + :se "Ute eller inne. Utrustning och grenar i karakteristika.", + :en + "Outdoors or indoors. Equipment and the various sports detailed in properties."}, + :tags {:fi ["jousiampumarata"]}, + :name + {:fi "Jousiammuntarata", :se "Bågskyttebana", :en "Archery range"}, + :type-code 4830, + :main-category 4000, + :status "active", + :sub-category 4800, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :track-width-m {:priority 90}, + :free-customer-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shooting-positions-count {:priority 90}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 1180 + {:description + {:fi "Frisbeegolfin pelaamiseen rakennettu rata.", + :se "En bana byggt för frisbeegolf.", + :en "Track built for disc golf. "}, + :tags {:fi []}, + :name + {:fi "Frisbeegolfrata", + :se "Frisbeegolfbana", + :en "Disc golf course"}, + :type-code 1180, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :holes-count {:priority 90}, + :free-use? {:priority 50}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :altitude-difference {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :lighting-info {:priority 60}, + :ligthing? {:priority 70}, + :accessibility-info {:priority 50}, + :school-use? {:priority 50}, + :track-type {:priority 80}, + :range? {:priority 80}, + :track-length-m {:priority 90}}}, + 4422 + {:description + {:fi + "Moottorikelkkailuun tarkoitettu reitti, jolla ei ole tehty virallista reittitoimitusta. Reitillä on kuitenkin ylläpitäjä ja maanomistajien lupa.", + :se "Ingen ruttexpedition.", + :en "No official approval."}, + :tags {:fi []}, + :name + {:fi "Moottorikelkkaura", + :se "Snöskoterspår", + :en "Unofficial snowmobile route"}, + :type-code 4422, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-width-m {:priority 98}, + :route-length-km {:priority 99}, + :rest-places-count {:priority 70}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4430 + {:description + {:fi + "Ratsastukseen ja/tai kärryillä ajoon tarkoitettu reitti. Sallitut käyttötavat kerrotaan reitin tarkemmissa tiedoissa.", + :se + "Led avsedd för ridning och/eller häst med kärra. Användningsanvisningar i karakteristika.", + :en + "Route intended for horseback riding and/or carriage riding. Different uses specified in additional information."}, + :tags {:fi ["ratsastusreitti"]}, + :name {:fi "Hevosreitti", :se "Hästled", :en "Horse track"}, + :type-code 4430, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :route-width-m {:priority 98}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 97}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 204 + {:description + {:fi + "Luonnon tarkkailuun tarkoitettu rakennelma, lintutorni tai vastaava.", + :se + "Anordning avsedd för observationer i naturen, t ex fågeltorn.", + :en + "Structure built for nature observation. E.g. bird observation tower."}, + :tags {:fi ["lintutorni" "näkötorni" "torni"]}, + :name + {:fi "Luontotorni", + :se "Naturtorn", + :en "Nature observation tower"}, + :type-code 204, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:toilet? {:priority 80}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :free-use? {:priority 70}}}, + 106 + {:description + {:fi + "Monikäyttöalueiksi voidaan nimittää jokaisenoikeuksin ulkoiluun käytettäviä maa- ja metsätalousalueita. Monikäyttöalueita ovat erityisesti rakentamattomat rannat ja taajamien läheiset maa- ja metsätalousalueet. Kaavamerkintä MU. Virkistysmetsien metsätaloudessa on huomioitu mm. maisemalliset arvot, ja ne on perustettu Metsähallituksen päätöksellä. Virkistysmetsien osalta Lipas-aineisto perustuu Metsähallituksen tietoihin.", + :se + "Områden för mångsidig användning kan kallas jord- och skogsbruksområden som används för rekreation med allemansrätten. Områden för mångsidig användning är särskilt obebyggda stränder och jord- och skogsbruksområden nära tätorter. Planbeteckning MU. Inom skogsbruket i rekreationsskogar har man beaktat bl.a. landskapsvärden, och de har inrättats genom beslut av Forststyrelsen. När det gäller rekreationsskogar baseras Lipas-materialet på uppgifter från Forststyrelsen.", + :en + "Multi-use areas can be designated as agricultural and forestry areas used for outdoor activities under everyman's rights. Multi-use areas are particularly undeveloped shores and agricultural and forestry areas near urban areas. Plan designation MU. In the forestry of recreational forests, landscape values, among other things, have been considered, and they have been established by a decision of Metsähallitus. For recreational forests, the Lipas data is based on information from Metsähallitus."}, + :tags {:fi ["ulkoilualue" "virkistysalue"]}, + :name + {:fi + "Monikäyttöalue tai virkistysmetsä, jossa on virkistyspalveluita", + :se + "Mångbruksområde eller rekreationsskog med rekreationstjänster", + :en + "Multi-use area or recreational forest with recreational services"}, + :type-code 106, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2620 + {:description + {:fi + "Biljardisali on biljardin pelaamiseen tarkoitettu tila. Biljardipöytien määrä ja tyyppi kuvataan lisätiedoissa."}, + :tags {:fi []}, + :name {:fi "Biljardisali"}, + :type-code 2620, + :main-category 2000, + :status "active", + :sub-category 2600, + :geometry-type "Point", + :props + {:total-billiard-tables-count {:priority 90}, + :carom-tables-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :snooker-tables-count {:priority 80}, + :pool-tables-count {:priority 80}, + :customer-service-point? {:priority 70}, + :pyramid-tables-count {:priority 80}, + :kaisa-tables-count {:priority 80}, + :school-use? {:priority 40}}}, + 4610 + {:description + {:fi + "Ampumahiihdon harjoitteluun tarkoitettu alue, jossa on ainakin latu ja ampumapaikka/-paikkoja. ", + :se + "Annat träningsområde för skidskytte. Spår och skjutplats finns.", + :en + "Other training area for biathlon. Ski track and shooting range."}, + :tags {:fi ["ampumapaikka"]}, + :name + {:fi "Ampumahiihdon harjoittelualue", + :se "Träningsområde för skidskytte", + :en "Training area for biathlon"}, + :type-code 4610, + :main-category 4000, + :status "active", + :sub-category 4600, + :geometry-type "Point", + :props + {:stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :finish-line-camera? {:priority 70}, + :ski-track-traditional? {:priority 80}, + :route-width-m {:priority 98}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :changing-rooms? {:priority 70}, + :shooting-positions-count {:priority 80}, + :lit-route-length-km {:priority 97}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :changing-rooms-m2 {:priority 70}, + :loudspeakers? {:priority 70}, + :ski-track-freestyle? {:priority 80}, + :route-length-km {:priority 99}}}, + 2610 + {:description + {:fi + "Keilailuun varustettu halli. Ratojen määrä ja palveluvarustus kirjataan lisätietoihin.", + :se "Antalet banor och serviceutrustning i karakteristika.", + :en "Number of alleys and service facilities in properties."}, + :tags {:fi []}, + :name {:fi "Keilahalli", :se "Bowlinghall", :en "Bowling alley"}, + :type-code 2610, + :main-category 2000, + :status "active", + :sub-category 2600, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :bowling-lanes-count {:priority 90}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :cosmic-bowling? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :school-use? {:priority 40}}}, + 2110 + {:description + {:fi + "Erilasia liikuntapalveluita ja -tiloja tarjoava kuntokeskus. Kohteessa voi olla esimerkiksi kuntosali- ja ryhmäliikuntatiloja.", + :se + "Olika motionstjänster och -utrymmen, t ex gym och gruppidrottsutrymmen.", + :en + "Different sports services and premises, e.g., gym, group exercise premises. "}, + :tags {:fi ["kuntosali" "kuntoilu"]}, + :name + {:fi "Kuntokeskus", + :se "Konditionsidrottscentrum", + :en "Fitness centre"}, + :type-code 2110, + :main-category 2000, + :status "active", + :sub-category 2100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-customer-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :group-exercise-rooms-count {:priority 80}, + :area-m2 {:priority 90}, + :weight-lifting-spots-count {:priority 80}, + :customer-service-point? {:priority 70}, + :spinning-hall? {:priority 80}, + :school-use? {:priority 40}, + :exercise-machines-count {:priority 80}}}, + 3120 + {:description + {:fi + "Yksittäinen tai useampi pieni uima-allas muun kuin uimahallin tai kylpylän yhteydessä. Uima-allastilat voivat olla pääasiassa esim. kuntoutus- tai terapiakäytössä. Altaiden määrä ja vesipinta-ala kerrotaan ominaisuustiedoissa.", + :se + "Enstaka simbassäng , ofta i anslutning till en annan byggnad.", + :en + "Individual swimming pool, often in connection with other buildings."}, + :tags {:fi []}, + :name + {:fi "Uima-allastila", :se "Simbassäng", :en "Swimming pool"}, + :type-code 3120, + :main-category 3000, + :status "active", + :sub-category 3100, + :geometry-type "Point", + :props + {:pool-tracks-count {:priority 87}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :pool-width-m {:priority 89}, + :pool-min-depth-m {:priority 80}, + :swimming-pool-count {:priority 90}, + :pool-water-area-m2 {:priority 90}, + :pool-length-m {:priority 88}, + :pool-max-depth-m {:priority 80}, + :accessibility-info {:priority 40}, + :pool-temperature-c {:priority 80}, + :school-use? {:priority 40}}}, + 104 + {:description + {:fi + "Sijaitsevat kauempana taajamasta, automatkan päässä. Monipuolinen polku- ja reittiverkosto. Käyttö painottuu viikonloppuihin ja loma-aikoihin. Palvelevat usein useaa kuntaa. Kaavamerkintä VR.", + :se + "Ett område på bil avstånd från tätorten, Området har en stor variation av stig- och ruttnätverk. Användningen av området fokuserar sig mest till helgerna och semester tiderna. Området betjänar oftast mer än en kommun. Planbeteckning VR.", + :en + "Located further away from population centres, accessible by car. Complex network of paths and routes. Use concentrated during weekends and holidays. Often serves several municipalities. Symbol VR."}, + :tags {:fi ["virkistysalue"]}, + :name + {:fi "Retkeilyalue", :se "Utflyktsområde", :en "Hiking area"}, + :type-code 104, + :main-category 0, + :status "deprecated", + :sub-category 1, + :geometry-type "Polygon", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :area-km2 {:priority 90}, + :school-use? {:priority 70}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2330 + {:description + {:fi "Pysyvästi pöytätennikseen varustettu tila.", + :se "Permanent utrustning för att träna bordtennis.", + :en "Space permanently equipped for table tennis."}, + :tags {:fi ["pingis" "pingispöytä"]}, + :name + {:fi "Pöytätennistila", + :se "Utrymme för bordtennis", + :en "Table tennis venue"}, + :type-code 2330, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :active-space-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :active-space-length-m {:priority 90}, + :table-tennis-count {:priority 80}, + :school-use? {:priority 40}}}, + 2280 + {:description + {:fi + "Tenniksen pelaamiseen varusteltu halli. Kenttien lukumäärä ja pintamateriaali kerrotaan kohteen lisätiedoissa.", + :se "Antalet banor i karakteristika.", + :en "Number of courts specified in properties."}, + :tags {:fi []}, + :name {:fi "Tennishalli", :se "Tennishall", :en "Tennis hall"}, + :type-code 2280, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :floorball-fields-count {:priority 80}, + :squash-courts-count {:priority 80}, + :customer-service-point? {:priority 70}, + :volleyball-fields-count {:priority 80}, + :school-use? {:priority 40}}}, + 6140 + {:description + {:fi "Raviurheilun harjoitus- tai kilparata.", + :se "Övnings- eller tävlingsbana för travsport.", + :en "Training or competition track for horse racing."}, + :tags {:fi []}, + :name {:fi "Ravirata", :se "Travbana", :en "Horse racing track"}, + :type-code 6140, + :main-category 6000, + :status "active", + :sub-category 6100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :track-length-m {:priority 90}}}, + 2140 + {:description + {:fi + "Sali, jossa voi harrastaa kamppailulajeja kuten painia, nyrkkeilyä tai budolajeja. Tilan koko ja varustus kerrotaan kohteen lisätiedoissa.", + :se + "Sal där man kan utöva självförsvarsgrenar, t ex brottning, boxning. Storleken anges i karakteristika.", + :en + "Hall for self-defence sports, e.g., wrestling, boxing. Size specified in properties. "}, + :tags {:fi ["paini" "judo" "tatami"]}, + :name + {:fi "Kamppailulajien sali", + :se "Sal för kampsport", + :en "Martial arts hall"}, + :type-code 2140, + :main-category 2000, + :status "active", + :sub-category 2100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :group-exercise-rooms-count {:priority 80}, + :space-divisible {:priority 70}, + :tatamis-count {:priority 80}, + :area-m2 {:priority 90}, + :wrestling-mats-count {:priority 80}, + :boxing-rings-count {:priority 80}, + :weight-lifting-spots-count {:priority 80}, + :customer-service-point? {:priority 70}, + :school-use? {:priority 40}, + :exercise-machines-count {:priority 80}}}, + 4220 + {:description + {:fi + "Hiihtoon tarkoitettu katettu tila (esim. tunneli, putki, halli).", + :se + "Utrymme avsett för skidåkning under tak (tunnel, rör, hall el dyl).", + :en + "Covered space (tunnel, tube, hall, etc.) intended for skiing."}, + :tags {:fi []}, + :name {:fi "Hiihtotunneli", :se "Skidtunnel", :en "Ski tunnel"}, + :type-code 4220, + :main-category 4000, + :status "active", + :sub-category 4200, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :altitude-difference {:priority 90}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :equipment-rental? {:priority 70}, + :accessibility-info {:priority 40}, + :route-length-km {:priority 99}}}, + 2230 + {:description + {:fi + "Ensisijaisesti jalkapalloiluun tarkoitettu halli. Halli voi olla ympärivuotisessa käytössä tai erikseen talviajalle pystytettävä kevythalli. Kentän pintamateriaalina on yleensä tekonurmi.", + :se + "Hall avsedd för fotboll. Ytmaterial, antalet planer och storlek i karakteristika.", + :en + "Hall intended for football. Surface material, number and size of courts specified in properties."}, + :tags {:fi []}, + :name + {:fi "Jalkapallohalli", :se "Fotbollshall", :en "Football hall"}, + :type-code 2230, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :heating? {:priority 70}, + :surface-material {:priority 89}, + :basketball-fields-count {:priority 80}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :sprint-track-length-m {:priority 80}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :football-fields-count {:priority 80}, + :auxiliary-training-area? {:priority 80}, + :loudspeakers? {:priority 70}, + :customer-service-point? {:priority 70}, + :volleyball-fields-count {:priority 80}, + :school-use? {:priority 40}}}, + 1350 + {:description + {:fi + "Suuri jalkapallokenttä, katsomoita. Vähintään kansallisen tason pelipaikka.", + :se + "Stor fotbollsplan, flera läktare. Minimikrav: spelplats på nationell nivå.", + :en + "Large football field, stands. Can host at least national-level games."}, + :tags {:fi ["jalkapallokenttä"]}, + :name + {:fi "Jalkapallostadion", + :se "Fotbollsstadion", + :en "Football stadium"}, + :type-code 1350, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :customer-service-point? {:priority 70}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 4840 + {:description + {:fi + "Maastoon rakennettu jousiammuntarata. Radan käyttö edellyttää erillistä lupaa, seuran jäsenyyttä tai harjoitusvuoroa. ", + :se "Bågskyttebana byggd i terrängen.", + :en "Archery course built in rough terrain."}, + :tags {:fi ["jousiampumarata"]}, + :name + {:fi "Jousiammuntamaastorata", + :se "Terrängbana för bågskytte", + :en "Field archery course"}, + :type-code 4840, + :main-category 4000, + :status "active", + :sub-category 4800, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :free-customer-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shooting-positions-count {:priority 80}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 113 + {:description + {:fi + "Vapaa-ajankalastukseen sopiva alue. Kohteessa voi olla palvelurakenteita.", + :se + "Område eller en plats i ett naturligt vattendrag som ställts i ordning för fritidsfiske.", + :en + "Natural aquatic destination equipped and maintained for recreational fishing."}, + :tags {:fi ["kalastusalue" "kalastuspaikka"]}, + :name + {:fi "Kalastuskohde (alue)", + :se "Område eller plats för fiske", + :en "Fishing area/spot "}, + :type-code 113, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :pier? {:priority 80}, + :customer-service-point? {:priority 80}, + :equipment-rental? {:priority 80}}}, + 1510 + {:description + {:fi + "Koneellisesti tai keinotekoisesti jäähdytetty ulkokenttä. Kentän koko ja varustustiedot löytyvät lisätiedoista. Käytössä talvikaudella.", + :se + "En utomhusbana som är mekaniskt eller artificiellt kyld. Information om banans storlek och utrustning finns i ytterligare information. Används under vintersäsongen.", + :en + "An outdoor rink that is mechanically or artificially cooled. Details about the size and equipment of the rink can be found in the additional information. Used during the winter season."}, + :tags {:fi ["luistelukenttä" "luistelu"]}, + :name + {:fi "Tekojääkenttä/tekojäärata", + :se "Konstis", + :en "Mechanically frozen open-air ice rink"}, + :type-code 1510, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :fields-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :changing-rooms-m2 {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 5350 + {:description + {:fi + "Pääasiallisesti kiihdytysautoiluun tai kiihdytysmoottoripyöräilyyn käytetty rata.", + :se "Huvudsakligen för accelerationskörning.", + :en "Mainly for drag racing."}, + :tags {:fi []}, + :name + {:fi "Kiihdytysrata", :se "Accelerationsbana", :en "Dragstrip"}, + :type-code 5350, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :track-length-m {:priority 90}}}, + 2225 + {:description + {:fi + "Sisäleikkipuistot ovat yleensä pienille lapsille tarkoitettuja liikunnallisia leikkipaikkoja. Sisäaktiviteettipuistot ovat tyypillisesti lapsille ja nuorille tarkoitettuja liikuntakeskuksia, jotka sisältävät erilaisia liikunnallisia kohteita."}, + :tags {:fi []}, + :name {:fi "Sisäleikki-/sisäaktiviteettipuisto"}, + :type-code 2225, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:customer-service-point? {:priority 70}, + :height-m {:priority 90}, + :area-m2 {:priority 90}}}, + 4440 + {:description + {:fi + "Hiihtolatu, jossa on aina tai tiettyinä aikoina koirahiihto sallittua. Perinteinen tyyli tai vapaa tyyli.", + :se + "Ett skidspår där det alltid eller vissa tider är tillåtet att åka med hundspann. Klassisk stil eller fristil.", + :en + "Ski track on which dog skijoring is allowed either always or at given times. Traditional or free style."}, + :tags {:fi ["koiralatu"]}, + :name + {:fi "Koirahiihtolatu", + :se "Spår för hundspann", + :en "Dog skijoring track"}, + :type-code 4440, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :ski-track-traditional? {:priority 80}, + :route-width-m {:priority 98}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 97}, + :ski-track-freestyle? {:priority 80}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 2520 + {:description + {:fi + "Kilpajäähalli on jääurheilun kilpailu- ja ottelutapahtumiin soveltuva jäähalli. Katsomon koko, kenttien lukumäärä ja muut tarkemmat tiedot kuvataan lisätiedoissa.", + :se + "Läktare finns, storleken på läktaren anges i karakteristika, likaså antalet planer.", + :en + "Includes bleachers, whose size is specified in properties. Number of fields, heating, changing rooms, etc., specified in properties."}, + :tags {:fi ["jäähalli"]}, + :additional-type + {:small + {:fi "Pieni kilpahalli > 500 hlö", + :en "Small competition hall > 500 people", + :se "Liten tävlingshall > 500 personer"}, + :competition + {:fi "Kilpahalli < 3000 hlö", + :en "Competition hall < 3000 people", + :se "Tävlingshall < 3000 personer"}, + :large + {:fi "Suurhalli > 3000 hlö", + :en "Large hall > 3000 people", + :se "Större hall > 3000 personer"}}, + :name + {:fi "Kilpajäähalli", + :se "Tävlingsishall", + :en "Competition ice arena"}, + :type-code 2520, + :keywords {:fi ["Jäähalli"], :en [], :se []}, + :main-category 2000, + :status "active", + :sub-category 2500, + :geometry-type "Point", + :props + {:surface-material {:priority 79}, + :surface-material-info {:priority 78}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :field-2-flexible-rink? {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :curling-lanes-count {:priority 80}, + :scoreboard? {:priority 70}, + :auxiliary-training-area? {:priority 80}, + :ringette-boundary-markings? {:priority 80}, + :field-1-flexible-rink? {:priority 80}, + :loudspeakers? {:priority 70}, + :school-use? {:priority 40}, + :field-3-flexible-rink? {:priority 80}}}, + 4710 + {:description + {:fi + "Rakennettu kiipeilyseinä ulkona, köysikiipeilyrata tai vastaava kiipeilyä varten rakennettu paikka. Myös rakennetut boulderointipaikat. Paikan tarkempi kuvaus ominaisuustietoihin.", + :se + "Byggd klättervägg utomhus, klätterbana eller annan plats byggd för klättring. Även platser för bouldering. Precisering i karakteristika.", + :en + "Built outdoor climbing wall, rope climbing path or other place built for climbing. Also bouldering venues. Clarification in 'properties'."}, + :tags {:fi ["kiipeilyseinä" "köysikiipeily"]}, + :name + {:fi "Ulkokiipeilypaikka", + :se "Utomhusklätterplats", + :en "Open-air climbing venue"}, + :type-code 4710, + :main-category 4000, + :status "active", + :sub-category 4700, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :climbing-routes-count {:priority 80}, + :ice-climbing? {:priority 80}, + :climbing-wall-height-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :climbing-wall-width-m {:priority 90}, + :ligthing? {:priority 70}, + :school-use? {:priority 40}}}, + 304 + {:description + {:fi + "Tavallisen arkiliikunnan taukopaikka, päiväkäyttöön. Lisätietoihin merkitään kohteessa olevat palvelut, esim. kahvio, vuokrauspiste tai opastuspiste.", + :se "Rastplats för bruk under dagen, vardagsmotion.", + :en "Rest area for regular daily sports, for daytime use."}, + :tags {:fi ["tupa" "taukopaikka" "ulkoilumaja" "hiihtomaja"]}, + :name + {:fi "Ulkoilumaja/hiihtomaja", + :se "Friluftsstuga/skidstuga", + :en "Outdoor/ski lodge "}, + :type-code 304, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:toilet? {:priority 70}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :customer-service-point? {:priority 70}, + :equipment-rental? {:priority 70}, + :free-use? {:priority 60}}}, + 4412 + {:description + {:fi + "Pyöräilyreitti, joka kulkee enimmäkseen päällystetyillä teillä tai sorateillä. Reitti voi olla merkitty maastoon tai se on digitaalisesti opastettu.", + :se "Cykelled, ej för mountainbikar.", + :en "Biking route, not intended for cross-country biking."}, + :tags {:fi []}, + :name {:fi "Pyöräilyreitti", :se "Cykelled", :en "Biking route"}, + :type-code 4412, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :route-length-km {:priority 99}}}, + 4820 + {:description + {:fi + "Ampumarata jossa myös palveluita. SM-kisojen järjestäminen mahdollista.", + :se "Skjutbana med tjänster. Möjligt att arrangera FM-tävlingar.", + :en + "Shooting range with services. National competitions possible."}, + :tags {:fi ["ampumapaikka" "ammunta"]}, + :name + {:fi "Ampumaurheilukeskus", + :se "Sportskyttecentrum", + :en "Shooting sports centre"}, + :type-code 4820, + :main-category 4000, + :status "active", + :sub-category 4800, + :geometry-type "Point", + :props + {:stand-capacity-person {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :air-gun-shooting? {:priority 80}, + :toilet? {:priority 70}, + :pistol-shooting? {:priority 80}, + :shooting-positions-count {:priority 90}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :rifle-shooting? {:priority 80}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :shotgun-shooting? {:priority 80}, + :free-rifle-shooting? {:priority 80}, + :ligthing? {:priority 60}, + :accessibility-info {:priority 40}, + :track-length-m {:priority 90}}}, + 1170 + {:description + {:fi "Ratapyöräilyä varten rakennettu paikka, ulkona (velodromi).", + :se "Utomhus, velodrom.", + :en "For track racing outdoors (velodrome)."}, + :tags {:fi ["velodromi"]}, + :name + {:fi "Pyöräilyrata/velodromi", :se "Velodrom", :en "Velodrome"}, + :type-code 1170, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 50}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :ligthing? {:priority 70}, + :school-use? {:priority 50}, + :track-length-m {:priority 90}}}, + 6150 + {:description + {:fi + "Islanninhevosten askellajiratsastukseen varattu rata ulkona.", + :se + "En bana utomhus avsedd för gångartstävlingar med islandshästar.", + :en + "An outdoor track designated for gaited riding competitions with Icelandic horses."}, + :tags + {:fi + ["islanninhevosrata" + "islanninhevosratsastus" + "ovaalibaana" + "askellajiratsastus" + "askellajirata"], + :se + ["islandshästbana" + "islandshästridning" + "ovalbana" + "gångartstävling" + "gångartsbana"], + :en + ["Icelandic horse track" + "Icelandic horse riding" + "oval track" + "gaited riding" + "gaited track"]}, + :name {:fi "Ovaalirata", :se "Ovalbana", :en "Oval Track"}, + :type-code 6150, + :main-category 6000, + :status "active", + :sub-category 6100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 4404 + {:description + {:fi + "Erityisesti luontoharrastusta varten rakennettu ulkoilureitti. Reitin varrella opasteita tai infotauluja alueen luonnosta.", + :se + "I synnerhet för naturintresse, info- och orienteringstavlor längs leden.", + :en + "Intended particularly for nature activities; signposts or info boards along the route."}, + :tags {:fi ["retkeily"]}, + :name {:fi "Luontopolku", :se "Naturstig", :en "Nature trail"}, + :type-code 4404, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :accessibility-info {:priority 40}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 108 + {:description + {:fi + "Metsähallituksen päätöksellä perustettu virkistysmetsä. Metsätaloudessa huomioidaan mm. maisemalliset arvot. Metsähallitus tietolähde.", + :se + "Grundad enligt Forststyrelsens beslut. I skogsbruket tas hänsyn till bl a landskapsvärden. Källa: Forststyrelsen.", + :en + "Recreational forest designated by Metsähallitus. E.g. scenic value is considered in forestry. Source of information Metsähallitus."}, + :tags {:fi []}, + :name + {:fi "Virkistysmetsä", + :se "Friluftsskog", + :en "Recreational forest"}, + :type-code 108, + :main-category 0, + :status "deprecated", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :school-use? {:priority 70}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4401 + {:description + {:fi + "Kuntoiluun tarkoitettu hoidettu liikuntareitti asutuksen läheisyydessä. Usein ainakin osittain valaistu.", + :se "Led avsedd för konditionssport i närheten av bebyggelse.", + :en "Route intended for jogging in or near a residential area."}, + :tags {:fi ["pururata"]}, + :name {:fi "Kuntorata", :se "Konditionsbana", :en "Jogging track"}, + :type-code 4401, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :outdoor-exercise-machines? {:priority 80}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :shooting-positions-count {:priority 80}, + :lit-route-length-km {:priority 98}, + :accessibility-info {:priority 40}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 2350 + {:description + {:fi + "Pysyvästi tanssi-, ilmaisu- tai ryhmäliikuntaan varustettu itsenäinen tila, joka ei ole osa esim. kuntokeskusta. Myös boutique-, fitness- ja mikrostudiot ovat tanssi- tai ryhmäliikuntatiloja.", + :se "Permanent utrustning för dans och kreativ motion.", + :en + "Space permanently equipped for dance or expressive movement exercise."}, + :tags {:fi ["peilisali" "baletti" "tanssisali"]}, + :name + {:fi "Tanssi-/ryhmäliikuntatila", + :se "Utrymme för dans", + :en "Dance studio"}, + :type-code 2350, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :active-space-width-m {:priority 90}, + :mirror-wall? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :group-exercise-rooms-count {:priority 80}, + :area-m2 {:priority 90}, + :active-space-length-m {:priority 90}, + :school-use? {:priority 40}}}, + 2340 + {:description + {:fi "Pysyvästi miekkailuun varustettu tila.", + :se "Permanent utrustning för fäktning.", + :en "Space permanently equipped for fencing."}, + :tags {:fi []}, + :name + {:fi "Miekkailutila", + :se "Utrymme för fäktning", + :en "Fencing venue"}, + :type-code 2340, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :fencing-bases-count {:priority 80}, + :school-use? {:priority 40}}}, + 2120 + {:description + {:fi + "Liikuntatila, jossa on useita kuntosalilaitteita pysyvästi sijoitettuna.", + :se "Gymredskap osv. Storleken anges i karakteristika.", + :en "Gym equipment, etc. Size specified in properties."}, + :tags {:fi ["kuntoilu" "voimailu"]}, + :name {:fi "Kuntosali", :se "Gym", :en "Gym"}, + :type-code 2120, + :main-category 2000, + :status "active", + :sub-category 2100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-customer-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :group-exercise-rooms-count {:priority 80}, + :area-m2 {:priority 90}, + :weight-lifting-spots-count {:priority 80}, + :customer-service-point? {:priority 70}, + :spinning-hall? {:priority 80}, + :school-use? {:priority 40}, + :exercise-machines-count {:priority 80}}}, + 109 + {:description + {:fi + "Ulkoilulailla perustetut, retkeilyä ja luonnon virkistyskäyttöä varten. Metsähallitus tietolähteenä.", + :se + "Grundat enligt lagen om friluftsliv för att användas för friluftsliv och rekreation i naturen. Källa: Forststyrelsen.", + :en + "Established based on the Outdoor Recreation Act for hiking and recreational use of nature. Source of information Metsähallitus."}, + :tags {:fi []}, + :name + {:fi "Valtion retkeilyalue", + :se "Statens friluftsområde", + :en "National hiking area"}, + :type-code 109, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 1650 + {:description + {:fi + "Ensisijaisesti golfin pelaamiseen tarkoitettu alue kesäkaudella. Reikien määrä merkitään lisätietoihin.", + :se "Officiell golfbana. Antalet hål anges i karakteristika.", + :en + "Official golf course. Number of holes included in properties."}, + :tags {:fi ["greeni" "puttialue" "range"]}, + :name {:fi "Golfkenttä (alue)", :se "Golfbana", :en "Golf course"}, + :type-code 1650, + :main-category 1000, + :status "active", + :sub-category 1600, + :geometry-type "Polygon", + :props + {:holes-count {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :customer-service-point? {:priority 70}, + :green? {:priority 80}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :range? {:priority 80}}}, + 4441 + {:description + {:fi "Koiravaljakoille ylläpidetty reitti.", + :se "En rutt underhållen för hundspann.", + :en "A route maintained for dog sledding."}, + :tags {:fi ["valjakkoajo" "valjakkoreitti"], :se [], :en []}, + :name + {:fi "Koiravaljakkoreitti", + :se "Hundspannsrutt", + :en "Dog Sledding Route"}, + :type-code 4441, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :route-length-km {:priority 99}, + :lit-route-length-km {:priority 97}, + :route-width-m {:priority 98}, + :free-use? {:priority 40}, + :year-round-use? {:priority 80}}}, + 5160 + {:description + {:fi + "Soudun ja melonnan sisäharjoittelutila on erityisesti näihin lajeihin pysyvästi tarkoitettu liikuntapaikka.", + :se "Separat, ej normal bassäng.", + :en "Separate training facility, not a regular swimming pool."}, + :tags {:fi ["kajakki" "kanootti" "melonta"]}, + :name + {:fi "Soudun ja melonnan sisäharjoittelutila", + :se "Inomhusträningsutrymme för rodd och paddling", + :en "Indoor training facility for rowing and canoeing"}, + :type-code 5160, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:area-m2 {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 1550 + {:description + {:fi + "Luisteluun tarkoitettu luonnonjäälle tai maalle rakennettava huollettu luistelureitti. Rakennetaan talvisin samalle alueelle. ", + :se + "Byggs varje vinter på samma område t ex i en idrottspark eller på havsis.", + :en + "Built yearly in the same area, e.g., in a sports park or on frozen lake/sea."}, + :tags {:fi ["luistelu" "retkiluistelu" "retkiluistelurata"]}, + :name + {:fi "Luistelureitti", :se "Skridskoled", :en "Ice-skating route"}, + :type-code 1550, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :equipment-rental? {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :track-length-m {:priority 90}}}, + 3230 + {:description + {:fi + "Pieni yleinen uimaranta tai uimapaikka, jossa pelastusväline ja ilmoitustaulu. Veden laadun seuranta ja alueen hoito järjestetty.", + :se + "Liten allmän badstrand eller badplats. Räddningsutrustning och en anslagstavla finns. Kvaliteten på vattnet följs upp och området underhålls.", + :en + "Small public beach or swimming site. Rescue equipment and a notice board are available. The quality of the water is monitored and the area is maintained."}, + :tags {:fi ["uimaranta"]}, + :name {:fi "Uimapaikka", :se "Badplats", :en "Swimming site"}, + :type-code 3230, + :main-category 3000, + :status "active", + :sub-category 3200, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :pier? {:priority 80}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :beach-length-m {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :changing-rooms? {:priority 70}, + :sauna? {:priority 70}, + :other-platforms? {:priority 80}, + :school-use? {:priority 40}}}, + 5130 + {:description + {:fi "Pysyvä moottorivenekilpailujen rata-alue.", + :se "Permanent banområde för hastighetstävlingar.", + :en "Permanent track area for speed competitions."}, + :tags {:fi []}, + :name + {:fi "Moottoriveneurheilualue", + :se "Område för motorbåtsport", + :en "Motor boat sports area"}, + :type-code 5130, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:pier? {:priority 80}, + :area-km2 {:priority 90}, + :boat-places-count {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 5110 + {:description + {:fi + "Soutustadion sisältää pysyvästi soutuun käytettäviä rakenteita. Soutustadionissa on katsomo ja valmius ratamerkintöihin.", + :se + "Byggt för rodd, permanent. Läktare och förberett för banmärkning.", + :en + "Permanent construction for rowing. Bleachers, track markings possible."}, + :tags {:fi []}, + :name + {:fi "Soutustadion", :se "Roddstadion", :en "Rowing stadium"}, + :type-code 5110, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :pier? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :scoreboard? {:priority 70}, + :track-length-m {:priority 90}}}, + 3240 + {:description + {:fi + "Talviuintipaikka voi sijaita avannossa, avovedessä tai maauimalassa talvikaudella. Talviuintipaikka merkitään omaksi liikuntapaikakseen.", + :se + "Vinterbadplats kan vara belägen i en vak, öppet vatten eller utomhuspool. Vinterbadplats är markerad som egen idrottsanläggning", + :en + "Winter swimming area may be located in an ice hole, open water or open air pool. Winter swimming area is marked as its own sports facility."}, + :tags {:fi ["avanto" "avantouinti" "talviuinti"]}, + :name + {:fi "Talviuintipaikka", + :se "Vinterbadplats", + :en "Winter swimming area"}, + :type-code 3240, + :main-category 3000, + :status "active", + :sub-category 3200, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :pier? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :changing-rooms? {:priority 70}, + :ice-reduction? {:priority 70}, + :sauna? {:priority 70}, + :school-use? {:priority 40}}}, + 4510 + {:description + {:fi + "Suunnistukseen käytetty alue. Lisätietoihin merkitään, jos aluetta käytetään mobo-, pyörä- tai hiihtosuunnistukseen. Suunnistusalueesta on saatavilla kartta ja maankäyttöön on maanomistajan suostumus.", + :se + "Anmält till orienteringsförbundet. Karta över området tillgänglig.", + :en + "The Finnish Orienteering Federation has been informed. A map of the area available."}, + :tags {:fi []}, + :name + {:fi "Suunnistusalue", + :se "Orienteringsområde", + :en "Orienteering area"}, + :type-code 4510, + :main-category 4000, + :status "active", + :sub-category 4500, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :school-use? {:priority 40}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :mobile-orienteering? {:priority 80}, + :bike-orienteering? {:priority 80}, + :ski-orienteering? {:priority 80}}}, + 4240 + {:description + {:fi "Katettu laskettelurinne.", + :se + "Slalombacke med tak. Höjdskillnad och längd i karakteristika.", + :en + "Covered ski slope. Height and length specified in attributes."}, + :tags {:fi []}, + :name + {:fi "Lasketteluhalli", + :se "Slalomhall", + :en "Downhill skiing hall"}, + :type-code 4240, + :main-category 4000, + :status "active", + :sub-category 4200, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :altitude-difference {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :equipment-rental? {:priority 70}, + :route-length-km {:priority 99}}}, + 2270 + {:description + {:fi + "Ensisijaisesti squashin pelaamiseen tarkoitettu halli. Yksittäisen kentän mitat 9,75 m x 6,4 m. Vapaa korkeus ja pintamateriaali ilmoitetaan lisätiedoissa.", + :se + "En eller flera squashplaner. Antalet planer i karakteristika.", + :en + "One or more squash courts. Number of courts specified in properties."}, + :tags + {:fi ["squash" "squash-kenttä" "squashkenttä" "squashhalli"]}, + :name {:fi "Squash-halli", :se "Squashhall", :en "Squash hall"}, + :type-code 2270, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 80}, + :free-use? {:priority 40}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :floorball-fields-count {:priority 80}, + :squash-courts-count {:priority 80}, + :customer-service-point? {:priority 70}, + :volleyball-fields-count {:priority 80}, + :school-use? {:priority 40}}}, + 4210 + {:description + {:fi + "Pysyvästi lajiin varustettu tila, esim. curlingrata tai curlinghalli.", + :se "Curlingbana med tak och permanent utrustning för grenen.", + :en "Covered track permanently equipped for curling."}, + :tags {:fi ["curlinghalli" "curling-halli" "curling-rata"]}, + :name + {:fi "Curlingrata/-halli", :se "Curlingbana", :en "Curling sheet"}, + :type-code 4210, + :main-category 4000, + :status "active", + :sub-category 4200, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :curling-lanes-count {:priority 90}, + :changing-rooms-m2 {:priority 70}}}, + 301 + {:description + {:fi + "Päiväsaikainen levähdyspaikka retkeilijöille. Esimerkiksi kodalla tarkoitetaan kotamallista sääsuojaa tai levähdyspaikkaa ja laavu on kaltevakattoinen sääsuoja, joka sisältää tulipaikan. Lisätietoihin merkitään tieto tulentekopaikasta.", + :se "Viloplats för vandrare under dagtid.", + :en "Daytime rest stop for hikers."}, + :tags {:fi ["taukopaikka"]}, + :name + {:fi "Laavu, kota tai kammi", + :se "Vindskydd eller kåta", + :en "Lean-to, goahti (Lapp tent shelter) or 'kammi' earth lodge"}, + :type-code 301, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :toilet? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :water-point {:priority 70}, + :free-use? {:priority 60}}}, + 111 + {:description + {:fi + "Kansallispuistot ovat luonnonsuojelualueita, joiden perustamisesta ja tarkoituksesta on säädetty lailla. Kansallispuistoissa on merkittyjä reittejä, luontopolkuja ja tulentekopaikkoja. Kansallispuistoissa voi myös yöpyä, sillä niissä on telttailualueita tai yöpymiseen tarkoitettuja rakennuksia. LIPAS-aineisto perustuu Metsähallituksen tietoihin.", + :se + "Naturskyddsområden med lagstadgad status och uppgift. Areal minst 1000 ha. Källa: Forststyrelsen.", + :en + "Nature conservation areas whose establishment and purpose are based on legislation. Min. area 1,000 ha. Source of information Metsähallitus."}, + :tags {:fi []}, + :name + {:fi "Kansallispuisto", :se "Nationalpark", :en "National park"}, + :type-code 111, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4630 + {:description + {:fi + "Hiihtokilpailujen järjestämiseen soveltuva kisakeskus, jossa on esimerkiksi lähtö- ja maalialue, huoltotilat ja riittävä latuverkosto.", + :se "Start- och målområden, serviceutrymmen, spårsystem.", + :en "Start and finish area, service premises. Tracks."}, + :tags {:fi ["hiihtostadion"]}, + :name + {:fi "Kilpahiihtokeskus", + :se "Maastohiihtokeskus", + :en "Ski competition centre"}, + :type-code 4630, + :main-category 4000, + :status "active", + :sub-category 4600, + :geometry-type "Point", + :props + {:stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :finish-line-camera? {:priority 70}, + :ski-track-traditional? {:priority 80}, + :route-width-m {:priority 98}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :rest-places-count {:priority 70}, + :changing-rooms? {:priority 70}, + :lit-route-length-km {:priority 97}, + :scoreboard? {:priority 70}, + :sauna? {:priority 70}, + :loudspeakers? {:priority 70}, + :accessibility-info {:priority 40}, + :ski-track-freestyle? {:priority 80}, + :route-length-km {:priority 99}}}, + 4810 + {:description + {:fi "Ulkoampumarata yhdelle tai useammalle lajille.", + :se "Utomhusskjutbana för en eller flera grenar.", + :en "Outdoor shooting range for one or more sports. "}, + :tags {:fi ["ampumapaikka" "ammunta"]}, + :name + {:fi "Ampumarata", :se "Skjutbana", :en "Open-air shooting range"}, + :type-code 4810, + :main-category 4000, + :status "active", + :sub-category 4800, + :geometry-type "Point", + :props + {:stand-capacity-person {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :air-gun-shooting? {:priority 80}, + :toilet? {:priority 70}, + :pistol-shooting? {:priority 80}, + :shooting-positions-count {:priority 90}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :rifle-shooting? {:priority 80}, + :year-round-use? {:priority 70}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :shotgun-shooting? {:priority 80}, + :free-rifle-shooting? {:priority 80}, + :ligthing? {:priority 60}, + :accessibility-info {:priority 40}, + :track-length-m {:priority 90}}}, + 1540 + {:description + {:fi + "Pikaluisteluun varusteltu luistelurata. Radan koko ja pituus lisätään ominaisuustietoihin. Käytössä talvikaudella.", + :se "Bana för hastighetsåkning. Används under vintersäsongen.", + :en "Track size and length specified in properties."}, + :tags {:fi ["luistelurata"]}, + :name + {:fi "Pikaluistelurata", + :se "Bana för hastighetsåkning på skridsko", + :en "Speed-skating track"}, + :type-code 1540, + :main-category 1000, + :status "active", + :sub-category 1500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :changing-rooms-m2 {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :track-length-m {:priority 90}}}, + 5320 + {:description + {:fi + "Pääasiassa moottoripyöräilyä varten rakennettu, luonnonmukainen ei-asfalttipintainen alue (esim. enduroreitit ja trial-harjoittelualueet maastoliikennealueilla).", + :se "Huvudsakligen för motorcykelsport.", + :en "Mainly for motorcycling."}, + :tags {:fi ["motocross"]}, + :name + {:fi "Moottoripyöräilyalue", + :se "Område för motorcykelsport", + :en "Motorcycling area"}, + :type-code 5320, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :track-length-m {:priority 90}}}, + 3210 + {:description + {:fi + "Maauimala tai vesipuisto on ulkona sijaitseva, vedenpuhdistusjärjestelmällä varustettu uintiin tarkoitettu ja hoidettu vesistö tai uima-altaita/allas. Lisäksi kohteessa voi olla vesiliukumäkiä.", + :se "Vattenreningssystem.", + :en "Water treatment system."}, + :tags {:fi ["uima-allas" "ulkoallas" "ulkouima-allas"]}, + :name + {:fi "Maauimala/vesipuisto", + :se "Utebassäng", + :en "Open-air pool "}, + :type-code 3210, + :main-category 3000, + :status "active", + :sub-category 3200, + :geometry-type "Point", + :props + {:pool-tracks-count {:priority 87}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :pool-width-m {:priority 89}, + :pool-min-depth-m {:priority 80}, + :toilet? {:priority 70}, + :swimming-pool-count {:priority 90}, + :pool-water-area-m2 {:priority 90}, + :pool-length-m {:priority 88}, + :pool-max-depth-m {:priority 80}, + :loudspeakers? {:priority 70}, + :pool-temperature-c {:priority 80}, + :school-use? {:priority 40}}}, + 4640 + {:description + {:fi + "Hiihdon opetteluun ja harjoitteluun tarkoitettu paikka erityisesti lapsille. Erilaisia harjoittelupaikkoja, kuten latuja, mäkiä ym.", + :se "Träningsplats för skidåkning, teknikhaster mm.", + :en + "Ski training venue, an area of parallel short ski tracks for ski instruction, etc."}, + :tags {:fi []}, + :name + {:fi "Hiihtomaa", :se "Skidland", :en "Cross-country ski park"}, + :type-code 4640, + :main-category 4000, + :status "active", + :sub-category 4600, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :ski-service? {:priority 70}, + :ski-track-traditional? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :scoreboard? {:priority 70}, + :equipment-rental? {:priority 70}, + :loudspeakers? {:priority 70}, + :accessibility-info {:priority 0}, + :ski-track-freestyle? {:priority 80}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 1150 + {:description + {:fi + "Rullaluistelua, skeittausta, potkulautailua varten varustettu paikka. Ominaisuustiedoissa tarkemmat tiedot kohteesta.", + :se + "Plats utrustad för rullskridskoåkning, skejtning och sparkcykelåkning.", + :en + "An area equipped for roller-blading, skateboarding, kick scooting."}, + :tags + {:fi ["ramppi" "skeittipaikka" "skeittipuisto" "skeittiparkki"]}, + :name + {:fi "Skeitti-/rullaluistelupaikka", + :se "Plats för skejtning/rullskridskoåkning", + :en "Skateboarding/roller-blading rink "}, + :type-code 1150, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:area-m2 {:priority 90}, + :surface-material-info {:priority 80}, + :surface-material {:priority 80}, + :ligthing? {:priority 70}, + :school-use? {:priority 50}, + :free-use? {:priority 50}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :lighting-info {:priority 60}}}, + 2310 + {:description + {:fi + "Yksittäinen yleisurheilun olosuhde sisätiloissa esim. liikunta- tai monitoimihallin yhteydessä. Suorituspaikat kuvataan lisätiedoissa.", + :se + "Fristående, ej i anslutning till en friidrottshall. I karakteristika anges övningsplatserna.", + :en + "Stand-alone, not in an athletics hall. Venues specified under properties."}, + :tags {:fi ["yleisurheilu" "juoksurata"]}, + :name + {:fi "Yksittäinen yleisurheilun suorituspaikka", + :se "Enstaka övningsplats för friidrott", + :en "Stand-alone athletics venue"}, + :type-code 2310, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :free-use? {:priority 40}, + :javelin-throw-places-count {:priority 80}, + :sprint-track-length-m {:priority 80}, + :inner-lane-length-m {:priority 80}, + :discus-throw-places {:priority 80}, + :hammer-throw-places-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :polevault-places-count {:priority 80}, + :area-m2 {:priority 90}, + :shotput-count {:priority 80}, + :longjump-places-count {:priority 80}, + :school-use? {:priority 40}, + :highjump-places-count {:priority 80}}}, + 5210 + {:description + {:fi + "Harraste- tai urheiluilmailuun tarkoitettu alue, esim. lentopaikka.", + :se "Flygsportarena, t ex en flygplats.", + :en "Area for air sports, e.g. an airfield."}, + :tags + {:fi + ["lentäminen" + "lento" + "lentokone" + "ilmailu" + "ilmailualue" + "lentokenttä"]}, + :name + {:fi "Urheiluilmailualue", + :se "Område för flygsport", + :en "Sport aviation area"}, + :type-code 5210, + :main-category 5000, + :status "active", + :sub-category 5200, + :geometry-type "Point", + :props + {:track-length-m {:priority 90}, + :area-m2 {:priority 90}, + :surface-material-info {:priority 80}, + :surface-material {:priority 80}, + :track-width-m {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2380 + {:description + {:fi "Parkouria varten varustettu sisätila.", + :se "Inomhusutrymme utrustat för parkour.", + :en "Indoor space equipped for parkour. "}, + :tags {:fi ["parkour"]}, + :name {:fi "Parkour-sali", :se "Parkoursal", :en "Parkour hall"}, + :type-code 2380, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :parkour-hall-equipment-and-structures {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :highest-obstacle-m {:priority 80}, + :auxiliary-training-area? {:priority 70}, + :school-use? {:priority 40}}}, + 103 + {:description + {:fi + "Voivat sijaita taajaman reunoilla, vyöhykkeittäin taajaman sisällä tai taajaman ulkopuolella. Kohteissa voi olla myös taajamasta lähteviä tai taajamaan palaavia reittejä tai polku- ja reittiverkosto. Kohteet sisältävät vaihtelevaa maastoa ja luonnonmukaisia tai puistomaisia alueita. Kohteet voivat myös sijaita vesialuiden lähellä kuten rannoilla tai saarissa. Kohteiden pääasiallinen käyttö on retkeilyä ja luonnossa virkistäytymistä, mutta niitä voidaan käyttää monipuolisesti erilaisen liikunnan kuten hiihdon, lenkkeilyn tai uinnin harrastamiseen. Kaavamerkintä esim. VR. HUOM! Uusien liikunta- ja ulkoilupaikkojen lisäksi Ulkoilu-/virkistysalueluokka sisältää ennen vuotta 2024 Ulkoilualueet ja Retkeilyalueet tyyppiluokkiin lisätyt olosuhteet", + :se + "Området befinner sig i utkanten av tätorter eller i zoner inom tätorten. På 1-10 kilometers avstånd från bebyggelse. Friluftsområdet används för t.ex. promenader, skidning, joggning, simning. Serverar oftast friluftsaktiviteter för en kommun. Området erbjuder en stor variation av motions möjligheter. Området kan bestå av skog, kärr, åkrar, naturenliga områden och parkliknande delar. Planbeteckning VR.", + :en + "On the edge of population centres or zoned within population centres. 1-10 km from residential areas. Used for e.g. walks, skiing, jogging, swimming. Serves usually recreational needs within one municipality, offers versatile sports facilities. May include forest, swamp, fields, natural areas and park areas. Symbol VR."}, + :tags {:fi ["puisto" "virkistysalue"]}, + :name + {:fi "Ulkoilu-/virkistysalue", + :se "Friluftsområde", + :en "Outdoor area"}, + :type-code 103, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :area-km2 {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :water-point {:priority 80}}}, + 201 + {:description + {:fi + "Vapaa-ajankalastukseen sopiva kohde. Kohteessa voi olla palvelurakenteita.", + :se + "Område eller en plats i ett naturligt vattendrag som ställts i ordning för fritidsfiske.", + :en + "Natural aquatic destination equipped and maintained for recreational fishing."}, + :tags {:fi ["kalastusalue" "kalastuspaikka"]}, + :name + {:fi "Kalastuskohde (piste)", + :se "Område eller plats för fiske", + :en "Fishing area/spot "}, + :type-code 201, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:toilet? {:priority 80}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :pier? {:priority 80}, + :customer-service-point? {:priority 80}, + :equipment-rental? {:priority 80}}}, + 1220 + {:description + {:fi + "Hyvin varusteltu yleisurheilukenttä. Yleissurheilukentällä on ratoja ja yleisurheilun suorituspaikkoja. Myös kisakäyttö on mahdollista. Lyhytrataiset (juoksurata alle 400 m) yleisurheilukentät tallennettaan yleisurheilun harjoitusalueeksi. Yleisurheilukentällä sijaitseva jalkapallon tai muun lajin keskeinen suorituspaikka merkitään omaksi liikuntapaikakseen (esim. jalkapallostadion tai pallokenttä). ", + :se + "En plan, banor och träningsplatser för friidrott. Centrum, banor, ytbeläggningar samt träningsplatser med beskrivningar.", + :en + "Field, track and athletic venues/facilities. Centre, tracks, surfaces, venues specified in properties. "}, + :tags + {:fi + ["keihäs" + "keihäänheitto" + "moukari" + "pituushyppy" + "juoksurata" + "kolmiloikka" + "seiväs" + "kuula" + "urheilukenttä"]}, + :name + {:fi "Yleisurheilukenttä", + :se "Friidrottsplan", + :en "Athletics field"}, + :type-code 1220, + :main-category 1000, + :status "active", + :sub-category 1200, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :sprint-lanes-count {:priority 80}, + :javelin-throw-places-count {:priority 80}, + :finish-line-camera? {:priority 70}, + :field-length-m {:priority 90}, + :circular-lanes-count {:priority 80}, + :match-clock? {:priority 70}, + :sprint-track-length-m {:priority 80}, + :inner-lane-length-m {:priority 80}, + :discus-throw-places {:priority 80}, + :hammer-throw-places-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :polevault-places-count {:priority 80}, + :toilet? {:priority 70}, + :running-track-surface-material {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :scoreboard? {:priority 70}, + :shotput-count {:priority 80}, + :longjump-places-count {:priority 80}, + :loudspeakers? {:priority 70}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :school-use? {:priority 40}, + :highjump-places-count {:priority 80}, + :training-spot-surface-material {:priority 80}}}, + 4411 + {:description + {:fi + "Maastopyöräilyyn tarkoitettu reitti, joka kulkee vaihtelevassa maastossa ja on merkitty maastoon. Reitti voi hyödyntää muita olemassa olevia ulkoilureittipohjia.", + :se "Led avsedd framför allt för mountainbikar, märkt.", + :en "Marked route intended especially for cross-country biking."}, + :tags {:fi []}, + :name + {:fi "Maastopyöräilyreitti", + :se "Mountainbikeled", + :en "Cross-country biking route"}, + :type-code 4411, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}, + 1140 + {:description + {:fi "Parkouria varten varustettu alue.", + :se "Område utrustat för parkour.", + :en "An area equipped for parkour."}, + :tags {:fi []}, + :name + {:fi "Parkour-alue", :se "Parkourområde", :en "Parkour area"}, + :type-code 1140, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 50}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :highest-obstacle-m {:priority 80}, + :ligthing? {:priority 70}, + :climbing-wall? {:priority 80}, + :school-use? {:priority 50}}}, + 4520 + {:description + {:fi + "Hiihtosuunnistukseen soveltuva alue, alueesta hiihtosuunnistuskartta saatavilla.", + :se + "Skidorienteringskarta över området, ej för sommarorientering.", + :en + "A ski orienteering map of the area available; no summer orienteering."}, + :tags {:fi []}, + :name + {:fi "Hiihtosuunnistusalue", + :se "Skidorienteringsområde", + :en "Ski orienteering area"}, + :type-code 4520, + :main-category 4000, + :status "deprecated", + :sub-category 4500, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :school-use? {:priority 40}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 107 + {:description + {:fi + "Matkailupalvelujen alueet ovat matkailua palveleville toiminnoille varattuja alueita, jotka sisältävät myös sisäiset liikenneväylät ja -alueet, alueen toimintoja varten tarpeelliset palvelut ja virkistysalueet sekä yhdyskuntateknisen huollon alueet. Kohteet voivat toimia myös retkeilyauto- ja pyörämatkailijoiden tauko- ja yöpymispaikkoina. Kaavamerkintä RM.", + :se + "Områden med turisttjänster har reserverats för turist- och semestercentra, semesterbyar, semesterhotell och motsvarande aktörer. De har egna trafikleder och -områden samt områden för egna serviceenheter och egen infrastruktur för aktiviteter. Planbeteckning RM.", + :en + "Area reserved for tourism and holiday centres, holiday villages, hotels, etc., also including internal traffic routes and areas; services and recreational areas needed for operations, as well as technical maintenance areas. Symbol RM."}, + :tags {:fi ["ulkoilualue" "virkistysalue" "leirintäalue"]}, + :name + {:fi "Matkailupalveluiden alue", + :se "Område med turisttjänster", + :en "Tourist services area"}, + :type-code 107, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:area-km2 {:priority 90}, + :free-use? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 80}}}, + 6110 + {:description + {:fi "Ratsastukseen varustettu kenttä.", + :se "Bana avsedd för ridning. Storlek i karakteristika.", + :en + "Field reserved for horseback riding. Size specified in properties."}, + :tags {:fi []}, + :name + {:fi "Ratsastuskenttä", :se "Ridbana", :en "Equestrian field"}, + :type-code 6110, + :main-category 6000, + :status "active", + :sub-category 6100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :show-jumping? {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}}}, + 1120 + {:description + {:fi + "Lähiliikuntapaikka on tarkoitettu päivittäiseen ulkoiluun ja liikuntaan. Se sijaitsee asutuksen läheisyydessä, on pienimuotoinen ja alueelle on vapaa pääsy. Yleensä tarjolla on erilaisia suorituspaikkoja. Suorituspaikat tulee tallentaa omiksi liikuntapaikoikseen (esim. pallokenttä, ulkokuntosali tai parkour-alue). Lähiliikuntapaikka voi olla myös koulun tai päiväkodin piha, jos liikuntapaikan käyttö on mahdollista kouluajan ulkopuolella.", + :se + "Ett näridrottsområde är avsett för daglig utomhusaktivitet och motion. Det ligger nära bostadsområden, är småskaligt och har fri tillgång. Vanligtvis erbjuds olika aktivitetsplatser. Aktivitetsplatserna bör registreras som egna idrottsplatser (t.ex. bollplan, utomhusgym eller parkourområde). Ett näridrottsområde kan också vara en skolgård eller en daghemsgård om idrottsplatsen kan användas utanför skoltid.", + :en + "A local sports facility is intended for daily outdoor activities and exercise. It is located near residential areas, is small-scale, and has free access. Usually, various activity sites are available. Activity sites should be recorded as individual sports facilities (e.g., ball field, outdoor gym, or parkour area). A local sports facility can also be a school or daycare yard if the sports facility can be used outside school hours."}, + :tags + {:fi + ["ässäkenttä" "monitoimikenttä" "monitoimikaukalo" "lähipuisto"]}, + :name + {:fi "Lähiliikuntapaikka", + :se "Näridrottsplats", + :en "Neighbourhood sports area"}, + :type-code 1120, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 90}, + :surface-material-info {:priority 90}, + :fields-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :ligthing? {:priority 70}, + :accessibility-info {:priority 50}, + :playground? {:priority 80}, + :school-use? {:priority 50}, + :exercise-machines-count {:priority 80}}}, + 1390 + {:description + {:fi + "Padelin pelaamiseen tarkoitettu kenttä ulkona. Pintamateriaali hiekkatekonurmi. Lajivaatimusten mukaiset seinät. Voi olla myös katettu.", + :se + "En utomhusbana avsedd för padelspel. Underlagsmaterialet är sandkonstgräs. Väggar enligt sportens krav. Kan även vara täckt.", + :en + "An outdoor court intended for playing padel. The surface material is sand artificial grass. Walls meet the sport's requirements. It can also be covered."}, + :tags {:fi ["padel" "padel-kenttä"]}, + :name + {:fi "Padelkenttä", + :se "Område med padelbanor", + :en "Padel court area"}, + :type-code 1390, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 5340 + {:description + {:fi "Pääasiallisesti kartingajoon tai supermotoon käytetty rata.", + :se "Huvudsakligen för karting.", + :en "Mainly for karting."}, + :tags {:fi []}, + :name {:fi "Karting-rata", :se "Kartingbana", :en "Kart circuit"}, + :type-code 5340, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :free-use? {:priority 40}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 302 + {:description + {:fi + "Autiotupa, varaustupa, taukotupa, päivätupa. Yöpymis- ja levähdyspaikka retkeilijöille. Autiotupa avoin, varaustupa lukittu ja maksullinen. Päivätupa päiväkäyttöön.", + :se + "Övernattningsstuga, reserveringsstuga, raststuga, dagstuga. Övernattnings- och rastplats för vandrare. Övernattningsstugan öppen, reserveringsstugan låst och avgiftsbelagd. Dagstuga för bruk under dagen.", + :en + "Open hut, reservable hut, rest hut, day hut. Overnight resting place for hikers. An open hut is freely available; a reservable hut locked and subject to a charge. A day hut is for daytime use."}, + :tags {:fi ["taukopaikka"]}, + :name {:fi "Tupa", :se "Stuga", :en "Hut"}, + :type-code 302, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :toilet? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :water-point {:priority 70}, + :free-use? {:priority 60}}}, + 4405 + {:description + {:fi + "Maastossa oleva retkeilyreitti, yleensä kauempana asutuksesta. Reitin varrella retkeilyn palveluita, esim. laavuja.", + :se + "Utflyktsled i terrängen, oftast längre borta från bebyggelse. Längs rutten friluftstjänster, t ex vindskydd.", + :en + "Natural hiking route, usually further away from residential areas. Provides hiking facilities, e.g. lean-to structures."}, + :tags {:fi ["retkeily" "vaellus" "vaelluspolku"]}, + :name + {:fi "Retkeilyreitti", :se "Utflyktsled", :en "Hiking route"}, + :type-code 4405, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :accessibility-info {:priority 40}, + :route-length-km {:priority 99}}}, + 6120 + {:description + {:fi "Kylmä tai lämmin katettu tila ratsastukseen.", + :se "Kallt eller varmt takförsett utrymme för ridning.", + :en "Cold or warm, covered space for horseback riding."}, + :tags {:fi ["ratsastushalli" "maneesi"]}, + :name + {:fi "Ratsastusmaneesi", :se "Ridmanege", :en "Riding manège"}, + :type-code 6120, + :main-category 6000, + :status "active", + :sub-category 6100, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :show-jumping? {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :school-use? {:priority 40}}}, + 4407 + {:description + {:fi + "Asfaltoitu rullahiihtoon lumettomana aikana tarkoitettu reitti. Reitti kulkee maastossa, ja sen käyttöä muilla kulkutavoilla on rajoitettu.", + :se + "En asfalterad bana avsedd för rullskidåkning under snöfria perioder. Banan går genom terrängen och användningen av andra färdsätt är begränsad.", + :en + "An asphalt track intended for roller skiing during snow-free periods. The track runs through terrain, and the use of other modes of travel is restricted."}, + :tags + {:fi ["rullahiihto"], + :se ["rullskidåkning"], + :en ["roller skiing"]}, + :name + {:fi "Rullahiihtorata", + :se "Rullskidbana", + :en "Roller Ski Track"}, + :type-code 4407, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :free-use? {:priority 40}, + :outdoor-exercise-machines? {:priority 80}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :route-length-km {:priority 99}}}, + 1310 + {:description + {:fi + "Koripalloon varustettu kenttä, kiinteät tai siirrettävät koritelineet. Minikenttä ja yhden korin kenttä lisätiedoissa.", + :se + "Plan utrustad för basket med fasta eller flyttbara ställningar. Miniplan och enkorgsplan i tilläggsupgifter.", + :en + "A field equipped for basketball, with fixed or movable apparatus. 'Mini-court' and 'one-basket court' included in additional information. "}, + :tags {:fi []}, + :name + {:fi "Koripallokenttä", :se "Basketplan", :en "Basketball court"}, + :type-code 1310, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :height-of-basket-or-net-adjustable? {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 40}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :scoreboard? {:priority 70}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :basketball-field-type {:priority 80}, + :school-use? {:priority 40}}}, + 202 + {:description + {:fi "Telttailualue tai muu leiriytymiseen osoitettu paikka.", + :se "Tältplats eller annat område ordnat för tältning.", + :en "Camping site for tents or other encampment. "}, + :tags + {:fi ["yöpyminen" "taukopaikka" "telttapaikka" "leirintäalue"]}, + :name + {:fi "Telttailu ja leiriytyminen", + :se "Tältning och läger", + :en "Camping"}, + :type-code 202, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:toilet? {:priority 70}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :water-point {:priority 70}, + :free-use? {:priority 70}}}, + 1190 + {:description + {:fi + "Yleinen mäenlaskuun esimerkiksi pulkalla tai liukurilla tarkoitettu mäki. Kohde on ylläpidetty ja hoidettu, ja se voi muodostua luonnon mäestä tai rakennetuista kumpareista.", + :se + "En allmän backe avsedd för åkning med till exempel pulka eller stjärtlapp. Backen är underhållen och skött och kan bestå av en naturlig backe eller konstruerade högar.", + :en + "A common hill intended for sledding with, for example, a sled or a slider. The hill is maintained and taken care of, and it can consist of a natural hill or constructed mounds."}, + :tags {:fi ["pulkkailu" "pulkka" "mäenlasku"]}, + :name {:fi "Pulkkamäki", :se "Pulkabacke", :en "Sledding hill"}, + :type-code 1190, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:ligthing? {:priority 70}, + :school-use? {:priority 50}, + :may-be-shown-in-harrastuspassi-fi? {:priority 50}, + :toilet? {:priority 70}, + :lighting-info {:priority 60}}}, + 1620 + {:description + {:fi + "Ensisijaisesti golfin pelaamiseen tarkoitettu alue kesäkaudella. Reikien määrä merkitään lisätietoihin.", + :se "Officiell golfbana. Antalet hål anges i karakteristika.", + :en + "Official golf course. Number of holes included in properties."}, + :tags {:fi ["greeni" "puttialue" "range"]}, + :name + {:fi "Golfkenttä (piste)", :se "Golfbana", :en "Golf course"}, + :type-code 1620, + :main-category 1000, + :status "active", + :sub-category 1600, + :geometry-type "Point", + :props + {:holes-count {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 70}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :customer-service-point? {:priority 70}, + :green? {:priority 80}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :range? {:priority 80}}}, + 2250 + {:description + {:fi + "Ensisijaisesti skeittausta varten varustettu halli. Hallia voidaan käyttää bmx-pyöräilyn tai muiden soveltuvien lajien harrastamiseen.", + :se + "Hall utrustad för skejtning, rullskridskoåkning, bmx-åkning osv.", + :en + "An area for skateboarding, roller-blading, BMX biking, etc."}, + :tags {:fi ["ramppi"]}, + :name + {:fi "Skeittihalli", :se "Skateboardhall", :en "Indoor skatepark"}, + :type-code 2250, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :scoreboard? {:priority 70}, + :customer-service-point? {:priority 70}, + :school-use? {:priority 40}}}, + 2530 + {:description + {:fi "Pikaluisteluun tarkoitettu halli.", + :se + "Hall avsedd för hastighetsåkning på skridsko. Storlek > 333 1/3 m.", + :en "Hall intended for speed-skating. Size > 333.3 m."}, + :tags {:fi ["jäähalli"]}, + :name + {:fi "Pikaluisteluhalli", + :se "Skridskohall", + :en "Speed-skating hall"}, + :type-code 2530, + :main-category 2000, + :status "active", + :sub-category 2500, + :geometry-type "Point", + :props + {:field-2-area-m2 {:priority 80}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :field-3-length-m {:priority 80}, + :field-2-length-m {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-1-length-m {:priority 80}, + :finish-line-camera? {:priority 70}, + :match-clock? {:priority 70}, + :field-1-width-m {:priority 80}, + :field-3-width-m {:priority 80}, + :field-2-width-m {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :field-1-area-m2 {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :scoreboard? {:priority 70}, + :auxiliary-training-area? {:priority 80}, + :loudspeakers? {:priority 70}, + :field-3-area-m2 {:priority 80}, + :school-use? {:priority 40}}}, + 112 + {:description + {:fi + "Muut luonnonsuojelualueet kuin kansallispuistot. Tietoja kerätään vain sellaisilta luonnonsuojelualueilta ja luonnonpuistoilta, joiden virkistyskäyttö on mahdollista. Esim. kunta- tai yksityisomisteisille maille perustetut suojelualueet. Kaavamerkintä S, SL.", + :se + "Andra naturskyddsområden än nationalparker och naturparker. Endast de naturskyddsområden där friluftsanvändning är möjlig. T ex skyddsområden som grundats på kommunal eller privat mark. Planbeteckning S, SL.", + :en + "Nature conservation areas other than national parks and natural parks. Only nature conservation areas with opportunities for recreation. E.g. protection areas established on municipal and private land. Symbol S, SL."}, + :tags {:fi ["virkistysalue"]}, + :name + {:fi "Muu luonnonsuojelualue, jolla on virkistyspalveluita", + :se "Annat naturskyddsområde med rekreationstjänster", + :en "Other nature conservation area with recreational services"}, + :type-code 112, + :main-category 0, + :status "active", + :sub-category 1, + :geometry-type "Polygon", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :area-km2 {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2130 + {:description + {:fi + "Painonnostoon tai toiminnalliseen voimaharjoitteluun varustettu kuntoilutila tai voimailusali. Esimerkiksi crossfit- ja painonnostosalit.", + :se + "Utrustad för tyngdlyftning och boxning. Storleken anges i karakteristika.", + :en + "Equipped for weightlifting and boxing. Size specified in properties."}, + :tags {:fi ["kuntosali" "kuntoilu" "painonnosto" "voimanosto"]}, + :name + {:fi "Voimailusali", + :se "Styrketräningssal", + :en "Weight training hall "}, + :type-code 2130, + :main-category 2000, + :status "active", + :sub-category 2100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-customer-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :group-exercise-rooms-count {:priority 80}, + :tatamis-count {:priority 80}, + :area-m2 {:priority 90}, + :wrestling-mats-count {:priority 80}, + :boxing-rings-count {:priority 80}, + :weight-lifting-spots-count {:priority 80}, + :customer-service-point? {:priority 70}, + :school-use? {:priority 40}, + :exercise-machines-count {:priority 80}}}, + 4406 + {:description + {:fi + "Talvisin tai ympärivuotisesti käytössä oleva maastoreitti, joka soveltuu usealle kulkutavalle (esim. jalan, lumikengille, läskipyörälle). Lisätietoihin merkitään, jos reitti on ympärivuotisessa käytössä ja mahdolliset kulkutavat.", + :se + "En terrängled som används på vintern eller året runt och som är lämplig för flera färdsätt (t.ex. till fots, med snöskor, fatbike). Ytterligare information anger om leden är i bruk året runt och möjliga färdsätt.", + :en + "A terrain trail used in winter or year-round, suitable for multiple modes of travel (e.g., on foot, with snowshoes, fat bike). Additional information indicates if the trail is in year-round use and possible modes of travel."}, + :tags + {:fi ["yhteiskäyttöreitti" "talvipolku"], + :se ["gemensam led" "vinterstig"], + :en ["shared trail" "winter path"]}, + :name + {:fi "Monikäyttöreitti", + :se "Multianvändningsled", + :en "Multi-use Trail"}, + :type-code 4406, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :travel-mode-info {:priority 80}, + :route-width-m {:priority 97}, + :toilet? {:priority 70}, + :rest-places-count {:priority 80}, + :lit-route-length-km {:priority 98}, + :travel-modes {:priority 80}, + :year-round-use? {:priority 80}, + :route-length-km {:priority 99}}}, + 3220 + {:description + {:fi + "Yleinen uimaranta, EU-uimaranta. Pelastusväline ja ilmoitustaulu, jäteastia ja käymälä. Veden laadun seuranta ja alueen hoito järjestetty.", + :se + "Allmän badstrand, EU badstrand. Räddningsutrustning, en anslagstavla samt ett sopkärl och en toalett finns. Kvaliteten på vattnet följs upp och området underhålls.", + :en + "Public beach, EU bathing beach. Rescue equipment, a notice board, a waste bin, and a toilet are available. The quality of the water is monitored and the area is maintained."}, + :tags {:fi ["uimapaikka"]}, + :name {:fi "Uimaranta", :se "Badstrand", :en "Public beach"}, + :type-code 3220, + :main-category 3000, + :status "active", + :sub-category 3200, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :pier? {:priority 80}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :beach-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :changing-rooms? {:priority 70}, + :sauna? {:priority 70}, + :other-platforms? {:priority 80}, + :school-use? {:priority 40}}}, + 5330 + {:description + {:fi + "Suuri rata-autoiluun tai moottoripyöräilyyn tarkoitettu asfaltoitu moottoriurheilupaikka.", + :se "Stor motorsportplats avsedd för bankörning.", + :en "Large motor sports venue for formula racing."}, + :tags {:fi ["autourheilu"]}, + :name + {:fi "Moottorirata", :se "Motorbana", :en "Formula race track"}, + :type-code 5330, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 4230 + {:description + {:fi "Lumilautailua varten rakennettu halli.", + :se + "Tunnel avsedd för snowboardåkning. Olika användningsmöjligheter i tilläggsinformation.", + :en + "Tunnel intended for snowboarding. Different uses specified in additional information."}, + :tags {:fi ["laskettelu"]}, + :name + {:fi "Lumilautatunneli", + :se "Snowboardtunnel", + :en "Snowboarding tunnel"}, + :type-code 4230, + :main-category 4000, + :status "active", + :sub-category 4200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :altitude-difference {:priority 90}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :equipment-rental? {:priority 70}, + :route-length-km {:priority 99}}}, + 4320 + {:description + {:fi + "Mäkihyppyyn soveltuva mäki, vauhtimäessä on jää-, keramiikka- tai muovilatu. Mäen koko, materiaalit ja mahdollinen kesäkäyttö kuvataan lisätiedoissa.", + :se + "Is-, keramik- eller plastspår. K-punkt samt sommar- och vinteranvändning i karakteristika. Minimikrav: en liten backe med k-punkt på 75 m eller mera.", + :en + "Ice, ceramic or plastic track. Summer and winter use specified in attributes, along with K point, etc. Minimum normal hill, K point minimum 75 m."}, + :tags {:fi ["mäkihyppy" "hyppyri"]}, + :name {:fi "Hyppyrimäki", :se "Hoppbacke", :en "Ski jumping hill"}, + :type-code 4320, + :main-category 4000, + :status "active", + :sub-category 4300, + :geometry-type "Point", + :props + {:skijump-hill-type {:priority 80}, + :lifts-count {:priority 70}, + :plastic-outrun? {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :skijump-hill-material {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :hs-point {:priority 90}, + :k-point {:priority 90}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :year-round-use? {:priority 80}, + :changing-rooms-m2 {:priority 70}, + :jumps-count {:priority 80}, + :inruns-material {:priority 80}}}, + 3130 + {:description + {:fi + "Monipuolinen uimala kuntoutus-, virkistys- tai hyvinvointipalveluilla. Vesipinta-ala ja allasmäärät/tyypit ominaisuuksiin.", + :se + "Mångsidig badinrättning med rehabiliterings- och rekreationstjänster. Vattenareal samt antal och typ av bassänger i karakteristika.", + :en + "Versatile spa with rehabilitation or wellness services. Water volume and number/types of pools specified in properties."}, + :tags {:fi []}, + :name {:fi "Kylpylä", :se "Badinrättning", :en "Spa"}, + :type-code 3130, + :main-category 3000, + :status "active", + :sub-category 3100, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :swimming-pool-count {:priority 90}, + :pool-water-area-m2 {:priority 90}, + :accessibility-info {:priority 40}, + :school-use? {:priority 40}}}, + 3110 + {:description + {:fi + "Halli, jossa on yksi tai useampia uima-altaita. Altaiden määrä ja vesipinta-ala kysytään ominaisuustiedoissa.", + :se + "Hall med en eller flera simbassänger. Antalet bassänger och vattenareal anges i karakteristika.", + :en + "Hall with one or several swimming pools. Number of pools and water surface area is requested in properties."}, + :tags {:fi []}, + :name + {:fi "Uimahalli", + :se "Simhall", + :en "Public indoor swimming pool"}, + :type-code 3110, + :main-category 3000, + :status "active", + :sub-category 3100, + :geometry-type "Point", + :props + {:automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :swimming-pool-count {:priority 90}, + :pool-water-area-m2 {:priority 90}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :accessibility-info {:priority 40}, + :school-use? {:priority 40}}}, + 203 + {:description + {:fi + "Kohteessa on veneilyyn liittyviä palveluita kuten säilytysmahdollisuus, vesillelaskupaikka tai veneen kiinnitysmahdollisuus. Kohteelle määritetään venesatamaluokka, jonka palveluvarustus kuvataan lisätiedoissa. Jos kyse on melontalaiturista, se kirjataan kyseisen laituriluokan alle. Kohde tulee merkitä tärkeimmän laiturin läheisyyteen, jos sellainen kohteessa on.", + :se "Tjänster för båtfarare. Precisering i karakteristika.", + :en "Facilities related to boating. Specififed in 'attributes'."}, + :tags {:fi ["satama" "laituri"]}, + :name + {:fi "Veneilyn palvelupaikka", + :se "Serviceplats för båtfarare", + :en "Boating services"}, + :type-code 203, + :main-category 0, + :status "active", + :sub-category 2, + :geometry-type "Point", + :props + {:free-use? {:priority 70}, + :pier? {:priority 80}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :boat-launching-spot? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :boating-service-class {:priority 90}, + :customer-service-point? {:priority 80}, + :water-point {:priority 70}, + :accessibility-info {:priority 70}}}, + 4620 + {:description + {:fi + "Vähintään kansallisen tason kilpailujen järjestämiseen soveltuva ampumahiihtokeskus. Ampumahiihtokeskuksessa useita ampumapaikkoja ja latuverkosto.", + :se "Tillräckligt stort för åtminstone nationella tävlingar.", + :en "For minimum national level competitions."}, + :tags {:fi ["ampumapaikka"]}, + :name + {:fi "Ampumahiihtokeskus", + :se "Skidskyttecentrum", + :en "Biathlon centre"}, + :type-code 4620, + :main-category 4000, + :status "active", + :sub-category 4600, + :geometry-type "Point", + :props + {:stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :finish-line-camera? {:priority 70}, + :ski-track-traditional? {:priority 80}, + :route-width-m {:priority 98}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :shower? {:priority 70}, + :rest-places-count {:priority 70}, + :changing-rooms? {:priority 70}, + :shooting-positions-count {:priority 80}, + :lit-route-length-km {:priority 97}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :sauna? {:priority 70}, + :loudspeakers? {:priority 70}, + :accessibility-info {:priority 40}, + :ski-track-freestyle? {:priority 80}, + :route-length-km {:priority 99}}}, + 5360 + {:description + {:fi + "Pääasiallisesti jokamiesajoa, rallicrossia tai moottoripyöräilyä varten.", + :se "Huvudsakligen för allemanskörning och/eller rallycross.", + :en "Mainly for everyman racing and/or rallycross."}, + :tags {:fi []}, + :name + {:fi "Jokamies- ja rallicross-rata", + :se "Allemans- och rallycrossbana", + :en "Everyman racing and rallycross track "}, + :type-code 5360, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :covered-stand-person-count {:priority 70}, + :track-length-m {:priority 90}}}, + 2290 + {:description + {:fi "Petanque-peliin tarkoitettu halli.", + :se + "Hall avsedd för petanque. Storlek, antalet planer och utrustning i karakteristika.", + :en "Hall intended for petanque."}, + :tags {:fi ["petankki"]}, + :name + {:fi "Petanque-halli", :se "Petanquehall", :en "Petanque hall"}, + :type-code 2290, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :fields-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :scoreboard? {:priority 70}, + :customer-service-point? {:priority 70}, + :school-use? {:priority 40}}}, + 2260 + {:description + {:fi + "Ensisijaisesti sulkapallon pelaamiseen tarkoitettu halli. Vapaa korkeus ilmoitetaan lisätiedoissa.", + :se "Hall i första hand avsedd för badminton.", + :en "Hall intended primarily for badminton."}, + :tags {:fi []}, + :name + {:fi "Sulkapallohalli", :se "Badmintonhall", :en "Badminton hall"}, + :type-code 2260, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :floorball-fields-count {:priority 80}, + :squash-courts-count {:priority 80}, + :customer-service-point? {:priority 70}, + :volleyball-fields-count {:priority 80}, + :school-use? {:priority 40}}}, + 1160 + {:description + {:fi + "Pyöräilyä ja temppuilua varten varattu alue, esim. bmx-, pump track- tai dirt-pyöräilyalue.", + :se + "Ett område avsett för cykling och trick, till exempel BMX-, pump track- eller dirtcyklingsområde.", + :en + "An area designated for cycling and stunts, such as a BMX, pump track, or dirt biking area."}, + :tags {:fi ["pumptrack" "bmx" "pump" "track"]}, + :name + {:fi "Pyöräilyalue", :se "Cykelåkningsområde", :en "Cycling area"}, + :type-code 1160, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 50}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :customer-service-point? {:priority 70}, + :ligthing? {:priority 70}, + :accessibility-info {:priority 50}, + :covered-stand-person-count {:priority 70}, + :school-use? {:priority 50}}}, + 1210 + {:description + {:fi + "Yleisurheilun harjoitusalueeksi merkitään kohde, jossa on yleisurheilun harjoitteluun soveltuvia suorituspaikkoja, esim. kenttä, ratoja tai eri lajien suorituspaikkoja, mutta ei virallisen yleisurheilukentän kaikkia suorituspaikkoja. Lyhytrataiset (juoksurata alle 400 m) yleisurheilukentät tallennetaan yleisurheilun harjoitusalueeksi.", + :se + "Ett område för friidrottsträning markeras där det finns anläggningar som är lämpliga för friidrottsträning, till exempel en plan, banor eller platser för olika grenar, men inte alla anläggningar för en officiell friidrottsarena. Kortbaniga friidrottsarenor (löparbana under 400 m) registreras som friidrottsträningsområden.", + :en + "An athletics training area is designated for locations with facilities suitable for athletics training, such as a field, tracks, or various event areas, but not all facilities of an official athletics stadium. Short track athletics fields (running track under 400 m) are recorded as athletics training areas."}, + :tags + {:fi + ["keihäs" + "keihäänheitto" + "moukari" + "pituushyppy" + "juoksurata" + "kolmiloikka" + "seiväs" + "kuula" + "urheilukenttä" + "yleisurheilukenttä"]}, + :name + {:fi "Yleisurheilun harjoitusalue", + :se "Träningsområde för friidrott", + :en "Athletics training area"}, + :type-code 1210, + :main-category 1000, + :status "active", + :sub-category 1200, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :free-use? {:priority 50}, + :sprint-lanes-count {:priority 80}, + :javelin-throw-places-count {:priority 80}, + :field-length-m {:priority 90}, + :circular-lanes-count {:priority 80}, + :sprint-track-length-m {:priority 80}, + :inner-lane-length-m {:priority 80}, + :discus-throw-places {:priority 80}, + :hammer-throw-places-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :polevault-places-count {:priority 80}, + :toilet? {:priority 70}, + :running-track-surface-material {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 60}, + :shotput-count {:priority 80}, + :longjump-places-count {:priority 80}, + :ligthing? {:priority 70}, + :school-use? {:priority 50}, + :highjump-places-count {:priority 80}, + :training-spot-surface-material {:priority 80}}}, + 5140 + {:description + {:fi + "Rakennettu pysyvästi vesihiihdolle. Vesihiihtoalueella on laituri.", + :se "Byggt för vattenskidåkning, permanent. Minimikrav: brygga.", + :en + "Permanent construction for water skiing. Minimum equipment pier."}, + :tags {:fi []}, + :name + {:fi "Vesihiihtoalue", + :se "Område för vattenskidåkning", + :en "Water ski area"}, + :type-code 5140, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:pier? {:priority 80}, + :area-km2 {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4310 + {:description + {:fi + "Mäkihypyn harjoitteluun rakennettu mäki. K-piste ominaisuustietoihin, materiaalit, kesä- ja talvikäyttö ominaisuuksiin. ", + :se + "K-punkt, material samt sommar- och vinteranvändning i karakteristika.", + :en + "K point in properties; materials, summer and winter use specified in attributes. "}, + :tags {:fi ["mäkihyppy" "hyppyri" "hyppyrimäki"]}, + :name + {:fi "Harjoitushyppyrimäki", + :se "Träningshoppbacke", + :en "Ski jumping hill for training"}, + :type-code 4310, + :main-category 4000, + :status "deprecated", + :sub-category 4300, + :geometry-type "Point", + :props + {:skijump-hill-type {:priority 80}, + :lifts-count {:priority 70}, + :plastic-outrun? {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :skijump-hill-material {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :k-point {:priority 90}, + :toilet? {:priority 70}, + :year-round-use? {:priority 80}, + :p-point {:priority 90}, + :inruns-material {:priority 80}, + :school-use? {:priority 40}}}, + 207 + {:description + {:fi + "Opastuspiste on ulkoilureitin, virkistysalueen tai muun liikuntapaikan yhteydessä oleva lisätieto. Paikassa voi olla esimerkiksi opastustaulu ja kartta tai laajempi palvelupiste. Opastuspiste-merkintää voidaan käyttää myös ilmoittamaan reitin lähtöpisteen.", + :se + "En informationspunkt är en extra information vid en friluftsled, ett rekreationsområde eller en annan idrottsplats. På platsen kan det till exempel finnas en informationstavla och karta eller en mer omfattande servicestation. Informationspunkt-markeringen kan också användas för att ange startpunkten för en rutt.", + :en + "An information point is additional information associated with an outdoor trail, recreational area, or other sports facility. The location may include, for example, an information board and map or a more extensive service point. The information point marking can also be used to indicate the starting point of a route."}, + :tags {:fi ["info" "opastaulu" "infopiste"]}, + :name {:fi "Opastuspiste", :se "Informationspunkt", :en "Info"}, + :type-code 207, + :main-category 0, + :status "deprecated", + :sub-category 2, + :geometry-type "Point", + :props + {:parking-place? {:priority 70}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :toilet? {:priority 70}, + :school-use? {:priority 60}, + :free-use? {:priority 60}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 1130 + {:description + {:fi + "Ulkokuntoilupaikka on esimerkiksi kuntoilulaitteita, voimailulaitteita tai kuntoportaat sisältävä liikuntapaikka. Kohde voi olla osa liikuntapuistoa, liikuntareitin varrella oleva kuntoilupaikka tai ns. ulkokuntosali.", + :se + "En utomhusträningsplats är en idrottsplats som innehåller till exempel träningsutrustning, styrketräningsutrustning eller träningsstegar. Platsen kan vara en del av en idrottspark, en träningsplats längs en motionsslinga eller en så kallad utomhusgym.", + :en + "An outdoor fitness area is a sports facility that includes, for example, exercise equipment, strength training equipment, or fitness stairs. The location can be part of a sports park, a fitness spot along a trail, or a so-called outdoor gym"}, + :tags + {:fi + ["kuntoilulaite" + "ulkokuntoilupiste" + "kuntoilupiste" + "kuntoilupaikka" + "kuntoportaat"]}, + :name + {:fi "Ulkokuntoilupaikka", + :se "Konditionspark för utomhusaktiviteter", + :en "Street workout park"}, + :type-code 1130, + :main-category 1000, + :status "active", + :sub-category 1100, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 0}, + :fitness-stairs-length-m {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :lighting-info {:priority 60}, + :ligthing? {:priority 70}, + :playground? {:priority 80}, + :school-use? {:priority 0}, + :exercise-machines-count {:priority 80}}}, + 5120 + {:description + {:fi "Pysyvästi purjehdusta varten varustettu alue.", + :se "Byggt för segling, permanent.", + :en "Permanent construction for sailing."}, + :tags {:fi []}, + :name + {:fi "Purjehdusalue", :se "Seglingsområde", :en "Sailing area"}, + :type-code 5120, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:area-km2 {:priority 90}, + :pier? {:priority 80}, + :boat-places-count {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4110 + {:description + {:fi + "Laskettelun suorituspaikka on lasketteluun tai lumilautailuun tarkoitettu liikuntapaikka, esim. laskettelukeskus. Laskettelun suorituspaikassa voi olla laskettelurinteitä, parkkeja tai muita rinnerakenteita ja hiihtohissejä.", + :se + "Slalombacke, rodelbana, pipe, puckelpist, freestyle ramp, trickbana.", + :en "Ski slopes, half pipes and other slope structures."}, + :tags {:fi ["rinne" "laskettelu" "laskettelurinne"]}, + :name + {:fi "Laskettelun suorituspaikka", + :se "Slalombackar och alpina skidcentra", + :en "Ski slopes and downhill ski resorts"}, + :type-code 4110, + :main-category 4000, + :status "active", + :sub-category 4100, + :geometry-type "Point", + :props + {:lifts-count {:priority 80}, + :freestyle-slope? {:priority 80}, + :free-use? {:priority 40}, + :ski-service? {:priority 70}, + :sledding-hill? {:priority 80}, + :longest-slope-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :snowpark-or-street? {:priority 80}, + :max-vertical-difference {:priority 90}, + :toilet? {:priority 70}, + :halfpipe-count {:priority 80}, + :equipment-rental? {:priority 70}, + :slopes-count {:priority 90}, + :shortest-slope-m {:priority 90}, + :jumps-count {:priority 80}, + :customer-service-point? {:priority 70}, + :lit-slopes-count {:priority 80}, + :accessibility-info {:priority 40}}}, + 4452 + {:description + {:fi + "Merkitty vesireitti, ei kuitenkaan veneväylä. Vesireitti on reittiehdotus-tyyppinen suositeltu vesiretkeilyyn soveltuva reitti. Esim. kirkkovenesoutureitti.", + :se + "Märkt vattenled, endast ruttförslag t ex för kyrkbåtsrodd, inte som farled för småbåtar.", + :en + "Marked water route, not a navigation channel. Route suggestions included. E.g., route for \"church rowing boats\"."}, + :tags {:fi ["kanootti" "kajakki" "melonta"]}, + :name + {:fi "Vesiretkeilyreitti", + :se "Utflyktsled i vattendrag", + :en "Water route"}, + :type-code 4452, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-length-km {:priority 90}, + :rest-places-count {:priority 70}, + :school-use? {:priority 40}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :jumps-count {:priority 80}}}, + 5370 + {:description + {:fi "Pääasiallisesti jääspeedwayta varten.", + :se "Huvudsakligen för isracing.", + :en "Speedway mainly for ice racing."}, + :tags {:fi ["speedway"]}, + :name + {:fi "Speedway-/jääspeedwayrata", + :se "Isracingbana", + :en "Ice speedway track"}, + :type-code 5370, + :main-category 5000, + :status "active", + :sub-category 5300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :track-width-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :scoreboard? {:priority 70}, + :loudspeakers? {:priority 70}, + :ligthing? {:priority 60}, + :track-length-m {:priority 90}}}, + 2240 + {:description + {:fi + "Ensisijaisesti salibandyyn tarkoitettu halli. Kenttien määrä ja pintamateriaali kirjataan lisätietoihin.", + :se + "Hall i första hand avsedd för innebandy. Antalet planer samt ytmaterial i karakteristika.", + :en + "Hall primarily intended for floorball. Number of courts and surface material specified in properties."}, + :tags {:fi ["sähly"]}, + :name + {:fi "Salibandyhalli", :se "Innebandyhall", :en "Floorball hall"}, + :type-code 2240, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90, :derived? true}, + :surface-material {:priority 89, :derived? true}, + :surface-material-info {:priority 88, :derived? true}, + :stand-capacity-person {:priority 70, :derived? true}, + :free-use? {:priority 40}, + :field-length-m {:priority 90, :derived? true}, + :badminton-courts-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90, :derived? true}, + :field-width-m {:priority 90, :derived? true}, + :scoreboard? {:priority 70}, + :floorball-fields-count {:priority 80, :derived? true}, + :auxiliary-training-area? {:priority 80}, + :customer-service-point? {:priority 70}, + :school-use? {:priority 40}}}, + 2510 + {:description + {:fi + "Harjoitusjäähalli on pääasiassa jääurheilun harjoitteluun ja jääliikuntaan käytettävä jäähalli.", + :se + "Antalet planer och omklädningshytter, uppvärmning osv anges i karakteristika.", + :en + "Number of fields, heating, changing rooms, etc., specified in properties."}, + :tags {:fi ["jäähalli"]}, + :name + {:fi "Harjoitusjäähalli", + :se "Övningsishall", + :en "Training ice arena"}, + :type-code 2510, + :keywords {:fi ["Jäähalli"], :en [], :se []}, + :main-category 2000, + :status "active", + :sub-category 2500, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :finish-line-camera? {:priority 70}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :ice-rinks-count {:priority 80}, + :field-2-flexible-rink? {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :curling-lanes-count {:priority 80}, + :scoreboard? {:priority 70}, + :auxiliary-training-area? {:priority 80}, + :ringette-boundary-markings? {:priority 80}, + :field-1-flexible-rink? {:priority 80}, + :loudspeakers? {:priority 70}, + :school-use? {:priority 40}, + :field-3-flexible-rink? {:priority 80}}}, + 1640 + {:description + {:fi "Ratagolfliiton hyväksymät ratagolf-/minigolfradat.", + :se + "Bangolf / minigolf, enligt Finlands Bangolfförbundets regler.", + :en + "A course built for miniature golf, accepted by the Ratagolf Union."}, + :tags {:fi ["minigolf"]}, + :name {:fi "Ratagolf", :se "Bangolf", :en "Minigolf course"}, + :type-code 1640, + :main-category 1000, + :status "active", + :sub-category 1600, + :geometry-type "Point", + :props + {:holes-count {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :customer-service-point? {:priority 70}, + :green? {:priority 80}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :range? {:priority 80}}}, + 1380 + {:description + {:fi "Rullakiekon pelaamiseen varustettu kenttä.", + :se "Plan utrustad för inlinehockey.", + :en "Field equipped for roller hockey."}, + :tags {:fi ["rullakiekko"]}, + :name + {:fi "Rullakiekkokenttä", + :se "Inlinehockeyplan", + :en "Roller hockey field"}, + :type-code 1380, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :match-clock? {:priority 70}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}}}, + 4451 + {:description + {:fi + "Erityisesti melontaan tarkoitettu vesistöreitti. Reitistä on laadittu reittikuvaus ja maastossa löytyy opasteita (esim. rantautumispaikoista). Reittiehdotus-tyyppinen, ei navigointiin.", + :se + "Särskilt för paddling, märkt med ruttförslag, ej för navigering.", + :en + "Marked route particularly for canoeing. Route suggestions are not intended for navigation."}, + :tags {:fi ["kanootti" "kajakki"]}, + :name {:fi "Melontareitti", :se "Paddlingsled", :en "Canoe route"}, + :type-code 4451, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:route-length-km {:priority 90}, + :rest-places-count {:priority 70}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 4403 + {:description + {:fi + "Jalkaisin tapahtuvaan ulkoiluun tarkoitettu reitti. Suhteellisen leveä ja helppokulkuinen reitti, yleensä valaistu ja pinnoitettu.", + :se + "Promenadled. Relativt bred och lättilgänglig, eventuellt belyst och asfalterad.", + :en + "Route intended for outdoor pedestrian activities. Relatively broad and passable. Potentially lit and surfaced."}, + :tags {:fi ["ulkoilu" "kävely" "ulkoilureitti" "kävelyreitti"]}, + :name + {:fi "Kävelyreitti/ulkoilureitti", + :se "Promenadled/friluftsled", + :en "Walking route/outdoor route"}, + :type-code 4403, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 89}, + :surface-material-info {:priority 88}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :outdoor-exercise-machines? {:priority 80}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :lit-route-length-km {:priority 98}, + :accessibility-info {:priority 40}, + :route-length-km {:priority 99}}}, + 5150 + {:description + {:fi + "Melontaan tarkoitettu palvelupaikka, jossa voi olla esim. vuokrauspalveluita. Melontakeskuksesta voi lähteä melontareitti tai sen yhteydessä voi olla melontaratoja.", + :se + "En paddlingsanläggning med uthyrningstjänster. Från paddlingscentret kan det finnas paddelleder eller paddelbanor i närheten.", + :en + "A canoeing facility with rental services. From the canoeing center, there may be canoeing routes or paddling tracks nearby."}, + :tags {:fi ["melonta" "kajakki" "kanootti" "melontakeskus"]}, + :name + {:fi "Melontakeskus", + :se "Centrum för paddling", + :en "Canoeing centre"}, + :type-code 5150, + :main-category 5000, + :status "active", + :sub-category 5100, + :geometry-type "Point", + :props + {:free-use? {:priority 40}, + :pier? {:priority 80}, + :canoeing-club? {:priority 80}, + :altitude-difference {:priority 90}, + :rapid-canoeing-centre? {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :equipment-rental? {:priority 70}, + :activity-service-company? {:priority 80}}}, + 1630 + {:description + {:fi + "Golfia varten varustettu sisäharjoittelutila. Voi olla useita erilaisia suorituspaikkoja.", + :se "Övningsutrymme byggt för golf. Storlek i karakteristika.", + :en + "Training space built for golf. Size specified in properties."}, + :tags {:fi ["greeni" "puttialue"]}, + :name + {:fi "Golfin harjoitushalli", + :se "Övningshall för golf", + :en "Golf training hall"}, + :type-code 1630, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :holes-count {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :customer-service-point? {:priority 70}, + :green? {:priority 80}, + :school-use? {:priority 40}, + :range? {:priority 80}}}, + 2295 + {:description + {:fi + "Yksi tai useampi padelkenttä sisällä. Pintamateriaali tekonurmi (hiekkatekonurmi). Lajivaatimusten mukaiset seinät. Vapaa korkeus ilmoitetaan lisätiedoissa.", + :se + "En eller flera padelbanor inomhus. Ytmaterial konstgräs (med sand), 20 x 10 m. Väggar måste uppfylla spelets krav. Höjd anges i karakteristika.", + :en + "One or more indoor padel courts. The court has an artificial grass surface and its measurements are 20 x 10 metres. The walls must meet the requirements for the sport. Height given in 'properties'."}, + :tags {:fi ["padel" "padel-halli"]}, + :name {:fi "Padelhalli", :se "Padelhall", :en "Padel hall"}, + :type-code 2295, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 90}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :school-use? {:priority 40}}}, + 2370 + {:description + {:fi "Kiipeilyyn varustettu sisätila. Myös boulderointipaikat.", + :se + "Inomhusutrymme utrustat för klättring, även platser för bouldering.", + :en + "Indoor space equipped for climbing. Also bouldering venues."}, + :tags {:fi ["kiipeilyseinä"]}, + :name + {:fi "Sisäkiipeilyseinä", + :se "Klättervägg inomhus", + :en "Indoor climbing wall"}, + :type-code 2370, + :main-category 2000, + :status "active", + :sub-category 2300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :climbing-routes-count {:priority 80}, + :climbing-wall-height-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :area-m2 {:priority 90}, + :climbing-wall-width-m {:priority 90}, + :school-use? {:priority 40}}}, + 1340 + {:description + {:fi + "Palloiluun tarkoitettu kenttä, jonka pintamateriaali on esim. hiekka, nurmi tai hiekkatekonurmi. Kentällä on mahdollista pelata yhtä tai useampaa palloilulajia. Kentän koko merkitään lisätietoihin. Kevyt poistettava kate on mahdollinen.", + :se + "Plan avsedd för bollspel. Sand, konstgräs med sand el dyl, storleken varierar. En eller flera bollspelsgrenar möjliga.", + :en + "A field intended for ball games. Sand, grass, artificial turf, etc., size varies. One or more types of ball games possible."}, + :tags {:fi []}, + :name {:fi "Pallokenttä", :se "Bollplan", :en "Ball field"}, + :type-code 1340, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:heating? {:priority 70}, + :surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :changing-rooms? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :year-round-use? {:priority 80}, + :customer-service-point? {:priority 70}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :light-roof? {:priority 70}}}, + 1610 + {:description + {:fi + "Golfin harjoittelua varten varustettu alue. Harjoitusalue voi sisältää useampia suorituspaikkoja, kuten rangen ja puttausviheriön. Harjoitusalue sijaitsee ulkona.", + :se + "Ett område utrustat för golfträning. Träningsområdet kan innehålla flera träningsplatser, såsom en range och en puttningsgreen. Träningsområdet ligger utomhus.", + :en + "An area equipped for golf practice. The practice area may include multiple facilities, such as a driving range and a putting green. The practice area is located outdoors."}, + :tags {:fi ["greeni" "puttialue" "range"]}, + :name + {:fi "Golfin harjoitusalue", + :se "Träningsområde för golf", + :en "Golf training area"}, + :type-code 1610, + :main-category 1000, + :status "active", + :sub-category 1600, + :geometry-type "Point", + :props + {:holes-count {:priority 90}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :lighting-info {:priority 50}, + :customer-service-point? {:priority 70}, + :green? {:priority 80}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}, + :range? {:priority 80}}}, + 4421 + {:description + {:fi + "Reittitoimituksella hyväksytty, virallinen moottorikelkkailureitti.", + :se "En officiell rutt som godkänts genom en ruttexpedition.", + :en + "Officially approved route (in compliance with Act 670/1991)."}, + :tags {:fi []}, + :name + {:fi "Moottorikelkkareitti", + :se "Snöskoterled", + :en "Official snowmobile route"}, + :type-code 4421, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:rest-places-count {:priority 70}, + :route-width-m {:priority 98}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :route-length-km {:priority 99}, + :free-use? {:priority 40}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}}}, + 2220 + {:description + {:fi + "Monitoimihalli on suuri liikuntatila, joka on merkittävä monien lajien kilpailu- ja tapahtumapaikka. Liikuntapinta-ala on suurempi kuin 5 000 m2.", + :se "Större tävlingsplats för ett flertal grenar, >= 5 000 m2.", + :en + "Significant competition venue for various sports, >=5000 m2."}, + :tags {:fi ["liikuntahalli" "urheilutalo" "urheiluhalli"]}, + :name + {:fi "Monitoimihalli/areena", + :se "Allaktivitetshall/multiarena", + :en "Multipurpose hall/arena"}, + :type-code 2220, + :main-category 2000, + :status "active", + :sub-category 2200, + :geometry-type "Point", + :props + {:height-m {:priority 80}, + :surface-material {:priority 89}, + :basketball-fields-count {:priority 80}, + :surface-material-info {:priority 88}, + :automated-timing? {:priority 70}, + :stand-capacity-person {:priority 70}, + :free-use? {:priority 40}, + :sprint-lanes-count {:priority 80}, + :javelin-throw-places-count {:priority 80}, + :tennis-courts-count {:priority 80}, + :field-length-m {:priority 90}, + :circular-lanes-count {:priority 80}, + :match-clock? {:priority 70}, + :sprint-track-length-m {:priority 80}, + :inner-lane-length-m {:priority 80}, + :discus-throw-places {:priority 80}, + :badminton-courts-count {:priority 80}, + :hammer-throw-places-count {:priority 80}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :padel-courts-count {:priority 80}, + :polevault-places-count {:priority 80}, + :space-divisible {:priority 70}, + :toilet? {:priority 70}, + :gymnastics-space? {:priority 80}, + :running-track-surface-material {:priority 80}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :scoreboard? {:priority 70}, + :shotput-count {:priority 80}, + :longjump-places-count {:priority 80}, + :football-fields-count {:priority 80}, + :floorball-fields-count {:priority 80}, + :auxiliary-training-area? {:priority 80}, + :squash-courts-count {:priority 80}, + :loudspeakers? {:priority 70}, + :customer-service-point? {:priority 70}, + :accessibility-info {:priority 40}, + :handball-fields-count {:priority 80}, + :volleyball-fields-count {:priority 80}, + :climbing-wall? {:priority 80}, + :school-use? {:priority 40}, + :highjump-places-count {:priority 80}}}, + 1320 + {:description + {:fi + "Lentopalloon varustettu kenttä, jossa on kiinteät lentopallotolpat.", + :se "Plan utrustad för volleyboll. Fasta volleybollställningar.", + :en + "A field equipped for volleyball. Fixed volleyball apparatus."}, + :tags {:fi []}, + :name + {:fi "Lentopallokenttä", + :se "Volleybollplan", + :en "Volleyball court"}, + :type-code 1320, + :main-category 1000, + :status "active", + :sub-category 1300, + :geometry-type "Point", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :height-of-basket-or-net-adjustable? {:priority 80}, + :free-use? {:priority 40}, + :field-length-m {:priority 90}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :area-m2 {:priority 90}, + :field-width-m {:priority 90}, + :lighting-info {:priority 50}, + :water-point {:priority 70}, + :ligthing? {:priority 60}, + :school-use? {:priority 40}}}, + 4402 + {:description + {:fi + "Talvikaudella hiihtoa varten ylläpidetty reitti. Hiihtotyylit kerrotaan ominaisuustiedoissa.", + :se + "Led avsedd för skidåkning. Ej sommaranvändning och -underhåll. Åkstilar anges i karakteristika.", + :en + "Route intended for skiing. Not in use and unmaintained in summer. Ski styles provided in properties."}, + :tags {:fi ["hiihto" "hiihtolatu"]}, + :name {:fi "Hiihtolatu", :se "Skidspår", :en "Ski track"}, + :type-code 4402, + :main-category 4000, + :status "active", + :sub-category 4400, + :geometry-type "LineString", + :props + {:surface-material {:priority 80}, + :surface-material-info {:priority 80}, + :free-use? {:priority 40}, + :may-be-shown-in-excursion-map-fi? {:priority 0}, + :outdoor-exercise-machines? {:priority 80}, + :ski-track-traditional? {:priority 80}, + :route-width-m {:priority 97}, + :may-be-shown-in-harrastuspassi-fi? {:priority 0}, + :toilet? {:priority 70}, + :rest-places-count {:priority 70}, + :shooting-positions-count {:priority 80}, + :lit-route-length-km {:priority 98}, + :ski-track-freestyle? {:priority 80}, + :school-use? {:priority 40}, + :route-length-km {:priority 99}}}}) (def active (reduce-kv (fn [m k v] (if (not= "active" (:status v)) (dissoc m k) m)) all all)) (def unknown - old/unknown) + {:name {:fi "Ei tietoa" :se "Unknown" :en "Unknown"} + :type-code -1 + :main-category -1 + :sub-category -1}) (def by-main-category (group-by :main-category (vals active))) (def by-sub-category (group-by :sub-category (vals active))) diff --git a/webapp/src/cljc/lipas/data/types_new.cljc b/webapp/src/cljc/lipas/data/types_new.cljc index 364facafc..9e21f5d14 100644 --- a/webapp/src/cljc/lipas/data/types_new.cljc +++ b/webapp/src/cljc/lipas/data/types_new.cljc @@ -9,7 +9,11 @@ old/main-categories) (def sub-categories - old/sub-categories) + (-> old/sub-categories + ;; alaluokka "Keilahallit" tulee uudelleennimetä "Keilahallit ja biljardisalit" + (assoc-in [2600 :name] {:fi "Keilahallit ja biljardisalit" + :se "Bowlinghallar och biljardsalonger" + :en "Bowling alleys and billiard halls"}))) (def all1 (-> old/all @@ -150,8 +154,9 @@ (assoc-in [1130 :props :fitness-stairs-length-m] {:priority 0}) ;; 1140 Parkour-alue - (assoc-in [1140 :props :highest-drop-m] {:priority 0}) + (assoc-in [1140 :props :highest-obstacle-m] {:priority 0}) (assoc-in [1140 :props :climbing-wall?] {:priority 0}) + (assoc-in [1140 :props :lighting-info] {:priority 0}) ;; 1150 skeitti/rullaluistelupaikka (assoc-in [1150 :description :fi] "Rullaluistelua, skeittausta, potkulautailua varten varustettu paikka. Ominaisuustiedoissa tarkemmat tiedot kohteesta.") @@ -335,8 +340,10 @@ true (update :props dissoc :kiosk?) + ;; There's unfortunate typo in the + ;; old lighting? prop (contains? (:props v) :ligthing?) - (assoc-in [:props :ligthing-info] {:priority 0})))) + (assoc-in [:props :lighting-info] {:priority 0})))) {})))) (def all4 @@ -486,7 +493,7 @@ ;; Devissä tietokentän nimenä on vielä "Vapaa käyttö" -> Pitäisi olla "Kohde on vapaasti käytettävissä" (assoc-in [2510 :props :free-use?] {:priority 0}) (assoc-in [2520 :description :fi] "Kilpajäähalli on jääurheilun kilpailu- ja ottelutapahtumiin soveltuva jäähalli. Katsomon koko, kenttien lukumäärä ja muut tarkemmat tiedot kuvataan lisätiedoissa.") - (assoc-in [2520 :props :ringette-boundary-markings] {:priority 0}) + (assoc-in [2520 :props :ringette-boundary-markings?] {:priority 0}) ;; Olisiko kenttien/kaukaloiden tiedonhallintaa mahdollista helpottaa Lipaksessa (mallia esim. uimahallien altaista tai salibandyn ominaisuustiedoista?) (assoc-in [2520 :props :field-1-flexible-rink?] {:priority 0}) ;; Olisiko kenttien/kaukaloiden tiedonhallintaa mahdollista helpottaa Lipaksessa (mallia esim. uimahallien altaista tai salibandyn ominaisuustiedoista?) @@ -541,7 +548,303 @@ (assoc-in [3250 :props :free-use?] {:priority 0}) (update-in [3220 :props] dissoc :eu-beach?))) -(def all all4) +(def all5 + (-> all4 + + ;; Add new rullahiihto type. Props added via script + (assoc 4407 + {:name + {:fi "Rullahiihtorata" + :se "Rullskidbana" + :en "Roller Ski Track"} + :description + {:fi "Asfaltoitu rullahiihtoon lumettomana aikana tarkoitettu reitti. Reitti kulkee maastossa, ja sen käyttöä muilla kulkutavoilla on rajoitettu." + :se "En asfalterad bana avsedd för rullskidåkning under snöfria perioder. Banan går genom terrängen och användningen av andra färdsätt är begränsad." + :en "An asphalt track intended for roller skiing during snow-free periods. The track runs through terrain, and the use of other modes of travel is restricted."} + :geometry-type "LineString" + :tags {:fi ["rullahiihto"] :se ["rullskidåkning"] :en ["roller skiing"]} + :main-category 4000 + :sub-category 4400 + :status "active" + :type-code 4407 + :props + {}}) + + ;; Add new monikäyttöreitti type. Props added via script + (assoc 4406 + {:name + {:fi "Monikäyttöreitti" + :se "Multianvändningsled" + :en "Multi-use Trail"} + :description + {:fi "Talvisin tai ympärivuotisesti käytössä oleva maastoreitti, joka soveltuu usealle kulkutavalle (esim. jalan, lumikengille, läskipyörälle). Lisätietoihin merkitään, jos reitti on ympärivuotisessa käytössä ja mahdolliset kulkutavat." + :se "En terrängled som används på vintern eller året runt och som är lämplig för flera färdsätt (t.ex. till fots, med snöskor, fatbike). Ytterligare information anger om leden är i bruk året runt och möjliga färdsätt." + :en "A terrain trail used in winter or year-round, suitable for multiple modes of travel (e.g., on foot, with snowshoes, fat bike). Additional information indicates if the trail is in year-round use and possible modes of travel."} + :geometry-type "LineString" + :tags {:fi ["yhteiskäyttöreitti" "talvipolku"] :se ["gemensam led" "vinterstig"] :en ["shared trail" "winter path"]} + :main-category 4000 + :sub-category 4400 + :status "active" + :type-code 4406 + :props + {}}) + + ;; Add new koiravaljakkoreitti type + (assoc 4441 + {:name + {:fi "Koiravaljakkoreitti" + :se "Hundspannsrutt" + :en "Dog Sledding Route"} + :description + {:fi "Koiravaljakoille ylläpidetty reitti." + :se "En rutt underhållen för hundspann." + :en "A route maintained for dog sledding."} + :geometry-type "LineString" + :tags {:fi ["valjakkoajo" "valjakkoreitti"] :se [] :en []} + :main-category 4000 + :sub-category 4400 + :status "active" + :type-code 4441 + :props + {}}) + + (assoc 6150 + {:name + {:fi "Ovaalirata" + :se "Ovalbana" + :en "Oval Track"} + :description + {:fi "Islanninhevosten askellajiratsastukseen varattu rata ulkona." + :se "En bana utomhus avsedd för gångartstävlingar med islandshästar." + :en "An outdoor track designated for gaited riding competitions with Icelandic horses."} + :geometry-type "LineString" + :tags {:fi ["islanninhevosrata" "islanninhevosratsastus" "ovaalibaana" "askellajiratsastus" "askellajirata"] + :se ["islandshästbana" "islandshästridning" "ovalbana" "gångartstävling" "gångartsbana"] + :en ["Icelandic horse track" "Icelandic horse riding" "oval track" "gaited riding" "gaited track"]} + :main-category 6000 + :sub-category 6100 + :status "active" + :type-code 6150 + :props + {}}))) + +(def all6 + (-> all5 + + (assoc-in [4110 :description :fi] "Laskettelun suorituspaikka on lasketteluun tai lumilautailuun tarkoitettu liikuntapaikka, esim. laskettelukeskus. Laskettelun suorituspaikassa voi olla laskettelurinteitä, parkkeja tai muita rinnerakenteita ja hiihtohissejä.") + (assoc-in [4110 :props :customer-service-point?] {:priority 0}) + (update-in [4110 :props] clojure.core/dissoc :toboggan-run?) + (update-in [4110 :props] clojure.core/dissoc :school-use?) + (update-in [4110 :props] clojure.core/dissoc :free-use) + (assoc-in [4110 :props :sledding-hill?] {:priority 0}) + (assoc-in [4210 :name :fi] "Curlingrata/-halli") + (assoc-in [4210 :description :fi] "Pysyvästi lajiin varustettu tila, esim. curlingrata tai curlinghalli.") + (update-in [4210 :props] clojure.core/dissoc :fields-count) + (assoc-in [4210 :props :curling-lanes-count] {:priority 0}) + (assoc-in [4210 :props :stand-capacity-person] {:priority 0}) + (assoc-in [4210 :props :changing-rooms?] {:priority 0}) + (assoc-in [4210 :props :changing-rooms-m2] {:priority 0}) + (update-in [4210 :props] clojure.core/dissoc :school-use?) + (update-in [4210 :props] clojure.core/dissoc :free-use) + (update-in [4220 :props] clojure.core/dissoc :school-use?) + (update-in [4220 :props] clojure.core/dissoc :free-use) + (update-in [4230 :props] clojure.core/dissoc :school-use?) + (update-in [4230 :props] clojure.core/dissoc :free-use) + (update-in [4240 :props] clojure.core/dissoc :school-use?) + (update-in [4240 :props] clojure.core/dissoc :free-use) + (assoc-in [4320 :description :fi] "Mäkihyppyyn soveltuva mäki, vauhtimäessä on jää-, keramiikka- tai muovilatu. Mäen koko, materiaalit ja mahdollinen kesäkäyttö kuvataan lisätiedoissa.") + (assoc-in [4320 :props :changing-rooms?] {:priority 0}) + (assoc-in [4320 :props :changing-rooms-m2] {:priority 0}) + (assoc-in [4320 :props :jumps-count] {:priority 0}) + (assoc-in [4402 :name :fi] "Hiihtolatu") + (assoc-in [4402 :description :fi] "Talvikaudella hiihtoa varten ylläpidetty reitti. Hiihtotyylit kerrotaan ominaisuustiedoissa.") + (assoc-in [4407 :description :fi] "Asfaltoitu rullahiihtoon lumettomana aikana tarkoitettu reitti. Reitti kulkee maastossa, ja sen käyttöä muilla kulkutavoilla on rajoitettu.") + (assoc-in [4407 :props :surface-material] {:priority 0}) + (assoc-in [4407 :props :surface-material-info] {:priority 0}) + (assoc-in [4407 :props :route-length-km] {:priority 0}) + (assoc-in [4407 :props :lit-route-length-km] {:priority 0}) + (assoc-in [4407 :props :route-width-m] {:priority 0}) + (assoc-in [4407 :props :free-use] {:priority 0}) + (assoc-in [4407 :props :outdoor-exercise-machines?] {:priority 0}) + (assoc-in [4407 :props :rest-places-count] {:priority 0}) + (assoc-in [4407 :props :toilet?] {:priority 0}) + (assoc-in [4407 :props :may-be-shown-in-harrastuspassi-fi?] {:priority 0}) + (update-in [4403 :props] clojure.core/dissoc :free-use) + (update-in [4403 :props] clojure.core/dissoc :school-use?) + (update-in [4404 :props] clojure.core/dissoc :free-use) + (update-in [4405 :props] clojure.core/dissoc :free-use) + (update-in [4405 :props] clojure.core/dissoc :school-use?) + (assoc-in [4412 :description :fi] "Pyöräilyreitti, joka kulkee enimmäkseen päällystetyillä teillä tai sorateillä. Reitti voi olla merkitty maastoon tai se on digitaalisesti opastettu") + (update-in [4412 :props] clojure.core/dissoc :free-use) + (update-in [4412 :props] clojure.core/dissoc :school-use?) + (update-in [4421 :props] clojure.core/dissoc :school-use?) + (update-in [4422 :props] clojure.core/dissoc :school-use?) + (update-in [4451 :props] clojure.core/dissoc :school-use?) + (update-in [4451 :props] clojure.core/dissoc :free-use) + (update-in [4452 :props] clojure.core/dissoc :free-use) + (assoc-in [4452 :props :jumps-count] {:priority 0}) + (assoc-in [4320 :description :fi] "Mäkihyppyyn soveltuva mäki, vauhtimäessä on jää-, keramiikka- tai muovilatu. Mäen koko, materiaalit ja mahdollinen kesäkäyttö kuvataan lisätiedoissa.") + (assoc-in [4320 :props :changing-rooms?] {:priority 0}) + (update-in [4320 :props] clojure.core/dissoc :school-use?) + (assoc-in [4406 :description :fi] "Talvisin tai ympärivuotisesti käytössä oleva maastoreitti, joka soveltuu usealle kulkutavalle (esim. jalan, lumikengille, läskipyörälle). Lisätietoihin merkitään, jos reitti on ympärivuotisessa käytössä ja mahdolliset kulkutavat.") + (assoc-in [4406 :props :surface-material] {:priority 0}) + (assoc-in [4406 :props :surface-material-info] {:priority 0}) + (assoc-in [4406 :props :route-length-km] {:priority 0}) + (assoc-in [4406 :props :lit-route-length-km] {:priority 0}) + (assoc-in [4406 :props :rest-places-count] {:priority 0}) + (assoc-in [4406 :props :toilet?] {:priority 0}) + (assoc-in [4406 :props :route-width-m] {:priority 0}) + (assoc-in [4406 :props :travel-modes] {:priority 0}) + (assoc-in [4406 :props :travel-mode-info] {:priority 0}) + (assoc-in [4406 :props :summer-usage?] {:priority 0}) + (assoc-in [4441 :description :fi] "Koiravaljakoille ylläpidetty reitti.") + (assoc-in [4441 :props :surface-material] {:priority 0}) + (assoc-in [4441 :props :surface-material-info] {:priority 0}) + (assoc-in [4441 :props :route-length-km] {:priority 0}) + (assoc-in [4441 :props :lit-route-length-km] {:priority 0}) + (assoc-in [4441 :props :route-width-m] {:priority 0}) + (assoc-in [4441 :props :free-use] {:priority 0}) + (assoc-in [4441 :props :summer-usage?] {:priority 0}) + (assoc-in [4510 :description :fi] "Suunnistukseen käytetty alue. Lisätietoihin merkitään, jos aluetta käytetään mobo-, pyörä- tai hiihtosuunnistukseen. Suunnistusalueesta on saatavilla kartta ja maankäyttöön on maanomistajan suostumus.") + (update-in [4510 :props] clojure.core/dissoc :free-use) + (assoc-in [4510 :props :mobile-orienteering?] {:priority 0}) + (assoc-in [4510 :props :bike-orienteering?] {:priority 0}) + (assoc-in [4510 :props :ski-orienteering?] {:priority 0}) + (update-in [4610 :props] clojure.core/dissoc :automated-timing?) + (assoc-in [4610 :props :changing-rooms?] {:priority 0}) + (assoc-in [4610 :props :changing-rooms-m2] {:priority 0}) + (update-in [4610 :props] clojure.core/dissoc :surface-material) + (update-in [4610 :props] clojure.core/dissoc :surface-material-info) + (update-in [4610 :props] clojure.core/dissoc :school-use?) + (update-in [4620 :props] clojure.core/dissoc :automated-timing?) + (update-in [4620 :props] clojure.core/dissoc :surface-material) + (update-in [4620 :props] clojure.core/dissoc :surface-material-info) + (assoc-in [4620 :props :summer-usage?] {:priority 0}) + (update-in [4620 :props] clojure.core/dissoc :school-use?) + (assoc-in [4630 :name :se] "Maastohiihtokeskus") + (update-in [4630 :props] clojure.core/dissoc :automated-timing?) + (update-in [4630 :props] clojure.core/dissoc :school-use?) + (update-in [4640 :props] clojure.core/dissoc :automated-timing?) + (update-in [4720 :props] clojure.core/dissoc :school-use?) + (update-in [4720 :props] clojure.core/dissoc :free-use) + (update-in [4810 :props] clojure.core/dissoc :track-width-m) + (update-in [4810 :props] clojure.core/dissoc :school-use?) + (update-in [4810 :props] clojure.core/dissoc :free-use) + (update-in [4820 :props] clojure.core/dissoc :track-width-m) + (update-in [4820 :props] clojure.core/dissoc :school-use?) + (update-in [4820 :props] clojure.core/dissoc :free-use) + (assoc-in [4830 :description :fi] "Ulkona tai sisällä sijaitseva jousiammuntarata. Radan käyttö edellyttää erillistä lupaa, seuran jäsenyyttä tai harjoitusvuoroa. Radan varustus ja soveltuvat lajit kuvataan lisätiedoissa.") + (update-in [4830 :props] clojure.core/dissoc :school-use?) + (update-in [4830 :props] clojure.core/dissoc :free-use) + (assoc-in [4830 :props :free-customer-use?] {:priority 0}) + (assoc-in [4840 :description :fi] "Maastoon rakennettu jousiammuntarata. Radan käyttö edellyttää erillistä lupaa, seuran jäsenyyttä tai harjoitusvuoroa. ") + (assoc-in [4840 :props :free-customer-use?] {:priority 0}) + (update-in [4840 :props] clojure.core/dissoc :school-use?) + (update-in [4840 :props] clojure.core/dissoc :free-use) + (assoc-in [5110 :description :fi] "Soutustadion sisältää pysyvästi soutuun käytettäviä rakenteita. Soutustadionissa on katsomo ja valmius ratamerkintöihin.") + (update-in [5110 :props] clojure.core/dissoc :school-use?) + (update-in [5120 :props] clojure.core/dissoc :school-use?) + (assoc-in [5130 :description :fi] "Pysyvä moottorivenekilpailujen rata-alue.") + (update-in [5130 :props] clojure.core/dissoc :school-use?) + (assoc-in [5140 :description :fi] "Rakennettu pysyvästi vesihiihdolle. Vesihiihtoalueella on laituri.") + (update-in [5140 :props] clojure.core/dissoc :school-use?) + (update-in [5150 :props] clojure.core/dissoc :school-use?) + (assoc-in [5160 :description :fi] "Soudun ja melonnan sisäharjoittelutila on erityisesti näihin lajeihin pysyvästi tarkoitettu liikuntapaikka.") + (update-in [5160 :props] clojure.core/dissoc :school-use?) + (assoc-in [5210 :description :fi] "Harraste- tai urheiluilmailuun tarkoitettu alue, esim. lentopaikka.") + (update-in [5210 :props] clojure.core/dissoc :school-use?) + (update-in [5310 :props] clojure.core/dissoc :school-use?) + (assoc-in [5310 :props :summer-usage?] {:priority 0}) + (assoc-in [5320 :description :fi] "Pääasiassa moottoripyöräilyä varten rakennettu, luonnonmukainen ei-asfalttipintainen alue (esim. enduroreitit ja trial-harjoittelualueet maastoliikennealueilla).") + (update-in [5320 :props] clojure.core/dissoc :school-use?) + (assoc-in [5320 :props :summer-usage?] {:priority 0}) + (assoc-in [5330 :description :fi] "Suuri rata-autoiluun tai moottoripyöräilyyn tarkoitettu asfaltoitu moottoriurheilupaikka.") + (update-in [5330 :props] clojure.core/dissoc :school-use?) + (assoc-in [5330 :props :summer-usage?] {:priority 0}) + (assoc-in [5340 :description :fi] "Pääasiallisesti kartingajoon tai supermotoon käytetty rata.") + (update-in [5340 :props] clojure.core/dissoc :school-use?) + (assoc-in [5340 :props :summer-usage?] {:priority 0}) + (assoc-in [5350 :description :fi] "Pääasiallisesti kiihdytysautoiluun tai kiihdytysmoottoripyöräilyyn käytetty rata.") + (update-in [5350 :props] clojure.core/dissoc :school-use?) + (assoc-in [5350 :props :summer-usage?] {:priority 0}) + (assoc-in [5360 :description :fi] "Pääasiallisesti jokamiesajoa, rallicrossia tai moottoripyöräilyä varten.") + (update-in [5360 :props] clojure.core/dissoc :school-use?) + (assoc-in [5360 :props :summer-usage?] {:priority 0}) + (assoc-in [5370 :name :fi] "Speedway- ja jääspeedwayrata") + (update-in [5370 :props] clojure.core/dissoc :school-use?) + (assoc-in [5370 :props :summer-usage?] {:priority 0}) + (update-in [6120 :props] clojure.core/dissoc :ligthing?) + (assoc-in [6130 :name :fi] "Esteratsastuskenttä tai -alue") + (assoc-in [6130 :description :fi] "Pysyvästi esteratsastukseen varusteltu kenttä tai alue ulkona.") + (assoc-in [6150 :description :fi] "Islanninhevosten askellajiratsastukseen varattu rata ulkona.") + (assoc-in [6150 :props :customer-service-point?] {:priority 0}) + (assoc-in [6150 :props :surface-material] {:priority 0}) + (assoc-in [6150 :props :surface-material-info] {:priority 0}) + (assoc-in [6150 :props :ligthing?] {:priority 0}) + (assoc-in [6150 :props :toilet?] {:priority 0}) + (assoc-in [6150 :props :track-width-m] {:priority 0}) + (assoc-in [6150 :props :track-length-m] {:priority 0}) + (assoc-in [6150 :props :may-be-shown-in-harrastuspassi-fi?] {:priority 0}) + (assoc-in [6150 :props :free-use] {:priority 0}) + (assoc-in [5360 :props :summer-usage?] {:priority 0}) + (update-in [6140 :props] clojure.core/dissoc :school-use?) + (update-in [6210 :props] clojure.core/dissoc :school-use?) + (update-in [6220 :props] clojure.core/dissoc :school-use?) + + ) + ) + +(def all7 + (-> all6 + + ;; Remove :winter-use?, summer-use? and add :year-round-use? to + ;; replace them + (->> (reduce-kv (fn [m k v] + (assoc m k + (cond-> v + (contains? (:props v) :summer-usage?) + (assoc-in [:props :year-round-use?] {:priority 0}) + + (contains? (:props v) :winter-usage?) + (assoc-in [:props :year-round-use?] {:priority 0}) + + true (update :props dissoc :winter-usage? :summer-usage?) + + ))) + {})) + + ;; Deprecate pyörä- and hiihtosuunnistusallue + ;; They were merged to "suunnistusalue" 4510 + (assoc-in [4530 :status] "deprecated") + (assoc-in [4520 :status] "deprecated") + + ;; Migrate harjoityshyppyrimäki to hyppyrimäki + (assoc-in [4310 :status] "deprecated") + (update-in [4320 :props] assoc :hs-point {:priority 0}) + (update-in [4320 :props] dissoc :p-point) + + )) + +(def all8 + (-> all7 + (update-in [4810 :props] dissoc :free-use?) + (update-in [4820 :props] dissoc :free-use?) + (assoc-in [5120 :description :fi] "Pysyvästi purjehdusta varten varustettu alue.") + (assoc-in [202 :props :free-use?] {:priority 0}) + (assoc-in [203 :props :free-use?] {:priority 0}) + (assoc-in [204 :props :free-use?] {:priority 0}) + (assoc-in [206 :props :free-use?] {:priority 0}) + (assoc-in [301 :props :free-use?] {:priority 0}) + (assoc-in [302 :props :free-use?] {:priority 0}) + (assoc-in [304 :props :free-use?] {:priority 0}) + (assoc-in [4412 :description :fi] "Pyöräilyreitti, joka kulkee enimmäkseen päällystetyillä teillä tai sorateillä. Reitti voi olla merkitty maastoon tai se on digitaalisesti opastettu.") + (assoc-in [5370 :name :fi] "Speedway-/jääspeedwayrata") + (assoc-in [6130 :name :fi] "Esteratsastuskenttä/-alue") + (assoc-in [3210 :name :fi] "Maauimala/vesipuisto") + + )) + +(def all all8) (def active (reduce-kv (fn [m k v] (if (not= "active" (:status v)) (dissoc m k) m)) all all)) @@ -559,5 +862,9 @@ (utils/index-by (comp :fi :name) (vals sub-categories))) (comment - (all 2620) + (require '[clojure.pprint :as pprint]) + #?(:clj (spit "/tmp/types.edn" (with-out-str (pprint/pprint all)))) + #?(:clj (spit "/tmp/sub-cats.edn" (with-out-str (pprint/pprint sub-categories)))) + #?(:clj (spit "/tmp/main-cats.edn" (with-out-str (pprint/pprint main-categories)))) + ) diff --git a/webapp/src/cljc/lipas/i18n/core.cljc b/webapp/src/cljc/lipas/i18n/core.cljc index 909ecf41f..df6a7b790 100644 --- a/webapp/src/cljc/lipas/i18n/core.cljc +++ b/webapp/src/cljc/lipas/i18n/core.cljc @@ -122,6 +122,41 @@ :translations materials/surface-materials :many? true} + ;; Proerties->travel-modes + {:path [:properties :travel-modes] + :translations (-> prop-types/all + (get-in [:travel-modes :opts]) + (update-vals :label)) + :many? true} + + ;; Proerties->parkour-hall-equipment-and-structures + {:path [:properties :parkour-hall-equipment-and-structures] + :translations (-> prop-types/all + (get-in [:parkour-hall-equipment-and-structures :opts]) + (update-vals :label)) + :many? true} + + ;; Proerties->boating-service-class + {:path [:properties :boating-service-class] + :translations (-> prop-types/all + (get-in [:boating-service-class :opts]) + (update-vals :label)) + :many? false} + + ;; Properties->water-point + {:path [:properties :water-point] + :translations (-> prop-types/all + (get-in [:water-point :opts]) + (update-vals :label)) + :many? false} + + ;; Properties->sport-specification + {:path [:properties :sport-specification] + :translations (-> prop-types/all + (get-in [:sport-specification :opts]) + (update-vals :label)) + :many? false} + ;; Ice stadiums {:path [:type :size-category] :translations ice/size-categories} @@ -221,7 +256,57 @@ {:path [:properties :surface-material] :target-path [:properties :surface-material-localized] :translate-fn (fn [locales vs] - (map (fn [v] (-> v materials/surface-materials (select-keys locales))) vs))}] + (map (fn [v] (-> v materials/surface-materials (select-keys locales))) vs))} + + ;; Proerties->travel-modes + {:path [:properties :travel-modes] + :translate-fn (fn [locales vs] + (-> prop-types/all + (get-in [:travel-modes :opts]) + (select-keys vs) + (->> (map :label) + (map #(select-keys % locales)))))} + + ;; Proerties->parkour-hall-equipment-and-structures + {:path [:properties :parkour-hall-equipment-and-structures] + :target-path [:properties :parkour-hall-equipment-and-structures-localized] + :translate-fn (fn [locales vs] + (-> prop-types/all + (get-in [:parkour-hall-equipment-and-structures :opts]) + (select-keys vs) + (->> (map :label) + (map #(select-keys % locales)))))} + + ;; Proerties->boating-service-class + {:path [:properties :boating-service-class] + :target-path [:properties :boating-service-class-localized] + :translate-fn (fn [locales vs] + (-> prop-types/all + (get-in [:boating-service-class :opts]) + (select-keys vs) + (->> (map :label) + (map #(select-keys % locales)))))} + + ;; Properties->water-point + {:path [:properties :water-point] + :target-path [:properties :water-point-localized] + :translate-fn (fn [locales vs] + (-> prop-types/all + (get-in [:water-point :opts]) + (select-keys vs) + (->> (map :label) + (map #(select-keys % locales)))))} + + ;; Properties->sport-specification + {:path [:properties :sport-specification] + :target-path [:properties :sport-specification-localized] + :translate-fn (fn [locales vs] + (-> prop-types/all + (get-in [:sport-specification :opts]) + (select-keys vs) + (->> (map :label) + (map #(select-keys % locales)))))}] + (map #(update % :translate-fn memoize)))) (defn localize2 diff --git a/webapp/src/cljc/lipas/schema/core.cljc b/webapp/src/cljc/lipas/schema/core.cljc index 375b37649..439f5eecf 100644 --- a/webapp/src/cljc/lipas/schema/core.cljc +++ b/webapp/src/cljc/lipas/schema/core.cljc @@ -13,6 +13,7 @@ [lipas.data.loi :as loi] [lipas.data.materials :as materials] [lipas.data.owners :as owners] + [lipas.data.prop-types :as prop-types] [lipas.data.reminders :as reminders] [lipas.data.sports-sites :as sports-sites] [lipas.data.swimming-pools :as swimming-pools] @@ -724,22 +725,25 @@ (s/def :lipas.sports-site.properties/canoeing-club? boolean?) (s/def :lipas.sports-site.properties/activity-service-company? boolean?) (s/def :lipas.sports-site.properties/boating-service-class string?) -(s/def :lipas.sports-site.properties/water-point string?) +(s/def :lipas.sports-site.properties/water-point + (into #{} (keys (get-in prop-types/all [:water-point :opts])))) (s/def :lipas.sports-site.properties/customer-service-point? boolean?) (s/def :lipas.sports-site.properties/height-of-basket-or-net-adjustable? boolean?) (s/def :lipas.sports-site.properties/changing-rooms-m2 ::real) (s/def :lipas.sports-site.properties/ligthing-info string?) (s/def :lipas.sports-site.properties/highest-obstacle-m ::real) (s/def :lipas.sports-site.properties/fitness-stairs-length-m ::real) -(s/def :lipas.sports-site.properties/fitness-stairs-length-m ::real) (s/def :lipas.sports-site.properties/free-customer-use? boolean?) -(s/def :lipas.sports-site.properties/space-divisible string?) +(s/def :lipas.sports-site.properties/space-divisible ::real) (s/def :lipas.sports-site.properties/auxiliary-training-area? boolean?) (s/def :lipas.sports-site.properties/sport-specification string?) -(s/def :lipas.sports-site.properties/width-of-active-space-m ::real) -(s/def :lipas.sports-site.properties/length-of-active-space-m ::real) +(s/def :lipas.sports-site.properties/active-space-width-m ::real) +(s/def :lipas.sports-site.properties/active-space-length-m ::real) (s/def :lipas.sports-site.properties/mirror-wall? boolean?) -(s/def :lipas.sports-site.properties/parkour-hall-equipment-and-structures string?) +(s/def :lipas.sports-site.properties/parkour-hall-equipment-and-structures + (s/coll-of + (into #{} (keys (get-in prop-types/all [:parkour-hall-equipment-and-structures :opts]))) + :distinct true)) (s/def :lipas.sports-site.properties/ringette-boundary-markings? boolean?) (s/def :lipas.sports-site.properties/field-1-flexible-rink? boolean?) (s/def :lipas.sports-site.properties/field-2-flexible-rink? boolean?) @@ -750,6 +754,22 @@ (s/def :lipas.sports-site.properties/pyramid-tables-count ::real) (s/def :lipas.sports-site.properties/carom-tables-count ::real) (s/def :lipas.sports-site.properties/total-billiard-tables-count ::real) +(s/def :lipas.sports-site.properties/boating-service-class + (s/coll-of + (into #{} (keys (get-in prop-types/all [:boating-service-class :opts]))) + :distinct true)) +(s/def :lipas.sports-site.properties/travel-modes + (s/coll-of + (into #{} (keys (get-in prop-types/all [:travel-modes :opts]))) + :distinct true)) +(s/def :lipas.sports-site.properties/travel-mode-info string?) +(s/def :lipas.sports-site.properties/sledding-hill? boolean?) +(s/def :lipas.sports-site.properties/mobile-orienteering? boolean?) +(s/def :lipas.sports-site.properties/ski-orienteering? boolean?) +(s/def :lipas.sports-site.properties/bike-orienteering? boolean?) +(s/def :lipas.sports-site.properties/hs-point ::real) +(s/def :lipas.sports-site.properties/lighting-info string?) +(s/def :lipas.sports-site.properties/year-round-use? boolean?) (s/def :lipas.sports-site/properties (s/keys :opt-un [:lipas.sports-site.properties/height-m @@ -920,8 +940,8 @@ :lipas.sports-site.properties/space-divisible :lipas.sports-site.properties/auxiliary-training-area? :lipas.sports-site.properties/sport-specification - :lipas.sports-site.properties/width-of-active-space-m - :lipas.sports-site.properties/length-of-active-space-m + :lipas.sports-site.properties/active-space-width-m + :lipas.sports-site.properties/active-space-length-m :lipas.sports-site.properties/mirror-wall? :lipas.sports-site.properties/parkour-hall-equipment-and-structures :lipas.sports-site.properties/ringette-boundary-markings? @@ -934,7 +954,18 @@ :lipas.sports-site.properties/pyramid-tables-count :lipas.sports-site.properties/carom-tables-count :lipas.sports-site.properties/total-billiard-tables-count - ])) + :lipas.sports-site.properties/boating-service-class + :lipas.sports-site.properties/free-use? + :lipas.sports-site.properties/school-use? + :lipas.sports-site.properties/year-round-use? + :lipas.sports-site.properties/lighting-info + :lipas.sports-site.properties/hs-point + :lipas.sports-site.properties/bike-orienteering? + :lipas.sports-site.properties/ski-orienteering? + :lipas.sports-site.properties/mobile-orienteering? + :lipas.sports-site.properties/sledding-hill? + :lipas.sports-site.properties/travel-mode-info + :lipas.sports-site.properties/travel-modes])) (s/def :lipas.sports-site/properties-old (s/map-of keyword? (s/or :string? (str-in 1 100) @@ -1446,6 +1477,68 @@ :field/floorball :lipas.sports-site.fields/floorball) :into [])) +(s/def :lipas.sports-site.ptv/last-sync string?) +(s/def :lipas.sports-site.ptv/org-id string?) +(s/def :lipas.sports-site.ptv/sync-enabled boolean?) +(s/def :lipas.sports-site.ptv/source-id string?) +(s/def :lipas.sports-site.ptv/publishing-status string?) +(s/def :lipas.sports-site.ptv/previous-type-code int?) + +(s/def :lipas.ptv.summary/fi (str-in 0 150)) +(s/def :lipas.ptv.summary/se (str-in 0 150)) +(s/def :lipas.ptv.summary/en (str-in 0 150)) +(s/def :lipas.sports-site.ptv/summary + (s/keys :opt-un [:lipas.ptv.summary/fi + :lipas.ptv.summary/se + :lipas.ptv.summary/en])) + +(s/def :lipas.ptv.description/fi string?) +(s/def :lipas.ptv.description/se string?) +(s/def :lipas.ptv.description/en string?) +(s/def :lipas.sports-site.ptv/description + (s/keys :opt-un [:lipas.ptv.description/fi + :lipas.ptv.description/se + :lipas.ptv.description/en])) + +(s/def :lipas.sports-site.ptv/service-channel-integration #{"lipas-managed" "manual"}) +(s/def :lipas.sports-site.ptv/service-integration #{"lipas-managed" "manual"}) +(s/def :lipas.sports-site.ptv/descriptions-integration #{"lipas-managed-ptv-fields" "lipas-managed-comment-field" "ptv-managed"}) + +(s/def :lipas.sports-site.ptv/service-ids (s/coll-of string?)) +(s/def :lipas.sports-site.ptv/service-channel-ids (s/coll-of string?)) +(s/def :lipas.sports-site.ptv/languages (s/coll-of string?)) + +(s/def :lipas.ptv.error/message string?) +(s/def :lipas.ptv.error/data map?) +(s/def :lipas.sports-site.ptv/error + (s/keys :req-un [:lipas.ptv.error/message + :lipas.ptv.error/data])) + +(s/def :lipas.sports-site/ptv + (s/keys :req-un [:lipas.sports-site.ptv/org-id + :lipas.sports-site.ptv/sync-enabled + + :lipas.sports-site.ptv/summary + :lipas.sports-site.ptv/description] + :opt-un [;; Added on first successful sync + :lipas.sports-site.ptv/last-sync + :lipas.sports-site.ptv/previous-type-code + :lipas.sports-site.ptv/publishing-status + :lipas.sports-site.ptv/service-ids + :lipas.sports-site.ptv/languages + + ;; Added on sync - removed when archived + :lipas.sports-site.ptv/source-id + :lipas.sports-site.ptv/service-channel-ids + + ;; Previously used keys, not used now + :lipas.sports-site.ptv/service-channel-integration + :lipas.sports-site.ptv/service-integration + :lipas.sports-site.ptv/descriptions-integration + + ;; Only added on sync error - removed when success + :lipas.sports-site.ptv/error])) + (s/def :lipas/new-sports-site (s/keys :req-un [:lipas.sports-site/event-date :lipas.sports-site/status @@ -1472,7 +1565,8 @@ :lipas.sports-site/properties :lipas.sports-site/fields :lipas.sports-site/locker-rooms - :lipas.sports-site/circumstances])) + :lipas.sports-site/circumstances + :lipas.sports-site/ptv])) (s/def :lipas/sports-site (s/merge diff --git a/webapp/src/cljs/lipas/ui/components/autocompletes.cljs b/webapp/src/cljs/lipas/ui/components/autocompletes.cljs index 2aaac255e..46b05965d 100644 --- a/webapp/src/cljs/lipas/ui/components/autocompletes.cljs +++ b/webapp/src/cljs/lipas/ui/components/autocompletes.cljs @@ -91,7 +91,7 @@ x)) (defui autocomplete2 - "Helper for version if autocomplete where: + "Helper for version of autocomplete where: :options should be a cljs sequential collection with {:value ... :label ...}" [{:keys [options input-props label] :as props}] diff --git a/webapp/src/cljs/lipas/ui/map/projection.cljs b/webapp/src/cljs/lipas/ui/map/projection.cljs index 19de91b69..f89f34f18 100644 --- a/webapp/src/cljs/lipas/ui/map/projection.cljs +++ b/webapp/src/cljs/lipas/ui/map/projection.cljs @@ -1,5 +1,5 @@ (ns lipas.ui.map.projection - " Loading this namespace causes side-effects to global OpenLayers + "Loading this namespace causes side-effects to global OpenLayers object (js/ol)." (:require ["ol/extent" :as extent] ["ol/proj" :as proj] diff --git a/webapp/src/cljs/lipas/ui/map/styles.cljs b/webapp/src/cljs/lipas/ui/map/styles.cljs index 4ea36e306..f2d656b26 100644 --- a/webapp/src/cljs/lipas/ui/map/styles.cljs +++ b/webapp/src/cljs/lipas/ui/map/styles.cljs @@ -107,11 +107,11 @@ stroke-black (Stroke. #js {:color "#00000" :width 1}) - stroke-planned (Stroke. #js {:color "#3b3b3b" + stroke-planned (Stroke. #js {:color "#b1b7c4" :lineDash #js [2 20] ; :lineDashOffset 1 :width (case (:shape m) - ("polygon" "linestring") 10 + ("polygon" "linestring") 7 ("circle") 5 ("square") 4)}) @@ -119,7 +119,7 @@ :lineDash #js [2 20] ; :lineDashOffset 1 :width (case (:shape m) - ("polygon" "linestring") 10 + ("polygon" "linestring") 7 ("circle") 5 ("square") 4)}) diff --git a/webapp/src/cljs/lipas/ui/map/views.cljs b/webapp/src/cljs/lipas/ui/map/views.cljs index 4b2fb6872..2f6e81643 100644 --- a/webapp/src/cljs/lipas/ui/map/views.cljs +++ b/webapp/src/cljs/lipas/ui/map/views.cljs @@ -11,7 +11,7 @@ ["mdi-material-ui/MapSearchOutline$default" :as MapSearchOutline] ["react" :as react] [clojure.spec.alpha :as s] - [clojure.string :as string] + [clojure.string :as str] [lipas.data.activities :as activities-data] [lipas.data.sports-sites :as ss] [lipas.roles :as roles] @@ -26,6 +26,7 @@ [lipas.ui.map.subs :as subs] [lipas.ui.mui :as mui] [lipas.ui.navbar :as nav] + [lipas.ui.ptv.site-view :as ptv-site] [lipas.ui.ptv.views :as ptv] [lipas.ui.reminders.views :as reminders] [lipas.ui.reports.views :as reports] @@ -39,8 +40,7 @@ [lipas.ui.utils :refer [<== ==>] :as utils] [re-frame.core :as rf] [reagent.core :as r] - [uix.core :as uix] - [uix.core :refer [$ defui]])) + [uix.core :as uix :refer [$ defui]])) ;; TODO: Juho later This pattern makes development inconvenient as ;; the component might crash and shadow-cljs reloads don't update it. @@ -276,11 +276,11 @@ "my_location"]]]) (defn filter-by-term [term table-data] - (let [lower-case-term (string/lower-case term)] - (filter #(or (string/includes? (string/lower-case (% :name)) lower-case-term) - (string/includes? (string/lower-case (% :geometry-type)) lower-case-term) - (string/includes? (string/lower-case (% :description)) lower-case-term) - (string/includes? (string/lower-case (string/join (% :tags))) lower-case-term)) + (let [lower-case-term (str/lower-case term)] + (filter #(or (str/includes? (str/lower-case (% :name)) lower-case-term) + (str/includes? (str/lower-case (% :geometry-type)) lower-case-term) + (str/includes? (str/lower-case (% :description)) lower-case-term) + (str/includes? (str/lower-case (str/join (% :tags))) lower-case-term)) table-data))) (defn type-helper-table [{:keys [tr on-select types]}] @@ -533,7 +533,7 @@ [mui/typography (tr :analysis/mode)]] [mui/table-cell (when (seq (:diversity-idx-mode data)) - (string/join "," (:diversity-idx-mode data)))]]]] + (str/join "," (:diversity-idx-mode data)))]]]] ;; No data available [:div {:style {:width "200px" :padding "0.5em 0.5em 0em 0.5em"}} @@ -810,6 +810,9 @@ view-floorball? (when floorball-type? (<== [:lipas.ui.user.subs/check-privilege role-site-ctx :floorball/view])) edit-floorball? (when floorball-type? (<== [:lipas.ui.user.subs/check-privilege role-site-ctx :floorball/edit])) + ;; TODO: Maybe always show the ptv tab if the ptv integration is enabled for the site? + view-ptv? (<== [:lipas.ui.user.subs/check-privilege role-site-ctx :ptv/manage]) + hide-actions? (<== [::subs/hide-actions?]) ;; FIXME: Bad pattern to combine n subs into one @@ -930,7 +933,13 @@ [mui/tab {:style {:min-width 0} :value 4 - :label (tr :sports-site.elevation-profile/headline)}])] + :label (tr :sports-site.elevation-profile/headline)}]) + + (when view-ptv? + [mui/tab + {:style {:min-width 0} + :value 6 + :label "PTV"}])] (when delete-dialog-open? [sports-sites/delete-dialog @@ -1034,7 +1043,17 @@ :type-code type-code :display-data display-data :edit-data edit-data - :can-edit? edit-activities?}]])] + :can-edit? edit-activities?}]] + + 6 [mui/grid {:item true :xs 12} + ($ ptv-site/site-view + {:tr tr + :lipas-id lipas-id + :type-code type-code + ; :display-data display-data + :edit-data edit-data + :can-edit? can-publish? + })])] ;; "Landing bay" for floating tools [mui/grid {:item true :xs 12 :style {:height "3em"}}] diff --git a/webapp/src/cljs/lipas/ui/ptv/controls.cljs b/webapp/src/cljs/lipas/ui/ptv/controls.cljs new file mode 100644 index 000000000..38dd8dcf9 --- /dev/null +++ b/webapp/src/cljs/lipas/ui/ptv/controls.cljs @@ -0,0 +1,42 @@ +(ns lipas.ui.ptv.controls + (:require ["@mui/material/FormControl$default" :as FormControl] + ["@mui/material/FormControlLabel$default" :as FormControlLabel] + ["@mui/material/FormLabel$default" :as FormLabel] + ["@mui/material/Radio$default" :as Radio] + ["@mui/material/RadioGroup$default" :as RadioGroup] + ["@mui/material/Tab$default" :as Tab] + ["@mui/material/Tabs$default" :as Tabs] + ["@mui/material/Typography$default" :as Typography] + [lipas.ui.components.autocompletes :refer [autocomplete2]] + [uix.core :as uix :refer [$ defui]])) + +(defui info-text [{:keys [children]}] + ($ Typography + {:variant "body1"} + children)) + +(defui services-selector + [{:keys [options value on-change label value-fn] + :or {value-fn identity + label ""}}] + (let [options* (uix/use-memo (fn [] + (map (fn [x] + {:value (value-fn x) + :label (:label x)}) + options)) + [options value-fn])] + ($ autocomplete2 + {:options options* + :multiple true + :label label + :value (to-array value) + :on-change (fn [_e v] + (on-change (:value v)))}))) + +(defui lang-selector [{:keys [value on-change]}] + ($ Tabs + {:value value + :on-change (fn [_e v] (on-change (keyword v)))} + ($ Tab {:value "fi" :label "FI"}) + ($ Tab {:value "se" :label "SE"}) + ($ Tab {:value "en" :label "EN"}))) diff --git a/webapp/src/cljs/lipas/ui/ptv/db.cljs b/webapp/src/cljs/lipas/ui/ptv/db.cljs index ac9429bc4..f26b63b13 100644 --- a/webapp/src/cljs/lipas/ui/ptv/db.cljs +++ b/webapp/src/cljs/lipas/ui/ptv/db.cljs @@ -5,7 +5,9 @@ :loading-from-ptv {:services false :service-channels false :service-collections false} - :default-settings {:service-integration "lipas-managed" + :default-settings {:sync-enabled true + ;; TODO: These are not used now, could be removed once made optional in the schemas: + :service-integration "lipas-managed" :service-channel-integration "lipas-managed" :descriptions-integration "lipas-managed-ptv-fields" :integration-interval "manual"} diff --git a/webapp/src/cljs/lipas/ui/ptv/events.cljs b/webapp/src/cljs/lipas/ui/ptv/events.cljs index 28fc941bc..b4a7edede 100644 --- a/webapp/src/cljs/lipas/ui/ptv/events.cljs +++ b/webapp/src/cljs/lipas/ui/ptv/events.cljs @@ -6,12 +6,6 @@ [lipas.ui.utils :as utils] [re-frame.core :as rf])) -(def ptv-root-url-prod - "https://api.palvelutietovaranto.suomi.fi") - -(def ptv-root-url-test - "https://api.palvelutietovaranto.trn.suomi.fi") - (rf/reg-event-db ::open-dialog (fn [db [_ _]] (assoc-in db [:ptv :dialog :open?] true))) @@ -30,13 +24,6 @@ (fn [db [_ v]] (assoc-in db [:ptv :selected-tab] v))) -(def org-id->params - {ptv-data/uta-org-id-test ;; Utajärvi - {:org-id ptv-data/uta-org-id-test - :city-codes [889] - :owners ["city" "city-main-owner"] - :supported-languages ["fi" "se" "en"]}}) - (rf/reg-event-fx ::fetch-integration-candidates (fn [{:keys [db]} [_ org]] (when org @@ -46,7 +33,7 @@ {:method :post :headers {:Authorization (str "Token " token)} :uri (str (:backend-url db) "/actions/get-ptv-integration-candidates") - :params (get org-id->params (:id org)) + :params (get ptv-data/org-id->params (:id org)) :format (ajax/transit-request-format) :response-format (ajax/transit-response-format) :on-success [::fetch-integration-candidates-success (:id org)] @@ -79,14 +66,18 @@ (rf/reg-event-fx ::fetch-org (fn [{:keys [db]} [_ org]] - (when org - {:db (assoc-in db [:ptv :loading-from-ptv :org] true) - :fx [[:http-xhrio - {:method :get - :uri (str ptv-root-url-test "/api/v11/Organization/" (:id org)) - :response-format (ajax/json-response-format {:keywords? true}) - :on-success [::fetch-org-success (:id org)] - :on-failure [::fetch-org-failure]}]]}))) + (let [token (-> db :user :login :token)] + (when org + {:db (assoc-in db [:ptv :loading-from-ptv :org] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/fetch-ptv-org") + :params {:org-id (:id org)} + :format (ajax/transit-request-format) + :response-format (ajax/transit-response-format) + :on-success [::fetch-org-success (:id org)] + :on-failure [::fetch-org-failure]}]]})))) (rf/reg-event-fx ::fetch-org-success (fn [{:keys [db]} [_ org-id resp]] @@ -140,16 +131,17 @@ (rf/reg-event-fx ::fetch-service-channels (fn [{:keys [db]} [_ org]] (when org - {:db (assoc-in db [:ptv :loading-from-ptv :service-channels] true) - :fx [[:http-xhrio - {:method :get - :uri (str ptv-root-url-test - "/api/v11/ServiceChannel/organization/" - (:id org)) - - :response-format (ajax/json-response-format {:keywords? true}) - :on-success [::fetch-service-channels-success (:id org)] - :on-failure [::fetch-service-channels-failure]}]]}))) + (let [token (-> db :user :login :token)] + {:db (assoc-in db [:ptv :loading-from-ptv :service-channels] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/fetch-ptv-service-channels") + :params {:org-id (:id org)} + :format (ajax/transit-request-format) + :response-format (ajax/json-response-format {:keywords? true}) + :on-success [::fetch-service-channels-success (:id org)] + :on-failure [::fetch-service-channels-failure]}]]})))) (rf/reg-event-fx ::fetch-service-channels-success (fn [{:keys [db]} [_ org-id resp]] @@ -171,15 +163,17 @@ (rf/reg-event-fx ::fetch-service-collections (fn [{:keys [db]} [_ org]] (when org - {:db (assoc-in db [:ptv :loading-from-ptv :service-collections] true) - :fx [[:http-xhrio - {:method :get - :uri (str ptv-root-url-test - "/api/v11/ServiceCollection/organization" - "?organizationId=" (:id org)) - :response-format (ajax/json-response-format {:keywords? true}) - :on-success [::fetch-service-collections-success (:id org)] - :on-failure [::fetch-service-collections-failure]}]]}))) + (let [token (-> db :user :login :token)] + {:db (assoc-in db [:ptv :loading-from-ptv :service-collections] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/fetch-ptv-service-collections") + :params {:org-id (:id org)} + :format (ajax/transit-request-format) + :response-format (ajax/json-response-format {:keywords? true}) + :on-success [::fetch-service-collections-success (:id org)] + :on-failure [::fetch-service-collections-failure]}]]})))) (rf/reg-event-fx ::fetch-service-collections-success (fn [{:keys [db]} [_ org-id resp]] @@ -304,6 +298,71 @@ :fx (or extra-fx [[:dispatch [:lipas.ui.events/set-active-notification notification]]])}))) +(rf/reg-event-fx ::generate-descriptions-from-data + (fn [{:keys [db]} [_ lipas-id]] + (let [token (-> db :user :login :token) + edit-data (-> db :sports-sites (get lipas-id) :editing)] + {:db (assoc-in db [:ptv :loading-from-lipas :descriptions] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/generate-ptv-descriptions-from-data") + ;; :ptv data isn't used as AI input, and the data might not we valid spec yet? + :params (utils/make-saveable (dissoc edit-data :ptv)) + :format (ajax/transit-request-format) + :response-format (ajax/transit-response-format) + :on-success [::generate-descriptions-from-data-success lipas-id] + :on-failure [::generate-descriptions-from-data-failure lipas-id]}]]}))) + +(rf/reg-event-fx ::generate-descriptions-from-data-success + (fn [{:keys [db]} [_ lipas-id resp]] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :descriptions] false) + (update-in [:sports-sites lipas-id :editing :ptv] merge resp))})) + +(rf/reg-event-fx ::generate-descriptions-from-data-failure + (fn [{:keys [db]} [_]] + (let [tr (:translator db) + notification {:message (tr :notifications/get-failed) + :success? false}] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :descriptions] false)) + :fx [[:dispatch [:lipas.ui.events/set-active-notification notification]]]}))) + +(rf/reg-event-fx ::translate-to-other-langs + (fn [{:keys [db]} [_ lipas-id y]] + (let [token (-> db :user :login :token) + edit-data (-> db :sports-sites (get lipas-id) :editing) + ptv (-> edit-data :ptv) + data (assoc y + :summary (-> (:summary ptv) (get (keyword (:from y)))) + :description (-> (:description ptv) (get (keyword (:from y)))))] + {:db (assoc-in db [:ptv :loading-from-lipas :descriptions] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/translate-to-other-langs") + :params data + :format (ajax/transit-request-format) + :response-format (ajax/transit-response-format) + :on-success [::translate-to-other-langs-success lipas-id] + :on-failure [::translate-to-other-langs-failure lipas-id]}]]}))) + +(rf/reg-event-fx ::translate-to-other-langs-success + (fn [{:keys [db]} [_ lipas-id resp]] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :descriptions] false) + (update-in [:sports-sites lipas-id :editing :ptv] merge resp))})) + +(rf/reg-event-fx ::translate-to-other-langs-failure + (fn [{:keys [db]} [_]] + (let [tr (:translator db) + notification {:message (tr :notifications/get-failed) + :success? false}] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :descriptions] false)) + :fx [[:dispatch [:lipas.ui.events/set-active-notification notification]]]}))) + (rf/reg-event-db ::toggle-sync-all (fn [db [_ enabled]] (let [org-id (get-in db [:ptv :selected-org :id])] @@ -359,9 +418,8 @@ ;;; Service descriptions generation ;;; (rf/reg-event-fx ::generate-service-descriptions - (fn [{:keys [db]} [_ id success-fx failure-fx]] - (let [token (-> db :user :login :token) - org-id (-> db :ptv :selected-org :id)] + (fn [{:keys [db]} [_ org-id id overview success-fx failure-fx]] + (let [token (-> db :user :login :token)] {:db (assoc-in db [:ptv :loading-from-lipas :service-descriptions] true) :fx [[:http-xhrio {:method :post @@ -369,24 +427,24 @@ :uri (str (:backend-url db) "/actions/generate-ptv-service-descriptions") :params (merge - (org-id->params org-id) + (ptv-data/org-id->params org-id) {:sourceId id - :sub-category-id (parse-long (last (str/split id #"-")))}) + :sub-category-id (parse-long (last (str/split id #"-"))) + :overview overview}) :format (ajax/transit-request-format) :response-format (ajax/transit-response-format) - :on-success [::generate-service-descriptions-success id success-fx] - :on-failure [::generate-service-descriptions-failure id failure-fx]}]]}))) + :on-success [::generate-service-descriptions-success org-id id success-fx] + :on-failure [::generate-service-descriptions-failure org-id id failure-fx]}]]}))) (rf/reg-event-fx ::generate-service-descriptions-success - (fn [{:keys [db]} [_ id extra-fx resp]] - (let [org-id (get-in db [:ptv :selected-org :id])] - {:db (-> db - (assoc-in [:ptv :loading-from-lipas :lipas-managed-service-descriptions] false) - (update-in [:ptv :org org-id :data :service-candidates id] merge resp)) - :fx extra-fx}))) + (fn [{:keys [db]} [_ org-id id extra-fx resp]] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :lipas-managed-service-descriptions] false) + (update-in [:ptv :org org-id :data :service-candidates id] merge resp)) + :fx extra-fx})) (rf/reg-event-fx ::generate-service-descriptions-failure - (fn [{:keys [db]} [_ _id extra-fx resp]] + (fn [{:keys [db]} [_ _org-id _id extra-fx resp]] (let [tr (:translator db) notification {:message (tr :notifications/get-failed) :success? false}] @@ -411,7 +469,9 @@ :halt? halt?})) :fx [(when (and (not halt?) (seq ids)) [:dispatch [::generate-service-descriptions + org-id (first ids) + nil on-single-success on-single-failure]])]}))) @@ -445,33 +505,32 @@ ;;; Create Services in PTV ;;; (rf/reg-event-fx ::create-ptv-service - (fn [{:keys [db]} [_ id success-fx failure-fx]] - (let [token (-> db :user :login :token) - org-id (-> db :ptv :selected-org :id) - data (-> (get-in db [:ptv :services-creation :data id]))] + (fn [{:keys [db]} [_ org-id id data success-fx failure-fx]] + (let [token (-> db :user :login :token)] {:db (assoc-in db [:ptv :loading-from-lipas :services] true) :fx [[:http-xhrio {:method :post :headers {:Authorization (str "Token " token)} :uri (str (:backend-url db) "/actions/save-ptv-service") - :params (merge data (org-id->params org-id)) + ;; FIXME: org-id->params adds some extra keys that aren't used? + ;; supported-languages (languages is used instead) + :params (merge data (ptv-data/org-id->params org-id)) :format (ajax/transit-request-format) :response-format (ajax/transit-response-format) - :on-success [::create-ptv-service-success id success-fx] - :on-failure [::create-ptv-service-failure id failure-fx]}]]}))) + :on-success [::create-ptv-service-success org-id id success-fx] + :on-failure [::create-ptv-service-failure org-id id failure-fx]}]]}))) (rf/reg-event-fx ::create-ptv-service-success - (fn [{:keys [db]} [_ id extra-fx resp]] - (let [org-id (get-in db [:ptv :selected-org :id])] - {:db (-> db - (assoc-in [:ptv :loading-from-lipas :services] false) - (assoc-in [:ptv :org org-id :data :services (:id resp)] resp) - (update-in [:ptv :org org-id :data :service-candidates id] - assoc :created-in-ptv true)) - :fx extra-fx}))) + (fn [{:keys [db]} [_ org-id id extra-fx resp]] + {:db (-> db + (assoc-in [:ptv :loading-from-lipas :services] false) + (assoc-in [:ptv :org org-id :data :services (:id resp)] resp) + (update-in [:ptv :org org-id :data :service-candidates id] + assoc :created-in-ptv true)) + :fx extra-fx})) (rf/reg-event-fx ::create-ptv-service-failure - (fn [{:keys [db]} [_ _id extra-fx resp]] + (fn [{:keys [db]} [_ _org-id _id extra-fx resp]] (let [tr (:translator db) notification {:message (tr :notifications/get-failed) :success? false}] @@ -497,7 +556,9 @@ :halt? halt?})) :fx [(when (and (not halt?) (seq ids)) [:dispatch [::create-ptv-service + org-id (first ids) + (get-in db [:ptv :services-creation :data (first ids)]) on-single-success on-single-failure]])]}))) @@ -510,6 +571,7 @@ {:batch-size (count ids) :halt? false :size (count ids) + ;; TODO: Is this necessary? Maybe? :data (utils/index-by :source-id ms) :ids (set ids)}) :fx [[:dispatch [::create-all-ptv-services* org-id ids]]]}))) @@ -523,63 +585,68 @@ (fn [db _] (let [org-id (get-in db [:ptv :selected-org :id]) types (get-in db [:sports-sites :types]) - services (->> (get-in db [:ptv :org org-id :data :services]) - vals - (utils/index-by :sourceId))] - (-> db - (update-in [:ptv :org org-id :data :sports-sites] - (fn [m] - (reduce-kv - (fn [m lipas-id sports-site] - (let [sub-cat-id (-> sports-site :type :type-code types :sub-category) - source-id (str "lipas-" org-id "-" sub-cat-id)] - (if-let [service (get services source-id)] - (update-in m [lipas-id :ptv :service-ids] #(set (conj % (:id service)))) - m))) - m - m))))))) + source-id->service (->> (get-in db [:ptv :org org-id :data :services]) + vals + (utils/index-by :sourceId)) + sports-sites (get-in db [:ptv :org org-id :data :sports-sites]) + sports-sites (reduce-kv + (fn [sports-sites lipas-id sports-site] + (let [service-ids (ptv-data/sports-site->service-ids types source-id->service sports-site)] + (if (seq service-ids) + (update-in sports-sites [lipas-id :ptv :service-ids] (fnil into #{}) service-ids) + sports-sites))) + sports-sites + sports-sites)] + (assoc-in db [:ptv :org org-id :data :sports-sites] sports-sites)))) ;;; Create service locations in PTV ;;; (rf/reg-event-fx ::create-ptv-service-location (fn [{:keys [db]} [_ lipas-id success-fx failure-fx]] (let [token (-> db :user :login :token) + ;; Or per site? org-id (-> db :ptv :selected-org :id) - site (get-in db [:ptv :org org-id :data :sports-sites lipas-id]) - data (get-in db [:ptv :service-locations-creation :data lipas-id]) - ks [:languages - :summary - :description - :last-sync - :org-id - :sync-enabled - :service-integration - :descriptions-integration - :service-channel-integration - :service-ids - :service-channel-ids]] + + types (get-in db [:sports-sites :types]) + source-id->service (->> (get-in db [:ptv :org org-id :data :services]) + vals + (utils/index-by :sourceId)) + + sports-site (get-in db [:ptv :org org-id :data :sports-sites lipas-id]) + + ;; Add default org-id for service-ids linking + sports-site (update sports-site :ptv #(merge {:org-id org-id} %)) + + service-ids (ptv-data/sports-site->service-ids types source-id->service sports-site) + + ;; Add other defaults and merge with summary/description from the UI + ptv-data (merge (:default-settings (:ptv db)) + {:service-ids service-ids + :service-channel-ids []} + ;; {:org-id org-id} + (:ptv sports-site))] {:db (assoc-in db [:ptv :loading-from-lipas :service-locations] true) :fx [[:http-xhrio {:method :post :headers {:Authorization (str "Token " token)} :uri (str (:backend-url db) "/actions/save-ptv-service-location") - :params {:sports-site site - :ptv-meta data - :org (org-id->params org-id)} + :params {:lipas-id lipas-id + :org-id org-id + :ptv ptv-data} :format (ajax/transit-request-format) :response-format (ajax/transit-response-format) :on-success [::create-ptv-service-location-success lipas-id success-fx] :on-failure [::create-ptv-service-location-failure lipas-id failure-fx]}]]}))) (rf/reg-event-fx ::create-ptv-service-location-success - (fn [{:keys [db]} [_ lipas-id extra-fx resp]] + (fn [{:keys [db]} [_ lipas-id extra-fx {:keys [ptv-resp ptv]}]] (let [org-id (get-in db [:ptv :selected-org :id])] {:db (-> db (assoc-in [:ptv :loading-from-lipas :service-channels] false) - (assoc-in [:ptv :org org-id :data :service-channels (:id resp)] resp) - (update-in [:ptv :org org-id :data :sports-sites lipas-id] - update-in [:ptv :service-channel-ids] (fn [ids] - (set (conj ids (:id resp)))))) + (assoc-in [:ptv :org org-id :data :service-channels (:id ptv-resp)] ptv-resp) + ;; Update the lipas TS also, it will be the same TS as PTV last-sync now + (assoc-in [:ptv :org org-id :data :sports-sites lipas-id :event-date] (:last-sync ptv)) + (assoc-in [:ptv :org org-id :data :sports-sites lipas-id :ptv] ptv)) :fx extra-fx}))) (rf/reg-event-fx ::create-ptv-service-location-failure @@ -629,8 +696,7 @@ {:db (update-in db [:ptv :service-locations-creation] merge - {:data (utils/index-by :lipas-id to-sync) - :batch-size (count ids) + {:batch-size (count ids) :halt? false :size (count ids) :ids (set ids)}) @@ -644,31 +710,32 @@ (rf/reg-event-fx ::save-ptv-meta (fn [{:keys [db]} [_ sports-sites]] - (let [token (-> db :user :login :token) - ks [:languages - :summary - :description - :last-sync - :org-id - :sync-enabled - :service-integration - :descriptions-integration - :service-channel-integration - :service-ids - :service-channel-ids]] - {:db (assoc-in db [:ptv :save-in-progress] true) - :fx [[:http-xhrio - {:method :post - :headers {:Authorization (str "Token " token)} - :uri (str (:backend-url db) "/actions/save-ptv-meta") - :params (reduce (fn [m site] - (assoc m (:lipas-id site) (select-keys site ks))) - {} - sports-sites) - :format (ajax/transit-request-format) - :response-format (ajax/transit-response-format) - :on-success [::save-ptv-meta-success] - :on-failure [::save-ptv-meta-failure]}]]}))) + (when (seq sports-sites) + (let [token (-> db :user :login :token) + ks [:languages + :summary + :description + :last-sync + :org-id + :sync-enabled + :service-integration + :descriptions-integration + :service-channel-integration + :service-ids + :service-channel-ids]] + {:db (assoc-in db [:ptv :save-in-progress] true) + :fx [[:http-xhrio + {:method :post + :headers {:Authorization (str "Token " token)} + :uri (str (:backend-url db) "/actions/save-ptv-meta") + :params (reduce (fn [m site] + (assoc m (:lipas-id site) (select-keys site ks))) + {} + sports-sites) + :format (ajax/transit-request-format) + :response-format (ajax/transit-response-format) + :on-success [::save-ptv-meta-success] + :on-failure [::save-ptv-meta-failure]}]]})))) (rf/reg-event-fx ::save-ptv-meta-success (fn [{:keys [db]} _] diff --git a/webapp/src/cljs/lipas/ui/ptv/site_view.cljs b/webapp/src/cljs/lipas/ui/ptv/site_view.cljs new file mode 100644 index 000000000..a08d527d8 --- /dev/null +++ b/webapp/src/cljs/lipas/ui/ptv/site_view.cljs @@ -0,0 +1,323 @@ +(ns lipas.ui.ptv.site-view + (:require ["@mui/material/Alert$default" :as Alert] + ["@mui/material/Button$default" :as Button] + ["@mui/material/CircularProgress$default" :as CircularProgress] + ["@mui/material/FormControl$default" :as FormControl] + ["@mui/material/FormControlLabel$default" :as FormControlLabel] + ["@mui/material/FormLabel$default" :as FormLabel] + ["@mui/material/Paper$default" :as Paper] + ["@mui/material/Stack$default" :as Stack] + ["@mui/material/Switch$default" :as Switch] + ["@mui/material/Typography$default" :as Typography] + [lipas.data.ptv :as ptv-data] + [lipas.data.types :as types] + [lipas.ui.components :as lui] + [lipas.ui.components.autocompletes :refer [autocomplete2]] + [lipas.ui.ptv.controls :as controls] + [lipas.ui.ptv.events :as events] + [lipas.ui.ptv.subs :as subs] + [lipas.ui.uix.hooks :refer [use-subscribe]] + [lipas.utils :as utils] + [re-frame.core :as rf] + [reagent.core :as r] + [uix.core :as uix :refer [$ defui]])) + +(defui new-service-form [{:keys [org-id tr service data]}] + (let [{:keys [source-id sub-category-id]} service + loading? (use-subscribe [::subs/generating-descriptions?]) + read-only? false + + [selected-tab set-selected-tab] (uix/use-state :fi) + + org-languages (use-subscribe [::subs/org-languages org-id]) + service-candidate-descriptions (use-subscribe [::subs/service-candidate-descriptions org-id]) + {:keys [summary description]} (get service-candidate-descriptions source-id) + + city (use-subscribe [:lipas.ui.sports-sites.subs/city (-> data :location :city :city-code)]) + type-data (use-subscribe [:lipas.ui.sports-sites.subs/type-by-type-code (-> data :type :type-code)]) + overview {:city-name (:name city) + :service-name (:name (get types/sub-categories sub-category-id)) + :sports-facilties [{:type (-> type-data :name :fi)}]}] + ($ Paper + {:sx #js {:p 2}} + + ($ Stack + {:direction "column" + :sx #js {:gap 2}} + ($ Typography + {:variant "h5"} + "Luo " + (:fi (:name (get types/sub-categories sub-category-id)))) + + ($ Stack + {:sx #js {:position "relative"}} + ($ Button + {:disabled (or loading? + read-only?) + :variant "outlined" + :on-click (fn [_e] + (rf/dispatch [::events/generate-service-descriptions org-id source-id overview [] []]))} + (tr :ptv.actions/generate-with-ai)) + (when loading? + ($ CircularProgress + {:size 24 + :sx #js {:position "absolute" + :top "50%" + :left "50%" + :mt "-12px"}}))) + + ($ controls/lang-selector + {:value selected-tab + :on-change set-selected-tab}) + + ;; Summary + (r/as-element + [lui/text-field + {:disabled (or loading? + read-only?) + :multiline true + :variant "outlined" + :on-change (fn [v] + ) + :label "Tiivistelmä" + :value (get summary selected-tab)}]) + + ;; Description + (r/as-element + [lui/text-field + {:disabled (or loading? + read-only?) + :variant "outlined" + :rows 5 + :multiline true + :on-change (fn [v] + ) + :label "Kuvaus" + :value (get description selected-tab)}]) + + ($ Button + {:variant "contained" + :disabled (or loading? + read-only?) + :on-click (fn [_e] + (let [data {:source-id source-id + :sub-category-id sub-category-id + :summary summary + :description description + :languages (vec org-languages)}] + (rf/dispatch [::events/create-ptv-service org-id source-id data [] []])))} + "Luo Palvelu")) + ))) + +(defui site-view [{:keys [tr lipas-id can-edit? edit-data]}] + (let [[selected-tab set-selected-tab] (uix/use-state :fi) + locale (tr) + + editing* (boolean (use-subscribe [:lipas.ui.sports-sites.subs/editing? lipas-id])) + editing? (and can-edit? editing*) + read-only? (not editing?) + sports-site (use-subscribe [:lipas.ui.sports-sites.subs/latest-rev lipas-id]) + + ;; NOTE: Edit-data and sports-site schema can be a bit different. + ;; Shouldn't matter for the :ptv fields we mostly need here. + site (if editing? + edit-data + sports-site) + + ;; _ (js/console.log edit-data sports-site) + + {:keys [org-id sync-enabled last-sync publishing-status]} (:ptv site) + + ;; _ (js/console.log org-id) + + ;; default-settings {} + ;; enabled (boolean (:ptv site)) + + loading? (use-subscribe [::subs/generating-descriptions?]) + + type-code (-> site :type :type-code) + + type-code-changed? (not= type-code (:previous-type-code (:ptv site))) + previous-sent? (ptv-data/is-sent-to-ptv? site) + ready? (ptv-data/ptv-ready? site) + candidate-now? (ptv-data/ptv-candidate? site) + + types (use-subscribe [:lipas.ui.sports-sites.subs/all-types]) + loading-ptv? (use-subscribe [::subs/loading-from-ptv?]) + services (use-subscribe [::subs/services-by-id org-id]) + missing-services-input [{:service-ids #{} + :sub-category-id (-> site :type :type-code types :sub-category) + :sub-category (-> site :search-meta :type :sub-category :name :fi)}] + missing-services (when (and org-id (not loading-ptv?)) + (ptv-data/resolve-missing-services org-id services missing-services-input)) + + source-id->service (utils/index-by :sourceId (vals services)) + new-service (ptv-data/sub-category-id->service org-id source-id->service (-> site :type :type-code types :sub-category)) + + new-service-sub-cat (get types/sub-categories (-> site :type :type-code types :sub-category)) + + to-archive? (and previous-sent? + (not candidate-now?))] + + (js/console.log missing-services new-service new-service-sub-cat) + + (uix/use-effect (fn [] + (rf/dispatch [::events/fetch-org {:id org-id}]) + (rf/dispatch [::events/fetch-services {:id org-id}])) + [org-id]) + + ($ Stack + {:direction "column" + :sx #js {:gap 2}} + + ;; TODO: Spinneri? + (when loading-ptv? + ($ Alert {:severity "info"} + "Ladataan PTV tietoja...")) + + ; ($ FormControl + ; ($ FormLabel + ; "Lipas tila") + ; ($ Typography + ; status)) + + (when (not (:org-id (:ptv site))) + ($ :<> + ($ Alert {:severity "warning"} + "Valitse organisaatio:"))) + + (let [options (uix/use-memo (fn [] + (->> ptv-data/orgs + (map (fn [{:keys [name id]}] + {:label name + :value id})))) + [])] + ($ autocomplete2 + {:options options + :disabled (or loading? + read-only?) + :label "Organisaatio" + :value (:org-id (:ptv site)) + :on-change (fn [_e v] + (rf/dispatch [:lipas.ui.sports-sites.events/edit-field lipas-id [:ptv :org-id] (:value v)]))})) + + ($ FormControl + ($ FormLabel + "PTV Tila") + (cond + (:error (:ptv site)) + (let [e (:error (:ptv site))] + ($ Alert {:severity "error"} + "Virhe PTV integraatiossa, uusimpia tietoja ei ole viety PTV: " (:message e))) + + (and previous-sent? candidate-now? ready?) + (if sync-enabled + ($ Alert {:severity "success"} "PTV integraatio käytössä") + ($ Alert {:severity "success"} "PTV integraatio käytössä, mutta paikan synkronointi PTV on kytketty pois päältä.")) + + (and previous-sent? (not candidate-now?)) + ($ Alert {:severity "warning"} "Paikka on viety PTV, mutta on muutettu niin että näyttää nyt siltä että sen ei pidä mennä PTV -> PTV palvelu paikka arkistoidaan tallennuksessa.") + + (and candidate-now? ready?) + ($ Alert {:severity "info"} "Paikkaa ei viety PTV, mutta palvelu paikka luodaan tallennuksessa") + + (not ready?) + ($ Alert {:severity "info"} "PTV tiedot ovat vielä puutteelliset, täytä tiedot niin paikka viedään PTV tallennuksen yhteydessä") + + :else + ($ Alert {:severity "warning"} "Paikka näyttää siltä ettei sitä pidä viedä PTV") + + ; ($ Typography + ; publishing-status) + ; ($ Typography + ; last-sync) + )) + + (when candidate-now? + ($ FormControl + ($ FormLabel + "PTV Palvelu") + ($ Typography + (get-in new-service-sub-cat [:name locale])) + (cond + (seq missing-services) + ($ Alert {:severity "warning"} "Liikuntapaikkatyyppiä vaihdettu, uusi PTV Palvelu puuttuu") + + (and previous-sent? type-code-changed?) + ($ Alert {:severity "info"} "Liikuntapaikkatyyppiä vaihdettu, vaihdetaan PTV Palvelu")) + )) + + (when (seq missing-services) + ($ new-service-form + {:data site + :tr tr + :org-id org-id + :service (first missing-services)})) + + ($ FormControlLabel + {:label "Sync-enabled" + :control ($ Switch + {:disabled read-only? + :value sync-enabled + :checked sync-enabled + :on-change (fn [_e v] + (js/console.log _e v) + (rf/dispatch [:lipas.ui.sports-sites.events/edit-field lipas-id [:ptv :sync-enabled] v]))})}) + + ($ Stack + {:sx #js {:position "relative"}} + ($ Button + {:disabled (or loading? + read-only?) + :variant "outlined" + ;; NOTE: Could use the lipas-id version when not editing? But then we don't have + ;; place to store the results. + :on-click (fn [_e] + (rf/dispatch [::events/generate-descriptions-from-data lipas-id]))} + (tr :ptv.actions/generate-with-ai)) + (when loading? + ($ CircularProgress + {:size 24 + :sx #js {:position "absolute" + :top "50%" + :left "50%" + :mt "-12px"}}))) + + ($ controls/lang-selector + {:value selected-tab + :on-change set-selected-tab}) + + ;; Summary + (r/as-element + [lui/text-field + {:disabled (or loading? + read-only?) + :multiline true + :variant "outlined" + :on-change (fn [v] + (rf/dispatch [:lipas.ui.sports-sites.events/edit-field lipas-id [:ptv :summary selected-tab] v])) + :label "Tiivistelmä" + :value (or (get-in edit-data [:ptv :summary selected-tab]) + (get-in sports-site [:ptv :summary selected-tab]))}]) + + ;; Description + (r/as-element + [lui/text-field + {:disabled (or loading? + read-only?) + :variant "outlined" + :rows 5 + :multiline true + :on-change (fn [v] + (rf/dispatch [:lipas.ui.sports-sites.events/edit-field lipas-id [:ptv :description selected-tab] v])) + :label "Kuvaus" + :value (or (get-in edit-data [:ptv :description selected-tab]) + (get-in sports-site [:ptv :description selected-tab]))}]) + + ($ Button + {:disabled (or loading? + read-only?) + :on-click (fn [_e] + (rf/dispatch [::events/translate-to-other-langs lipas-id {:from (name selected-tab) + :to (disj #{"fi" "en" "se"} (name selected-tab))}]))} + "Käännä muille kielille")))) diff --git a/webapp/src/cljs/lipas/ui/ptv/subs.cljs b/webapp/src/cljs/lipas/ui/ptv/subs.cljs index cbef5fbd3..eb7233497 100644 --- a/webapp/src/cljs/lipas/ui/ptv/subs.cljs +++ b/webapp/src/cljs/lipas/ui/ptv/subs.cljs @@ -1,5 +1,6 @@ (ns lipas.ui.ptv.subs (:require [clojure.string :as str] + [lipas.data.ptv :as ptv-data] [lipas.ui.utils :as utils] [re-frame.core :as rf])) @@ -50,23 +51,24 @@ (rf/reg-sub ::org-default-settings :<- [::ptv] - :<- [::selected-org-id] - (fn [[ptv org-id] _] + (fn [ptv [_ org-id]] (get-in ptv [:org org-id :default-settings]))) (rf/reg-sub ::default-settings - :<- [::ptv] - :<- [::org-default-settings] + (fn [[_ org-id]] + [(rf/subscribe [::ptv]) + (rf/subscribe [::org-default-settings org-id])]) (fn [[ptv org-defaults] _] (merge (:default-settings ptv) org-defaults))) (def lang - {"fi" "fi" "sv" "se" "en" "en"}) + {"fi" "fi" + "sv" "se" + "en" "en"}) (rf/reg-sub ::org-languages :<- [::ptv] - :<- [::selected-org-id] - (fn [[ptv org-id] _] + (fn [ptv [_ org-id]] ;; There are (undocumented) business rules regarding in which lang ;; data can be entered. One of the rules seems to be that the org ;; must be described with all the desired languages before other @@ -74,12 +76,12 @@ ;; 'supported languages' so we infer them from org name ;; translations. wtf (->> (get-in ptv [:org org-id :data :org org-id :organizationNames]) - (keep #(get lang (:language %)))))) + (keep #(get lang (:language %))) + vec))) (rf/reg-sub ::selected-org-data :<- [::ptv] - :<- [::selected-org-id] - (fn [[ptv org-id] _] + (fn [ptv [_ org-id]] (when org-id (let [{:keys [services _service-channels]} (get-in ptv [:org org-id :data])] (for [s services @@ -101,15 +103,15 @@ (rf/reg-sub ::services-by-id :<- [::ptv] - :<- [::selected-org-id] - (fn [[ptv org-id] _] + (fn [ptv [_ org-id]] (when org-id (get-in ptv [:org org-id :data :services])))) (def langs {"sv" "se" "fi" "fi" "en" "en"}) (rf/reg-sub ::services - :<- [::services-by-id] + (fn [[_ org-id]] + (rf/subscribe [::services-by-id org-id])) (fn [services _] (for [[id service] services] (let [service-name (->> service :serviceNames (some #(when (= "fi" (:language %)) @@ -142,49 +144,32 @@ (:services-filter ptv))) (rf/reg-sub ::services-filtered - :<- [::services] - :<- [::services-filter] + (fn [[_ org-id]] + [(rf/subscribe [::services org-id]) + (rf/subscribe [::services-filter])]) (fn [[services filter'] _] (sort-by :label (case filter' "lipas-managed" (filter (fn [m] (some-> m :source-id (str/starts-with? "lipas-"))) services) services)))) -(defn ->source-id - [org-id sub-category-id] - (str "lipas-" org-id "-" sub-category-id)) - -(defn resolve-missing-services - "Infer services (sub-categories) that need to be created in PTV and - attached to sports-sites." - [org-id services types sports-sites] - (let [source-ids (->> services vals (keep :sourceId) set)] - (->> sports-sites - (filter (fn [{:keys [ptv]}] (empty? (:service-ids ptv)))) - (map (fn [site] {:source-id (->source-id org-id (:sub-category-id site)) - :sub-category (-> site :sub-category) - :sub-category-id (-> site :sub-category-id)})) - distinct - (remove (fn [m] (contains? source-ids (:source-id m))))))) - (rf/reg-sub ::missing-services - :<- [::selected-org-id] - :<- [::services-by-id] - :<- [::sports-sites] - :<- [:lipas.ui.sports-sites.subs/all-types] - (fn [[org-id services sports-sites types] _] - (resolve-missing-services org-id services types sports-sites))) + (fn [[_ org-id]] + [(rf/subscribe [::services-by-id org-id]) + (rf/subscribe [::sports-sites org-id])]) + (fn [[services sports-sites] [_ org-id]] + (ptv-data/resolve-missing-services org-id services sports-sites))) (rf/reg-sub ::service-candidate-descriptions - :<- [::selected-org-id] :<- [::ptv] - (fn [[org-id ptv] _] + (fn [ptv [_ org-id]] (get-in ptv [:org org-id :data :service-candidates]))) (rf/reg-sub ::service-candidates - :<- [::missing-services] - :<- [::service-candidate-descriptions] - :<- [::org-languages] + (fn [[_ org-id]] + [(rf/subscribe [::missing-services org-id]) + (rf/subscribe [::service-candidate-descriptions org-id]) + (rf/subscribe [::org-languages org-id])]) (fn [[missing-services descriptions org-langs] _] (->> missing-services (map (fn [{:keys [source-id] :as m}] @@ -201,131 +186,52 @@ (rf/reg-sub ::service-channels-by-id :<- [::ptv] - :<- [::selected-org-id] - (fn [[ptv org-id] _] + (fn [ptv [_ org-id]] (when org-id (get-in ptv [:org org-id :data :service-channels])))) (rf/reg-sub ::service-channels - :<- [::service-channels-by-id] + (fn [[_ org-id]] + (rf/subscribe [::service-channels-by-id org-id])) (fn [channels _] (vals channels))) -(defn resolve-service-channel-name - "Sometimes these seem to have the name under undocumented :name - property and sometimes under documented :serviceChannelNames - array. Wtf." - [service-channel] - (or (:name service-channel) - (some (fn [m] - (when (= "fi" (:language m)) - (:value m))) - (:serviceChannelNames service-channel)))) - (rf/reg-sub ::service-channels-list - :<- [::service-channels] - (fn [channels _] + (fn [[_ org-id]] + (rf/subscribe [::service-channels org-id])) + (fn [channels [_ _org-id]] (for [m channels] {:service-channel-id (:id m) - :name (resolve-service-channel-name m)}))) - -(defn parse-summary - "Returns first line-delimited paragraph." - [s] - (when (string? s) - (first (str/split s #"\r?\n")))) - -(defn detect-name-conflict - [sports-site service-channels] - (let [s1 (some-> sports-site :name str/trim str/lower-case) - attached-channels (-> sports-site :ptv :service-channel-ids set)] - (some (fn [service-channel] - (let [ssname (resolve-service-channel-name service-channel) - s2 (some-> ssname str/trim str/lower-case)] - (when (and - (not (contains? attached-channels (:id service-channel))) - (= s1 s2)) - {:service-channel-id (:id service-channel)}))) - service-channels))) + :name (ptv-data/resolve-service-channel-name m)}))) (rf/reg-sub ::sports-sites - :<- [::ptv] - :<- [::selected-org-id] - :<- [::services-by-id] - :<- [::service-channels-by-id] - :<- [::default-settings] - :<- [:lipas.ui.subs/translator] - :<- [:lipas.ui.sports-sites.subs/all-types] - :<- [::org-languages] - (fn [[ptv org-id services service-channels org-defaults tr types org-langs] _] + (fn [[_ org-id]] + [(rf/subscribe [::ptv]) + (rf/subscribe [::services-by-id org-id]) + (rf/subscribe [::service-channels-by-id org-id]) + (rf/subscribe [::default-settings org-id]) + (rf/subscribe [:lipas.ui.sports-sites.subs/all-types]) + (rf/subscribe [::org-languages org-id])]) + (fn [[ptv services service-channels org-defaults types org-langs] [_ org-id]] (let [lipas-id->site (get-in ptv [:org org-id :data :sports-sites])] (for [site (vals lipas-id->site)] - (let [service-id (-> site :ptv :service-ids first) - service-channel-id (-> site :ptv :service-channel-ids first) - descriptions-integration (or (-> site :ptv :descriptions-integration) - (:descriptions-integration org-defaults)) - - summary (case descriptions-integration - "lipas-managed-comment-field" - (-> site :comment parse-summary) - - "lipas-managed-ptv-fields" - (-> site :ptv :summary) - - "ptv-managed" - (tr :ptv.integration.description/ptv-managed-helper)) - description (case descriptions-integration - "lipas-managed-comment-field" - (-> site :comment) - - "lipas-managed-ptv-fields" - (-> site :ptv :description) - - "ptv-managed" - (tr :ptv.integration.description/ptv-managed-helper))] - {:valid (boolean (and (some-> description :fi count (> 5)) - (some-> summary :fi count (> 5)))) - :lipas-id (:lipas-id site) - :name (:name site) - :name-conflict (detect-name-conflict site (vals service-channels)) - :marketing-name (:marketing-name site) - :type (-> site :search-meta :type :name :fi) - :sub-category (-> site :search-meta :type :sub-category :name :fi) - :sub-category-id (-> site :type :type-code types :sub-category) - :org-id org-id - :admin (-> site :search-meta :admin :name :fi) - :owner (-> site :search-meta :owner :name :fi) - :summary summary - :description description - :languages (or (-> site :ptv :languages) org-langs) - - :descriptions-integration descriptions-integration - :sync-enabled (get-in site [:ptv :sync-enabled] true) - :last-sync (-> site :ptv :last-sync) - :last-sync-human (or (some-> site - :ptv - :last-sync - utils/->human-date-time-at-user-tz) - "Ei koskaan") - :service-ids (-> site :ptv :service-ids) - :service-name (-> services (get service-id) :serviceNames - (->> (some #(when (= "fi" (:language %)) (:value %))))) - :service-integration (or (-> site :ptv :service-integration) - (:service-integration org-defaults)) - :service-channel-id service-channel-id - :service-channel-ids (-> site :ptv :service-channel-ids) - :service-channel-name (-> (get service-channels service-channel-id) - (resolve-service-channel-name)) - :service-channel-integration (or (-> site :ptv :service-channel-integration) - (:service-channel-integration org-defaults))}))))) + (ptv-data/sports-site->ptv-input {:org-id org-id + :types types + :org-defaults org-defaults + :org-langs org-langs} + service-channels + services + site))))) (rf/reg-sub ::sports-sites-count - :<- [::sports-sites] + (fn [[_ org-id]] + (rf/subscribe [::sports-sites org-id])) (fn [sports-sites _] (count sports-sites))) (rf/reg-sub ::sync-all-enabled? - :<- [::sports-sites] + (fn [[_ org-id]] + (rf/subscribe [::sports-sites org-id])) (fn [sports-sites _] (every? true? (map :sync-enabled sports-sites)))) @@ -350,7 +256,8 @@ 100)}))) (rf/reg-sub ::sports-site-setup-done - :<- [::sports-sites] + (fn [[_ org-id]] + (rf/subscribe [::sports-sites org-id])) (fn [ms _] (every? (fn [{:keys [last-sync sync-enabled] :as _m}] (or (utils/iso-date-time-string? (or last-sync "")) @@ -363,8 +270,9 @@ (:sports-sites-filter m))) (rf/reg-sub ::sports-sites-filtered - :<- [::sports-sites] - :<- [::sports-sites-filter] + (fn [[_ org-id]] + [(rf/subscribe [::sports-sites org-id]) + (rf/subscribe [::sports-sites-filter])]) (fn [[sites filter*] _] (case filter* "all" @@ -385,7 +293,8 @@ sites)))) (rf/reg-sub ::sports-sites-filtered-count - :<- [::sports-sites-filtered] + (fn [[_ org-id]] + (rf/subscribe [::sports-sites-filtered org-id])) (fn [sports-sites _] (count sports-sites))) diff --git a/webapp/src/cljs/lipas/ui/ptv/views.cljs b/webapp/src/cljs/lipas/ui/ptv/views.cljs index 931496d23..0c559cebb 100644 --- a/webapp/src/cljs/lipas/ui/ptv/views.cljs +++ b/webapp/src/cljs/lipas/ui/ptv/views.cljs @@ -1,13 +1,28 @@ (ns lipas.ui.ptv.views - (:require ["@mui/material/Paper$default" :as Paper] + (:require ["@mui/icons-material/Sync$default" :as Sync] + ["@mui/icons-material/SyncDisabled$default" :as SyncDisabled] + ["@mui/icons-material/SyncProblem$default" :as SyncProblem] + ["@mui/material/Accordion$default" :as Accordion] + ["@mui/material/AccordionDetails$default" :as AccordionDetails] + ["@mui/material/AccordionSummary$default" :as AccordionSummary] + ["@mui/material/Avatar$default" :as Avatar] + ["@mui/material/Icon$default" :as Icon] + ["@mui/material/Paper$default" :as Paper] + ["@mui/material/Stack$default" :as Stack] + ["@mui/material/Typography$default" :as Typography] [goog.string.format] [lipas.data.ptv :as ptv-data] [lipas.ui.components :as lui] + [lipas.ui.components.autocompletes :refer [autocomplete2]] [lipas.ui.mui :as mui] + [lipas.ui.ptv.controls :as controls] [lipas.ui.ptv.events :as events] [lipas.ui.ptv.subs :as subs] + [lipas.ui.uix.hooks :refer [use-subscribe]] [lipas.ui.utils :refer [<== ==>]] - [reagent.core :as r])) + [re-frame.core :as rf] + [reagent.core :as r] + [uix.core :as uix :refer [$ defui]])) ;; Memo ;; - preset service structure with descriptions @@ -17,12 +32,6 @@ ;; - ...anyway, somehow re-using stuff that's already there ;; - auto-sync on save -(def orgs - [{:name "Utajärven kunta (test)" - :id ptv-data/uta-org-id-test} - #_{:name "Utajärven kunta (prod)" - :id ptv-data/uta-org-id-prod}]) - (defn lang-selector [{:keys [value on-change opts]}] (let [opts (set opts)] @@ -39,181 +48,36 @@ [{:keys [label]}] (let [selected-org (<== [::subs/selected-org])] [lui/select - {:items orgs + {:items ptv-data/orgs :label label :label-fn :name :value-fn identity :value selected-org :on-change #(==> [::events/select-org %])}])) -(defn services-selector - [{:keys [value on-change label value-fn] - :or {value-fn identity - label ""}}] - (let [items (<== [::subs/services])] - [lui/autocomplete - {:items items - :multi? true - :label label - :label-fn :label - :value-fn value-fn - :value value - :on-change on-change}])) - -(defn service-channel-selector - [{:keys [value on-change label value-fn] +(defui service-channel-selector + [{:keys [org-id value on-change label value-fn] :or {value-fn identity label ""}}] - (let [items (<== [::subs/service-channels-list])] - [lui/autocomplete - {:items items - :multi? false - :label label - :label-fn :name - :value-fn value-fn - :value (first value) - :on-change (fn [v] - (println v) - (on-change [v]))}])) - -(defn info-text - [s] - #_[mui/paper {:style {:padding "1em" :background-color mui/gray3}}] - [mui/typography {:variant "body1" #_#_:style {:font-size "0.9rem"}} s]) - -(defn settings - [] - (let [tr (<== [:lipas.ui.subs/translator]) - default-settings (<== [::subs/default-settings])] - [mui/grid {:container true :spacing 4 :style {:margin-left "-32px"}} - - [mui/grid {:item true :xs 12} - [mui/stack {:spacing 2} - - [mui/typography {:variant "h5"} - (tr :ptv.integration.interval/headline)] - - [mui/form-control - [mui/form-label (tr :ptv.integration.interval/label)] - [mui/radio-group - {:on-change #(==> [::events/select-integration-interval %2]) - :value (:integration-interval default-settings)} - [mui/form-control-label - {:value "immediate" - :label (tr :ptv.integration.interval/immediate) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "daily" - :label (tr :ptv.integration.interval/daily) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration.interval/manual) - :control (r/as-element [mui/radio])}]]]]] - - [mui/grid {:item true :xs 12} - [mui/typography {:variant "h5"} (tr :ptv.integration.default-settings/headline)]] - - [mui/grid {:item true :xs 12} - [info-text (tr :ptv.integration.default-settings/helper)]] - - ;; Service - [mui/grid {:item true :xs 12 :lg 4} - [mui/stack {:spacing 2} - [mui/typography {:variant "h6"} - (tr :ptv/services)] - - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-service-integration-default %2]) - :value (:service-integration default-settings)} - [mui/form-control-label - {:value "lipas-managed" - :label (tr :ptv.integration.service/lipas-managed) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration/manual) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed" (:service-integration default-settings)) - [info-text (tr :ptv.integration.service/lipas-managed-helper)]) - - (when (= "manual" (:service-integration default-settings)) - [info-text (tr :ptv.integration.service/manual-helper)])]] - - ;; Service channel - [mui/grid {:item true :xs 12 :lg 4} - [mui/stack {:spacing 2} - [mui/typography {:variant "h6"} - (tr :ptv/service-channels)] - - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-service-channel-integration-default %2]) - :value (:service-channel-integration default-settings)} - [mui/form-control-label - {:value "lipas-managed" - :label (tr :ptv.integration.service-channel/lipas-managed) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration/manual) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed" (:service-channel-integration default-settings)) - [info-text (tr :ptv.integration.service-channel/lipas-managed-helper)]) - - (when (= "manual" (:service-channel-integration default-settings)) - [info-text (tr :ptv.integration.service-channel/manual-helper)])]] - - ;; Descriptions - [mui/grid {:item true :xs 12 :lg 4} - [mui/stack {:spacing 2} - [mui/typography {:variant "h6"} - (tr :ptv/descriptions)] - - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-descriptions-integration-default %2]) - :value (:descriptions-integration default-settings)} - [mui/form-control-label - {:value "lipas-managed-ptv-fields" - :label (tr :ptv.integration.description/lipas-managed-ptv-fields) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "lipas-managed-comment-field" - :label (tr :ptv.integration.description/lipas-managed-comment-field) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "ptv-managed" - :label (tr :ptv.integration.description/ptv-managed) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed-ptv-fields" (:descriptions-integration default-settings)) - [info-text (tr :ptv.integration.description/lipas-managed-ptv-fields-helper)]) - - (when (= "lipas-managed-comment-field" (:descriptions-integration default-settings)) - [info-text (tr :ptv.integration.description/lipas-managed-comment-field-helper)]) - - (when (= "ptv-managed" (:descriptions-integration default-settings)) - [info-text (tr :ptv.integration.description/ptv-managed-helper)])]]])) + (let [items (use-subscribe [::subs/service-channels-list org-id]) + options (uix/use-memo (fn [] + (map (fn [x] + {:value (value-fn x) + :label (:name x)}) + items)) + [items value-fn])] + ($ autocomplete2 + {:options options + :multiple false + :label label + :value (first value) + :on-change (fn [_e v] + (println v) + (on-change [(:value v)]))}))) (defn form - [{:keys [tr site]}] - (let [locale (tr)] + [{:keys [org-id tr site]}] + (let [services @(rf/subscribe [::subs/services org-id])] [mui/grid {:container true :spacing 2 @@ -225,31 +89,12 @@ [mui/typography {:variant "h6"} (tr :ptv/services)] - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-service-integration site %2]) - :value (:service-integration site)} - [mui/form-control-label - {:value "lipas-managed" - :label (tr :ptv.integration.service/lipas-managed) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration/manual) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed" (:service-integration site)) - (tr :ptv.integration.service/lipas-managed-helper)) - - (when (= "manual" (:service-integration site)) - [services-selector - {:value (:service-ids site) - :on-change #(==> [::events/select-services site %]) + ($ controls/services-selector + {:options services + :value (:service-ids site) + :on-change (fn [ids] (rf/dispatch [::events/select-services site ids])) :value-fn :service-id - :label (tr :ptv.actions/select-service)}])]] + :label (tr :ptv.actions/select-service)})]] ;; Service channels [mui/grid {:item true :xs 12 :lg 4} @@ -257,76 +102,24 @@ [mui/typography {:variant "h6"} (tr :ptv/service-channels)] - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-service-channel-integration site %2]) - :value (:service-channel-integration site)} - [mui/form-control-label - {:value "lipas-managed" - :label (tr :ptv.integration.service-channel/lipas-managed) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration/manual) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed" (:service-channel-integration site)) - (tr :ptv.integration.service-channel/lipas-managed-helper)) - - (when (= "manual" (:service-channel-integration site)) - [service-channel-selector - {:value (:service-channel-ids site) - :value-fn :id + ($ service-channel-selector + {:org-id org-id + :value (:service-channel-ids site) + :value-fn :service-channel-id :on-change #(==> [::events/select-service-channels site %]) - :label (tr :ptv.actions/select-service-channel)}])]] + :label (tr :ptv.actions/select-service-channel)})]] ;; Descriptions (r/with-let [selected-tab (r/atom :fi)] (let [loading? (<== [::subs/generating-descriptions?])] [mui/grid {:item true :xs 12 :lg 4} [mui/stack {:spacing 2} - [mui/typography {:variant "h6"} (tr :ptv/descriptions)] - ;; Integration type - [mui/form-control - [mui/form-label (tr :ptv.actions/select-integration)] - [mui/radio-group - {:on-change #(==> [::events/select-descriptions-integration site %2]) - :value (:descriptions-integration site)} - - [mui/form-control-label - {:value "lipas-managed-ptv-fields" - :label (tr :ptv.integration.description/lipas-managed-ptv-fields) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "lipas-managed-comment-field" - :label (tr :ptv.integration.description/lipas-managed-comment-field) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "ptv-managed" - :label (tr :ptv.integration.description/ptv-managed) - :control (r/as-element [mui/radio])}]]] - - (when (= "lipas-managed-ptv-fields" (:descriptions-integration site)) - (tr :ptv.integration.description/lipas-managed-ptv-fields-helper)) - - (when (= "lipas-managed-comment-field" (:descriptions-integration site)) - (tr :ptv.integration.description/lipas-managed-comment-field-helper)) - - (when (= "ptv-managed" (:descriptions-integration site)) - (tr :ptv.integration.description/ptv-managed-helper)) - - (when (= "lipas-managed-ptv-fields" (:descriptions-integration site)) - [mui/button {:disabled loading? - :on-click #(==> [::events/generate-descriptions (:lipas-id site)])} - (tr :ptv.actions/generate-with-ai)]) + [mui/button {:disabled loading? + :on-click #(==> [::events/generate-descriptions (:lipas-id site) [] []])} + (tr :ptv.actions/generate-with-ai)] (when loading? [mui/circular-progress]) @@ -342,7 +135,6 @@ [lui/text-field {:disabled loading? :multiline true - :read-only? (not= "manual" (:descriptions-integration site)) :variant "outlined" :on-change #(==> [::events/set-summary site @selected-tab %]) :label "Tiivistelmä" @@ -352,20 +144,27 @@ [lui/text-field {:disabled loading? :variant "outlined" - :read-only? (not= "manual" (:descriptions-integration site)) :rows 5 :multiline true :on-change #(==> [::events/set-description site @selected-tab %]) :label "Kuvaus" - :value (get-in site [:description @selected-tab])}]]]))])) + :value (get-in site [:description @selected-tab])}] + + [mui/button {:disabled loading? + :on-click #(==> [::events/create-ptv-service-location (:lipas-id site) [] []])} + "Vie PTV"] + + ]]))])) (defn table [] (r/with-let [expanded-rows (r/atom {})] (let [tr (<== [:lipas.ui.subs/translator]) - sites (<== [::subs/sports-sites]) - sync-all-enabled? (<== [::subs/sync-all-enabled?]) + org-id (<== [::subs/selected-org-id]) + sites (<== [::subs/sports-sites org-id]) + sync-all-enabled? (<== [::subs/sync-all-enabled? org-id]) headers [{:key :expand :label "" :padding "checkbox"} + #_ {:key :selected :label (tr :ptv.actions/export) :padding "checkbox" :action-component @@ -373,6 +172,7 @@ {:value sync-all-enabled? :on-change #(==> [::events/toggle-sync-all %2])}]} #_{:key :auto-sync :label "Vie automaattisesti"} + {:key :event-data :label "Tila"} {:key :last-sync :label "Viety viimeksi"} {:key :name :label (tr :general/name)} {:key :type :label (tr :general/type)} @@ -399,59 +199,82 @@ ;; Body [mui/table-body (doall - (for [{:keys [lipas-id] :as site} (sort-by :type sites)] + (for [{:keys [lipas-id sync-status] :as site} (sort-by :type sites)] [:<> {:key lipas-id} - ;; Summary row - [mui/table-row #_{:on-click (fn [] (swap! expanded-rows update lipas-id not))} + [mui/table-row + {:sx [{} + ]} ;; Expand toggle - [mui/table-cell - [mui/icon-button - {:style {:zIndex 1} - :size "small" - :on-click (fn [] (swap! expanded-rows update lipas-id not))} - [mui/icon - (if (get @expanded-rows lipas-id false) - "keyboard_arrow_up_icon" - "keyboard_arrow_down_icon")]]] + [mui/table-cell + [mui/icon-button + {:style {:zIndex 1} + :size "small" + :on-click (fn [] (swap! expanded-rows update lipas-id not))} + [mui/icon + (if (get @expanded-rows lipas-id false) + "keyboard_arrow_up_icon" + "keyboard_arrow_down_icon")]]] ;; Enable sync - [mui/table-cell - [lui/switch - {:value (:sync-enabled site) - :on-change #(==> [::events/toggle-sync-enabled site %])}]] + #_ + [mui/table-cell + [lui/switch + {:value (:sync-enabled site) + :on-change #(==> [::events/toggle-sync-enabled site %])}]] + + [mui/table-cell + ($ Stack + {:direction "row" + :alignItems "center"} + ($ Avatar + {:sx #js {:bgcolor (if (:sync-enabled site) + (case sync-status + :ok "success.main" + :not-synced "error.main" + :out-of-date "warning.main") + mui/gray3) + :mr 2}} + (if (:sync-enabled site) + (if (= :ok sync-status) + ($ Sync {:color "white"}) + ($ SyncProblem + {:color "white"})) + ($ SyncDisabled {:background "white"}))) + #_ + (:event-date-human site))] ;; Last-sync - [mui/table-cell - (:last-sync-human site)] + [mui/table-cell + (:last-sync-human site)] ;; Name - [mui/table-cell - (:name site)] + [mui/table-cell + (:name site)] ;; Type - [mui/table-cell - (:type site)] + [mui/table-cell + (:type site)] ;; Admin ;;[mui/table-cell] ;; Owner - [mui/table-cell - (:owner site)] + [mui/table-cell + (:owner site)] ;; Service - #_[mui/table-cell - [services-selector]] + #_[mui/table-cell + [services-selector]] ;; Service channell - #_[mui/table-cell - #_[service-channel-selector]] + #_[mui/table-cell + #_[service-channel-selector]] ;; Description - #_[mui/table-cell]] + #_[mui/table-cell]] ;; Details row [mui/table-row @@ -461,141 +284,23 @@ [mui/collapse {:in (get @expanded-rows lipas-id false) :timeout "auto" :unmountOnExit true} - [form {:tr tr :site site}]]]]]))]]])))) - -(defn descriptions-generator - [] - (let [tr (<== [:lipas.ui.subs/translator]) - sports-sites (<== [::subs/sports-sites-filtered]) - sports-sites-count (<== [::subs/sports-sites-filtered-count]) - sports-sites-filter (<== [::subs/sports-sites-filter]) - - {:keys [in-progress? - processed-lipas-ids - processed-count - total-count - processed-percent - halt?] :as m} (<== [::subs/batch-descriptions-generation-progress])] - - [lui/expansion-panel {:default-expanded false :label (tr :ptv.tools.ai/headline)} - [mui/grid {:container true :spacing 4} - [mui/grid {:item true :xs 12 :lg 4} - - ;; Settings - [mui/stack {:spacing 4} - [mui/typography (tr :ptv.tools.ai/start-helper)] - - [mui/form-control - [mui/form-label (tr :ptv.tools.ai.sports-sites-filter/label)] - [mui/radio-group - {:on-change #(==> [::events/select-sports-sites-filter %2]) - :value sports-sites-filter} - [mui/form-control-label - {:value "all" - :label (tr :ptv.tools.ai.sports-sites-filter/all) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "no-existing-description" - :label (tr :ptv.tools.ai.sports-sites-filter/no-existing-description) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "sync-enabled" - :label (tr :ptv.tools.ai.sports-sites-filter/sync-enabled) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "sync-enabled-no-existing-description" - :label (tr :ptv.tools.ai.sports-sites-filter/sync-enabled-no-existing-description) - :control (r/as-element [mui/radio])}] - - #_[mui/form-control-label - {:value "manual" - :label (tr :ptv.tools.ai.sports-sites-filter/manual) - :control (r/as-element [mui/radio])}]]] - - ;; Start button - [mui/button - {:variant "outlined" - :disabled in-progress? - :color "secondary" - :startIcon (r/as-element [mui/icon "play_arrow"]) - :on-click #(==> [::events/generate-all-descriptions sports-sites])} - (tr :ptv.tools.ai/start)] - - ;; Cancel button - (when in-progress? - [mui/button - {:variant "outlined" - :disabled halt? - :color "secondary" - :startIcon (r/as-element [mui/icon "cancel"]) - :on-click #(==> [::events/halt-descriptions-generation])} - (tr :actions/cancel)]) - - (when (and halt? in-progress?) - [mui/typography (tr :ptv.tools.ai/canceling)]) - - (when in-progress? - [mui/stack {:direction "row" :spacing 2 :align-items "center"} - [mui/circular-progress {:variant "indeterminate" :value processed-percent}] - [mui/typography (str processed-count "/" total-count)]])]] - - ;; Results - (r/with-let [selected-tab (r/atom :fi)] - [mui/grid {:item true :xs 12 :lg 8} - [mui/stack {:spacing 4} - - [mui/typography {:variant "h6"} - (tr :ptv/sports-sites)] - - [mui/typography {:variant "subtitle1" :style {:margin-top "0px"}} - (str sports-sites-count " kpl")] - - (doall - (for [{:keys [lipas-id] :as site} sports-sites] - ^{:key lipas-id} - [lui/expansion-panel - {:label (:name site) - :disabled (not (contains? processed-lipas-ids lipas-id))} - [mui/stack {:spacing 2} - [mui/tabs - {:value @selected-tab - :on-change #(reset! selected-tab (keyword %2))} - [mui/tab {:value "fi" :label "FI"}] - [mui/tab {:value "se" :label "SE"}] - [mui/tab {:value "en" :label "EN"}]] - - ;; Summary - [lui/text-field - {:multiline true - :read-only? (not= "manual" (:descriptions-integration site)) - :variant "outlined" - :on-change #(==> [::events/set-summary site @selected-tab %]) - :label (tr :ptv/summary) - :value (get-in site [:summary @selected-tab])}] - - ;; Description - [lui/text-field - {:variant "outlined" - :read-only? (not= "manual" (:descriptions-integration site)) - :rows 5 - :multiline true - :on-change #(==> [::events/set-description site @selected-tab %]) - :label (tr :ptv/description) - :value (get-in site [:description @selected-tab])}]]]))]])]])) + [form {:tr tr + :org-id org-id + :site site}]]]]]))]]])))) (defn create-services [] (r/with-let [selected-tab (r/atom :fi)] (let [tr (<== [:lipas.ui.subs/translator]) - service-candidates (<== [::subs/service-candidates]) + org-id (<== [::subs/selected-org-id]) + service-candidates (<== [::subs/service-candidates org-id]) {:keys [in-progress? halt? processed-percent total-count - processed-count]} (<== [::subs/service-descriptions-generation-progress])] + processed-count]} (<== [::subs/service-descriptions-generation-progress]) + + services @(rf/subscribe [::subs/services org-id])] [lui/expansion-panel {:label (str "1. " (tr :ptv.tools.generate-services/headline)) @@ -670,22 +375,6 @@ [mui/icon {:color "success"} "done"] [mui/icon {:color "disabled"} "done"])} [mui/stack {:spacing 2} - - #_[mui/form-control - [mui/form-label (tr :ptv.integration.interval/label)] - [mui/radio-group - {:on-change #(==> [::events/select-integration-interval %2]) - :value "lipas-managed"} - [mui/form-control-label - {:value "lipas-managed" - :label (tr :ptv.integration.service/lipas-managed) - :control (r/as-element [mui/radio])}] - - [mui/form-control-label - {:value "manual" - :label (tr :ptv.integration/manual) - :control (r/as-element [mui/radio])}]]] - [lui/autocomplete {:label (tr :ptv.actions/select-languages) :multi? true @@ -697,11 +386,12 @@ :label-fn :label :on-change #(==> [::events/set-service-candidate-languages source-id %])}] - [services-selector - {:value (get m :service-ids) - :on-change #(==> [::events/link-candidate-to-existing-service source-id %]) - :value-fn :service-id - :label (tr :ptv/service)}] + ($ controls/services-selector + {:options services + :value (get m :service-ids) + :on-change #(==> [::events/link-candidate-to-existing-service source-id %]) + :value-fn :service-id + :label (tr :ptv/service)}) (let [languages (set languages)] [mui/tabs @@ -731,20 +421,134 @@ :label (tr :ptv/description) :value (get-in m [:description @selected-tab])}]]]))]]]]]))) +(defui service-location-details + [{:keys [org-id tr site lipas-id sync-enabled name-conflict service-ids selected-tab set-selected-tab service-channel-ids]}] + (let [services (use-subscribe [::subs/services org-id])] + ($ AccordionDetails + {} + (r/as-element + [mui/stack {:spacing 2} + + [lui/switch + {:label (tr :ptv.actions/export-disclaimer) + :value sync-enabled + :on-change #(==> [::events/toggle-sync-enabled site %])}] + + ;; Services selector + ($ controls/services-selector + {:options services + :value service-ids + :value-fn :service-id + :on-change #(==> [::events/select-services site %]) + :label (tr :ptv/services)}) + + ;; Service channel selector + + [:span (when name-conflict {:style + {:border "1px solid rgb(237, 108, 2)" + :padding "1em"}}) + + (when name-conflict + [mui/stack + [lui/icon-text + {:icon "warning" + :icon-color "warning" + :text (tr :ptv.wizard/service-channel-name-conflict (:name site))}] + + [mui/typography + {:style {:padding-left "1em" :margin-bottom "0"} + :variant "body2"} + (tr :ptv.name-conflict/do-one-of-these)] + + [:ul + [:li (tr :ptv.name-conflict/opt1)] + [:li (tr :ptv.name-conflict/opt2)] + [:li (tr :ptv.name-conflict/opt3)] + #_[:li (tr :ptv.name-conflict/opt4)]]]) + + (when name-conflict + [mui/button + {:on-click #(==> [::events/select-service-channels {:lipas-id lipas-id} + [(:service-channel-id name-conflict)]])} + (tr :ptv.wizard/attach-to-conflicting-service-channel)]) + + ($ service-channel-selector + {:org-id org-id + :value service-channel-ids + :value-fn :service-channel-id + :on-change #(==> [::events/select-service-channels site %]) + :label (tr :ptv/service-channel)})] + + [mui/tabs + {:value selected-tab + :on-change #(set-selected-tab (keyword %2))} + [mui/tab {:value "fi" :label "FI"}] + [mui/tab {:value "se" :label "SE"}] + [mui/tab {:value "en" :label "EN"}]] + + ;; Summary + [lui/text-field + {:multiline true + :variant "outlined" + :on-change #(==> [::events/set-summary site selected-tab %]) + :label (tr :ptv/summary) + :value (get-in site [:summary selected-tab])}] + + ;; Description + [lui/text-field + {:variant "outlined" + :rows 5 + :multiline true + :on-change #(==> [::events/set-description site selected-tab %]) + :label (tr :ptv/description) + :value (get-in site [:description selected-tab])}]])))) + +(defui service-location + [{:keys [site sync-enabled name-conflict valid] + :as props}] + ($ Accordion + {:defaultExpanded false + :disableGutters true + :square true + ;; Much faster this way, only render the accordion content for open sites + :slotProps #js {:transition #js {:unmountOnExit true}} + :sx #js {:mb 2 + :backgroundColor (when (false? sync-enabled) + mui/gray3)}} + ($ AccordionSummary + {:expandIcon ($ Icon "expand_more")} + ($ Typography + {:sx #js {:mr 1.5}} + ;; TODO: Should also show if already saved to ptv or not? + (cond + name-conflict ($ Icon {:color "warning"} "warning") + valid ($ Icon {:color "success"} "done") + :else ($ Icon {:color "disabled"} "done"))) + ($ Typography + {:sx #js {:color "inherit" + :variant "button"}} + (:name site))) + + ($ service-location-details props))) + (defn integrate-service-locations [] (let [tr (<== [:lipas.ui.subs/translator]) - sports-sites (<== [::subs/sports-sites]) - setup-done? (<== [::subs/sports-site-setup-done]) - sports-sites-count (<== [::subs/sports-sites-count]) + org-id (<== [::subs/selected-org-id]) + sports-sites (<== [::subs/sports-sites org-id]) + setup-done? (<== [::subs/sports-site-setup-done org-id]) + sports-sites-count (<== [::subs/sports-sites-count org-id]) sports-sites-filter (<== [::subs/sports-sites-filter]) - {:keys [in-progress? - processed-lipas-ids - processed-count - total-count - processed-percent - halt?] :as m} (<== [::subs/batch-descriptions-generation-progress])] + [selected-tab set-selected-tab] (uix/use-state :fi) + + {:keys [in-progress? + processed-lipas-ids + processed-count + total-count + processed-percent + halt?] :as m} + (<== [::subs/batch-descriptions-generation-progress])] [lui/expansion-panel {:label (str "2. " (tr :ptv.wizard/integrate-service-locations)) @@ -839,12 +643,12 @@ :on-click #(==> [::events/create-all-ptv-service-locations sports-sites])} (tr :ptv.wizard/export-service-locations-to-ptv)] - (let [{:keys [in-progress? - processed-lipas-ids - processed-count - total-count - processed-percent - halt?] :as m} + (let [{:keys [in-progress? + processed-lipas-ids + processed-count + total-count + processed-percent + halt?] :as m} (<== [::subs/service-location-creation-progress])] (when in-progress? @@ -855,111 +659,32 @@ (when halt? "Something went wrong, ask engineer."))]] -;; Results + ;; Results - (r/with-let [selected-tab (r/atom :fi)] - [mui/grid {:item true :xs 12 :lg 8} - [mui/stack {:spacing 4} + [mui/grid {:item true :xs 12 :lg 8} + [mui/stack {:spacing 4} - [mui/typography {:variant "h6"} - (tr :ptv/sports-sites)] - - [mui/typography {:variant "subtitle1" :style {:margin-top "0px"}} - (str sports-sites-count " kpl")] - - (doall - (for [{:keys [lipas-id valid name-conflict sync-enabled service-ids service-channel-ids service-name] :as site} sports-sites] - ^{:key lipas-id} - [lui/expansion-panel - {:label (:name site) - :style (merge {:margin-top "1em"} - (when (false? sync-enabled) {:background-color mui/gray3})) - :label-icon - (cond - name-conflict [mui/icon {:color "warning"} "warning"] - valid [mui/icon {:color "success"} "done"] - :else [mui/icon {:color "disabled"} "done"])} - - [mui/stack {:spacing 2} - - [lui/switch - {:label (tr :ptv.actions/export-disclaimer) - :value sync-enabled - :on-change #(==> [::events/toggle-sync-enabled site %])}] - - ;; Services selector - [services-selector - {:value service-ids - :value-fn :service-id - :on-change #(==> [::events/select-services site %]) - :label (tr :ptv/services)}] - - ;; Service channel selector - - [:span (when name-conflict {:style - {:border "1px solid rgb(237, 108, 2)" - :padding "1em"}}) - - (when name-conflict - [mui/stack - [lui/icon-text - {:icon "warning" - :icon-color "warning" - :text (tr :ptv.wizard/service-channel-name-conflict (:name site))}] - - [mui/typography - {:style {:padding-left "1em" :margin-bottom "0"} - :variant "body2"} - (tr :ptv.name-conflict/do-one-of-these)] - - [:ul - [:li (tr :ptv.name-conflict/opt1)] - [:li (tr :ptv.name-conflict/opt2)] - [:li (tr :ptv.name-conflict/opt3)] - #_[:li (tr :ptv.name-conflict/opt4)]]]) - - (when name-conflict - [mui/button - {:on-click #(==> [::events/select-service-channels {:lipas-id lipas-id} - [(:service-channel-id name-conflict)]])} - (tr :ptv.wizard/attach-to-conflicting-service-channel)]) - - [service-channel-selector - {:value service-channel-ids - :value-fn :service-channel-id - :on-change #(==> [::events/select-service-channels site %]) - :label (tr :ptv/service-channel)}]] - - [mui/tabs - {:value @selected-tab - :on-change #(reset! selected-tab (keyword %2))} - [mui/tab {:value "fi" :label "FI"}] - [mui/tab {:value "se" :label "SE"}] - [mui/tab {:value "en" :label "EN"}]] - - ;; Summary - [lui/text-field - {:multiline true - :read-only? (not= "manual" (:descriptions-integration site)) - :variant "outlined" - :on-change #(==> [::events/set-summary site @selected-tab %]) - :label (tr :ptv/summary) - :value (get-in site [:summary @selected-tab])}] - - ;; Description - [lui/text-field - {:variant "outlined" - :read-only? (not= "manual" (:descriptions-integration site)) - :rows 5 - :multiline true - :on-change #(==> [::events/set-description site @selected-tab %]) - :label (tr :ptv/description) - :value (get-in site [:description @selected-tab])}]]]))]])]])) - -(defn tools - [] - [mui/paper - [descriptions-generator]]) + [mui/typography {:variant "h6"} + (tr :ptv/sports-sites)] + + [mui/typography {:variant "subtitle1" :style {:margin-top "0px"}} + (str sports-sites-count " kpl")]] + + [mui/stack + (for [{:keys [lipas-id valid name-conflict sync-enabled service-ids service-channel-ids service-name] :as site} sports-sites] + ($ service-location + {:key lipas-id + :tr tr + :site site + :org-id org-id + :lipas-id lipas-id + :name-conflict name-conflict + :sync-enabled sync-enabled + :valid valid + :service-ids service-ids + :selected-tab selected-tab + :set-selected-tab set-selected-tab + :service-channel-ids service-channel-ids}))]]]])) (defn service-panel [{:keys [service]}] @@ -1032,7 +757,8 @@ [] (let [tr (<== [:lipas.ui.subs/translator]) services-filter (<== [::subs/services-filter]) - services (<== [::subs/services-filtered])] + org-id (<== [::subs/selected-org-id]) + services (<== [::subs/services-filtered org-id])] [mui/paper ;; Filter checkbox @@ -1051,24 +777,26 @@ [] [mui/paper [create-services] - [integrate-service-locations]]) + [:f> integrate-service-locations]]) (defn dialog [{:keys [tr]}] (let [open? (<== [::subs/dialog-open?]) selected-tab (<== [::subs/selected-tab]) loading? (<== [::subs/loading-from-ptv?]) - org-data (<== [::subs/selected-org-data]) - sites (<== [::subs/sports-sites])] + org-id (<== [::subs/selected-org-id]) + org-data (<== [::subs/selected-org-data org-id]) + sites (<== [::subs/sports-sites org-id])] [lui/dialog {:open? open? - :on-save #(==> [::events/save sites]) - :save-enabled? true - :save-label (tr :actions/save) + ;; FIXME: This isn't implemented, what should this do? + ; :on-save #(==> [::events/save sites]) + ; :save-enabled? true + ; :save-label (tr :actions/save) :title (tr :ptv/tooltip) :max-width "xl" - :cancel-label (tr :actions/cancel) + :cancel-label "Sulje" ;; (tr :actions/cancel) :on-close #(==> [::events/close-dialog])} [mui/stack {:spacing 2} @@ -1090,9 +818,7 @@ [mui/tab {:value "wizard" :label (tr :ptv/wizard)}] [mui/tab {:value "services" :label (tr :ptv/services)}] - [mui/tab {:value "sports-sites" :label (tr :ptv/sports-sites)}] - [mui/tab {:value "ai" :label (tr :ptv/tools)}] - [mui/tab {:value "settings" :label (tr :ptv/settings)}]] + [mui/tab {:value "sports-sites" :label (tr :ptv/sports-sites)}]] (when (= selected-tab "wizard") [wizard]) @@ -1103,11 +829,7 @@ (when (= selected-tab "sports-sites") [table]) - (when (= selected-tab "settings") - [settings]) - - (when (= selected-tab "ai") - [tools])])]])) + ])]])) ;; Juhan kommentit wizardiin ;; - mahdollisuus valita kielet diff --git a/webapp/src/cljs/lipas/ui/sports_sites/events.cljs b/webapp/src/cljs/lipas/ui/sports_sites/events.cljs index bee580612..8c0c66801 100644 --- a/webapp/src/cljs/lipas/ui/sports_sites/events.cljs +++ b/webapp/src/cljs/lipas/ui/sports_sites/events.cljs @@ -16,12 +16,24 @@ (assoc-in [:sports-sites :name-check] {})) :fx [[:dispatch [:lipas.ui.sports-sites.activities.events/init-edit-view lipas-id rev]]]}))) -(defmulti calc-derived-fields (comp :type-code :type)) -(defmethod calc-derived-fields :default [sports-site] sports-site) +(defmulti calc-derived-fields-for-type (comp :type-code :type)) + +(defmethod calc-derived-fields-for-type :default + [sports-site] + sports-site) + +(defn calc-derived-fields [db sports-site] + (-> sports-site + (calc-derived-fields-for-type) + ;; IF has :ptv key, initialize the default parameters to that map + ;; NOTE: Does add extra slowness to any ::edit-field calls... + (cond-> + (:ptv sports-site) + (update :ptv #(merge (:default-settings (:ptv db)) %))))) (rf/reg-event-db ::calc-derived-fields (fn [db [_ lipas-id sports-site]] - (assoc-in db [:sports-sites lipas-id :editing] (calc-derived-fields sports-site)))) + (assoc-in db [:sports-sites lipas-id :editing] (calc-derived-fields db sports-site)))) (rf/reg-event-fx ::edit-field (fn [{:keys [db]} [_ lipas-id path value]] @@ -214,7 +226,7 @@ (rf/reg-event-db ::calc-new-site-derived-fields (fn [db [_ sports-site]] - (assoc-in db [:new-sports-site :data] (calc-derived-fields sports-site)))) + (assoc-in db [:new-sports-site :data] (calc-derived-fields db sports-site)))) (rf/reg-event-fx ::edit-new-site-field (fn [{:keys [db]} [_ path value]] diff --git a/webapp/src/cljs/lipas/ui/sports_sites/floorball/events.cljs b/webapp/src/cljs/lipas/ui/sports_sites/floorball/events.cljs index 5ca4d01b0..97fc2f3d9 100644 --- a/webapp/src/cljs/lipas/ui/sports_sites/floorball/events.cljs +++ b/webapp/src/cljs/lipas/ui/sports_sites/floorball/events.cljs @@ -42,7 +42,7 @@ (apply +) pos))}) -(defmethod sports-sites.events/calc-derived-fields 2240 +(defmethod sports-sites.events/calc-derived-fields-for-type 2240 [sports-site] (-> sports-site (update :properties (fn [props] diff --git a/webapp/src/cljs/lipas/ui/sports_sites/subs.cljs b/webapp/src/cljs/lipas/ui/sports_sites/subs.cljs index 6f8cbac19..f3b424018 100644 --- a/webapp/src/cljs/lipas/ui/sports_sites/subs.cljs +++ b/webapp/src/cljs/lipas/ui/sports_sites/subs.cljs @@ -2,6 +2,7 @@ (:require ["@turf/helpers" :refer [lineString]] ["@turf/length$default" :as turf-length] [clojure.spec.alpha :as s] + [lipas.data.prop-types :as prop-types] [lipas.data.types :as types] [lipas.roles :as roles] [lipas.ui.utils :as utils] @@ -332,7 +333,16 @@ city (get cities (-> latest :location :city :city-code)) status (statuses (-> latest :status)) - get-material #(get-in materials [% locale])] + get-material #(get-in materials [% locale]) + get-travel-mode #(get-in prop-types/all [:travel-modes :opts % :label locale]) + + get-parkour-structure #(get-in prop-types/all [:parkour-hall-equipment-and-structures :opts % :label locale]) + + get-boating-service-class #(get-in prop-types/all [:boating-service-class :opts % :label locale]) + + get-water-point #(get-in prop-types/all [:water-point :opts % :label locale]) + + get-sport-specification #(get-in prop-types/all [:sport-specification :opts % :label locale])] (merge {:status (-> status locale) @@ -360,7 +370,12 @@ :properties (update :surface-material #(map get-material %)) (update :running-track-surface-material get-material) - (update :training-spot-surface-material get-material)) + (update :training-spot-surface-material get-material) + (update :travel-modes #(map get-travel-mode %)) + (update :parkour-hall-equipment-and-structures #(map get-parkour-structure %)) + (update :boating-service-class get-boating-service-class) + (update :water-point get-water-point) + (update :sport-specification get-sport-specification)) :location {:address (-> latest :location :address) diff --git a/webapp/src/cljs/lipas/ui/sports_sites/views.cljs b/webapp/src/cljs/lipas/ui/sports_sites/views.cljs index b5a9c90b1..f05435320 100644 --- a/webapp/src/cljs/lipas/ui/sports_sites/views.cljs +++ b/webapp/src/cljs/lipas/ui/sports_sites/views.cljs @@ -503,6 +503,25 @@ :items (-> data :edit-data :rinks) :lipas-id (-> data :edit-data :lipas-id)}])])) +(defn space-divisible-field + [{:keys [tr value label helper-text tooltip on-change spec disabled?] :as props}] + (r/with-let [checkbox-state (r/atom (some? value))] + [:<> + [lui/checkbox + {:label label + :tooltip tooltip + :value @checkbox-state + :on-change #(reset! checkbox-state %)}] + (when @checkbox-state + [lui/text-field + {:value value + :label helper-text + :disabled disabled? + #_#_:tooltip tooltip + :spec spec + :type "number" + :on-change on-change}])])) + ;; Used from activities -> lipas-property now ;; Regular perustiedot uses the properties-form directly, which doesn't use this (defn make-prop-field @@ -513,6 +532,7 @@ spec (keyword :lipas.sports-site.properties prop-k) disabled? read-only? label (or label (get-in prop-type [:name locale])) + helper-text (get-in prop-type [:helper-text locale]) tooltip (or description (get-in prop-type [:description locale])) data-type (get prop-type :data-type) on-change set-field @@ -558,6 +578,18 @@ :tooltip tooltip :geoms geoms :on-change on-change}] + + (= :space-divisible k) [space-divisible-field + {:tr tr + :value value + :helper-text helper-text + :type "number" + :spec spec + :label label + :tooltip tooltip + :geoms geoms + :on-change on-change}] + (= "boolean" data-type) [lui/checkbox {:value value :label label @@ -573,8 +605,20 @@ :label label :disabled disabled? :value-fn first + :on-change on-change :label-fn (comp locale :name second)}] + (= "enum-coll" data-type k) [lui/multi-select + {:items (:opts prop-type) + :deselect? true + :value value + :helper-text tooltip + :on-change on-change + :label label + :disabled disabled? + :value-fn first + :label-fn (comp locale :name second)}] + :else [lui/text-field {:value value :label label @@ -585,6 +629,7 @@ "number") :on-change on-change}]))) +;; TODO refactor to use `make-prop-field` function above (defn properties-form [{:keys [tr edit-data editing? display-data type-code on-change read-only? key geoms geom-type problems? width pools]}] @@ -641,6 +686,7 @@ (into (for [[k v] types-props :let [label (-> types-props k :name locale) + helper-text (-> types-props k :helper-text locale) data-type (:data-type v) tooltip (if (:derived? v) "Lasketaan automaattisesti olosuhdetiedoista" @@ -653,6 +699,7 @@ :value (-> display-data k) :disabled? disabled? :priority (:priority v) + ;; TODO Could be nicer with a multi-method :form-field (cond (material-field? k) [surface-material-selector @@ -694,11 +741,24 @@ :tooltip tooltip :geoms geoms :on-change on-change}] + + (= :space-divisible k) [space-divisible-field + {:tr tr + :value value + :type "number" + :helper-text helper-text + :spec spec + :label label + :tooltip tooltip + :geoms geoms + :on-change on-change}] + (= "boolean" data-type) [lui/checkbox {:value value :tooltip tooltip :disabled disabled? :on-change on-change}] + (= "enum" data-type) [lui/select {:items (:opts v) :deselect? true @@ -710,6 +770,17 @@ :value-fn first :label-fn (comp locale :label second)}] + (= "enum-coll" data-type) [lui/autocomplete + {:multi? true + :items (:opts v) + :deselect? true + :value value + :helper-text tooltip + :on-change on-change + :label label + :disabled disabled? + :value-fn first + :label-fn (comp locale :label second)}] :else (let [el [lui/text-field {;; form ->field adds the :label, but that doesn't work diff --git a/webapp/test/clj/lipas/backend/handler_test.clj b/webapp/test/clj/lipas/backend/handler_test.clj index 14a64e204..3b6024bb0 100644 --- a/webapp/test/clj/lipas/backend/handler_test.clj +++ b/webapp/test/clj/lipas/backend/handler_test.clj @@ -1,176 +1,27 @@ (ns lipas.backend.handler-test - (:require [cheshire.core :as j] - [clojure.java.jdbc :as jdbc] - [clojure.spec.alpha :as s] + (:require [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen] - [clojure.string :as str] [clojure.test :refer [deftest is use-fixtures] :as t] - [cognitect.transit :as transit] [dk.ative.docjure.spreadsheet :as excel] - [lipas.backend.analysis.diversity :as diversity] - [lipas.backend.config :as config] [lipas.backend.core :as core] - [lipas.backend.email :as email] [lipas.backend.jwt :as jwt] - [lipas.backend.search :as search] - [lipas.backend.system :as system] [lipas.data.loi :as loi] [lipas.schema.core] [lipas.seed :as seed] - [lipas.test-utils :as tu] + [lipas.test-utils :refer [->transit <-transit <-json ->json app search db] :as tu] [lipas.utils :as utils] - [migratus.core :as migratus] - [ring.mock.request :as mock]) - (:import [java.io ByteArrayOutputStream] - java.util.Base64)) - -;;; Setup ;;; - -(def <-json #(j/parse-string (slurp %) true)) -(def ->json j/generate-string) - -(defn ->transit [x] - (let [out (ByteArrayOutputStream. 4096) - writer (transit/writer out :json)] - (transit/write writer x) - (.toString out))) - -(defn <-transit [in] - (let [reader (transit/reader in :json)] - (transit/read reader))) - -(defn ->base64 - "Encodes a string as base64." - [s] - (.encodeToString (Base64/getEncoder) (.getBytes s))) - -(defn auth-header - "Adds Authorization header to the request - with base64 encoded \"Basic user:pass\" value." - [req user passwd] - (mock/header req "Authorization" (str "Basic " (->base64 (str user ":" passwd))))) - -(defn token-header - [req token] - (mock/header req "Authorization" (str "Token " token))) - -(defn- test-suffix [s] (str s "_test")) - -(def config (-> config/default-config - (select-keys [:db :app :search :mailchimp :aws]) - (assoc-in [:app :emailer] (email/->TestEmailer)) - (update-in [:db :dbname] test-suffix) - (assoc-in [:db :dev] true) ;; No connection pool - (update-in [:search :indices :sports-site :search] test-suffix) - (update-in [:search :indices :sports-site :analytics] test-suffix) - (update-in [:search :indices :report :subsidies] test-suffix) - (update-in [:search :indices :report :city-stats] test-suffix) - (update-in [:search :indices :analysis :schools] test-suffix) - (update-in [:search :indices :analysis :population] test-suffix) - (update-in [:search :indices :analysis :population-high-def] test-suffix) - (update-in [:search :indices :analysis :diversity] test-suffix) - (update-in [:search :indices :lois :search] test-suffix))) - -(defn init-db! [] - (let [migratus-opts {:store :database - :migration-dir "migrations" - :db (:db config)}] - (try - (jdbc/db-do-commands (-> config :db (assoc :dbname "")) - false - [(str "CREATE DATABASE " (-> config :db :dbname))]) - (catch Exception e - (when-not (= "ERROR: database \"lipas_test\" already exists" - (-> e .getCause .getMessage)) - (throw e)))) - - (jdbc/db-do-commands (:db config) - false - [(str "CREATE EXTENSION IF NOT EXISTS postgis") - (str "CREATE EXTENSION IF NOT EXISTS postgis_topology") - (str "CREATE EXTENSION IF NOT EXISTS citext") - (str "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")]) - - (migratus/init migratus-opts) - (migratus/migrate migratus-opts))) - -(def tables - ["account" - "analysis_queue" - "city" - "elevation_queue" - "email_out_queue" - "integration_log" - "integration_out_queue" - "reminder" - "sports_site" - "subsidy"]) - -(defn prune-db! [] - (jdbc/execute! (:db config) [(str "TRUNCATE " - (str/join "," tables) - " RESTART IDENTITY CASCADE")])) - -(println "Starting test-system!") -(def system (system/start-system! (config/->system-config config))) - -(def db (:lipas/db system)) -(def app (:lipas/app system)) -(def search (:lipas/search system)) - -(defn prune-es! [] - (let [client (:client search) - mappings {(-> search :indices :sports-site :search) (:sports-sites search/mappings) - (-> search :indices :analysis :diversity) diversity/mappings - (-> search :indices :lois :search) (:lois search/mappings)}] - - (doseq [idx-name (-> search :indices vals (->> (mapcat vals)))] - (try - (search/delete-index! client idx-name) - (catch Exception ex - (when (not= "index_not_found_exception" - (-> ex ex-data :body :error :root_cause first :type)) - (throw ex)))) - (when-let [mapping (mappings idx-name)] - (search/create-index! client idx-name mapping))))) - -(comment - (init-db!) - (prune-es!) - (prune-db!) - (ex-data *e) - ) - -(defn gen-user - ([] - (gen-user {:db? false :admin? false :status "active"})) - ([{:keys [db? admin? status] - :or {admin? false status "active"}}] - (let [user (-> (gen/generate (s/gen :lipas/user)) - (assoc :password (str (gensym)) :status status) - ;; Ensure :permissions is a map always, generate doesn't always add the key because it it is optional in - ;; the :lipas/user spec but required e.g. update-user-permissions endpoint. - (update :permissions (fn [permissions] - (cond-> (or permissions {}) - ;; generated result might include admin role, remove if the flag is false - (not admin?) (update :roles (fn [roles] - (into [] (remove (fn [x] (= :admin (:role x))) roles)))) - admin? (update :roles (fnil conj []) {:role :admin})))))] - (if db? - (do - (core/add-user! db user) - (assoc user :id (:id (core/get-user db (:email user))))) - user)))) - -(defn gen-loi! [] - (-> (gen/generate (s/gen :lipas.loi/document)) - (assoc :status "active") - (assoc :id (str (java.util.UUID/randomUUID))))) + [ring.mock.request :as mock])) ;;; Fixtures ;;; -(use-fixtures :once (fn [f] (init-db!) (f))) -(use-fixtures :each (fn [f] (prune-db!) (prune-es!) (f))) +(use-fixtures :once (fn [f] + (tu/init-db!) + (f))) + +(use-fixtures :each (fn [f] + (tu/prune-db!) + (tu/prune-es!) + (f))) ;;; The tests ;;; @@ -179,7 +30,7 @@ (deftest search-loi-by-type (let [loi-type "fishing-pier" loi-category "outdoor-recreation-facilities" - loi (-> (gen-loi!) + loi (-> (tu/gen-loi!) (assoc :loi-type loi-type) (assoc :loi-category loi-category)) _ (core/index-loi! search loi :sync) @@ -199,7 +50,7 @@ (deftest search-loi-by-category (let [loi-category "outdoor-recreation-facilities" - loi (-> (gen-loi!) + loi (-> (tu/gen-loi!) (assoc :loi-category loi-category)) _ (core/index-loi! search loi :sync) resp (app (-> (mock/request :get (str "/api/lois/category/" loi-category)) @@ -208,7 +59,7 @@ (is (= loi-category (:loi-category response-loi))))) (deftest get-loi-by-id - (let [{:keys [id] :as loi} (gen-loi!) + (let [{:keys [id] :as loi} (tu/gen-loi!) _ (core/index-loi! search loi :sync) resp (app (-> (mock/request :get (str "/api/lois/" id)) (mock/content-type "application/json"))) @@ -217,7 +68,7 @@ (deftest search-loi-by-invalid-category (let [loi-category "kekkonen-666-category" - loi (-> (gen-loi!) + loi (-> (tu/gen-loi!) (assoc :loi-category loi-category)) _ (core/index-loi! search loi :sync) response (app (-> (mock/request :get (str "/api/lois/category/" loi-category)) @@ -321,14 +172,14 @@ ) (deftest register-user-test - (let [user (gen-user) + (let [user (tu/gen-user) resp (app (-> (mock/request :post "/api/actions/register") (mock/content-type "application/json") (mock/body (->json user))))] (is (= 201 (:status resp))))) (deftest register-user-conflict-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) resp (app (-> (mock/request :post "/api/actions/register") (mock/content-type "application/json") (mock/body (->json user)))) @@ -339,25 +190,25 @@ (deftest login-failure-test (let [resp (app (-> (mock/request :post "/api/actions/login") (mock/content-type "application/json") - (auth-header "this" "fails")))] + (tu/auth-header "this" "fails")))] (is (= (:status resp) 401)))) (deftest login-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) resp (app (-> (mock/request :post "/api/actions/login") (mock/content-type "application/json") - (auth-header (:username user) (:password user)))) + (tu/auth-header (:username user) (:password user)))) body (<-json (:body resp))] (is (= 200 (:status resp))) (is (= (:email user) (:email body))))) (deftest refresh-login-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token1 (jwt/create-token user :terse? true) _ (Thread/sleep 1000) ; to see effect between timestamps resp (app (-> (mock/request :get "/api/actions/refresh-login") (mock/content-type "application/json") - (token-header token1))) + (tu/token-header token1))) body (<-json (:body resp)) token2 (:token body) @@ -369,7 +220,7 @@ ;; TODO how to test side-effects? (sending email) (deftest request-password-reset-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) resp (app (-> (mock/request :post "/api/actions/request-password-reset") (mock/content-type "application/json") (mock/body (->json (select-keys user [:email])))))] @@ -384,27 +235,27 @@ (is (= "email-not-found" (:type body))))) (deftest reset-password-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user :terse? true) resp (app (-> (mock/request :post "/api/actions/reset-password") (mock/content-type "application/json") (mock/body (->json {:password "blablaba"})) - (token-header token)))] + (tu/token-header token)))] (is (= 200 (:status resp))))) (deftest reset-password-expired-token-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user :terse? true :valid-seconds 0) _ (Thread/sleep 100) ; make sure token expires resp (app (-> (mock/request :post "/api/actions/reset-password") (mock/content-type "application/json") (mock/body (->json {:password "blablaba"})) - (token-header token)))] + (tu/token-header token)))] (is (= 401 (:status resp))))) (deftest send-magic-link-requires-admin-test - (let [admin (gen-user {:db? true :admin? false}) - user (-> (gen-user {:db? false}) + (let [admin (tu/gen-user {:db? true :admin? false}) + user (-> (tu/gen-user {:db? false}) (dissoc :password :id)) token (jwt/create-token admin) resp (app (-> (mock/request :post "/api/actions/send-magic-link") @@ -412,15 +263,15 @@ (mock/body (->json {:user user :login-url "https://localhost" :variant "lipas"})) - (token-header token)))] + (tu/token-header token)))] (is (= 403 (:status resp))))) (deftest update-user-permissions-test ;; Updating permissions has side-effect of publshing drafts the user ;; has done earlier to sites where permissions are now being ;; granted - (let [admin (gen-user {:db? true :admin? true}) - user (gen-user {:db? true}) + (let [admin (tu/gen-user {:db? true :admin? true}) + user (tu/gen-user {:db? true}) ;; Add 'draft' which is expected to get publshed as side-effect event-date (utils/timestamp) @@ -438,7 +289,7 @@ (mock/body (->json {:id (:id user) :permissions perms :login-url "https://localhost"})) - (token-header token))) + (tu/token-header token))) site-log (->> (core/get-sports-site-history db 123) (utils/index-by :event-date))] @@ -449,7 +300,7 @@ (is (= "active" (:status (get site-log event-date)))))) (deftest update-user-permissions-requires-admin-test - (let [user (gen-user {:db? true :admin? false}) + (let [user (tu/gen-user {:db? true :admin? false}) token (jwt/create-token user) resp (app (-> (mock/request :post "/api/actions/update-user-permissions") (mock/content-type "application/json") @@ -457,46 +308,46 @@ (select-keys [:id :permissions]) (assoc :login-url "https://localhost") ->json)) - (token-header token)))] + (tu/token-header token)))] (is (= 403 (:status resp))))) (deftest update-user-data-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user :terse? true) user-data (gen/generate (s/gen :lipas.user/user-data)) resp (app (-> (mock/request :post "/api/actions/update-user-data") (mock/content-type "application/json") (mock/body (->json user-data)) - (token-header token))) + (tu/token-header token))) user-data2 (-> resp :body <-json)] (is (= 200 (:status resp))) (is (= user-data user-data2)))) (deftest update-user-status-test - (let [admin (gen-user {:db? true :admin? true}) - user (gen-user {:db? true :status "active"}) + (let [admin (tu/gen-user {:db? true :admin? true}) + user (tu/gen-user {:db? true :status "active"}) token (jwt/create-token admin) resp (app (-> (mock/request :post "/api/actions/update-user-status") (mock/content-type "application/json") (mock/body (->json {:id (:id user) :status "archived"})) - (token-header token))) + (tu/token-header token))) user2 (-> resp :body <-json)] (is (= 200 (:status resp))) (is (= "archived" (:status user2))))) (deftest update-user-status-requires-admin-test - (let [admin (gen-user {:db? true :admin? false}) - user (gen-user {:db? true :status "active"}) + (let [admin (tu/gen-user {:db? true :admin? false}) + user (tu/gen-user {:db? true :status "active"}) token (jwt/create-token admin) resp (app (-> (mock/request :post "/api/actions/update-user-status") (mock/content-type "application/json") (mock/body (->json {:id (:id user) :status "archived"})) - (token-header token)))] + (tu/token-header token)))] (is (= 403 (:status resp))))) (deftest send-magic-link-test - (let [admin (gen-user {:db? true :admin? true}) - user (-> (gen-user {:db? false}) + (let [admin (tu/gen-user {:db? true :admin? true}) + user (-> (tu/gen-user {:db? false}) (dissoc :password :id)) token (jwt/create-token admin) resp (app (-> (mock/request :post "/api/actions/send-magic-link") @@ -504,11 +355,11 @@ (mock/body (->json {:user user :login-url "https://localhost" :variant "lipas"})) - (token-header token)))] + (tu/token-header token)))] (is (= 200 (:status resp))))) (deftest order-magic-link-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) resp (app (-> (mock/request :post "/api/actions/order-magic-link") (mock/content-type "application/json") (mock/body (->json {:email (:email user) @@ -525,7 +376,7 @@ (is (= 400 (:status resp))))) (deftest upsert-sports-site-draft-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user) site (-> (tu/gen-sports-site) (assoc :status "active") @@ -533,11 +384,11 @@ resp (app (-> (mock/request :post "/api/sports-sites?draft=true") (mock/content-type "application/json") (mock/body (->json site)) - (token-header token)))] + (tu/token-header token)))] (is (= 201 (:status resp))))) (deftest upsert-invalid-sports-site-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user) site (-> (tu/gen-sports-site) (assoc :status "kebab") @@ -545,11 +396,11 @@ resp (app (-> (mock/request :post "/api/sports-sites?draft=true") (mock/content-type "application/json") (mock/body (->json site)) - (token-header token)))] + (tu/token-header token)))] (is (= 400 (:status resp))))) (deftest upsert-sports-site-no-permissions-test - (let [user (gen-user) + (let [user (tu/gen-user) _ (as-> user $ (dissoc $ :permissions) (core/add-user! db $)) @@ -560,11 +411,11 @@ resp (app (-> (mock/request :post "/api/sports-sites") (mock/content-type "application/json") (mock/body (->json site)) - (token-header token)))] + (tu/token-header token)))] (is (= 403 (:status resp))))) (deftest get-sports-sites-by-type-code-test - (let [user (gen-user {:db? true :admin? true}) + (let [user (tu/gen-user {:db? true :admin? true}) site (-> (tu/gen-sports-site) (assoc-in [:type :type-code] 3110) (assoc :status "active")) @@ -576,7 +427,7 @@ (is (s/valid? :lipas/sports-sites body)))) (deftest get-sports-sites-by-type-code-localized-test - (let [user (gen-user {:db? true :admin? true}) + (let [user (tu/gen-user {:db? true :admin? true}) site (-> (tu/gen-sports-site) (assoc-in [:type :type-code] 3110) (assoc-in [:admin] "state") @@ -593,7 +444,7 @@ :admin))))) (deftest get-sports-site-test - (let [user (gen-user {:db? true :admin? true}) + (let [user (tu/gen-user {:db? true :admin? true}) rev1 (-> (tu/gen-sports-site) (assoc :status "active")) _ (core/upsert-sports-site!* db user rev1) @@ -611,7 +462,7 @@ (is (= 404 (:status resp))))) (deftest get-sports-site-history-test - (let [user (gen-user {:db? true :admin? true}) + (let [user (tu/gen-user {:db? true :admin? true}) rev1 (-> (tu/gen-sports-site) (assoc :status "active")) rev2 (-> rev1 @@ -684,7 +535,7 @@ (deftest calculate-stats-test (let [_ (seed/seed-city-data! db search) - user (gen-user {:db? true :admin? true}) + user (tu/gen-user {:db? true :admin? true}) site (-> (tu/gen-sports-site) (assoc :status "active") (assoc-in [:location :city :city-code] 275) @@ -707,30 +558,30 @@ (is (= 200 (:status resp))))) (deftest add-reminder-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user :terse? true) reminder (gen/generate (s/gen :lipas/new-reminder)) resp (app (-> (mock/request :post "/api/actions/add-reminder") (mock/content-type "application/json") (mock/body (->json reminder)) - (token-header token))) + (tu/token-header token))) body (-> resp :body <-json)] (is (= 200 (:status resp))) (is (= "pending" (:status body))))) (deftest update-reminder-status-test - (let [user (gen-user {:db? true}) + (let [user (tu/gen-user {:db? true}) token (jwt/create-token user :terse? true) reminder (gen/generate (s/gen :lipas/new-reminder)) resp1 (app (-> (mock/request :post "/api/actions/add-reminder") (mock/content-type "application/json") (mock/body (->json reminder)) - (token-header token))) + (tu/token-header token))) id (-> resp1 :body <-json :id) resp2 (app (-> (mock/request :post "/api/actions/update-reminder-status") (mock/content-type "application/json") (mock/body (->json {:id id :status "canceled"})) - (token-header token)))] + (tu/token-header token)))] (is (= 200 (:status resp1))) (is (= 200 (:status resp2))))) diff --git a/webapp/test/clj/lipas/backend/ptv_test.clj b/webapp/test/clj/lipas/backend/ptv_test.clj new file mode 100644 index 000000000..75374485d --- /dev/null +++ b/webapp/test/clj/lipas/backend/ptv_test.clj @@ -0,0 +1,127 @@ +(ns lipas.backend.ptv-test + (:require [clojure.test :refer [deftest is use-fixtures] :as t] + [lipas.backend.core :as core] + [lipas.backend.jwt :as jwt] + [lipas.data.ptv :as ptv-data] + [lipas.data.types :as types] + [lipas.schema.core] + [lipas.test-utils :refer [<-json app db] :as tu] + [lipas.utils :as utils] + [ring.mock.request :as mock])) + +(use-fixtures :once (fn [f] + (tu/init-db!) + (f))) + +(use-fixtures :each (fn [f] + (tu/prune-db!) + (tu/prune-es!) + (f))) + +(deftest ^:ptv ^:integration init-site-ptv + (let [user (tu/gen-user {:db? true :admin? true}) + token (jwt/create-token user) + + rev1 (-> (tu/gen-sports-site) + (assoc :status "active") + ;; need to set up realistic type and location for the ptv integration to work + ;; yleisurheilukenttä + (assoc-in [:type :type-code] 1210) + (assoc-in [:location :postal-code] "91900") + (assoc-in [:location :city :city-code] 425)) + _ (core/upsert-sports-site!* db user rev1) + lipas-id (:lipas-id rev1) + resp (app (-> (mock/request :get (str "/api/sports-sites/" lipas-id)) + (mock/content-type "application/json") + (tu/token-header token))) + body (<-json (:body resp)) + site body + + ;; TODO: Use another id for tests run? + org-id ptv-data/liminka-org-id-test + org-langs ["fi" "se" "en"] + ;; re-frame app-db defaults + types types/all + default-settings {:service-integration "lipas-managed" + :service-channel-integration "lipas-managed" + :descriptions-integration "lipas-managed-ptv-fields" + :integration-interval "manual"} + + sports-sites (->> [body] + (utils/index-by :lipas-id))] + (println sports-sites) + (is (some? lipas-id)) + (is (= 200 (:status resp))) + ;; (is (some? (:ptv body))) + + (let [;; Get list of services already on PTV + resp (app (-> (mock/request :post (str "/api/actions/fetch-ptv-services")) + (mock/json-body {:org-id org-id}) + (tu/token-header token))) + services (->> (<-json (:body resp)) + :itemList + (utils/index-by :id)) + + _ (is (= 200 (:status resp))) + _ (is (> (count services) 1)) + + ;; Get list of service channels already on PTV + resp (app (-> (mock/request :post (str "/api/actions/fetch-ptv-service-channels")) + (mock/json-body {:org-id org-id}) + (tu/token-header token))) + service-channels (->> (<-json (:body resp)) + :itemList + (utils/index-by :id)) + + _ (is (= 200 (:status resp))) + _ (is (> (count service-channels) 1)) + + ;; Initializing a PTV data for a site that wasn't previously synced to ptv works like this: + ;; - summary and description are written or generated + ;; - after that this ptv-input functions should return that the site is valid (this is used in the view component) + ;; - save-ptv-service-location is called BUT this doesn't take the + ;; ptv-input but the lipas-id and ptv-meta as parameters + + ;; TODO: is this ptv-input data useful? Could everything (the view components) just use raw site data directly? + ptv-sites (for [site (vals sports-sites)] + (ptv-data/sports-site->ptv-input {:types types + :org-id org-id + :org-defaults default-settings + :org-langs org-langs} + service-channels + services + site))] + + (is (= 1 (count ptv-sites))) + + (is (= [] + (ptv-data/resolve-missing-services org-id + services + ptv-sites))) + + ;; Add ptv summary and description to the site, enabling the + ;; ptv integration for the site -> will create Service Location. + (let [updated-site (assoc site :ptv (merge default-settings + {:sync-enabled true + :org-id org-id + ;; TODO: Need to setup the link to services + :service-ids [] + :summary {:fi "foobar" + :se "foobar" + :en "foobar"} + :description {:fi "foobar" + :se "foobar" + :en "foobar"}})) + resp (app (-> (mock/request :post (str "/api/sports-sites")) + (mock/json-body updated-site) + (tu/token-header token))) + body (<-json (:body resp))] + ;; Responds with 201 for both creates and updates + (is (= 201 (:status resp))) + (println body) + (is (some? (:last-sync (:ptv body)))) + (is (= "Published" (:publishing-status (:ptv body)))))) + + ;; TODO: Archive the site in Lipas and PTV + )) + diff --git a/webapp/test/clj/lipas/test_utils.clj b/webapp/test/clj/lipas/test_utils.clj index 29cf6aa1e..6b2f59195 100644 --- a/webapp/test/clj/lipas/test_utils.clj +++ b/webapp/test/clj/lipas/test_utils.clj @@ -1,10 +1,171 @@ (ns lipas.test-utils - (:require - [clojure.spec.alpha :as s] - [clojure.spec.gen.alpha :as gen])) + (:require [cheshire.core :as j] + [clojure.java.jdbc :as jdbc] + [clojure.spec.alpha :as s] + [clojure.spec.gen.alpha :as gen] + [clojure.string :as str] + [cognitect.transit :as transit] + [lipas.backend.analysis.diversity :as diversity] + [lipas.backend.config :as config] + [lipas.backend.core :as core] + [lipas.backend.email :as email] + [lipas.backend.search :as search] + [lipas.backend.system :as sy] + [lipas.schema.core] + [migratus.core :as migratus] + [ring.mock.request :as mock]) + (:import [java.io ByteArrayOutputStream] + java.util.Base64)) (defn gen-sports-site [] (try (gen/generate (s/gen :lipas/sports-site)) (catch Throwable _t (gen-sports-site)))) + +(def <-json #(j/parse-string (slurp %) true)) +(def ->json j/generate-string) + +(defn ->transit [x] + (let [out (ByteArrayOutputStream. 4096) + writer (transit/writer out :json)] + (transit/write writer x) + (.toString out))) + +(defn <-transit [in] + (let [reader (transit/reader in :json)] + (transit/read reader))) + +(defn ->base64 + "Encodes a string as base64." + [s] + (.encodeToString (Base64/getEncoder) (.getBytes s))) + +(defn auth-header + "Adds Authorization header to the request + with base64 encoded \"Basic user:pass\" value." + [req user passwd] + (mock/header req "Authorization" (str "Basic " (->base64 (str user ":" passwd))))) + +(defn token-header + [req token] + (mock/header req "Authorization" (str "Token " token))) + +(defn- test-suffix [s] (str s "_test")) + +(def config (-> config/default-config + (select-keys [:db :app :search :mailchimp :aws :ptv]) + (assoc-in [:app :emailer] (email/->TestEmailer)) + (update-in [:db :dbname] test-suffix) + (assoc-in [:db :dev] true) ;; No connection pool + (update-in [:search :indices :sports-site :search] test-suffix) + (update-in [:search :indices :sports-site :analytics] test-suffix) + (update-in [:search :indices :report :subsidies] test-suffix) + (update-in [:search :indices :report :city-stats] test-suffix) + (update-in [:search :indices :analysis :schools] test-suffix) + (update-in [:search :indices :analysis :population] test-suffix) + (update-in [:search :indices :analysis :population-high-def] test-suffix) + (update-in [:search :indices :analysis :diversity] test-suffix) + (update-in [:search :indices :lois :search] test-suffix))) + +(defn init-db! [] + (let [migratus-opts {:store :database + :migration-dir "migrations" + :db (:db config)}] + (try + (jdbc/db-do-commands (-> config :db (assoc :dbname "")) + false + [(str "CREATE DATABASE " (-> config :db :dbname))]) + (catch Exception e + (when-not (= "ERROR: database \"lipas_test\" already exists" + (-> e .getCause .getMessage)) + (throw e)))) + + (jdbc/db-do-commands (:db config) + false + [(str "CREATE EXTENSION IF NOT EXISTS postgis") + (str "CREATE EXTENSION IF NOT EXISTS postgis_topology") + (str "CREATE EXTENSION IF NOT EXISTS citext") + (str "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"")]) + + (migratus/init migratus-opts) + (migratus/migrate migratus-opts))) + +(def tables + ["account" + "analysis_queue" + "city" + "elevation_queue" + "email_out_queue" + "integration_log" + "integration_out_queue" + "reminder" + "sports_site" + "subsidy"]) + +(defn prune-db! [] + (jdbc/execute! (:db config) [(str "TRUNCATE " + (str/join "," tables) + " RESTART IDENTITY CASCADE")])) + +;; Likely all test system components are stateless, so maybe ok to start without ever halting this. +;; But take steps to also stop the system before starting it again on each reload of this ns. +(defonce system nil) + +(alter-var-root #'system (fn [x] + (when x + (sy/stop-system! x)) + (sy/start-system! (config/->system-config config)))) + +;; These need to be redefined after each alter-var-root. +(def db (:lipas/db system)) +(def app (:lipas/app system)) +(def search (:lipas/search system)) + +(defn prune-es! [] + (let [client (:client search) + mappings {(-> search :indices :sports-site :search) (:sports-sites search/mappings) + (-> search :indices :analysis :diversity) diversity/mappings + (-> search :indices :lois :search) (:lois search/mappings)}] + + (doseq [idx-name (-> search :indices vals (->> (mapcat vals)))] + (try + (search/delete-index! client idx-name) + (catch Exception ex + (when (not= "index_not_found_exception" + (-> ex ex-data :body :error :root_cause first :type)) + (throw ex)))) + (when-let [mapping (mappings idx-name)] + (search/create-index! client idx-name mapping))))) + +(comment + (init-db!) + (prune-es!) + (prune-db!) + (ex-data *e)) + +(defn gen-user + ([] + (gen-user {:db? false :admin? false :status "active"})) + ([{:keys [db? admin? status] + :or {admin? false status "active"}}] + (let [user (-> (gen/generate (s/gen :lipas/user)) + (assoc :password (str (gensym)) :status status) + ;; Ensure :permissions is a map always, generate doesn't always add the key because it it is optional in + ;; the :lipas/user spec but required e.g. update-user-permissions endpoint. + (update :permissions (fn [permissions] + (cond-> (or permissions {}) + ;; generated result might include admin role, remove if the flag is false + (not admin?) (update :roles (fn [roles] + (into [] (remove (fn [x] (= :admin (:role x))) roles)))) + admin? (update :roles (fnil conj []) {:role :admin})))))] + (if db? + (do + (core/add-user! db user) + (assoc user :id (:id (core/get-user db (:email user))))) + user)))) + +(defn gen-loi! [] + (-> (gen/generate (s/gen :lipas.loi/document)) + (assoc :status "active") + (assoc :id (str (java.util.UUID/randomUUID)))))