From c8691807791826ea91f8191732e3aea2b19d7f00 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Mon, 16 Mar 2026 11:30:25 +0100 Subject: [PATCH 1/2] feat: mock process for natura2000 data --- backend/src/db/mod.rs | 15 +- backend/src/processes/habitat_distance.rs | 410 ++++++++++++++++++++++ backend/src/processes/mod.rs | 3 + backend/src/processes/ndvi.rs | 51 +-- backend/src/processes/parameters.rs | 50 +++ backend/src/processes/path_info.rs | 29 +- backend/src/server.rs | 37 +- backend/test-data/DE5417402.wkt | 1 + mock/.gitignore | 1 + mock/README.md | 16 + test-client/call.http | 30 ++ 11 files changed, 586 insertions(+), 57 deletions(-) create mode 100644 backend/src/processes/habitat_distance.rs create mode 100644 backend/src/processes/parameters.rs create mode 100644 backend/test-data/DE5417402.wkt create mode 100644 mock/.gitignore create mode 100644 mock/README.md diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index a494136..b273c33 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -45,7 +45,9 @@ pub async fn setup_db(config: &crate::config::Database) -> Result { let db_pool_builder = if cfg!(test) { db_pool_builder .max_size(1) // Use a single connection for tests - .connection_customizer(Box::new(TestCustomizer)) + .connection_customizer(Box::new(TestCustomizer { + schema: config.schema.clone(), + })) } else { db_pool_builder .max_size(8) // TODO: investigate a good size @@ -107,7 +109,7 @@ impl CustomizeConnection for SchemaSettingConnecti ) -> Pin> + Send + 'a>> { let schema = self.schema.clone(); Box::pin(async move { - conn.batch_execute(&format!("SET search_path TO {schema};")) + conn.batch_execute(&format!("SET search_path TO {schema},public;")) .await .map_err(PoolError::QueryError) }) @@ -120,14 +122,21 @@ impl CustomizeConnection for SchemaSettingConnecti /// /// Built after [`diesel::r2d2::TestCustomizer`] #[derive(Debug)] -struct TestCustomizer; +struct TestCustomizer { + schema: String, +} impl CustomizeConnection for TestCustomizer { fn on_acquire<'a>( &'a self, conn: &'a mut AsyncPgConnection, ) -> Pin> + Send + 'a>> { + let schema = self.schema.clone(); Box::pin(async move { + conn.batch_execute(&format!("SET search_path TO {schema},public;")) + .await + .map_err(PoolError::QueryError)?; + conn.begin_test_transaction() .await .map_err(PoolError::QueryError) diff --git a/backend/src/processes/habitat_distance.rs b/backend/src/processes/habitat_distance.rs new file mode 100644 index 0000000..1eabdd1 --- /dev/null +++ b/backend/src/processes/habitat_distance.rs @@ -0,0 +1,410 @@ +use crate::{ + db::{DbPool, PooledConnection}, + processes::parameters::{Coordinate, PointGeoJsonInput}, +}; +use anyhow::{Context, Result}; +use diesel::{ + sql_query, + sql_types::{Double, Text}, +}; +use diesel_async::RunQueryDsl; +use ogcapi::{ + processes::Processor, + types::{ + common::Link, + processes::{ + Execute, ExecuteResult, ExecuteResults, InlineOrRefData, InputValueNoObject, + JobControlOptions, Output, Process, ProcessSummary, TransmissionMode, + description::{DescriptionType, InputDescription, OutputDescription}, + }, + }, +}; +use schemars::{JsonSchema, generate::SchemaSettings}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tracing::instrument; +use utoipa::ToSchema; + +/// Calculates the Normalized Difference Vegetation Index (NDVI) and the corrected NDVI (kNDVI) from satellite imagery. +#[derive(Debug, Clone)] +pub struct HabitatDistanceProcess { + connection: DbPool, +} + +use diesel::QueryableByName; +use diesel::sql_types::Bool; + +#[derive(QueryableByName)] +struct Natura2000Exists { + #[diesel(sql_type = Bool)] + exists: bool, +} + +impl HabitatDistanceProcess { + pub async fn new(connection: DbPool) -> Result { + let this = Self { connection }; + + let mut conn = this.connection().await?; + let table: Natura2000Exists = sql_query(indoc::indoc! {" + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'Natura2000' + AND table_name = 'naturasite_polygon' + ) as exists + "}) + .get_result(&mut *conn) + .await + .context("Failed to check if Natura2000.naturasite_polygon exists")?; + + if !table.exists { + anyhow::bail!("Table Natura2000.naturasite_polygon does not exist"); + } + + // drop the pooled connection before moving `this` out + drop(conn); + + Ok(this) + } + + async fn connection(&self) -> anyhow::Result> { + self.connection + .get() + .await + .context("could not get db connection from pool") + } +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +pub struct HabitatDistanceProcessInputs { + pub coordinate: PointGeoJsonInput, +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct HabitatDistanceProcessOutputs { + pub habitat_code: Option, + pub habitat_name: Option, + pub distance_m: Option, +} + +impl From for ExecuteResults { + fn from(outputs: HabitatDistanceProcessOutputs) -> Self { + let mut result = ExecuteResults::default(); + if let Some(habitat_code) = outputs.habitat_code { + result.insert( + "habitatCode".to_string(), + ExecuteResult { + output: Output { + format: None, + transmission_mode: Default::default(), + }, + data: InlineOrRefData::InputValueNoObject(InputValueNoObject::String( + habitat_code, + )), + }, + ); + } + if let Some(habitat_name) = outputs.habitat_name { + result.insert( + "habitatName".to_string(), + ExecuteResult { + output: Output { + format: None, + transmission_mode: Default::default(), + }, + data: InlineOrRefData::InputValueNoObject(InputValueNoObject::String( + habitat_name, + )), + }, + ); + } + if let Some(distance_m) = outputs.distance_m { + result.insert( + "distanceM".to_string(), + ExecuteResult { + output: Output { + format: None, + transmission_mode: Default::default(), + }, + data: InlineOrRefData::InputValueNoObject(InputValueNoObject::Integer( + distance_m, + )), + }, + ); + } + result + } +} + +#[async_trait::async_trait] +impl Processor for HabitatDistanceProcess { + fn id(&self) -> &'static str { + "habitatDistance" + } + + fn version(&self) -> &'static str { + "0.1.0" + } + + #[allow( + clippy::too_many_lines, + reason = "description is long but better understood this way" + )] + fn process(&self) -> Result { + let mut settings = SchemaSettings::default(); + settings.meta_schema = None; + + let mut generator = settings.into_generator(); + Ok(Process { + summary: ProcessSummary { + id: self.id().into(), + version: self.version().into(), + job_control_options: vec![ + JobControlOptions::SyncExecute, + JobControlOptions::AsyncExecute, + // TODO: implement "dismiss extension" + // JobControlOptions::Dismiss, + ], + output_transmission: vec![TransmissionMode::Value], + links: vec![ + Link::new( + // TODO: ./ … does not work for some clients + format!("./{}/execution", self.id()), + "http://www.opengis.net/def/rel/ogc/1.0/execute", + ) + .title("Execution endpoint"), + ], + }, + inputs: HashMap::from([( + "coordinate".to_string(), + InputDescription { + description_type: DescriptionType { + title: Some("Coordinate in WGS84".to_string()), + description: Some( + "This is a POINT input in WGS84 (EPSG:4326) format.".to_string(), + ), + ..Default::default() + }, + schema: generator.root_schema_for::().to_value(), + ..Default::default() + }, + )]), + outputs: HashMap::from([ + ( + "habitatCode".to_string(), + OutputDescription { + description_type: DescriptionType { + title: Some( + "Habitat Code".to_string(), + ), + description: Some( + "This is the habitat code value of a Natura 2000 site. \ + This is the state of 2024.".to_string(), + ), + ..Default::default() + }, + schema: generator.root_schema_for::().to_value(), + }, + ), + ( + "habitatName".to_string(), + OutputDescription { + description_type: DescriptionType { + title: Some( + "Habitat Name".to_string(), + ), + description: Some( + "This is the human-readable habitat name.".to_string(), + ), + ..Default::default() + }, + schema: generator.root_schema_for::().to_value(), + }, + ), + ( + "habitatDistance".to_string(), + OutputDescription { + description_type: DescriptionType { + title: Some( + "Habitat Distance".to_string(), + ), + description: Some( + "This is the habitat distance value. \ + The habitat distance is calculated based on the proximity to the nearest habitat of interest. \ + The value is represented in meters.".to_string(), + ), + ..Default::default() + }, + schema: generator.root_schema_for::().to_value(), + }, + ), + ]), + }) + } + + async fn execute(&self, execute: Execute) -> Result { + let value = serde_json::to_value(execute.inputs)?; + let inputs: HabitatDistanceProcessInputs = serde_json::from_value(value)?; + + match compute_habitat_distance( + self.connection().await?, + &inputs.coordinate.value.coordinates, + ) + .await + { + Ok(outputs) => Ok(outputs.into()), + Err(_e) => Err(anyhow::anyhow!( + "The server was unable to compute the habitat distance." + )), + } + } +} + +#[derive(QueryableByName)] +struct Natura2000NearestHabitat { + #[diesel(sql_type = Text)] + sitecode: String, + #[diesel(sql_type = Text)] + sitename: String, + #[diesel(sql_type = Double)] + distance_m: f64, +} + +#[instrument(skip(connection), err(Debug))] +async fn compute_habitat_distance( + mut connection: PooledConnection<'_>, + coordinate: &Coordinate, +) -> Result { + let [lon, lat] = coordinate.0; + let point_geometry = format!("SRID=4326;POINT({lon} {lat})"); + let table: Natura2000NearestHabitat = sql_query(indoc::indoc! {" + WITH reference AS ( + SELECT ST_Transform($1::geometry, 3035) AS point + ) + SELECT s.sitecode, + s.sitename, + ST_Distance(s.geom, reference.point) AS distance_m + FROM \"Natura2000\".naturasite_polygon s, reference + ORDER BY s.geom <-> reference.point + LIMIT 1 + "}) + .bind::(point_geometry) + .get_result(&mut *connection) + .await + .context("Failed to query Natura2000.naturasite_polygon")?; + + Ok(HabitatDistanceProcessOutputs { + habitat_code: Some(table.sitecode), + habitat_name: Some(table.sitename), + distance_m: Some(table.distance_m.round() as i64), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::CONFIG; + use crate::db::setup_db; + use diesel_async::SimpleAsyncConnection; + use ogcapi::types::processes::Input; + + async fn mock_db_pool() -> DbPool { + setup_db(&CONFIG.database).await.unwrap() + } + + #[test] + fn it_deserializes_the_input() { + let json = serde_json::json!({ + "coordinate": { + "value": { + "type": "Point", + "coordinates": [12.34, 56.78] + }, + "mediaType": "application/geo+json" + }, + }); + + let inputs: HashMap = serde_json::from_value(json).unwrap(); + + let json = serde_json::to_value(&inputs).unwrap(); + + let _inputs: HabitatDistanceProcessInputs = serde_json::from_value(json).unwrap(); + } + + #[tokio::test(flavor = "multi_thread")] + async fn compute_ndvi_integration_with_mock_backend() { + let pool = mock_db_pool().await; + + // create schema / table and insert a test site + let mut conn = pool.get().await.unwrap(); + conn.batch_execute(&indoc::formatdoc! {" + CREATE SCHEMA IF NOT EXISTS \"Natura2000\"; + CREATE TABLE IF NOT EXISTS \"Natura2000\".naturasite_polygon ( + sitecode TEXT, + sitename TEXT, + geom geometry + ); + + INSERT INTO \"Natura2000\".naturasite_polygon (sitecode, sitename, geom) + VALUES ( + 'DE5417402', + 'Feldflur bei Hüttenberg und Schöffengrund', + ST_GeomFromText('{wkt}', 3035) + ); + ", + wkt = include_str!("../../test-data/DE5417402.wkt"), + }) + .await + .unwrap(); + + // consume the same connection in the computation (transaction stays open for test cleanup) + let outputs = compute_habitat_distance(conn, &Coordinate([8.46, 50.49])) + .await + .unwrap(); + + assert_eq!(outputs.habitat_code.unwrap(), "DE5417402"); + assert_eq!( + outputs.habitat_name.unwrap(), + "Feldflur bei Hüttenberg und Schöffengrund" + ); + // distance should be very small (point exactly matches) + assert_eq!(outputs.distance_m.unwrap(), 1415); + } + + #[tokio::test(flavor = "multi_thread")] + async fn process_summary_has_expected_inputs_and_outputs() { + let pool = mock_db_pool().await; + let p = HabitatDistanceProcess::new(pool).await.unwrap(); + let process = p.process().expect("to produce process description"); + + // summary id / version + assert_eq!(process.summary.id, "habitatDistance"); + assert_eq!(process.summary.version, "0.1.0"); + + // job control options contain sync and async execute + let mut has_sync = false; + let mut has_async = false; + for opt in &process.summary.job_control_options { + match opt { + JobControlOptions::SyncExecute => has_sync = true, + JobControlOptions::AsyncExecute => has_async = true, + JobControlOptions::Dismiss => todo!(), + } + } + assert!(has_sync, "expected SyncExecute in job_control_options"); + assert!(has_async, "expected AsyncExecute in job_control_options"); + + // inputs contain only coordinate + assert!(process.inputs.contains_key("coordinate")); + + // outputs contain habitatCode, habitatName and habitatDistance + assert!(process.outputs.contains_key("habitatCode")); + assert!(process.outputs.contains_key("habitatName")); + assert!(process.outputs.contains_key("habitatDistance")); + + // some basic checks for descriptions and schema presence + let habitat_distance_output = &process.outputs["habitatDistance"]; + assert!(habitat_distance_output.schema.is_object()); + } +} diff --git a/backend/src/processes/mod.rs b/backend/src/processes/mod.rs index 0f76395..3768bc6 100644 --- a/backend/src/processes/mod.rs +++ b/backend/src/processes/mod.rs @@ -1,5 +1,8 @@ +mod habitat_distance; mod ndvi; +mod parameters; mod path_info; +pub use habitat_distance::HabitatDistanceProcess; pub use ndvi::NDVIProcess; pub use path_info::ProcessesOpenApiSpec; diff --git a/backend/src/processes/ndvi.rs b/backend/src/processes/ndvi.rs index 0da5350..20029d0 100644 --- a/backend/src/processes/ndvi.rs +++ b/backend/src/processes/ndvi.rs @@ -9,8 +9,8 @@ use geoengine_openapi_client::{ GdalSource, GdalSourceParameters, GeoJson, Measurement, MockPointSource, MockPointSourceParameters, Names, RasterBandDescriptor, RasterDataType, RasterOperator, RasterVectorJoin, RasterVectorJoinParameters, SingleRasterSource, - SingleVectorMultipleRasterSources, SpatialPartition2D, TemporalAggregationMethod, - VectorOperator, WfsRequest, WfsService, + SingleVectorMultipleRasterSources, TemporalAggregationMethod, VectorOperator, WfsRequest, + WfsService, }, }; use ogcapi::{ @@ -32,6 +32,7 @@ use utoipa::ToSchema; use crate::{ config::CONFIG, + processes::parameters::{Coordinate, PointGeoJsonInput, ToBbox}, state::USER, util::{error_response, to_api_workflow}, }; @@ -49,52 +50,6 @@ pub struct NDVIProcessInputs { pub month: Month, } -#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] -#[serde(transparent)] -pub struct Coordinate(pub [f64; 2]); - -trait ToBbox { - fn to_bbox(&self, buffer: f64) -> SpatialPartition2D; -} - -impl ToBbox for Coordinate { - fn to_bbox(&self, buffer: f64) -> SpatialPartition2D { - use geoengine_openapi_client::models::Coordinate2D; - - let [x, y] = self.0; - SpatialPartition2D::new( - Coordinate2D::new(x + buffer, y - buffer), - Coordinate2D::new(x - buffer, y + buffer), - ) - } -} - -#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PointGeoJsonInput { - pub value: PointGeoJson, - pub media_type: PointGeoJsonInputMediaType, -} - -#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] -#[serde(rename_all = "camelCase")] -pub enum PointGeoJsonInputMediaType { - #[serde(rename = "application/geo+json")] - GeoJson, -} - -#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct PointGeoJson { - pub r#type: PointGeoJsonType, - pub coordinates: Coordinate, -} - -#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] -pub enum PointGeoJsonType { - Point, -} - #[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema, Copy, Clone)] pub struct Year(u16); diff --git a/backend/src/processes/parameters.rs b/backend/src/processes/parameters.rs new file mode 100644 index 0000000..ff4e29e --- /dev/null +++ b/backend/src/processes/parameters.rs @@ -0,0 +1,50 @@ +use geoengine_openapi_client::models::SpatialPartition2D; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +#[serde(transparent)] +pub struct Coordinate(pub [f64; 2]); + +pub trait ToBbox { + fn to_bbox(&self, buffer: f64) -> SpatialPartition2D; +} + +impl ToBbox for Coordinate { + fn to_bbox(&self, buffer: f64) -> SpatialPartition2D { + use geoengine_openapi_client::models::Coordinate2D; + + let [x, y] = self.0; + SpatialPartition2D::new( + Coordinate2D::new(x + buffer, y - buffer), + Coordinate2D::new(x - buffer, y + buffer), + ) + } +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct PointGeoJsonInput { + pub value: PointGeoJson, + pub media_type: PointGeoJsonInputMediaType, +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +#[serde(rename_all = "camelCase")] +pub enum PointGeoJsonInputMediaType { + #[serde(rename = "application/geo+json")] + GeoJson, +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct PointGeoJson { + pub r#type: PointGeoJsonType, + pub coordinates: Coordinate, +} + +#[derive(Deserialize, Serialize, Debug, JsonSchema, ToSchema)] +pub enum PointGeoJsonType { + Point, +} diff --git a/backend/src/processes/path_info.rs b/backend/src/processes/path_info.rs index 79c9e99..f27c750 100644 --- a/backend/src/processes/path_info.rs +++ b/backend/src/processes/path_info.rs @@ -4,7 +4,10 @@ use std::collections::HashMap; -use crate::processes::ndvi::{NDVIProcessInputs, NDVIProcessOutputs}; +use crate::processes::{ + habitat_distance::{HabitatDistanceProcessInputs, HabitatDistanceProcessOutputs}, + ndvi::{NDVIProcessInputs, NDVIProcessOutputs}, +}; use axum::Json; use ogcapi::types::processes::Response; use serde::Deserialize; @@ -32,10 +35,32 @@ pub struct NDVIProcessParams { )] fn execute_ndvi(Json(_input): Json) {} +/// Process execution +#[allow(unused, reason = "Placeholder for spec only")] +// TODO: macro for generating this from the process definition +#[derive(Deserialize, ToSchema, Debug)] +pub struct HabitatDistanceProcessParams { + pub inputs: HabitatDistanceProcessInputs, + #[serde(default)] + #[allow(clippy::zero_sized_map_values, reason = "Placeholder for spec only")] + pub outputs: HashMap, + #[serde(default)] + pub response: Response, +} + +#[allow(unused, reason = "Placeholder for spec only")] +#[utoipa::path( + post, + path = "/processes/habitatDistance/execution", + tag = "Processes", + responses((status = OK, body = HabitatDistanceProcessOutputs)) +)] +fn execute_habitat_distance(Json(_input): Json) {} + /// OpenAPI extension to include process endpoints in the generated documentation #[allow(unused, reason = "Placeholder for spec only")] #[derive(OpenApi)] -#[openapi(paths(execute_ndvi))] +#[openapi(paths(execute_ndvi, execute_habitat_distance))] pub struct ProcessesOpenApiSpec; #[cfg(test)] diff --git a/backend/src/server.rs b/backend/src/server.rs index 8b6a628..6bdd796 100644 --- a/backend/src/server.rs +++ b/backend/src/server.rs @@ -5,11 +5,11 @@ use crate::{ db::setup_db, handler, jobs::JobHandler, - processes::{NDVIProcess, ProcessesOpenApiSpec}, + processes::{HabitatDistanceProcess, NDVIProcess, ProcessesOpenApiSpec}, state::spawn_with_user, }; use ogcapi::{ - processes::echo::Echo, + processes::{Processor, echo::Echo}, services::{self as ogcapi_services}, }; use std::mem; @@ -32,6 +32,9 @@ pub async fn server() -> anyhow::Result { .get_openapi_mut() .merge(ProcessesOpenApiSpec::openapi()); + let mut processors: Vec> = vec![Box::new(Echo), Box::new(NDVIProcess)]; + add_habitat_distance_process(&mut processors, db_pool.clone()).await; + let drivers = ogcapi_services::Drivers { jobs: Box::new(JobHandler::new(db_pool).await?), collections: Box::new(NoCollectionTransactions), @@ -39,10 +42,16 @@ pub async fn server() -> anyhow::Result { let ogcapi_state = ogcapi_services::AppState::new(drivers) .await - .processors(vec![Box::new(Echo), Box::new(NDVIProcess)]) + .processors(processors) .with_spawn_fn(spawn_with_user); - let mut service = ogcapi_services::Service::try_new(&(&CONFIG.server).into(), ogcapi_state) + let mut server_cfg: ogcapi_services::Config = (&CONFIG.server).into(); + if cfg!(test) { + // Use ephemeral port in tests to avoid "address already in use" when creating the service + server_cfg.port = 0; + } + + let mut service = ogcapi_services::Service::try_new(&server_cfg, ogcapi_state) .await? .processes_api(); @@ -72,3 +81,23 @@ fn add_openapi_info(openapi: &mut OpenApi) { .license(None) // TODO: add link to license .build(); } + +async fn add_habitat_distance_process( + processors: &mut Vec>, + db_pool: crate::db::DbPool, +) { + match HabitatDistanceProcess::new(db_pool).await { + Ok(habitat_distance_process) => { + tracing::info!( + "Successfully initialized HabitatDistanceProcess, adding it to the list of available processes." + ); + processors.push(Box::new(habitat_distance_process)); + } + Err(err) => { + tracing::warn!( + "Failed to initialize HabitatDistanceProcess, skipping it: {:#}", + err + ); + } + } +} diff --git a/backend/test-data/DE5417402.wkt b/backend/test-data/DE5417402.wkt new file mode 100644 index 0000000..b3f0107 --- /dev/null +++ b/backend/test-data/DE5417402.wkt @@ -0,0 +1 @@ +POLYGON((4217993.037599999 3046250.4839999974,4217992.5756 3046254.7606000016,4217992.442600001 3046256.0124999923,4217972.331900001 3046330.5732999947,4217968.6053 3046344.125299999,4217949.5088 3046415.2919000015,4217944.4899 3046433.842,4217913.559 3046548.2738000015,4217907.537900001 3046554.136900003,4217839.7567 3046569.3189000017,4217761.8259 3046586.8205000013,4217713.009299999 3046597.7718,4217684.020199999 3046604.7005000007,4217609.5616 3046622.334399997,4217599.8517 3046624.6279000035,4217589.4944 3046627.120399998,4217585.506899999 3046628.0751999975,4217576.126 3046630.2942,4217560.0462 3046634.115400006,4217535.1338 3046640.028000002,4217528.933 3046641.4932000004,4217524.197799999 3046642.6184000024,4217523.0414 3046642.8943000026,4217511.038699999 3046645.739300006,4217484.252 3046652.087699999,4217479.217599999 3046653.2769000027,4217474.775699999 3046653.907999999,4217464.444499999 3046655.370000007,4217448.2425999995 3046657.572700007,4217439.7074 3046658.730100002,4217429.764699999 3046660.076699999,4217410.8674 3046662.636500003,4217403.719799999 3046663.6048000026,4217364.427999999 3046668.9349000035,4217341.7574000005 3046672.016500001,4217335.272500001 3046664.3351999954,4217410.5550999995 3046451.658000001,4217414.9857 3046439.1363999997,4217449.228599999 3046313.7784000016,4217450.0557 3046310.6069000014,4217453.161599999 3046299.2234999947,4217483.7687 3046188.2165000006,4217467.581599999 3046183.0184000097,4217463.729900001 3046184.031400002,4217428.051999999 3046160.8700999958,4217413.169299999 3046151.203999993,4217406.580499999 3046146.924300001,4217401.4649 3046143.6044000015,4217396.4099 3046140.3236000016,4217382.5967 3046131.3528000056,4217373.979900001 3046125.7609,4217360.1467 3046116.7804000024,4217345.647399999 3046107.369000002,4217332.3791000005 3046098.7508000014,4217323.0359000005 3046092.688800002,4217321.26 3046091.533099999,4217318.795 3046088.9667999987,4217312.576199999 3046082.471900003,4217307.5243 3046077.2110000034,4217245.1557 3046012.193900003,4217233.905300001 3046000.4578000014,4217225.9013 3045992.1172000035,4217219.5001 3045985.4447999997,4217209.254000001 3045974.754899996,4217202.233999999 3045967.4409000007,4217201.921399999 3045959.884800002,4217232.7234000005 3045921.309299997,4217246.990499999 3045903.4422000023,4217253.4878 3045903.1929000015,4217246.0066 3045895.0552000026,4217249.298900001 3045890.0397,4217190.0956999995 3045851.4868000024,4217166.6097 3045836.1925000027,4217105.126700001 3045802.5451999977,4217069.9922 3045783.3168000085,4217037.0441 3045765.288400001,4217027.294299999 3045760.369000003,4217014.2436 3045753.784000003,4216979.5273 3045736.2668999974,4216968.0304000005 3045730.4655000023,4216957.32 3045725.061300003,4216945.093900001 3045719.549,4216828.740599999 3045667.134500001,4216827.1853 3045663.8057000027,4216825.4538 3045660.029299999,4216837.2546 3045621.6648000013,4216837.077500001 3045599.765999999,4216836.8946 3045584.807600002,4216830.8795 3045582.2700999985,4216826.1952 3045585.6745999996,4216733.4794 3045555.7468000054,4216717.2502 3045551.5095000034,4216687.9076000005 3045541.962100001,4216654.5989 3045531.2591000055,4216651.203299999 3045530.135699999,4216643.388599999 3045527.6329999994,4216628.7456 3045525.3140000007,4216602.8226 3045529.3515000013,4216547.478700001 3045537.971400007,4216532.155300001 3045541.077199997,4216468.0887 3045554.0632999963,4216451.8617 3045557.356400006,4216448.3906 3045558.0441999994,4216439.148499999 3045560.171299998,4216425.3026 3045562.7916999976,4216416.939200001 3045536.065000004,4216405.7654 3045511.5470999978,4216397.0306 3045499.4663999975,4216369.4998 3045479.7235000017,4216349.1811 3045470.3922000034,4216334.383199999 3045462.5550999995,4216310.593699999 3045452.451400004,4216295.3597 3045441.2200000035,4216276.266799999 3045424.201400001,4216249.8434 3045400.563100005,4216248.4475 3045399.402200003,4216244.731899999 3045396.0730000054,4216223.1786 3045376.8280999996,4216210.554400001 3045365.560899998,4216202.3254 3045359.0635999986,4216200.768300001 3045357.814900001,4216193.192 3045354.4588,4216135.649599999 3045328.9279999984,4216114.2161 3045319.9919999996,4216100.275900001 3045314.183199994,4216076.522500001 3045306.0092,4216071.257300001 3045304.1913999976,4216048.811000001 3045296.5294000003,4216043.0075 3045294.109000002,4216037.173699999 3045291.6589999995,4216017.283299999 3045277.781500004,4215989.645199999 3045239.799100002,4215964.7797 3045209.369000005,4215933.0383 3045174.123100002,4215889.2742 3045123.301499998,4215880.1699 3045112.735999999,4215844.122199999 3045075.589200001,4215818.9179 3045049.6640999997,4215814.059599999 3045044.670500002,4215795.488600001 3045025.574600006,4215780.183499999 3045009.8340000003,4215726.1852 3044941.8221000037,4215706.533600001 3044915.3205999983,4215693.0833 3044899.924500001,4215687.809 3044894.4867000007,4215688.2283 3044887.8004999994,4215689.514 3044886.732800002,4215707.5594 3044872.254000001,4215731.524700001 3044862.8741000006,4215753.994999999 3044850.1944999998,4215771.7576 3044836.239600001,4215776.163899999 3044835.1989999944,4215780.590399999 3044834.1680999985,4215846.305 3044848.005800003,4215869.2228 3044852.831200001,4215897.7247 3044860.119899999,4215942.6076 3044871.604599998,4215944.0163 3044865.774099999,4215942.2049 3044862.7388,4215940.4037 3044859.7134000007,4215952.1699 3044814.379000002,4215956.9033 3044796.162899996,4215961.6458 3044777.8866000017,4215963.9361000005 3044769.0446000006,4215978.0024999995 3044714.8582000006,4215937.706499999 3044700.7211,4215936.7897 3044695.803400004,4215966.1138 3044670.8189999997,4215994.5086 3044646.627300002,4216009.648499999 3044633.6755000013,4216012.724300001 3044634.0813000007,4216019.813200001 3044627.827500006,4216022.9627 3044627.545199999,4216033.4309 3044639.7141,4216039.6896 3044644.1363,4216101.0813 3044665.611300002,4216103.304300001 3044659.4730000007,4216140.7831999995 3044533.0505999993,4216145.621300001 3044516.6280000038,4216132.5644000005 3044521.4028000007,4216105.869200001 3044534.5004000035,4216080.6993 3044550.917300001,4216041.614 3044580.3161000013,4216025.8379 3044588.8334000004,4216018.829600001 3044591.9599000006,4216014.1636 3044590.0839000023,4216011.6393 3044589.0484999972,4215981.376 3044554.3924000063,4215965.452099999 3044535.060100003,4215948.966399999 3044515.6054999987,4215930.135199999 3044497.9733000044,4215908.930400001 3044482.303700007,4215885.7523 3044470.0916000013,4215861.185799999 3044462.4286999996,4215784.465500001 3044439.2021000073,4215786.360099999 3044434.5754000004,4215784.707900001 3044431.477900003,4215783.0052000005 3044428.341100002,4215815.711300001 3044320.939700003,4215810.8652 3044319.7619000007,4215804.6691 3044323.047299995,4215788.0823 3044317.8004000033,4215769.7958 3044312.0159000023,4215750.289999999 3044305.8436000035,4215700.912900001 3044290.2212000014,4215679.096999999 3044283.320700004,4215668.9725 3044280.119600002,4215636.539799999 3044269.8547,4215635.0273 3044264.5152000072,4215658.0178 3044234.1575000044,4215672.4965 3044207.407000005,4215686.916200001 3044175.5769000025,4215693.7486000005 3044172.7529000016,4215700.9417 3044175.134199999,4215711.017999999 3044178.4759000023,4215732.7379 3044185.6678000013,4215781.914000001 3044201.9529000064,4215784.9968 3044202.9737000014,4215786.905300001 3044198.324100002,4215858.428099999 3044038.7519000047,4215843.8179 3044034.422500003,4215765.736199999 3044011.3142000004,4215758.858100001 3044014.4589000004,4215752.967 3044012.906000003,4215749.5569 3044020.337,4215738.017100001 3044045.507100004,4215731.672900001 3044059.2551000016,4215719.862400001 3044084.8908999944,4215711.067500001 3044103.9809,4215709.487400001 3044107.672800002,4215688.097100001 3044157.939700003,4215681.4155 3044160.8217,4215631.0745 3044141.9724000013,4215567.816400001 3044118.2804000014,4215536.4036 3044106.521400004,4215532.3927 3044099.846200002,4215526.442399999 3044099.1379000014,4215518.022299999 3044133.1056000013,4215503.263 3044169.390600005,4215491.3301 3044213.497100002,4215485.4637 3044221.9182000007,4215481.7404 3044230.5499,4215476.892200001 3044241.777200001,4215474.2259 3044247.964200001,4215471.724400001 3044253.7688999977,4215469.4263 3044259.090800007,4215466.624299999 3044265.5996999997,4215463.899800001 3044271.917500002,4215462.0865 3044276.112699999,4215460.514900001 3044279.6945000067,4215463.144300001 3044289.208900004,4215454.8617 3044302.353500008,4215442.672599999 3044321.6821000003,4215435.165899999 3044323.4554,4215414.3211 3044313.6813000003,4215412.1471 3044312.661100002,4215392.214299999 3044299.3443000023,4215382.801999999 3044291.8731999984,4215372.520199999 3044283.714099994,4215369.956800001 3044281.2692000037,4215362.4187 3044274.0923999986,4215338.1413 3044250.8847,4215322.0593 3044232.434600003,4215318.9318 3044228.2174000037,4215309.8959 3044216.060800002,4215303.489600001 3044207.5384000028,4215292.2245000005 3044192.502400001,4215277.145199999 3044172.768500002,4215245.350099999 3044127.6728000054,4215233.796 3044111.240700003,4215190.9167 3044050.4365000045,4215162.115900001 3044009.3200000008,4215144.3619 3043989.2628000025,4215139.3814 3043984.100999999,4215131.9761 3043976.3923000097,4215083.549000001 3043943.0760000036,4215036.8631 3043921.6164999995,4215000.4505 3043895.9055000073,4214993.425000001 3043888.9216000014,4214982.0601 3043877.5872000013,4214976.6085 3043870.8817000007,4214967.4043000005 3043859.587600003,4214952.479 3043839.4016000032,4214942.6236000005 3043817.7057999996,4214933.8478 3043795.9651999967,4214926.455600001 3043771.5354,4214921.8989 3043756.337099997,4214911.1172 3043723.123400003,4214902.6129 3043699.2789,4214892.8561 3043673.7915999983,4214876.380799999 3043629.295499999,4214867.056600001 3043596.6218000012,4214864.8302 3043582.1615999993,4214860.854900001 3043557.474799997,4214853.0801 3043510.7389999996,4214852.112199999 3043505.0020000003,4214848.604800001 3043483.849000004,4214840.3487 3043434.8096000007,4214835.968900001 3043409.7884000037,4214830.3782 3043383.983800001,4214822.0995000005 3043359.0762,4214811.9679000005 3043336.184100006,4214801.011499999 3043313.6834000004,4214785.0438 3043288.1715000025,4214764.8532 3043258.7973000025,4214743.497400001 3043231.9993000003,4214748.646400001 3043230.408500002,4214764.352399999 3043225.572300006,4214789.1535 3043216.6109,4214806.069499999 3043216.9284000006,4214829.6676 3043221.0243000058,4214854.200200001 3043227.6674000015,4214898.3904 3043245.3520000004,4214900.831900001 3043238.4871000047,4214902.444599999 3043233.514599993,4214903.693399999 3043229.7272999976,4214907.1603999995 3043219.1590000014,4214869.2849 3043175.5571000036,4214872.3956 3043170.4241000023,4214877.932499999 3043167.9378000023,4214876.360099999 3043161.139100003,4214881.109999999 3043153.7233000025,4214894.2892 3043133.140999996,4214896.6778 3043129.4109999994,4214906.602600001 3043113.9107000018,4214931.392000001 3043032.5852999985,4214944.452 3042989.7234000047,4214954.440099999 3042984.3458000016,4214957.8092 3042973.1988000004,4214942.5733 3042968.4580000024,4214852.489800001 3042940.8846000014,4214830.210999999 3042935.168600002,4214809.796800001 3042929.930899999,4214754.8017 3042918.9262000015,4214749.8475 3042917.914200005,4214783.416300001 3042849.008899998,4214791.862 3042831.671800008,4214823.833699999 3042765.938600003,4214835.947000001 3042741.020700002,4214841.6196 3042727.1620000014,4214857.947000001 3042667.8642000016,4214859.6041 3042661.741100001,4214848.918199999 3042651.067399999,4214844.698000001 3042647.405199997,4214837.5944 3042641.2925000023,4214838.5811 3042577.860300005,4214838.952299999 3042553.996600004,4214834.0041000005 3042552.8368000044,4214828.944800001 3042558.0867000003,4214809.6095 3042559.0965000023,4214788.3993 3042560.204100002,4214751.261700001 3042562.1446999996,4214704.7598 3042564.574000006,4214654.1304 3042567.2201000093,4214639.0395 3042568.0076000034,4214635.357899999 3042566.448100002,4214628.467800001 3042563.5426999955,4214625.6032 3042538.1605999963,4214612.695800001 3042497.9057999994,4214606.906400001 3042499.465500008,4214594.3816 3042502.1577999922,4214563.4175 3042507.9937000014,4214560.3445 3042503.375700001,4214552.4396 3042502.334300003,4214550.5077 3042491.1502999975,4214505.051100001 3042484.3847000045,4214505.0515 3042484.3924000002,4214500.0165 3042483.5395000023,4214501.649900001 3042517.4934000075,4214499.2522 3042539.797600001,4214496.854800001 3042562.1218,4214491.583900001 3042581.2652999996,4214489.9254 3042587.2885000035,4214486.387599999 3042590.4272999973,4214449.066299999 3042573.65940001,4214446.4802 3042572.4948999984,4214446.9451 3042571.3784000007,4214429.5943 3042572.9269999987,4214424.4816 3042580.8777000024,4214421.8641 3042585.5040000025,4214413.911900001 3042600.8841999997,4214410.9988 3042605.1045000013,4214401.843599999 3042601.1000999995,4214403.969900001 3042607.451200001,4214401.8211 3042611.241000002,4214393.8168 3042633.102200001,4214389.039999999 3042647.3787,4214383.071599999 3042665.971800003,4214374.118899999 3042694.596500003,4214369.2147 3042709.8048,4214361.271500001 3042726.5849000034,4214357.7916 3042728.0928000035,4214354.3617 3042729.600100001,4214348.346000001 3042727.7526999996,4214336.8671 3042724.2302999995,4214325.990499999 3042720.8796000015,4214319.181500001 3042718.7931000027,4214311.4487 3042716.4393000077,4214298.393200001 3042712.448499995,4214284.544500001 3042708.228700003,4214276.0485 3042705.6352999997,4214261.667400001 3042701.2428000025,4214210.955 3042685.7520999988,4214177.9136 3042675.6588,4214101.768200001 3042652.398299998,4214087.4363 3042647.9451,4214014.1097 3042624.431900005,4214008.931 3042617.9327000016,4214003.9287 3042617.947,4214002.6601 3042603.1681000013,4213998.112 3042589.3398000007,4213990.0273 3042569.1499,4213980.2708 3042550.312899999,4213968.698100001 3042533.251099992,4213930.705399999 3042483.2105999975,4213901.7853999995 3042442.8860000037,4213898.818600001 3042438.7265000017,4213850.8168 3042371.872700002,4213830.043 3042345.2169000017,4213816.532500001 3042326.1115000043,4213790.1512 3042287.1520999987,4213783.7225 3042277.7099000006,4213764.661599999 3042298.4332000003,4213750.4364 3042318.799900004,4213725.8442 3042350.5198000004,4213719.9266 3042362.5418000007,4213704.495200001 3042429.637700004,4213668.6183 3042585.6798000014,4213665.6623 3042598.5311000003,4213656.5208 3042638.2990000024,4213653.7873 3042639.5366999996,4213650.9747 3042640.8354000007,4213647.7085 3042647.0406999984,4213649.7996 3042653.742300001,4213562.4045 3042819.883299998,4213555.423800001 3042833.1599999983,4213498.8978 3042940.692300004,4213467.8353 3042999.7837000014,4213460.7064 3043002.871900003,4213427.332800001 3042993.2702000025,4213409.8026 3042983.0707,4213372.164899999 3042972.4676000006,4213367.477700001 3042972.197000002,4213351.616699999 3042971.2800999978,4213334.925899999 3042972.0895999996,4213294.668299999 3042974.0433,4213287.164000001 3042973.046399999,4213266.7349 3042970.317100001,4213196.0088 3042911.4263000004,4213137.896299999 3042863.0327000017,4213131.0514 3042857.566499999,4213101.579399999 3042834.050400003,4213093.672700001 3042827.718800005,4213118.92 3042774.258600004,4213150.729800001 3042695.126800003,4213157.111300001 3042682.6583000026,4213164.9953000005 3042671.099300007,4213210.329600001 3042613.5827000006,4213263.9068 3042548.1023000013,4213327.1501 3042484.469100001,4213335.8222 3042475.7094000005,4213349.8829 3042455.0149000036,4213362.0077 3042433.1669999976,4213371.5645 3042410.0042999987,4213379.614499999 3042386.352299998,4213424.669299999 3042249.255100001,4213430.8005 3042205.7983999974,4213441.2422 3042126.230400002,4213445.257999999 3042112.6244,4213449.5956 3042099.1440000013,4213455.090299999 3042086.177799999,4213461.174799999 3042073.1933999998,4213469.9647 3042062.0619,4213542.6613 3042007.439300001,4213551.2765999995 3042008.5009000013,4213567.3366 3041998.0595,4213568.529100001 3041991.6027000016,4213555.2564 3041978.234500003,4213550.048 3041976.916000001,4213544.9701000005 3041975.6358000007,4213482.8037 3041918.8774000024,4213466.568600001 3041900.9296000013,4213452.839400001 3041880.4573000018,4213443.453400001 3041860.9152000016,4213419.245200001 3041854.5978000015,4213414.932600001 3041858.1273000017,4213390.8072999995 3041850.8406000067,4213332.1248 3041833.1145000006,4213318.8289 3041829.0971000036,4213292.031300001 3041813.264699999,4213279.328199999 3041805.758900002,4213275.0132 3041803.2081000023,4213253.864800001 3041793.9084000005,4213241.434699999 3041788.0990000004,4213221.9924 3041779.965900003,4213203.6162 3041772.2782000005,4213162.3981 3041755.7739999997,4213158.536699999 3041754.227000002,4213156.5353 3041753.4045000006,4213045.5032 3041735.6603000024,4213033.786599999 3041733.7912999974,4213003.543 3041724.1667000046,4213000.4461 3041722.9491999988,4212910.317 3041687.576600007,4212903.3792 3041684.8419000027,4212844.391799999 3041641.9907000028,4212830.946 3041635.0251999917,4212789.6314 3041613.622200003,4212787.8935 3041607.895699999,4212800.9737 3041574.634000002,4212802.950200001 3041552.2456000005,4212803.0143 3041548.864600002,4212799.438100001 3041546.2236000025,4212795.244899999 3041546.0212000017,4212759.5975 3041544.271400003,4212714.1165 3041553.3873000024,4212695.1676 3041563.9285000004,4212685.4726 3041568.792100002,4212653.932399999 3041571.966000001,4212631.116699999 3041603.4214999983,4212598.7574000005 3041633.9382000025,4212586.5208 3041633.546399998,4212528.2533 3041613.526600004,4212527.7543 3041608.4432000006,4212524.741900001 3041612.714900001,4212515.604900001 3041608.5803000005,4212488.320800001 3041597.1319000013,4212404.581599999 3041561.9946000017,4212390.761499999 3041556.1943000033,4212263.9158 3041502.915899996,4212262.093 3041496.830599998,4212280.9277 3041469.0201000054,4212293.148 3041450.962000002,4212301.2916 3041438.9283999945,4212320.498500001 3041410.5427000006,4212330.636299999 3041395.562400001,4212339.9407 3041381.8137,4212358.3829 3041354.558599999,4212365.8993 3041352.0251000025,4212366.586300001 3041345.9053,4212362.327199999 3041339.363499999,4212363.584100001 3041327.335600001,4212368.1779 3041283.3500000024,4212369.602399999 3041269.6596000036,4212373.3638 3041233.615900004,4212373.8390999995 3041229.063200004,4212377.8871 3041190.331400005,4212379.3478999995 3041176.3705000062,4212381.038000001 3041160.146400005,4212386.2618 3041156.7044,4212387.573999999 3041151.888800001,4212334.7215 3041134.436000003,4212321.4537 3041130.054200001,4212308.0671999995 3041125.2580000004,4212181.5283 3041080.5858999994,4212177.207900001 3041073.9550000024,4212182.972999999 3041002.781800003,4212178.9025 3040991.7172,4212171.137399999 3040989.9239000017,4212175.392999999 3040968.1942000007,4212195.3671 3040974.2397999987,4212209.3697 3040973.5871999986,4212204.3478 3040957.2654000004,4212194.6697 3040948.638000003,4212199.906400001 3040948.356000002,4212216.3288 3040927.0290000006,4212227.4232 3040932.4266999993,4212231.010299999 3040927.767100002,4212232.0814 3040926.362299999,4212241.6426 3040933.011100003,4212249.053200001 3040938.1595000024,4212270.649700001 3040903.0905999984,4212277.3005 3040909.051900001,4212279.705800001 3040904.9461000026,4212279.4892 3040898.5688000033,4212308.660499999 3040863.0556000024,4212317.554500001 3040852.222699998,4212326.615800001 3040841.1975000007,4212329.3236 3040838.060100002,4212332.782400001 3040831.312200002,4212360.0901 3040778.5937000033,4212365.9605 3040773.422700007,4212415.2369 3040752.8739000023,4212449.586100001 3040738.550700006,4212470.142100001 3040729.9876000006,4212494.665899999 3040719.759700001,4212497.141100001 3040722.3358000065,4212501.7377 3040719.8425,4212506.9771 3040716.8103000037,4212598.772 3040730.478600001,4212610.1262 3040732.172600002,4212720.098200001 3040745.4909000006,4212723.1591 3040749.219,4212710.4805 3040787.7554999967,4212704.6216 3040805.5770000005,4212692.0854 3040846.501599999,4212690.103 3040852.969199998,4212687.7107 3040860.776699995,4212691.0504 3040861.616699999,4212696.229900001 3040858.5953,4212753.6807 3040875.9062,4212793.6984 3040887.9665,4212815.3968 3040894.5059999973,4212816.3628 3040891.065,4212836.257200001 3040824.887800003,4212839.338300001 3040814.624899997,4212855.2041 3040761.8538000006,4212894.982100001 3040768.0372000025,4212936.574200001 3040774.5056000003,4212938.98 3040778.602700002,4212945.6501 3040780.031100001,4212950.8335 3040776.5596,4213059.068499999 3040793.7920999993,4213062.1087 3040798.210600004,4213066.840399999 3040795.3454,4213071.556299999 3040792.800400004,4213130.7519000005 3040820.0378999985,4213137.9354 3040819.9025999955,4213137.916200001 3040819.5593000026,4213138.640699999 3040819.8894,4213208.6149 3040852.098899998,4213210.004799999 3040856.4999999953,4213223.6205 3040899.5752000017,4213224.8266 3040904.275000007,4213236.5516 3040903.9875999996,4213258.6565000005 3040904.173599999,4213260.118799999 3040896.2230999996,4213261.0263 3040874.6494000005,4213262.764699999 3040852.404300004,4213267.613399999 3040837.5267999996,4213272.457 3040825.959600003,4213275.4892 3040818.7275000014,4213281.804199999 3040806.5100000016,4213283.080800001 3040804.042300008,4213292.712099999 3040793.7393000033,4213315.335999999 3040769.5269,4213325.7245000005 3040761.223600006,4213340.8421 3040749.1350999987,4213347.690400001 3040745.270700004,4213358.677300001 3040739.0593000012,4213401.2969 3040718.062100004,4213397.5012 3040716.1942000054,4213395.148600001 3040713.8064,4213396.593900001 3040711.2364000035,4213400.6559999995 3040703.9902000013,4213394.618899999 3040695.4027000023,4213374.577400001 3040675.537199995,4213375.093699999 3040670.099800004,4213401.8632 3040645.5304000047,4213404.782 3040643.2101000007,4213408.8102 3040640.094600005,4213414.364499999 3040638.158099998,4213420.0067 3040639.010600001,4213436.0024999995 3040651.111300003,4213450.2042 3040651.8660000027,4213476.8364 3040658.460199994,4213489.6307 3040661.6244999957,4213507.7491 3040670.9558000006,4213531.2356 3040681.573500002,4213559.6634 3040683.402699995,4213595.658600001 3040685.717900001,4213634.285700001 3040697.7374000037,4213648.2158 3040702.0661000027,4213701.303099999 3040687.4453999973,4213765.2872 3040713.797099999,4213772.4682 3040716.758499999,4213785.0134 3040714.835900001,4213819.255999999 3040709.6048000036,4213840.8136 3040706.3083000006,4213922.802999999 3040681.2995999996,4213989.0320999995 3040711.680700004,4213996.791099999 3040715.2342000036,4214021.6657 3040707.2718000016,4214075.2842999995 3040706.0145,4214138.9564 3040724.8402000014,4214151.1766 3040728.4524000045,4214218.0921 3040748.2435,4214226.1151 3040750.6132999957,4214250.631100001 3040760.4468,4214258.032 3040763.4052,4214267.172800001 3040767.0797000085,4214408.0437 3040823.5561999995,4214410.417099999 3040829.6839,4214404.679 3040855.6741999993,4214396.2838 3040893.7017000015,4214389.568 3040925.7658000034,4214383.368899999 3040955.372599999,4214375.8639 3040991.187700004,4214375.394300001 3040993.434299999,4214374.924699999 3040995.6809,4214373.0733 3041004.4368000026,4214361.5813 3041058.937800001,4214359.420399999 3041069.208100007,4214383.881200001 3041097.0933000003,4214398.681500001 3041113.9607999995,4214393.3047 3041122.345200002,4214393.8824000005 3041127.337500001,4214397.276900001 3041132.8011000026,4214394.303099999 3041157.6134,4214382.5251 3041164.4857000005,4214361.9059 3041158.068800002,4214330.7075 3041158.4277000027,4214328.5842 3041160.4170000013,4214317.828 3041170.365500002,4214314.7192 3041191.6205000025,4214321.659499999 3041192.563999999,4214356.4234 3041199.216400003,4214389.783500001 3041210.7585000023,4214408.3013 3041218.564300005,4214507.481000001 3041264.9534000005,4214547.217499999 3041282.8182000006,4214624.1765 3041316.062100001,4214670.5089 3041335.0461999974,4214693.1007 3041343.846200001,4214749.182499999 3041364.1863999977,4214763.2839 3041369.3428000025,4214831.506999999 3041394.106400001,4214854.4627 3041403.1914000046,4214877.156099999 3041412.8499999996,4214898.5592 3041423.2363999994,4214908.893300001 3041429.3545999997,4214915.8313 3041439.4698000024,4214909.6262 3041446.515499997,4214880.9979 3041451.2893000008,4214856.2324 3041455.4999999995,4214832.6044 3041459.5250000004,4214814.8489 3041461.4691000013,4214740.3314 3041469.6039,4214735.473999999 3041470.5707000024,4214711.496300001 3041475.3506000093,4214696.6186 3041480.06540001,4214682.677300001 3041484.497199997,4214606.5966 3041514.5346999983,4214601.2015 3041516.4820000045,4214595.716 3041518.461500001,4214597.036599999 3041532.2971000015,4214601.761700001 3041535.5923999986,4214601.878900001 3041549.3914999994,4214602.021500001 3041565.800500002,4214602.228399999 3041590.3430000003,4214602.3489 3041604.6780999997,4214602.771500001 3041653.5250000022,4214602.8398 3041661.5145000024,4214602.9485 3041674.6838000077,4214603.172599999 3041700.7920999997,4214603.255899999 3041710.621500003,4214600.8665 3041715.1045999983,4214599.1109 3041718.3888999997,4214602.5578000005 3041724.0386000066,4214624.967700001 3041708.372999994,4214635.3925 3041704.219399998,4214640.028000001 3041702.3756000004,4214677.176899999 3041695.3746000025,4214677.6258000005 3041695.288399999,4214688.737600001 3041692.3855000036,4214698.8127999995 3041687.526699998,4214704.695 3041684.6957000014,4214713.069800001 3041685.7205999997,4214735.8583 3041703.868400006,4214746.4309 3041720.194000009,4214693.8719 3041758.013199992,4214691.0811 3041760.021400002,4214703.221899999 3041782.5314000025,4214724.1384 3041822.6661000014,4214722.5901999995 3041830.177800001,4214724.9222 3041834.736,4214733.164799999 3041834.122700002,4214771.510299999 3041856.346800001,4214777.729800001 3041859.9515000083,4214803.6866 3041874.9956000005,4214808.276900001 3041888.2532000015,4214814.591499999 3041884.116200011,4214919.3313 3041898.767300002,4214929.1917 3041900.1918000025,4215030.5404 3041914.7896000016,4215035.137700001 3041920.4567000065,4215034.4388 3041930.1267999993,4215033.42 3041944.231599999,4215030.624 3041983.5933000008,4215035.635500001 3041983.693399993,4215040.9904 3041978.849500006,4215126.7314 3041981.2051000027,4215328.9582 3041986.761899999,4215333.838500001 3041991.905100002,4215339.080399999 3041991.9311000044,4215339.6954 3041976.8838,4215339.489700001 3041972.5613999986,4215337.988500001 3041940.9952000035,4215342.552100001 3041935.332200008,4215360.797499999 3041932.7132999995,4215445.857000001 3041920.502499999,4215480.3375 3041915.5523,4215495.5944 3041913.3625000007,4215633.756200001 3041893.5274999943,4215638.2939 3041897.745400002,4215644.1439 3041899.915100002,4215652.6613 3041890.8076000037,4215776.0341 3041873.0810000007,4215834.1219 3041959.4674999993,4215836.4778 3041964.161900001,4215910.867900001 3041953.402499996,4215915.433599999 3041950.8396000024,4215989.7434 3042026.6216000007,4215991.544500001 3042028.4580000015,4216009.382300001 3042046.3678000015,4216074.117900001 3042111.3678000034,4216129.249500001 3042167.083300004,4216136.6251 3042174.812399998,4216191.921399999 3042230.1356000025,4216205.952500001 3042245.5936999996,4216211.7183 3042251.1347999997,4216228.889699999 3042276.9103000034,4216228.8784 3042280.5005999994,4216228.989 3042283.489299993,4216234.820599999 3042286.5092999972,4216246.9443 3042307.3339000023,4216254.3835 3042321.9625000022,4216262.1369 3042339.8469000016,4216288.9704 3042411.4121999987,4216283.609200001 3042411.3658999964,4216194.751800001 3042409.972900001,4216120.437000001 3042408.8081,4216105.3719999995 3042408.5550999953,4216087.0492 3042408.2759000007,4216038.0809 3042407.529799999,4216023.093699999 3042407.8557999996,4216017.3017 3042410.3375000046,4216009.3024 3042413.7655999996,4215998.5725 3042424.183700003,4215946.4683 3042483.2630000026,4215935.7414 3042495.3710999987,4215918.277000001 3042515.092300001,4215912.2795 3042519.7450000015,4215904.9977 3042521.885099996,4215813.5660999995 3042531.3322000015,4215810.6339 3042531.9326000023,4215771.6229 3042540.269200007,4215756.9834 3042543.370500002,4215696.9804 3042556.1559000015,4215592.625399999 3042582.4116,4215581.4453 3042582.4852000014,4215577.471100001 3042582.9498999957,4215564.626499999 3042584.380199998,4215567.2741 3042595.0407000002,4215577.7673 3042601.8369000023,4215587.182 3042630.859100004,4215596.4618999995 3042814.3520000004,4215597.820599999 3042859.9559000013,4215597.298699999 3042878.9840999995,4215595.5814 3042941.131300002,4215594.3478999995 3042985.8508000067,4215594.2053 3042990.823000004,4215595.3774999995 3043004.3167000036,4215604.445900001 3043108.7390000015,4215606.322899999 3043130.624400002,4215622.641100001 3043193.023700002,4215633.7059 3043235.314099999,4215653.895 3043312.501,4215646.673699999 3043371.4336000015,4215644.8354 3043386.3198000006,4215642.944499999 3043401.7466,4215642.429 3043405.7739000004,4215642.831700001 3043456.8313000025,4215642.112600001 3043476.812400001,4215648.0765 3043477.7905000006,4215653.8917 3043472.9702999988,4215694.5956 3043474.0468999986,4215760.233100001 3043475.7830000003,4215789.754799999 3043476.563300002,4215804.882200001 3043476.985400006,4215853.6296999995 3043478.1955999993,4215915.0868999995 3043479.7220999952,4215952.647600001 3043480.6549000023,4215968.0572999995 3043476.092900002,4215985.658399999 3043468.147600003,4216015.7205 3043454.576700004,4216029.025 3043452.593700003,4216163.5002999995 3043471.0968,4216174.789799999 3043471.7117000083,4216173.3144000005 3043476.482300004,4216179.334000001 3043473.459399999,4216222.8901 3043486.1015999997,4216272.9022 3043500.605099994,4216288.694 3043505.1894000033,4216310.0022 3043511.375900002,4216350.837200001 3043523.215500002,4216375.3061 3043530.319700002,4216413.821799999 3043541.4910999984,4216435.489399999 3043547.7836999972,4216454.687000001 3043553.3501999993,4216484.2765999995 3043561.9340999997,4216516.6676 3043571.3396000043,4216549.5002 3043580.8589000013,4216574.17 3043588.0204000077,4216588.5788 3043592.2426000023,4216618.7709 3043601.0083,4216728.7355 3043632.9190999987,4216778.838099999 3043647.461499999,4216785.3806 3043653.492000002,4216791.051999999 3043668.2949000006,4216808.501800001 3043713.8477999996,4216808.7815000005 3043714.563999998,4216819.4077 3043739.999500001,4216838.6537999995 3043779.3773,4216849.628900001 3043798.0975999953,4216834.5659 3043795.044399998,4216821.587300001 3043837.199100008,4216816.794 3043852.7679,4216802.457 3043899.331599997,4216798.0448 3043914.6830999996,4216794.8815 3043927.7372999988,4216791.2151 3043931.7178999996,4216788.988299999 3043934.1786999977,4216732.370999999 3043953.2577000023,4216723.940300001 3043976.1249,4216745.8638 3044016.016000002,4216753.7534 3044032.148500001,4216757.1237 3044041.722699999,4216762.6778 3044057.4673000015,4216764.905099999 3044063.7400999935,4216778.9783 3044103.3749999986,4216803.8345 3044173.3786000065,4216800.5448 3044177.854100009,4216776.859999999 3044176.5014000004,4216759.7589 3044175.5242999997,4216723.747099999 3044173.4589999984,4216704.830399999 3044172.3789000013,4216682.040200001 3044171.0718999924,4216674.928200001 3044170.9797000014,4216630.135500001 3044170.3951000012,4216614.761600001 3044170.2263,4216614.912599999 3044176.194599996,4216622.2793000005 3044183.2638000045,4216623.7973 3044216.2088999995,4216624.051100001 3044221.7217000006,4216625.9955 3044264.0474999957,4216626.664999999 3044278.5331000057,4216627.198999999 3044290.1023999997,4216627.6753 3044300.435500002,4216628.701400001 3044322.7037000046,4216629.5101 3044340.2936000014,4216624.6064 3044345.2213000017,4216624.977499999 3044351.196499999,4216630.077400001 3044356.306800001,4216631.1019 3044411.185899998,4216631.1468 3044413.7554,4216668.7355 3044453.911300001,4216694.0045 3044480.9157000007,4216704.2543 3044491.8755,4216718.076099999 3044506.6464000014,4216765.701300001 3044557.524999994,4216777.747300001 3044570.400299999,4216785.145300001 3044578.3091000007,4216744.760600001 3044586.9915999984,4216746.5066 3044603.7715000003,4216745.816 3044614.0516000036,4216742.0875 3044708.1113,4216739.6691 3044769.1351999985,4216739.0659 3044784.384299998,4216738.476 3044799.143300001,4216737.8336 3044815.1830999935,4216736.429400001 3044850.274399999,4216735.151000001 3044882.0939,4216732.165200001 3044883.9050000003,4216729.8696 3044891.617000006,4216733.584100001 3044895.606200001,4216757.9604 3044974.7560000005,4216768.0404 3044982.7878999957,4216900.846899999 3045116.391100002,4216912.8072 3045125.167300001,4216991.634500001 3045175.957200001,4216994.7248 3045180.375000003,4217086.669199999 3045239.6953000017,4217100.0295 3045244.7720000013,4217266.1346 3045305.033500008,4217280.3642 3045310.0482999994,4217288.1207 3045312.681900002,4217290.9738 3045313.622700005,4217322.058800001 3045324.0662999996,4217340.6655 3045330.311000008,4217354.339199999 3045334.9034000007,4217366.757099999 3045339.0731000095,4217392.517100001 3045347.719700003,4217426.274499999 3045359.0565999993,4217466.1504 3045372.449600006,4217484.8375 3045378.7232000045,4217499.5461 3045383.661400003,4217526.7228 3045392.788600001,4217530.100299999 3045399.212599998,4217531.514799999 3045394.3729000003,4217541.1217 3045397.021100008,4217646.7578 3045426.8616,4217670.612299999 3045435.8544000005,4217875.750700001 3045518.891300001,4217885.132999999 3045522.672599999,4217899.2928 3045530.6586000025,4217909.2238 3045533.6022999953,4217950.7366 3045546.0127999997,4218061.5602 3045583.0026999987,4218077.000499999 3045588.139899998,4218243.4628 3045643.6776000005,4218258.584899999 3045646.66000001,4218379.4584 3045662.9207000085,4218383.4879 3045670.225799998,4218363.585999999 3045713.7318000025,4218348.0743 3045747.6469000075,4218325.5154 3045779.5985999964,4218316.1053 3045792.9387000003,4218297.648 3045819.083800001,4218288.639799999 3045831.8183000023,4218168.080800001 3046002.3945999993,4218159.3594 3046014.8951000026,4218057.0156 3046159.8996999995,4218048.2721 3046172.240599995,4217993.037599999 3046250.4839999974),(4214825.329 3042265.5948999966,4214858.9691 3042276.5480999975,4214871.018300001 3042280.4359000013,4214843.7513 3042248.8265999993,4214813.1121 3042214.155100001,4214800.187999999 3042191.861100003,4214785.4506 3042172.054699997,4214794.5568 3042130.4020000016,4214795.7687 3042124.5226000026,4214750.2227 3042117.6739999945,4214731.8478 3042113.036300001,4214708.7347 3042106.3537000017,4214702.037799999 3042104.415600001,4214577.516100001 3042068.3953000037,4214571.1064 3042065.523200007,4214568.514799999 3042063.2086999994,4214565.903000001 3042060.8844999964,4214559.0156 3042048.5984999994,4214556.7367 3042034.6891000024,4214558.6073 3042008.1818999997,4214542.1765 3041958.4691000013,4214527.863399999 3041995.5938000004,4214507.6609000005 3042116.4782000026,4214497.9936 3042116.011100003,4214498.7892 3042121.5804999997,4214487.220799999 3042123.2596000023,4214477.528899999 3042124.672900001,4214471.725199999 3042126.6527999965,4214456.3133000005 3042125.8945999998,4214437.4487 3042122.0136999986,4214356.5844 3042054.501599998,4214352.273700001 3042050.9075000077,4214344.3245 3042044.279600005,4214333.710200001 3042035.3850000002,4214330.4474 3042039.1515000025,4214329.062899999 3042040.6201999993,4214327.7926 3042041.8175000004,4214321.9859 3042043.1566999974,4214319.0167 3042044.771700003,4214314.1822 3042048.1440000087,4214310.4495 3042051.5011000023,4214307.510199999 3042055.3195999986,4214305.0516 3042059.761100003,4214302.7782000005 3042066.2465999997,4214300.817399999 3042072.570299999,4214298.890799999 3042081.412300001,4214297.4065000005 3042088.0443000006,4214296.3795 3042093.5680999956,4214294.886600001 3042099.570600009,4214293.8617 3042105.251800001,4214292.679400001 3042110.935100001,4214291.488500001 3042115.988900002,4214290.4594 3042121.355300002,4214289.0122 3042124.9259000057,4214287.413899999 3042129.6005000006,4214284.1722 3042135.6269999994,4214280.206 3042142.2531999997,4214277.2499 3042147.5166000007,4214287.3858 3042155.3986000083,4214312.8268 3042175.1799999992,4214321.330800001 3042181.823399998,4214324.7645 3042184.506400002,4214410.3759 3042251.0330999964,4214418.2717 3042258.7750000046,4214430.864499999 3042266.252299993,4214440.539899999 3042242.248,4214453.3071 3042222.0412999997,4214455.1995 3042218.5151000028,4214497.8452 3042231.1396000036,4214505.192399999 3042226.448300003,4214516.254899999 3042230.9765000017,4214526.744200001 3042235.262600001,4214625.2798 3042226.0675000018,4214732.133099999 3042233.589200002,4214747.8366 3042240.3637000043,4214825.329 3042265.5948999966)) \ No newline at end of file diff --git a/mock/.gitignore b/mock/.gitignore new file mode 100644 index 0000000..50281c0 --- /dev/null +++ b/mock/.gitignore @@ -0,0 +1 @@ +Natura2000_end2024.gpkg \ No newline at end of file diff --git a/mock/README.md b/mock/README.md new file mode 100644 index 0000000..9af0ed0 --- /dev/null +++ b/mock/README.md @@ -0,0 +1,16 @@ +# Mock Data for Early Stage Processes + +## Natura 2000 + +Download gpkg from the [European Environment Agency](https://sdi.eea.europa.eu/datashare/s/mwzs9eNsJ9Sn4Q4/download?path=%2F&files=Natura2000_end2024.gpkg). + +Import to PostGIS with: + +```bash +psql -U geoengine -d biois -h localhost -p 5432 -c "CREATE SCHEMA IF NOT EXISTS \"Natura2000\";" +ogr2ogr \ + -f "PostgreSQL" \ + PG:"dbname=biois user=geoengine password=geoengine host=localhost port=5432" \ + -lco SCHEMA=Natura2000 \ + Natura2000_end2024.gpkg +``` diff --git a/test-client/call.http b/test-client/call.http index 2f1bf9a..b4703c3 100644 --- a/test-client/call.http +++ b/test-client/call.http @@ -169,4 +169,34 @@ GET http://localhost:4040/jobs/{{process.response.body.jobID}} Authorization: Bearer {{INVALID}} Content-Type: application/json +### + +GET http://localhost:4040/processes/habitatDistance +Authorization: Bearer {{anonymousSession.response.body.$.id}} + +### + +POST http://localhost:4040/processes/habitatDistance/execution +Authorization: Bearer {{anonymousSession.response.body.$.id}} +Content-Type: application/json + +{ + "inputs": { + "coordinate": { + "value": { + "type": "Point", + "coordinates": [8.46, 50.49] + }, + "mediaType": "application/geo+json" + } + }, + "response": "document" +} + +### + +GET http://localhost:4040/jobs/{{process.response.body.jobID}} +Authorization: Bearer {{anonymousSession.response.body.$.id}} +Content-Type: application/json + ### \ No newline at end of file From 7a0aeabf1794262a7bb9634e6f8f4d6c332d73d9 Mon Sep 17 00:00:00 2001 From: Christian Beilschmidt Date: Mon, 16 Mar 2026 12:11:13 +0100 Subject: [PATCH 2/2] fix: add PostGIS extension and Natura2000 schema setup for tests --- .github/workflows/ci.yml | 7 ++++++- backend/src/processes/habitat_distance.rs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed0960f..630f52b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,12 @@ jobs: - name: Start PostgreSQL run: | service postgresql start - psql postgres://geoengine:geoengine@localhost -c "CREATE DATABASE biois;" + sudo -u postgres psql << EOF + \set AUTOCOMMIT on + CREATE DATABASE biois OWNER geoengine; + \c biois + CREATE EXTENSION postgis; + EOF - name: setup rust build cache uses: Swatinem/rust-cache@v2 with: diff --git a/backend/src/processes/habitat_distance.rs b/backend/src/processes/habitat_distance.rs index 1eabdd1..ee7bae6 100644 --- a/backend/src/processes/habitat_distance.rs +++ b/backend/src/processes/habitat_distance.rs @@ -375,6 +375,23 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn process_summary_has_expected_inputs_and_outputs() { let pool = mock_db_pool().await; + + // create schema / table and insert a test site + { + let mut conn = pool.get().await.unwrap(); + conn.batch_execute(&indoc::formatdoc! {" + CREATE SCHEMA IF NOT EXISTS \"Natura2000\"; + CREATE TABLE IF NOT EXISTS \"Natura2000\".naturasite_polygon ( + sitecode TEXT, + sitename TEXT, + geom geometry + ); + " + }) + .await + .unwrap(); + } + let p = HabitatDistanceProcess::new(pool).await.unwrap(); let process = p.process().expect("to produce process description");