kystdatahuset-python-lib 0.9.5__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.

Potentially problematic release.


This version of kystdatahuset-python-lib might be problematic. Click here for more details.

__init__.py ADDED
File without changes
@@ -0,0 +1 @@
1
+ __version__ = "0.9.5"
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
+
@@ -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,3 @@
1
+ from .AuthData import AuthData
2
+ from .WebServiceResponse import WebServiceResponse
3
+ from .FileListing import FileListing
@@ -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,2 @@
1
+ from .PandasFrequency import PandasFreqency
2
+ from .UploadFileType import UploadFileType
@@ -0,0 +1,2 @@
1
+ from ._date_range import date_range
2
+ from ._slice_polygon_to_grid import slice_polygon_to_grid
@@ -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]
@@ -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.5
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=UvB3IZAWAs7vV6H9dpZvZPryrSNIyF5_bowVS4_YGFA,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.5.dist-info/licenses/LICENSE,sha256=x8ID0mDBvvKlF9ZZhYRj2-fSr20ZhXEdCFA4ewrEMxw,1086
21
+ kystdatahuset_python_lib-0.9.5.dist-info/METADATA,sha256=7o0gPTeAXc4-m8em0bis8yQUOGSMYnc_WbOYIgC6X3w,4055
22
+ kystdatahuset_python_lib-0.9.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ kystdatahuset_python_lib-0.9.5.dist-info/top_level.txt,sha256=vMgxr4VS5IiCspq8gKnP4Ceie8VNiPqQciArk4-0YmU,23
24
+ kystdatahuset_python_lib-0.9.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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.
@@ -0,0 +1,2 @@
1
+ __init__
2
+ kystdatahuset