kystdatahuset-python-lib 0.9.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- __init__.py +0 -0
- kystdatahuset/__init__.py +1 -0
- kystdatahuset/ais.py +29 -0
- kystdatahuset/api_client.py +103 -0
- kystdatahuset/auth.py +30 -0
- kystdatahuset/const.py +1 -0
- kystdatahuset/file_storage.py +54 -0
- kystdatahuset/logging.py +13 -0
- kystdatahuset/models/AuthData.py +10 -0
- kystdatahuset/models/FileListing.py +33 -0
- kystdatahuset/models/WebServiceResponse.py +11 -0
- kystdatahuset/models/__init__.py +3 -0
- kystdatahuset/types/PandasFrequency.py +8 -0
- kystdatahuset/types/UploadFileType.py +18 -0
- kystdatahuset/types/__init__.py +2 -0
- kystdatahuset/utils/__init__.py +2 -0
- kystdatahuset/utils/_date_range.py +15 -0
- kystdatahuset/utils/_slice_polygon_to_grid.py +48 -0
- kystdatahuset/voyage.py +45 -0
- kystdatahuset_python_lib-0.9.3.dist-info/METADATA +144 -0
- kystdatahuset_python_lib-0.9.3.dist-info/RECORD +24 -0
- kystdatahuset_python_lib-0.9.3.dist-info/WHEEL +5 -0
- kystdatahuset_python_lib-0.9.3.dist-info/licenses/LICENSE +21 -0
- kystdatahuset_python_lib-0.9.3.dist-info/top_level.txt +2 -0
__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.3"
|
kystdatahuset/ais.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
from kystdatahuset.api_client import post_api
|
|
4
|
+
|
|
5
|
+
def get_ais_positions_within_geom_time(
|
|
6
|
+
jwt_token: str,
|
|
7
|
+
geometry: str,
|
|
8
|
+
start_time: datetime,
|
|
9
|
+
end_time: datetime,
|
|
10
|
+
) -> List[Dict]:
|
|
11
|
+
"""
|
|
12
|
+
Placeholder for getting AIS positions for a given geometry and time range.
|
|
13
|
+
"""
|
|
14
|
+
response = post_api(
|
|
15
|
+
jwt_token=jwt_token,
|
|
16
|
+
fragment="api/ais/positions/within-geom-time",
|
|
17
|
+
payload={
|
|
18
|
+
"geom": geometry,
|
|
19
|
+
"start": start_time.isoformat(),
|
|
20
|
+
"end": end_time.isoformat(),
|
|
21
|
+
"minSpeed": 0,
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# This is a placeholder implementation.
|
|
26
|
+
if (response is None) or (not response["success"]):
|
|
27
|
+
raise Exception(f"Failed to get AIS positions: {response['msg'] if response else 'No response'}")
|
|
28
|
+
|
|
29
|
+
return response["data"]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
2
|
+
|
|
3
|
+
from kystdatahuset.models import WebServiceResponse
|
|
4
|
+
from .const import API_URL
|
|
5
|
+
import requests
|
|
6
|
+
from kystdatahuset.logging import logger
|
|
7
|
+
|
|
8
|
+
def get_headers(jwt_token: Optional[str] = None, json: bool = False) -> Dict[str, str]:
|
|
9
|
+
"""
|
|
10
|
+
Build headers for API requests.
|
|
11
|
+
"""
|
|
12
|
+
headers: Dict[str, str] = {}
|
|
13
|
+
if jwt_token:
|
|
14
|
+
headers["Authorization"] = f"Bearer {jwt_token}"
|
|
15
|
+
if json:
|
|
16
|
+
headers["Content-Type"] = "application/json"
|
|
17
|
+
headers["Accept"] = "application/json"
|
|
18
|
+
return headers
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def post_api_formdata(jwt_token: str, fragment: str, data: List[Tuple[str, Any]], filename: str) -> WebServiceResponse:
|
|
22
|
+
"""
|
|
23
|
+
POST multipart/form-data with a file and form fields.
|
|
24
|
+
"""
|
|
25
|
+
url = f"{API_URL}/{fragment}"
|
|
26
|
+
|
|
27
|
+
with open(filename, "rb") as f:
|
|
28
|
+
files = {"file": (filename, f, "application/octet-stream")}
|
|
29
|
+
response = requests.post(url, headers=get_headers(jwt_token), data=data, files=files)
|
|
30
|
+
|
|
31
|
+
if response.ok:
|
|
32
|
+
try:
|
|
33
|
+
logger.info(f"β
POST (FormData) {url} successful!")
|
|
34
|
+
data = response.json()
|
|
35
|
+
return data
|
|
36
|
+
except ValueError:
|
|
37
|
+
return WebServiceResponse[Any](**{"success": True, "msg": response.text, "data": None})
|
|
38
|
+
else:
|
|
39
|
+
logger.error(f"β POST (FormData) {url} failed with {response.status_code}")
|
|
40
|
+
logger.debug(response.text)
|
|
41
|
+
raise Exception(f"API POST (FormData) failed with status code {response.status_code}: {response.text}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_api(jwt_token: str, fragment: str, params: Optional[Dict[str, Any]] = None) -> WebServiceResponse:
|
|
45
|
+
"""
|
|
46
|
+
Perform a GET request and parse JSON response.
|
|
47
|
+
"""
|
|
48
|
+
url = f"{API_URL}/{fragment}"
|
|
49
|
+
response = requests.get(url, headers=get_headers(jwt_token, json=True), params=params)
|
|
50
|
+
|
|
51
|
+
if response.ok:
|
|
52
|
+
logger.info(f"β
GET {url} successful!")
|
|
53
|
+
try:
|
|
54
|
+
data = response.json()
|
|
55
|
+
logger.debug(f"Response JSON: {data}")
|
|
56
|
+
return data
|
|
57
|
+
except ValueError:
|
|
58
|
+
return WebServiceResponse[str](**{"success": True, "msg": response.text, "data": None})
|
|
59
|
+
else:
|
|
60
|
+
logger.error(f"β GET {url} failed with {response.status_code}")
|
|
61
|
+
logger.debug(response.text)
|
|
62
|
+
raise Exception(f"API GET failed with status code {response.status_code}: {response.text}")
|
|
63
|
+
|
|
64
|
+
def delete_api(jwt_token: str, fragment: str, params: Optional[Dict[str, Any]] = None) -> WebServiceResponse:
|
|
65
|
+
"""
|
|
66
|
+
Perform a DELETE request and parse the JSON response.
|
|
67
|
+
"""
|
|
68
|
+
url = f"{API_URL}/{fragment}"
|
|
69
|
+
response = requests.delete(url, headers=get_headers(jwt_token, json=True), params=params)
|
|
70
|
+
|
|
71
|
+
if response.ok:
|
|
72
|
+
logger.info(f"β
DELETE {url} successful!")
|
|
73
|
+
try:
|
|
74
|
+
data = response.json()
|
|
75
|
+
logger.debug(f"Response JSON: {data}")
|
|
76
|
+
return data
|
|
77
|
+
except ValueError:
|
|
78
|
+
return WebServiceResponse[str](**{"success": True, "msg": response.text, "data": None})
|
|
79
|
+
else:
|
|
80
|
+
logger.error(f"β GET {url} failed with {response.status_code}")
|
|
81
|
+
logger.debug(response.text)
|
|
82
|
+
raise Exception(f"API GET failed with status code {response.status_code}: {response.text}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def post_api(jwt_token: str, fragment: str, payload: Dict[str, Any]) -> WebServiceResponse:
|
|
86
|
+
"""
|
|
87
|
+
Perform a POST request with a JSON body and parse JSON response.
|
|
88
|
+
"""
|
|
89
|
+
url = f"{API_URL}/{fragment}"
|
|
90
|
+
response = requests.post(url, headers=get_headers(jwt_token, json=True), json=payload)
|
|
91
|
+
|
|
92
|
+
if response.ok:
|
|
93
|
+
logger.info(f"β
JSON POST to {url} successful!")
|
|
94
|
+
try:
|
|
95
|
+
data = response.json()
|
|
96
|
+
logger.debug(f"Response JSON: {data}")
|
|
97
|
+
return data
|
|
98
|
+
except ValueError:
|
|
99
|
+
return WebServiceResponse[str](**{"success": True, "message": response.text, "data": None})
|
|
100
|
+
else:
|
|
101
|
+
logger.error(f"β JSON POST to {url} failed with {response.status_code}")
|
|
102
|
+
logger.debug(response.text)
|
|
103
|
+
raise Exception(f"API JSON POST failed with status code {response.status_code}: {response.text}")
|
kystdatahuset/auth.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from kystdatahuset.models import AuthData, WebServiceResponse
|
|
5
|
+
from kystdatahuset.logging import logger
|
|
6
|
+
from .const import API_URL
|
|
7
|
+
|
|
8
|
+
def login(username: str, password: str) -> WebServiceResponse[AuthData]:
|
|
9
|
+
reqUrl = f"{API_URL}/api/auth/login"
|
|
10
|
+
|
|
11
|
+
headersList = {
|
|
12
|
+
"User-Agent": "Kystdatahuset Python Library (https://your-client.com)",
|
|
13
|
+
"accept": "*/*",
|
|
14
|
+
"Content-Type": "application/json"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
payload = json.dumps({
|
|
18
|
+
"username": username,
|
|
19
|
+
"password": password
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
response = requests.request("POST", reqUrl, data=payload, headers=headersList)
|
|
23
|
+
|
|
24
|
+
if response.status_code == 200:
|
|
25
|
+
logger.info("β
Login successful!")
|
|
26
|
+
return WebServiceResponse[AuthData](**response.json())
|
|
27
|
+
else:
|
|
28
|
+
logger.error(f"β Login failed with status code {response.status_code}")
|
|
29
|
+
logger.debug(response.text)
|
|
30
|
+
raise Exception(f"Login failed with status code {response.status_code}: {response.text}")
|
kystdatahuset/const.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
API_URL="https://kystdatahuset.no/ws"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
from typing import Sequence, List
|
|
3
|
+
from kystdatahuset.models import FileListing, WebServiceResponse
|
|
4
|
+
from kystdatahuset.types import UploadFileType
|
|
5
|
+
import os
|
|
6
|
+
from kystdatahuset.api_client import post_api_formdata, get_api, delete_api
|
|
7
|
+
|
|
8
|
+
def list(*, jwt_token: str, resource_uuid: UUID) -> List[FileListing]:
|
|
9
|
+
"""
|
|
10
|
+
Placeholder for listing files in storage.
|
|
11
|
+
"""
|
|
12
|
+
list_res = get_api(jwt_token, f"api/file-storage/list/{resource_uuid}")
|
|
13
|
+
file_listings = WebServiceResponse[List[FileListing]](**list_res)
|
|
14
|
+
return file_listings.data
|
|
15
|
+
|
|
16
|
+
def delete(* , jwt_token: str, file_uuid: UUID) -> bool:
|
|
17
|
+
"""
|
|
18
|
+
Placeholder for deleting a file in storage.
|
|
19
|
+
"""
|
|
20
|
+
delete_res = delete_api(jwt_token, f"api/file-storage/delete/{file_uuid}")
|
|
21
|
+
return delete_res.success
|
|
22
|
+
|
|
23
|
+
def publish(
|
|
24
|
+
*,
|
|
25
|
+
jwt_token: str,
|
|
26
|
+
resource_uuid: UUID,
|
|
27
|
+
file_path: str,
|
|
28
|
+
title: str,
|
|
29
|
+
upload_file_type: UploadFileType,
|
|
30
|
+
description: str = "",
|
|
31
|
+
categories: Sequence[str] = "",
|
|
32
|
+
compressed: bool = False,
|
|
33
|
+
) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Upload a file and metadata to the Kystdatahuset API.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
if not os.path.exists(file_path):
|
|
39
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
40
|
+
|
|
41
|
+
# Convert categories to multiple form fields (ASP.NET supports repeated keys)
|
|
42
|
+
# or as a JSON-like string, depending on the APIβs binding expectations.
|
|
43
|
+
# The safe bet for ASP.NET [FromForm(Name="categories")] string[] is to repeat the key.
|
|
44
|
+
data = [
|
|
45
|
+
("title", title),
|
|
46
|
+
("description", description),
|
|
47
|
+
("type", upload_file_type),
|
|
48
|
+
("compressed", str(compressed).lower()), # ASP.NET expects 'true'/'false'
|
|
49
|
+
] + [("categories", c) for c in categories]
|
|
50
|
+
|
|
51
|
+
response = post_api_formdata(jwt_token, f"api/file-storage/publish/{resource_uuid}", data, file_path)
|
|
52
|
+
return response.success
|
|
53
|
+
|
|
54
|
+
|
kystdatahuset/logging.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger_name = __name__.split(".")[0]
|
|
4
|
+
# Create a logger specific to your library
|
|
5
|
+
logger = logging.getLogger(logger_name)
|
|
6
|
+
logger.addHandler(logging.NullHandler())
|
|
7
|
+
|
|
8
|
+
def enable_default_logging(level=logging.INFO):
|
|
9
|
+
handler = logging.StreamHandler()
|
|
10
|
+
formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
11
|
+
handler.setFormatter(formatter)
|
|
12
|
+
logger.addHandler(handler)
|
|
13
|
+
logger.setLevel(level)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AuthData(BaseModel):
|
|
6
|
+
JWT: str = Field(..., description="JSON Web Token for authentication")
|
|
7
|
+
Username: str = Field(..., description="User's email or username")
|
|
8
|
+
Timestamp: datetime = Field(..., description="Timestamp when the token was issued")
|
|
9
|
+
|
|
10
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import List
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileListing(BaseModel):
|
|
7
|
+
uuid: UUID = Field(..., description="Unique identifier for this file record")
|
|
8
|
+
title: str = Field(..., description="Title or display name of the uploaded file")
|
|
9
|
+
description: str = Field(..., description="Descriptive text about the file contents")
|
|
10
|
+
categories: List[str] = Field(..., description="List of category tags assigned to the file")
|
|
11
|
+
filetype: str = Field(..., description="Type or format of the uploaded file, e.g. csv, pdf")
|
|
12
|
+
compressed: bool = Field(..., description="True if the file was uploaded as compressed archive")
|
|
13
|
+
filename: str = Field(..., description="Server-side absolute file path")
|
|
14
|
+
origFilename: str = Field(..., description="Original filename on the client before upload")
|
|
15
|
+
resourceUuid: UUID = Field(..., description="UUID of the resource to which this file belongs")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# # β
Example usage:
|
|
19
|
+
# example_json = {
|
|
20
|
+
# 'uuid': '69d47723-206c-4a89-8d67-654f23706e24',
|
|
21
|
+
# 'title': 'Test Upload',
|
|
22
|
+
# 'description': 'This is a test upload',
|
|
23
|
+
# 'categories': ['test', 'upload'],
|
|
24
|
+
# 'filetype': 'csv',
|
|
25
|
+
# 'compressed': True,
|
|
26
|
+
# 'filename': r'E:\storage\kystdathuset\catalog\2025\11\64\12\7f\64127fc2-2644-4ed9-b886-fecfb914c4b5\C_Users_runar.bergheim_Documents_Development_kystdatahuset-python-lib_test_data_content.txt',
|
|
27
|
+
# 'origFilename': r'C:\\Users\\runar.bergheim\\Documents\\Development\\kystdatahuset-python-lib\\test_data\\content.txt',
|
|
28
|
+
# 'resourceUuid': '64127fc2-2644-4ed9-b886-fecfb914c4b5'
|
|
29
|
+
# }
|
|
30
|
+
|
|
31
|
+
# listing = FileListing(**example_json)
|
|
32
|
+
# print(listing.title)
|
|
33
|
+
# print(listing.resourceUuid)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from pydantic import Field, BaseModel
|
|
2
|
+
from typing import Generic, TypeVar, Optional
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
class WebServiceResponse(BaseModel, Generic[T]):
|
|
7
|
+
success: bool = Field(..., description="Indicates if the request was successful")
|
|
8
|
+
msg: str = Field(..., description="Optional message from the API")
|
|
9
|
+
data: Optional[T] = Field(None, description="Container for typed response data")
|
|
10
|
+
time: Optional[float] = Field(None, description="Processing time in milliseconds or seconds")
|
|
11
|
+
executionTime: Optional[float] = Field(None, description="Processing time in milliseconds or seconds")
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing import Literal, TypeAlias
|
|
2
|
+
|
|
3
|
+
PandasFreqency: TypeAlias = Literal[
|
|
4
|
+
"B", "C", "D", "W", "W-MON", "W-TUE", "W-WED", "W-THU", "W-FRI", "W-SAT", "W-SUN",
|
|
5
|
+
"M", "MS", "Q", "QS", "A", "AS",
|
|
6
|
+
"H", "T", "min", "S", "L", "ms", "U", "us", "N",
|
|
7
|
+
"BH", "CBH", "BQS", "BA", "BAS", "BYS", "BY"
|
|
8
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from typing import Literal, TypeAlias
|
|
2
|
+
|
|
3
|
+
UploadFileType: TypeAlias = Literal[
|
|
4
|
+
"csv", # Comma Separated Variables
|
|
5
|
+
"shp-compressed", # Compressed ESRI Shapefile
|
|
6
|
+
"fgdb-compressed", # Compressed ESRI filegeodatabase
|
|
7
|
+
"json", # JSON file
|
|
8
|
+
"geojson", # GeoJSON file
|
|
9
|
+
"png", # Portable Network Graphics image
|
|
10
|
+
"jpg", # JPEG image
|
|
11
|
+
"pdf", # Portable Document Format
|
|
12
|
+
"xlsx", # Microsoft Excel spreadsheet
|
|
13
|
+
"xml", # XML file
|
|
14
|
+
"docx", # Microsoft Word document
|
|
15
|
+
"NetCDF", # Network Common Data Form (NetCDF)
|
|
16
|
+
"tiff", # Tagged Image File Format (*.tif)
|
|
17
|
+
"geotiff", # Georeferenced Tagged Image File Format (*.tif)
|
|
18
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from kystdatahuset.types import PandasFreqency
|
|
5
|
+
from more_itertools import pairwise
|
|
6
|
+
|
|
7
|
+
def date_range(start_date: datetime, end_date: datetime, freq: PandasFreqency = "D") -> List[datetime]:
|
|
8
|
+
"""
|
|
9
|
+
Generate a list of dates from start_date to end_date, inclusive.
|
|
10
|
+
"""
|
|
11
|
+
if start_date > end_date:
|
|
12
|
+
raise ValueError("start_date must be less than or equal to end_date")
|
|
13
|
+
|
|
14
|
+
dates = pd.date_range(start=start_date, end=end_date, freq=freq)
|
|
15
|
+
return pairwise([dt.to_pydatetime() for dt in dates])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from shapely.geometry import Polygon, box
|
|
2
|
+
from shapely.ops import unary_union
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import List, Tuple
|
|
5
|
+
from shapely import wkt
|
|
6
|
+
|
|
7
|
+
def slice_polygon_to_grid(
|
|
8
|
+
wkt_polygon: str,
|
|
9
|
+
grid_size: float,
|
|
10
|
+
bbox: Tuple[float, float, float, float] = None
|
|
11
|
+
) -> List[str]:
|
|
12
|
+
"""
|
|
13
|
+
Slice a large polygon into smaller pieces that fit a regular grid.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
wkt_polygon (str): The well-known text representation of the polygon to be sliced.
|
|
17
|
+
grid_size (float): The grid cell size (in same units as polygon coordinates).
|
|
18
|
+
bbox (tuple, optional): (minx, miny, maxx, maxy) to constrain the grid.
|
|
19
|
+
If not provided, the polygon's bounding box is used.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List[str]: List of wkt polygon strings (each is the intersection of the polygon and one grid cell).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
polygon = wkt.loads(wkt_polygon)
|
|
26
|
+
|
|
27
|
+
if bbox is None:
|
|
28
|
+
minx, miny, maxx, maxy = polygon.bounds
|
|
29
|
+
else:
|
|
30
|
+
minx, miny, maxx, maxy = bbox
|
|
31
|
+
|
|
32
|
+
# Create grid coordinates
|
|
33
|
+
x_coords = np.arange(minx, maxx, grid_size)
|
|
34
|
+
y_coords = np.arange(miny, maxy, grid_size)
|
|
35
|
+
|
|
36
|
+
pieces = []
|
|
37
|
+
for x in x_coords:
|
|
38
|
+
for y in y_coords:
|
|
39
|
+
cell = box(x, y, x + grid_size, y + grid_size)
|
|
40
|
+
intersection = polygon.intersection(cell)
|
|
41
|
+
if not intersection.is_empty:
|
|
42
|
+
# Handle MultiPolygons (split into individual polygons)
|
|
43
|
+
if intersection.geom_type == "Polygon":
|
|
44
|
+
pieces.append(intersection)
|
|
45
|
+
elif intersection.geom_type == "MultiPolygon":
|
|
46
|
+
pieces.extend(intersection.geoms)
|
|
47
|
+
|
|
48
|
+
return [wkt.dumps(p) for p in pieces]
|
kystdatahuset/voyage.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
from requests_cache import datetime
|
|
3
|
+
from kystdatahuset.api_client import post_api
|
|
4
|
+
from kystdatahuset.types import PandasFrequency
|
|
5
|
+
from kystdatahuset.utils import date_range
|
|
6
|
+
|
|
7
|
+
def get_voyages_for_ships_by_mmsi(auth_jwt: str, mmsi_ids: List[int], start_date: datetime, end_date: datetime, freq: PandasFrequency = "MS") -> List[Dict]:
|
|
8
|
+
"""Get voyagen data for ships identified by MMSI ids
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
auth_jwt (str): A valid JWT retrieved through an authentication call to the API
|
|
12
|
+
mmsi_ids (List[int]): A list of one or more MMSIs
|
|
13
|
+
start_date (datetime): A start date
|
|
14
|
+
end_date (datetime): An end date
|
|
15
|
+
freq (PandasFrequency, optional): An optional frequency that the request will be split into. Defaults to "MS".
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
Exception: If the API call fails or returns an error.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List[Dict]: List of voyages
|
|
22
|
+
"""
|
|
23
|
+
responses = []
|
|
24
|
+
|
|
25
|
+
date_ranges = date_range(start_date, end_date, freq)
|
|
26
|
+
for pair in date_ranges:
|
|
27
|
+
print(pair) # Debug print to verify date ranges
|
|
28
|
+
|
|
29
|
+
response = post_api(
|
|
30
|
+
jwt_token=auth_jwt,
|
|
31
|
+
fragment="api/voyage/for-ships/by-mmsi",
|
|
32
|
+
payload={
|
|
33
|
+
"mmsiIds": mmsi_ids,
|
|
34
|
+
"startTime": start_date.isoformat(),
|
|
35
|
+
"endTime": end_date.isoformat()
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if (response is None) or (not response["success"]):
|
|
40
|
+
raise Exception(f"Failed to get voyages for ships by MMSI: {response['msg'] if response else 'No response'}")
|
|
41
|
+
else:
|
|
42
|
+
responses.extend(response["data"])
|
|
43
|
+
|
|
44
|
+
return responses
|
|
45
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kystdatahuset-python-lib
|
|
3
|
+
Version: 0.9.3
|
|
4
|
+
Summary: A python library for accessing and querying data from Kystdatahuset
|
|
5
|
+
Author-email: Kystdatahuset developer team <support@kystdatahuset.no>, "(Stein) Runar Bergheim" <runar.bergheim@avinet.no>, Sigve Bergh <sigve.bergh@kystverket.no>, Hermann Klaus Kurt von Lupfert <hermann.lupfert@kystverket.no>
|
|
6
|
+
Maintainer-email: Kystdatahuset developer team <support@kystdatahuset.no>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Kystverket/kystdatahuset-python-lib
|
|
9
|
+
Project-URL: Repository, https://github.com/Kystverket/kystdatahuset-python-lib
|
|
10
|
+
Project-URL: Issues, https://github.com/Kystverket/kystdatahuset-python-lib/issues
|
|
11
|
+
Keywords: spatial,analytics,visualization,geodata
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: pandas>=2.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: requests>=2.28
|
|
24
|
+
Requires-Dist: shapely>=2.0
|
|
25
|
+
Requires-Dist: tqdm>=4.65
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Kystdatahuset Python Library
|
|
29
|
+
|
|
30
|
+
`kystdatahuset-python-lib` β Python SDK companion for the Kystdatahuset API
|
|
31
|
+
|
|
32
|
+
`kystdatahuset-python-lib` is the official Python client for accessing the **Kystdatahuset API**, a unified data and knowledge platform for coastal and maritime spatial analytics.
|
|
33
|
+
|
|
34
|
+
It provides a clean, Pythonic, and strongly typed interface for querying datasets, managing authentication, and performing efficient data access.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## β¨ Features
|
|
39
|
+
|
|
40
|
+
### π Easy Installation
|
|
41
|
+
Install directly from PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install kystdatahuset-python-lib
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Supports Python **3.9+** on Linux, macOS, and Windows.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### π Simple Authentication
|
|
52
|
+
The client offers:
|
|
53
|
+
|
|
54
|
+
- API key authentication
|
|
55
|
+
- Support for headless servers and notebooks
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from kystdatahuset.auth import login
|
|
61
|
+
import os
|
|
62
|
+
|
|
63
|
+
login_response = login("username", "password")
|
|
64
|
+
jwt = auth_res.data.JWT
|
|
65
|
+
voyages = get_voyages_for_ships_by_mmsi(
|
|
66
|
+
auth_jwt=jwt,
|
|
67
|
+
mmsi_ids=[258090000, 259028000],
|
|
68
|
+
start_date=datetime(2024,1,1),
|
|
69
|
+
end_date=datetime(2024,5,1),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## π Efficient & βSocialβ Data Access
|
|
76
|
+
|
|
77
|
+
Instead of fetching massive multi-GB extracts, the library is designed for **smart, cooperative usage patterns**, where users share infrastructure responsibly:
|
|
78
|
+
|
|
79
|
+
### β
Time Window Batching
|
|
80
|
+
Fetch long time periods in small, safe slices, python/Pandas "periods"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### β
Geographic Slicing
|
|
84
|
+
Request only the needed spatial extent by WKT filters
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## π§± Library Structure
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
+---kystdatahuset
|
|
92
|
+
| | ais.py
|
|
93
|
+
| | api_client.py
|
|
94
|
+
| | auth.py
|
|
95
|
+
| | const.py
|
|
96
|
+
| | file_storage.py
|
|
97
|
+
| | logging.py
|
|
98
|
+
| | voyage.py
|
|
99
|
+
| | __init__.py
|
|
100
|
+
| |
|
|
101
|
+
| +---models
|
|
102
|
+
| | | AuthData.py
|
|
103
|
+
| | | FileListing.py
|
|
104
|
+
| | | WebServiceResponse.py
|
|
105
|
+
| | | __init__.py
|
|
106
|
+
| | |
|
|
107
|
+
| |
|
|
108
|
+
| +---types
|
|
109
|
+
| | | PandasFrequency.py
|
|
110
|
+
| | | UploadFileType.py
|
|
111
|
+
| | | __init__.py
|
|
112
|
+
| | |
|
|
113
|
+
| |
|
|
114
|
+
| +---utils
|
|
115
|
+
| | | _date_range.py
|
|
116
|
+
| | | __init__.py
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## π¦ Development & Distribution
|
|
122
|
+
|
|
123
|
+
`kystdatahuset-py` uses standard packaging:
|
|
124
|
+
|
|
125
|
+
- `pyproject.toml` + `PEP 621` metadata
|
|
126
|
+
- versioning via Semantic Versioning
|
|
127
|
+
- full type hints (mypy-friendly)
|
|
128
|
+
- GitHub Actions for automated testing & publishing
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## π§ Typical Use Cases
|
|
133
|
+
|
|
134
|
+
- Query live AIS vessel data efficiently
|
|
135
|
+
- Retrieve spatial datasets in bounded windows
|
|
136
|
+
- Build dashboards, decision-support tools, or AI/ML pipelines
|
|
137
|
+
- Avoid oversized extracts by using time/space batching helpers
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## π License
|
|
142
|
+
|
|
143
|
+
Open source under the **MIT License**.
|
|
144
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
kystdatahuset/__init__.py,sha256=5uBZ3sUaocnyIWuJeW9M94Z77vA1kKmwKxiblrnbKlc,23
|
|
3
|
+
kystdatahuset/ais.py,sha256=oKdZKYpPhTJfsomtfSKkAKvMS3DNE8bGwxkeSZPIf-w,906
|
|
4
|
+
kystdatahuset/api_client.py,sha256=7JwlOjOQc8KUf_mbgikfEFF6I-e0ieoQW1z7rJJUT5Y,4216
|
|
5
|
+
kystdatahuset/auth.py,sha256=i4RykZNItbKDr3MmaMzTOE0KuJddaRVayOPp5RpmGH4,1029
|
|
6
|
+
kystdatahuset/const.py,sha256=joWTuKfhvJaCnbRNzt3mzx10QivShtD80ehDaREtTUY,37
|
|
7
|
+
kystdatahuset/file_storage.py,sha256=TIi9MPEnY9KoYVD4ppva5F-sRizGRnvtthHc8BbRcxc,1907
|
|
8
|
+
kystdatahuset/logging.py,sha256=OLd_HsQviwZtuKktTZ8xgSwen4PvMUswO8ERCRFnWc4,456
|
|
9
|
+
kystdatahuset/voyage.py,sha256=71WdAyjbTZ6zSXuPTK2s7CtTpUZJbNBWdtznxeor2s0,1711
|
|
10
|
+
kystdatahuset/models/AuthData.py,sha256=xGCEGH3IT1LOsgDLr9Mn7ovCfsBRzCGL-RjQPfCfsMg,343
|
|
11
|
+
kystdatahuset/models/FileListing.py,sha256=fTji8MLS9cwUZNrBsYYhydmrbjHJv5uBNp9Yg5ShYT4,1724
|
|
12
|
+
kystdatahuset/models/WebServiceResponse.py,sha256=BvPQmpXgrDxRLtm4KfMUguEM2UFWHtGfrs4pV0taXJ8,608
|
|
13
|
+
kystdatahuset/models/__init__.py,sha256=XEK05NfbaW8KD14KmlFCCf8rs3FfWpsd8j93tWYIin0,122
|
|
14
|
+
kystdatahuset/types/PandasFrequency.py,sha256=WAa2YRBX84i_QOvsmqLeUXO8dCEbVNT3PMbEsIAHQ8s,312
|
|
15
|
+
kystdatahuset/types/UploadFileType.py,sha256=j3Jmu-HcUoYVxLxLa8UvKAIp_hLUjEkpL6UAD9QdVQI,808
|
|
16
|
+
kystdatahuset/types/__init__.py,sha256=L53ckYQ0Jo-czN5UuFUJbgvFYJJ6mfTNxPrg4ooXNf8,87
|
|
17
|
+
kystdatahuset/utils/__init__.py,sha256=oDC86K-h5VDRtXgCNjuGck1ffbH7AVfI2hH1bzj1Iqk,94
|
|
18
|
+
kystdatahuset/utils/_date_range.py,sha256=aacQixPn0R8Os9hatf29GN5WgIJAVAHB7KqVQaEbnLI,607
|
|
19
|
+
kystdatahuset/utils/_slice_polygon_to_grid.py,sha256=2xH3RBLICcAFz5jtROkJ1xEr5umTFxEkNl7-Ycr02cU,1729
|
|
20
|
+
kystdatahuset_python_lib-0.9.3.dist-info/licenses/LICENSE,sha256=x8ID0mDBvvKlF9ZZhYRj2-fSr20ZhXEdCFA4ewrEMxw,1086
|
|
21
|
+
kystdatahuset_python_lib-0.9.3.dist-info/METADATA,sha256=4K8AwQEFQwQsdp3UNbYTSTmwbt_EHdJzkYHd3nKjvFE,4055
|
|
22
|
+
kystdatahuset_python_lib-0.9.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
kystdatahuset_python_lib-0.9.3.dist-info/top_level.txt,sha256=vMgxr4VS5IiCspq8gKnP4Ceie8VNiPqQciArk4-0YmU,23
|
|
24
|
+
kystdatahuset_python_lib-0.9.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kystverket
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|