99 |
104 | {{ tooltip.object.name }}
105 |
106 | {{ tooltip.object.aircraft_count }} aircraft
107 |
108 |
109 |
110 |
124 |
--------------------------------------------------------------------------------
/packages/tangram_core/tests/test_api.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from dataclasses import dataclass
3 | from pathlib import Path
4 | from tempfile import TemporaryDirectory
5 | from typing import AsyncGenerator, Generator
6 |
7 | import httpx
8 | import pytest
9 |
10 | # TODO(abr): share fixtures across packages with pytest plugins
11 |
12 |
13 | @pytest.fixture(scope="session")
14 | def anyio_backend() -> str:
15 | return "asyncio"
16 |
17 |
18 | @dataclass(frozen=True)
19 | class ServerConfig:
20 | config_path: Path
21 | server_url: str
22 |
23 |
24 | @pytest.fixture(scope="session")
25 | def server_config() -> Generator[ServerConfig, None, None]:
26 | host = "127.0.0.1"
27 | # TODO: find free port
28 | server_port = 2346
29 | channel_port = 8001
30 |
31 | config_content = f"""
32 | [core]
33 | redis_url = "redis://localhost:6379"
34 | plugins = []
35 |
36 | [server]
37 | host = "{host}"
38 | port = {server_port}
39 |
40 | [channel]
41 | host = "{host}"
42 | port = {channel_port}
43 | jwt_secret = "test-secret"
44 | """
45 |
46 | with TemporaryDirectory() as config_dir:
47 | config_path = Path(config_dir) / "tangram.toml"
48 | config_path.write_text(config_content)
49 |
50 | yield ServerConfig(
51 | config_path=config_path, server_url=f"http://{host}:{server_port}"
52 | )
53 |
54 |
55 | @pytest.fixture(scope="session")
56 | async def live_server(server_config: ServerConfig) -> AsyncGenerator[str, None]:
57 | """Starts the tangram server as a subprocess for the test session."""
58 |
59 | proc = await asyncio.create_subprocess_exec(
60 | "tangram",
61 | "serve",
62 | f"--config={server_config.config_path}",
63 | stdout=asyncio.subprocess.PIPE,
64 | stderr=asyncio.subprocess.PIPE,
65 | )
66 | max_wait_seconds = 30
67 | poll_interval_seconds = 0.1
68 | async with httpx.AsyncClient() as client:
69 | for _ in range(int(max_wait_seconds / poll_interval_seconds)):
70 | try:
71 | response = await client.get(
72 | server_config.server_url, timeout=poll_interval_seconds
73 | )
74 | if response.status_code == 200:
75 | await asyncio.sleep(1) # give it a moment to settle
76 | break
77 | except (httpx.ConnectError, httpx.TimeoutException):
78 | await asyncio.sleep(poll_interval_seconds)
79 | else:
80 | proc.terminate()
81 | stdout, stderr = await proc.communicate()
82 | pytest.fail(
83 | f"server did not start within {max_wait_seconds} seconds.\n"
84 | f"{stdout.decode()=}\n{stderr.decode()=}"
85 | )
86 |
87 | yield server_config.server_url
88 |
89 | proc.terminate()
90 | await proc.wait()
91 |
92 |
93 | @pytest.fixture(scope="session")
94 | def server_url(live_server: str) -> str:
95 | """Provides the URL of the live server started by the live_server fixture."""
96 | return live_server
97 |
98 |
99 | @pytest.fixture(scope="session")
100 | async def client() -> AsyncGenerator[httpx.AsyncClient, None]:
101 | async with httpx.AsyncClient() as client:
102 | yield client
103 |
104 |
105 | @pytest.mark.anyio
106 | async def test_static(client: httpx.AsyncClient, server_url: str) -> None:
107 | response = await client.get(f"{server_url}")
108 | response.raise_for_status()
109 | assert response.content.startswith(b"")
110 |
111 |
112 | @pytest.mark.anyio
113 | async def test_api(client: httpx.AsyncClient, server_url: str) -> None:
114 | response = await client.get(f"{server_url}/manifest.json")
115 | response.raise_for_status()
116 | manifest = response.json()
117 | assert "plugins" in manifest
118 |
--------------------------------------------------------------------------------
/packages/tangram_core/rust/src/bbox.rs:
--------------------------------------------------------------------------------
1 | use crate::stream::Positioned;
2 | use serde::{Deserialize, Serialize};
3 | use std::collections::{HashMap, HashSet};
4 | use tracing::info;
5 |
6 | #[derive(Debug, Clone, Serialize, Deserialize)]
7 | pub struct BoundingBox {
8 | pub north_east_lat: f64,
9 | pub north_east_lng: f64,
10 | pub south_west_lat: f64,
11 | pub south_west_lng: f64,
12 | }
13 |
14 | #[derive(Debug, Clone, Serialize, Deserialize)]
15 | pub struct SelectedEntity {
16 | pub id: String,
17 | #[serde(rename = "typeName")]
18 | pub type_name: String,
19 | }
20 |
21 | #[derive(Debug, Clone, Serialize, Deserialize)]
22 | pub struct BoundingBoxMessage {
23 | #[serde(rename = "connectionId")]
24 | pub connection_id: String,
25 | #[serde(rename = "northEastLat")]
26 | pub north_east_lat: f64,
27 | #[serde(rename = "northEastLng")]
28 | pub north_east_lng: f64,
29 | #[serde(rename = "southWestLat")]
30 | pub south_west_lat: f64,
31 | #[serde(rename = "southWestLng")]
32 | pub south_west_lng: f64,
33 | #[serde(rename = "selectedEntities")]
34 | pub selected_entities: Vec